DateTime Textbox (datePicker, timePicker, dateTimePicker)

As part of a new UI library, I have put together a full-featured date & time textbox using the jQuery UI Datepicker and a Timepicker Addon in a manner that follows the guidelines found in the Webgrown Solutions UI Manifesto. The DateTime Textbox supports the following features:

The Demo

The Markup

Just basic input elements, with a normal type attribute and some custom data (data-*) attributes to allow feature customization. Once the CSS and Script stuff are in place on a website, any page can take advantage of these full-featured date and time fields by simply setting an input's type attribute to one of the predetermined date & time values ("datetime" or "date" or "time") and setting the desired optional custom data attributes. The custom data attributes are optional, so the input element could simple have only the type attribute set without the many custom data attributes. If native browser support is available and that behavior is indicated (data-useNativeSupportIfAvailable), then the browser implementation of the control will be used.

That sounds like a DRY solution to me (see UI Manifesto).

 
<label for="txtDateTime">Date & Time:</label>
<input id="txtDateTime" type="datetime"
    data-useNativeSupportIfAvailable="true"
    data-dateQuickMonthChange="false"
    data-dateQuickYearChange="true"
    data-dateMin="null"
    data-dateMax="null"
    min=""
    max=""
    data-dateNumberOfMonthsToDisplay="1"
    data-dateCurrentMonthPositionInMultiMonthDisplay="0"
    data-dateShowOtherMonthDays="false"
    data-dateAllowSelectionOfOtherMonthDays="false"
    data-dateShowWeek="false"
    data-dateYearRange="nnnn:+20"
    data-dateAlternateDisplayElementSelector=""
    data-dateAlternateFormat=""
    data-timeHourStep="1"
    data-timeMinuteStep="5"
    data-timeHourDisplayStep="6"
    data-timeMinuteDisplayStep="10"
    data-timeHourMin="0"
    data-timeHourMax="23"
    data-showTodayNowAndCloseButtons="true"/>

<label for="txtDate">Date Only:</label>
<input id="txtDate" type="date"
    data-useNativeSupportIfAvailable="true"
    data-dateQuickMonthChange="false"
    data-dateQuickYearChange="false"
    data-dateMin="-3m"
    data-dateMax="+3m"
    min="2024-01-19"
    max="2024-07-19"
    data-dateNumberOfMonthsToDisplay="3"
    data-dateCurrentMonthPositionInMultiMonthDisplay="0"
    data-dateShowOtherMonthDays="false"
    data-dateAllowSelectionOfOtherMonthDays="false"
    data-dateShowWeek="true"
    data-dateYearRange="c-1:c+1"
    data-dateAlternateDisplayElementSelector="#altDisplay"
    data-dateI18nAlternateFormat="DD, MM d, yy"
    data-showTodayNowAndCloseButtons="true"/>
<input type="text" id="Text3" value="Select a date to see alternate display option." style="border-width:0; width:300px;" />

<label for="txtTime">Time Only:</label>
<input id="txtTime" type="time"
    data-useNativeSupportIfAvailable="true" 
    data-timeHourStep="1"
    data-timeMinuteStep="5"
    data-timeHourDisplayStep="6"
    data-timeMinuteDisplayStep="10"
    data-timeHourMin="0"
    data-timeHourMax="22"
    min="00:00:00"
    max="22:55:00"
    data-showTodayNowAndCloseButtons="true"/>

    <!-- Global DateTime Control Settings - Start -->
    <span id="lbl_dateI18nAlternateFormat">mm/dd/yy</span>
    <span id="lbl_dateI18nDateFormat">mm/dd/yy</span>
    <span id="lbl_dateI18nFirstDayOfWeekIndex">0</span>
    <span id="lbl_dateI18nPreviousText">Previous</span>
    <span id="lbl_dateI18nNextText">Next</span>
    <span id="lbl_dateI18nMonthNames">['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']</span>
    <span id="lbl_dateI18nMonthNamesAbbreviated3">['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']</span>
    <span id="lbl_dateI18nDayNames">['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']</span>
    <span id="lbl_dateI18nDayNamesAbbreviated3">['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']</span>
    <span id="lbl_dateI18nDayNamesAbbreviated2">['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']</span>
    <span id="lbl_dateI18nWeekTitleText">Wk</span>
    <span id="lbl_dateI18nIsRtl">false</span>
    <span id="lbl_dateI18nShowMonthAfterYear">false</span>
    <span id="lbl_dateI18nYearSuffix"></span>
    <span id="lbl_timeI18nIs24Hour">false</span>
    <span id="lbl_timeI18nTimeFormat">h:mm tt</span>
    <span id="lbl_timeI18nTimeOnlyHeaderText">Choose Time</span>
    <span id="lbl_timeI18nTimeTitleText">Time:</span>
    <span id="lbl_timeI18nHourTitleText">Hour:</span>
    <span id="lbl_timeI18nMinuteTitleText">Minute:</span>
    <span id="lbl_timeI18nSecondTitleText">Second:</span>
    <span id="lbl_i18nTodayNowButtonText">Now</span>
    <span id="lbl_i18nCloseButtonText">Done</span>
    <!-- Global DateTime Control Settings - End -->

The Script

//Enable DateTimePicker(s)
$(parentElementSelector + " input[type='datetime']").each(function () {
    if (Modernizr.inputtypes.datetime && $(this).attr("data-useNativeSupportIfAvailable") == "true") {
        //Use the browser supported functionality

        if (isAttrDefined($(this).attr("data-dateAlternateDisplayElementSelector"))) {
            $($(this).attr("data-dateAlternateDisplayElementSelector")).hide();
        }
    }
    else {
        //No native support or do not want to use it. Use JavaScript solution.
        $(this).datetimepicker({
            //Datetime stuff
            changeMonth: (($(this).attr("data-dateQuickMonthChange") == "true") ? true : false),
            changeYear: (($(this).attr("data-dateQuickYearChange") == "false") ? false : true),
            maxDate: ((!isAttrDefined($(this).attr("data-dateMax")) || $(this).attr("data-dateMax") == "null") ? null : $(this).attr("data-dateMax")),
            minDate: ((!isAttrDefined($(this).attr("data-dateMin")) || $(this).attr("data-dateMin") == "null") ? null : $(this).attr("data-dateMin")),
            numberOfMonths: ((!isAttrDefined($(this).attr("data-dateNumberOfMonthsToDisplay")) || isNaN($(this).attr("data-dateNumberOfMonthsToDisplay"))) ? 1 : eval($(this).attr("data-dateNumberOfMonthsToDisplay"))),
            showCurrentAtPos: ((!isAttrDefined($(this).attr("data-dateCurrentMonthPositionInMultiMonthDisplay")) || isNaN($(this).attr("data-dateCurrentMonthPositionInMultiMonthDisplay"))) ? 0 : eval($(this).attr("data-dateCurrentMonthPositionInMultiMonthDisplay"))),
            showOtherMonths: (($(this).attr("data-dateShowOtherMonthDays") == "true") ? true : false),
            selectOtherMonths: (($(this).attr("data-dateAllowSelectionOfOtherMonthDays") == "true") ? true : false),
            showWeek: (($(this).attr("data-dateShowWeek") == "true") ? true : false),
            yearRange: ((!isAttrDefined($(this).attr("data-dateYearRange"))) ? "-100:+100" : $(this).attr("data-dateYearRange")),
            altField: ((!isAttrDefined($(this).attr("data-dateAlternateDisplayElementSelector"))) ? "" : $(this).attr("data-dateAlternateDisplayElementSelector")), //When used with timepicker, the time is put in the altField (not really a desired feature)
            altFormat: ((!isAttrDefined($(this).attr("data-dateAlternateFormat"))) ? (($("#lbl_dateI18nAlternateFormat").html().length == 0) ? "mm/dd/yy" : $("#lbl_dateI18nAlternateFormat").html()) : $(this).attr("data-dateAlternateFormat")),
            dateFormat: (($("#lbl_dateI18nDateFormat").html().length == 0) ? "mm/dd/yy" : $("#lbl_dateI18nDateFormat").html()),
            firstDay: (($("#lbl_dateI18nFirstDayOfWeekIndex").html().length == 0) || isNaN($("#lbl_dateI18nFirstDayOfWeekIndex").html())) ? 0 : eval($("#lbl_dateI18nFirstDayOfWeekIndex").html()),
            prevText: (($("#lbl_dateI18nPreviousText").html().length == 0) ? "Previous" : $("#lbl_dateI18nPreviousText").html()),
            nextText: (($("#lbl_dateI18nNextText").html().length == 0) ? "Next" : $("#lbl_dateI18nNextText").html()),
            monthNames: (($("#lbl_dateI18nMonthNames").html().length == 0) ? ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] : eval($("#lbl_dateI18nMonthNames").html())),
            monthNamesShort: (($("#lbl_dateI18nMonthNamesAbbreviated3").html().length == 0) ? ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] : eval($("#lbl_dateI18nMonthNamesAbbreviated3").html())),
            dayNames: (($("#lbl_dateI18nDayNames").html().length == 0) ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] : eval($("#lbl_dateI18nDayNames").html())),
            dayNamesShort: (($("#lbl_dateI18nDayNamesAbbreviated3").html().length == 0) ? ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] : eval($("#lbl_dateI18nDayNamesAbbreviated3").html())),
            dayNamesMin: (($("#lbl_dateI18nDayNamesAbbreviated2").html().length == 0) ? ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] : eval($("#lbl_dateI18nDayNamesAbbreviated2").html())),
            weekHeader: (($("#lbl_dateI18nWeekTitleText").html().length == 0) ? "Wk" : $("#lbl_dateI18nWeekTitleText").html()),
            isRTL: (($("#lbl_data-dateI18nIsRtl").html() == "true") ? true : false),
            showMonthAfterYear: (($("#lbl_dateI18nShowMonthAfterYear").html() == "true") ? true : false),
            yearSuffix: (($("#lbl_dateI18nYearSuffix").html().length == 0) ? "" : eval($("#lbl_dateI18nYearSuffix").html())),
            //Timepicker stuff
            stepHour: ((!isAttrDefined($(this).attr("data-timeHourStep")) || isNaN($(this).attr("data-timeHourStep"))) ? 1 : eval($(this).attr("data-timeHourStep"))),
            stepMinute: ((!isAttrDefined($(this).attr("data-timeMinuteStep")) || isNaN($(this).attr("data-timeMinuteStep"))) ? 5 : eval($(this).attr("data-timeMinuteStep"))),
            hourGrid: ((!isAttrDefined($(this).attr("data-timeHourDisplayStep")) || isNaN($(this).attr("data-timeHourDisplayStep"))) ? 6 : eval($(this).attr("data-timeHourDisplayStep"))),
            minuteGrid: ((!isAttrDefined($(this).attr("data-timeMinuteDisplayStep")) || isNaN($(this).attr("data-timeMinuteDisplayStep"))) ? 10 : eval($(this).attr("data-timeMinuteDisplayStep"))),
            hourMin: ((!isAttrDefined($(this).attr("data-timeHourMin")) || isNaN($(this).attr("data-timeHourMin"))) ? 0 : eval($(this).attr("data-timeHourMin"))),
            hourMax: ((!isAttrDefined($(this).attr("data-timeHourMax")) || isNaN($(this).attr("data-timeHourMax"))) ? 23 : eval($(this).attr("data-timeHourMax"))),
            ampm: (($("#lbl_timeI18nIs24Hour").html() == "true") ? false : true),
            timeFormat: (($("#lbl_timeI18nTimeFormat").html().length == 0) ? "h:mm tt" : $("#lbl_timeI18nTimeFormat").html()),
            timeOnlyTitle: (($("#lbl_timeI18nTimeOnlyHeaderText").html().length == 0) ? "Choose Time" : $("#lbl_timeI18nTimeOnlyHeaderText").html()),
            timeText: (($("#lbl_timeI18nTimeTitleText").html().length == 0) ? "Time:" : $("#lbl_timeI18nTimeTitleText").html()),
            hourText: (($("#lbl_timeI18nHourTitleText").html().length == 0) ? "Hour:" : $("#lbl_timeI18nHourTitleText").html()),
            minuteText: (($("#lbl_timeI18nMinuteTitleText").html().length == 0) ? "Minute:" : $("#lbl_timeI18nMinuteTitleText").html()),
            secondText: (($("#lbl_timeI18nSecondTitleText").html().length == 0) ? "Second:" : $("#lbl_timeI18nSecondTitleText").html()),
            //Shared Stuff
            currentText: (($("#lbl_i18nTodayNowButtonText").html().length == 0) ? "Now" : $("#lbl_i18nTodayNowButtonText").html()),
            closeText: (($("#lbl_i18nCloseButtonText").html().length == 0) ? "Done" : $("#lbl_i18nCloseButtonText").html()),
            showButtonPanel: (($(this).attr("data-showTodayNowAndCloseButtons") == "false") ? false : true)
        });
    }
});

//Enable DatePicker(s)
$(parentElementSelector + " input[type='date']").each(function () {
    if (Modernizr.inputtypes.date && $(this).attr("data-useNativeSupportIfAvailable") == "true") {
        //Use the browser supported functionality

        if (isAttrDefined($(this).attr("data-dateAlternateDisplayElementSelector"))) {
            $($(this).attr("data-dateAlternateDisplayElementSelector")).hide();
        }
    }
    else {
        //No native support or do not want to use it. Use JavaScript solution.
        $(this).datepicker({
            //Datetime stuff
            changeMonth: (($(this).attr("data-dateQuickMonthChange") == "true") ? true : false),
            changeYear: (($(this).attr("data-dateQuickYearChange") == "false") ? false : true),
            maxDate: ((!isAttrDefined($(this).attr("data-dateMax")) || $(this).attr("data-dateMax") == "null") ? null : $(this).attr("data-dateMax")),
            minDate: ((!isAttrDefined($(this).attr("data-dateMin")) || $(this).attr("data-dateMin") == "null") ? null : $(this).attr("data-dateMin")),
            numberOfMonths: ((!isAttrDefined($(this).attr("data-dateNumberOfMonthsToDisplay")) || isNaN($(this).attr("data-dateNumberOfMonthsToDisplay"))) ? 1 : eval($(this).attr("data-dateNumberOfMonthsToDisplay"))),
            showCurrentAtPos: ((!isAttrDefined($(this).attr("data-dateCurrentMonthPositionInMultiMonthDisplay")) || isNaN($(this).attr("data-dateCurrentMonthPositionInMultiMonthDisplay"))) ? 0 : eval($(this).attr("data-dateCurrentMonthPositionInMultiMonthDisplay"))),
            showOtherMonths: (($(this).attr("data-dateShowOtherMonthDays") == "true") ? true : false),
            selectOtherMonths: (($(this).attr("data-dateAllowSelectionOfOtherMonthDays") == "true") ? true : false),
            showWeek: (($(this).attr("data-dateShowWeek") == "true") ? true : false),
            yearRange: ((!isAttrDefined($(this).attr("data-dateYearRange"))) ? "-100:+100" : $(this).attr("data-dateYearRange")),
            altField: ((!isAttrDefined($(this).attr("data-dateAlternateDisplayElementSelector"))) ? "" : $(this).attr("data-dateAlternateDisplayElementSelector")),
            altFormat: ((!isAttrDefined($(this).attr("data-dateAlternateFormat"))) ? (($("#lbl_dateI18nAlternateFormat").html().length == 0) ? "mm/dd/yy" : $("#lbl_dateI18nAlternateFormat").html()) : $(this).attr("data-dateAlternateFormat")),
            dateFormat: (($("#lbl_dateI18nDateFormat").html().length == 0) ? "mm/dd/yy" : $("#lbl_dateI18nDateFormat").html()),
            firstDay: (($("#lbl_dateI18nFirstDayOfWeekIndex").html().length == 0) || isNaN($("#lbl_dateI18nFirstDayOfWeekIndex").html())) ? 0 : eval($("#lbl_dateI18nFirstDayOfWeekIndex").html()),
            prevText: (($("#lbl_dateI18nPreviousText").html().length == 0) ? "Previous" : $("#lbl_dateI18nPreviousText").html()),
            nextText: (($("#lbl_dateI18nNextText").html().length == 0) ? "Next" : $("#lbl_dateI18nNextText").html()),
            monthNames: (($("#lbl_dateI18nMonthNames").html().length == 0) ? ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] : eval($("#lbl_dateI18nMonthNames").html())),
            monthNamesShort: (($("#lbl_dateI18nMonthNamesAbbreviated3").html().length == 0) ? ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] : eval($("#lbl_dateI18nMonthNamesAbbreviated3").html())),
            dayNames: (($("#lbl_dateI18nDayNames").html().length == 0) ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] : eval($("#lbl_dateI18nDayNames").html())),
            dayNamesShort: (($("#lbl_dateI18nDayNamesAbbreviated3").html().length == 0) ? ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] : eval($("#lbl_dateI18nDayNamesAbbreviated3").html())),
            dayNamesMin: (($("#lbl_dateI18nDayNamesAbbreviated2").html().length == 0) ? ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] : eval($("#lbl_dateI18nDayNamesAbbreviated2").html())),
            weekHeader: (($("#lbl_dateI18nWeekTitleText").html().length == 0) ? "Wk" : $("#lbl_dateI18nWeekTitleText").html()),
            isRTL: (($("#lbl_data-dateI18nIsRtl").html() == "true") ? true : false),
            showMonthAfterYear: (($("#lbl_dateI18nShowMonthAfterYear").html() == "true") ? true : false),
            yearSuffix: (($("#lbl_dateI18nYearSuffix").html().length == 0) ? "" : eval($("#lbl_dateI18nYearSuffix").html())),
            //Shared Stuff
            currentText: (($("#lbl_i18nTodayNowButtonText").html().length == 0) ? "Now" : $("#lbl_i18nTodayNowButtonText").html()),
            closeText: (($("#lbl_i18nCloseButtonText").html().length == 0) ? "Done" : $("#lbl_i18nCloseButtonText").html()),
            showButtonPanel: (($(this).attr("data-showTodayNowAndCloseButtons") == "false") ? false : true)
        });
    }
});

//Enable TimePicker(s)
$(parentElementSelector + " input[type='time']").each(function () {
    if (Modernizr.inputtypes.time && $(this).attr("data-useNativeSupportIfAvailable") == "true") {
        //Use the browser supported functionality
    }
    else {
        //No native support or do not want to use it. Use JavaScript solution.
        $(this).timepicker({
            //Timepicker stuff
            stepHour: ((!isAttrDefined($(this).attr("data-timeHourStep")) || isNaN($(this).attr("data-timeHourStep"))) ? 1 : eval($(this).attr("data-timeHourStep"))),
            stepMinute: ((!isAttrDefined($(this).attr("data-timeMinuteStep")) || isNaN($(this).attr("data-timeMinuteStep"))) ? 5 : eval($(this).attr("data-timeMinuteStep"))),
            hourGrid: ((!isAttrDefined($(this).attr("data-timeHourDisplayStep")) || isNaN($(this).attr("data-timeHourDisplayStep"))) ? 6 : eval($(this).attr("data-timeHourDisplayStep"))),
            minuteGrid: ((!isAttrDefined($(this).attr("data-timeMinuteDisplayStep")) || isNaN($(this).attr("data-timeMinuteDisplayStep"))) ? 10 : eval($(this).attr("data-timeMinuteDisplayStep"))),
            hourMin: ((!isAttrDefined($(this).attr("data-timeHourMin")) || isNaN($(this).attr("data-timeHourMin"))) ? 0 : eval($(this).attr("data-timeHourMin"))),
            hourMax: ((!isAttrDefined($(this).attr("data-timeHourMax")) || isNaN($(this).attr("data-timeHourMax"))) ? 23 : eval($(this).attr("data-timeHourMax"))),
            ampm: (($("#lbl_timeI18nIs24Hour").html() == "true") ? false : true),
            timeFormat: (($("#lbl_timeI18nTimeFormat").html().length == 0) ? "h:mm tt" : $("#lbl_timeI18nTimeFormat").html()),
            timeOnlyTitle: (($("#lbl_timeI18nTimeOnlyHeaderText").html().length == 0) ? "Choose Time" : $("#lbl_timeI18nTimeOnlyHeaderText").html()),
            timeText: (($("#lbl_timeI18nTimeTitleText").html().length == 0) ? "Time:" : $("#lbl_timeI18nTimeTitleText").html()),
            hourText: (($("#lbl_timeI18nHourTitleText").html().length == 0) ? "Hour:" : $("#lbl_timeI18nHourTitleText").html()),
            minuteText: (($("#lbl_timeI18nMinuteTitleText").html().length == 0) ? "Minute:" : $("#lbl_timeI18nMinuteTitleText").html()),
            secondText: (($("#lbl_timeI18nSecondTitleText").html().length == 0) ? "Second:" : $("#lbl_timeI18nSecondTitleText").html()),
            //Shared Stuff
            currentText: (($("#lbl_i18nTodayNowButtonText").html().length == 0) ? "Now" : $("#lbl_i18nTodayNowButtonText").html()),
            closeText: (($("#lbl_i18nCloseButtonText").html().length == 0) ? "Done" : $("#lbl_i18nCloseButtonText").html()),
            showButtonPanel: (($(this).attr("data-showTodayNowAndCloseButtons") == "false") ? false : true)
        });
    }
});  

The CSS

 
<!-- The jQuery UI styling of your choice -->
<link href="jquery-ui.css" rel="stylesheet" type="text/css" /> 
/* additional css for timepicker addon */
.ui-timepicker-div .ui-widget-header{ margin-bottom: 8px; }
.ui-timepicker-div dl{ text-align: left; }
.ui-timepicker-div dl dt{ height: 25px; }
.ui-timepicker-div dl dd{ margin: -25px 0 10px 65px; }
.ui-timepicker-div td { font-size: 90%; }