/* * jQuery UI Timepicker * * Copyright 2010-2013, Francois Gelinas * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://fgelinas.com/code/timepicker * * Depends: * jquery.ui.core.js * jquery.ui.position.js (only if position settings are used) * * Change version 0.1.0 - moved the t-rex up here * ____ ___ .-~. /_"-._ `-._~-. / /_ "~o\ :Y \ \ / : \~x. ` ') ] Y / | Y< ~-.__j / ! _.--~T : l l< /.-~ / / ____.--~ . ` l /~\ \<|Y / / .-~~" /| . ',-~\ \L| / / / .^ \ Y~Y \.^>/l_ "--' / Y .-"( . l__ j_j l_/ /~_.-~ . Y l / \ ) ~~~." / `/"~ / \.__/l_ | \ _.-" ~-{__ l : l._Z~-.___.--~ | ~---~ / ~~"---\_ ' __[> l . _.^ ___ _>-y~ \ \ . .-~ .-~ ~>--" / \ ~---" / ./ _.-' "-.,_____.,_ _.--~\ _.-~ ~~ ( _} -Row `. ~( ) \ /,`--'~\--'~\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ->T-Rex<- */ (function ($) { $.extend($.ui, { timepicker: { version: "0.3.3"} }); var PROP_NAME = 'timepicker', tpuuid = new Date().getTime(); /* Time picker manager. Use the singleton instance of this class, $.timepicker, to interact with the time picker. Settings for (groups of) time pickers are maintained in an instance object, allowing multiple different settings on the same page. */ function Timepicker() { this.debug = true; // Change this to true to start debugging this._curInst = null; // The current instance in use this._disabledInputs = []; // List of time picker inputs that have been disabled this._timepickerShowing = false; // True if the popup picker is showing , false if not this._inDialog = false; // True if showing within a "dialog", false if not this._dialogClass = 'ui-timepicker-dialog'; // The name of the dialog marker class this._mainDivId = 'ui-timepicker-div'; // The ID of the main timepicker division this._inlineClass = 'ui-timepicker-inline'; // The name of the inline marker class this._currentClass = 'ui-timepicker-current'; // The name of the current hour / minutes marker class this._dayOverClass = 'ui-timepicker-days-cell-over'; // The name of the day hover marker class this.regional = []; // Available regional settings, indexed by language code this.regional[''] = { // Default regional settings hourText: 'Hour', // Display text for hours section minuteText: 'Minute', // Display text for minutes link amPmText: ['AM', 'PM'], // Display text for AM PM closeButtonText: 'Done', // Text for the confirmation button (ok button) nowButtonText: 'Now', // Text for the now button deselectButtonText: 'Deselect' // Text for the deselect button }; this._defaults = { // Global defaults for all the time picker instances showOn: 'focus', // 'focus' for popup on focus, // 'button' for trigger button, or 'both' for either (not yet implemented) button: null, // 'button' element that will trigger the timepicker showAnim: 'fadeIn', // Name of jQuery animation for popup showOptions: {}, // Options for enhanced animations appendText: '', // Display text following the input box, e.g. showing the format beforeShow: null, // Define a callback function executed before the timepicker is shown onSelect: null, // Define a callback function when a hour / minutes is selected onClose: null, // Define a callback function when the timepicker is closed timeSeparator: ':', // The character to use to separate hours and minutes. periodSeparator: ' ', // The character to use to separate the time from the time period. showPeriod: false, // Define whether or not to show AM/PM with selected time showPeriodLabels: true, // Show the AM/PM labels on the left of the time picker showLeadingZero: true, // Define whether or not to show a leading zero for hours < 10. [true/false] showMinutesLeadingZero: true, // Define whether or not to show a leading zero for minutes < 10. altField: '', // Selector for an alternate field to store selected time into defaultTime: 'now', // Used as default time when input field is empty or for inline timePicker // (set to 'now' for the current time, '' for no highlighted time) myPosition: 'left top', // Position of the dialog relative to the input. // see the position utility for more info : http://jqueryui.com/demos/position/ atPosition: 'left bottom', // Position of the input element to match // Note : if the position utility is not loaded, the timepicker will attach left top to left bottom //NEW: 2011-02-03 onHourShow: null, // callback for enabling / disabling on selectable hours ex : function(hour) { return true; } onMinuteShow: null, // callback for enabling / disabling on time selection ex : function(hour,minute) { return true; } hours: { starts: 0, // first displayed hour ends: 23 // last displayed hour }, minutes: { starts: 0, // first displayed minute ends: 55, // last displayed minute interval: 5, // interval of displayed minutes manual: [] // optional extra manual entries for minutes }, rows: 4, // number of rows for the input tables, minimum 2, makes more sense if you use multiple of 2 // 2011-08-05 0.2.4 showHours: true, // display the hours section of the dialog showMinutes: true, // display the minute section of the dialog optionalMinutes: false, // optionally parse inputs of whole hours with minutes omitted // buttons showCloseButton: false, // shows an OK button to confirm the edit showNowButton: false, // Shows the 'now' button showDeselectButton: false, // Shows the deselect time button maxTime: { hour: null, minute: null }, minTime: { hour: null, minute: null } }; $.extend(this._defaults, this.regional['']); this.tpDiv = $(''); } $.extend(Timepicker.prototype, { /* Class name added to elements to indicate already configured with a time picker. */ markerClassName: 'hasTimepicker', /* Debug logging (if enabled). */ log: function () { if (this.debug) console.log.apply('', arguments); }, _widgetTimepicker: function () { return this.tpDiv; }, /* Override the default settings for all instances of the time picker. @param settings object - the new settings to use as defaults (anonymous object) @return the manager object */ setDefaults: function (settings) { extendRemove(this._defaults, settings || {}); return this; }, /* Attach the time picker to a jQuery selection. @param target element - the target input field or division or span @param settings object - the new settings to use for this time picker instance (anonymous) */ _attachTimepicker: function (target, settings) { // check for settings on the control itself - in namespace 'time:' var inlineSettings = null; for (var attrName in this._defaults) { var attrValue = target.getAttribute('time:' + attrName); if (attrValue) { inlineSettings = inlineSettings || {}; try { inlineSettings[attrName] = eval(attrValue); } catch (err) { inlineSettings[attrName] = attrValue; } } } var nodeName = target.nodeName.toLowerCase(); var inline = (nodeName == 'div' || nodeName == 'span'); if (!target.id) { this.uuid += 1; target.id = 'tp' + this.uuid; } var inst = this._newInst($(target), inline); inst.settings = $.extend({}, settings || {}, inlineSettings || {}); if (nodeName == 'input') { this._connectTimepicker(target, inst); // init inst.hours and inst.minutes from the input value this._setTimeFromField(inst); } else if (inline) { this._inlineTimepicker(target, inst); } }, /* Create a new instance object. */ _newInst: function (target, inline) { var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars return { id: id, input: target, // associated target inline: inline, // is timepicker inline or not : tpDiv: (!inline ? this.tpDiv : // presentation div $('
')) }; }, /* Attach the time picker to an input field. */ _connectTimepicker: function (target, inst) { var input = $(target); inst.append = $([]); inst.trigger = $([]); if (input.hasClass(this.markerClassName)) { return; } this._attachments(input, inst); input.addClass(this.markerClassName). keydown(this._doKeyDown). keyup(this._doKeyUp). bind("setData.timepicker", function (event, key, value) { inst.settings[key] = value; }). bind("getData.timepicker", function (event, key) { return this._get(inst, key); }); $.data(target, PROP_NAME, inst); }, /* Handle keystrokes. */ _doKeyDown: function (event) { var inst = $.timepicker._getInst(event.target); var handled = true; inst._keyEvent = true; if ($.timepicker._timepickerShowing) { switch (event.keyCode) { case 9: $.timepicker._hideTimepicker(); handled = false; break; // hide on tab out case 13: $.timepicker._updateSelectedValue(inst); $.timepicker._hideTimepicker(); return false; // don't submit the form break; // select the value on enter case 27: $.timepicker._hideTimepicker(); break; // hide on escape default: handled = false; } } else if (event.keyCode == 36 && event.ctrlKey) { // display the time picker on ctrl+home $.timepicker._showTimepicker(this); } else { handled = false; } if (handled) { event.preventDefault(); event.stopPropagation(); } }, /* Update selected time on keyUp */ /* Added verion 0.0.5 */ _doKeyUp: function (event) { var inst = $.timepicker._getInst(event.target); $.timepicker._setTimeFromField(inst); $.timepicker._updateTimepicker(inst); }, /* Make attachments based on settings. */ _attachments: function (input, inst) { var appendText = this._get(inst, 'appendText'); var isRTL = this._get(inst, 'isRTL'); if (inst.append) { inst.append.remove(); } if (appendText) { inst.append = $('' + appendText + ''); input[isRTL ? 'before' : 'after'](inst.append); } input.unbind('focus.timepicker', this._showTimepicker); input.unbind('click.timepicker', this._adjustZIndex); if (inst.trigger) { inst.trigger.remove(); } var showOn = this._get(inst, 'showOn'); if (showOn == 'focus' || showOn == 'both') { // pop-up time picker when in the marked field input.bind("focus.timepicker", this._showTimepicker); input.bind("click.timepicker", this._adjustZIndex); } if (showOn == 'button' || showOn == 'both') { // pop-up time picker when 'button' element is clicked var button = this._get(inst, 'button'); // Add button if button element is not set if(button == null) { button = $(''); input.after(button); } $(button).bind("click.timepicker", function () { if ($.timepicker._timepickerShowing && $.timepicker._lastInput == input[0]) { $.timepicker._hideTimepicker(); } else if (!inst.input.is(':disabled')) { $.timepicker._showTimepicker(input[0]); } return false; }); } }, /* Attach an inline time picker to a div. */ _inlineTimepicker: function(target, inst) { var divSpan = $(target); if (divSpan.hasClass(this.markerClassName)) return; divSpan.addClass(this.markerClassName).append(inst.tpDiv). bind("setData.timepicker", function(event, key, value){ inst.settings[key] = value; }).bind("getData.timepicker", function(event, key){ return this._get(inst, key); }); $.data(target, PROP_NAME, inst); this._setTimeFromField(inst); this._updateTimepicker(inst); inst.tpDiv.show(); }, _adjustZIndex: function(input) { input = input.target || input; var inst = $.timepicker._getInst(input); inst.tpDiv.css('zIndex', $.timepicker._getZIndex(input) +1); }, /* Pop-up the time picker for a given input field. @param input element - the input field attached to the time picker or event - if triggered by focus */ _showTimepicker: function (input) { input = input.target || input; if (input.nodeName.toLowerCase() != 'input') { input = $('input', input.parentNode)[0]; } // find from button/image trigger if ($.timepicker._isDisabledTimepicker(input) || $.timepicker._lastInput == input) { return; } // already here // fix v 0.0.8 - close current timepicker before showing another one $.timepicker._hideTimepicker(); var inst = $.timepicker._getInst(input); if ($.timepicker._curInst && $.timepicker._curInst != inst) { $.timepicker._curInst.tpDiv.stop(true, true); } var beforeShow = $.timepicker._get(inst, 'beforeShow'); extendRemove(inst.settings, (beforeShow ? beforeShow.apply(input, [input, inst]) : {})); inst.lastVal = null; $.timepicker._lastInput = input; $.timepicker._setTimeFromField(inst); // calculate default position if ($.timepicker._inDialog) { input.value = ''; } // hide cursor if (!$.timepicker._pos) { // position below input $.timepicker._pos = $.timepicker._findPos(input); $.timepicker._pos[1] += input.offsetHeight; // add the height } var isFixed = false; $(input).parents().each(function () { isFixed |= $(this).css('position') == 'fixed'; return !isFixed; }); var offset = { left: $.timepicker._pos[0], top: $.timepicker._pos[1] }; $.timepicker._pos = null; // determine sizing offscreen inst.tpDiv.css({ position: 'absolute', display: 'block', top: '-1000px' }); $.timepicker._updateTimepicker(inst); // position with the ui position utility, if loaded if ( ( ! inst.inline ) && ( typeof $.ui.position == 'object' ) ) { inst.tpDiv.position({ of: inst.input, my: $.timepicker._get( inst, 'myPosition' ), at: $.timepicker._get( inst, 'atPosition' ), // offset: $( "#offset" ).val(), // using: using, collision: 'flip' }); var offset = inst.tpDiv.offset(); $.timepicker._pos = [offset.top, offset.left]; } // reset clicked state inst._hoursClicked = false; inst._minutesClicked = false; // fix width for dynamic number of time pickers // and adjust position before showing offset = $.timepicker._checkOffset(inst, offset, isFixed); inst.tpDiv.css({ position: ($.timepicker._inDialog && $.blockUI ? 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none', left: offset.left + 'px', top: offset.top + 'px' }); if ( ! inst.inline ) { var showAnim = $.timepicker._get(inst, 'showAnim'); var duration = $.timepicker._get(inst, 'duration'); var postProcess = function () { $.timepicker._timepickerShowing = true; var borders = $.timepicker._getBorders(inst.tpDiv); inst.tpDiv.find('iframe.ui-timepicker-cover'). // IE6- only css({ left: -borders[0], top: -borders[1], width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight() }); }; // Fixed the zIndex problem for real (I hope) - FG - v 0.2.9 $.timepicker._adjustZIndex(input); //inst.tpDiv.css('zIndex', $.timepicker._getZIndex(input) +1); if ($.effects && $.effects[showAnim]) { inst.tpDiv.show(showAnim, $.timepicker._get(inst, 'showOptions'), duration, postProcess); } else { inst.tpDiv.show((showAnim ? duration : null), postProcess); } if (!showAnim || !duration) { postProcess(); } if (inst.input.is(':visible') && !inst.input.is(':disabled')) { inst.input.focus(); } $.timepicker._curInst = inst; } }, // This is an enhanced copy of the zIndex function of UI core 1.8.?? For backward compatibility. // Enhancement returns maximum zindex value discovered while traversing parent elements, // rather than the first zindex value found. Ensures the timepicker popup will be in front, // even in funky scenarios like non-jq dialog containers with large fixed zindex values and // nested zindex-influenced elements of their own. _getZIndex: function (target) { var elem = $(target); var maxValue = 0; var position, value; while (elem.length && elem[0] !== document) { position = elem.css("position"); if (position === "absolute" || position === "relative" || position === "fixed") { value = parseInt(elem.css("zIndex"), 10); if (!isNaN(value) && value !== 0) { if (value > maxValue) { maxValue = value; } } } elem = elem.parent(); } return maxValue; }, /* Refresh the time picker @param target element - The target input field or inline container element. */ _refreshTimepicker: function(target) { var inst = this._getInst(target); if (inst) { this._updateTimepicker(inst); } }, /* Generate the time picker content. */ _updateTimepicker: function (inst) { inst.tpDiv.empty().append(this._generateHTML(inst)); this._rebindDialogEvents(inst); }, _rebindDialogEvents: function (inst) { var borders = $.timepicker._getBorders(inst.tpDiv), self = this; inst.tpDiv .find('iframe.ui-timepicker-cover') // IE6- only .css({ left: -borders[0], top: -borders[1], width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight() }) .end() // after the picker html is appended bind the click & double click events (faster in IE this way // then letting the browser interpret the inline events) // the binding for the minute cells also exists in _updateMinuteDisplay .find('.ui-timepicker-minute-cell') .unbind() .bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectMinutes, this)) .bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectMinutes, this)) .end() .find('.ui-timepicker-hour-cell') .unbind() .bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectHours, this)) .bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectHours, this)) .end() .find('.ui-timepicker td a') .unbind() .bind('mouseout', function () { $(this).removeClass('ui-state-hover'); if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).removeClass('ui-timepicker-prev-hover'); if (this.className.indexOf('ui-timepicker-next') != -1) $(this).removeClass('ui-timepicker-next-hover'); }) .bind('mouseover', function () { if ( ! self._isDisabledTimepicker(inst.inline ? inst.tpDiv.parent()[0] : inst.input[0])) { $(this).parents('.ui-timepicker-calendar').find('a').removeClass('ui-state-hover'); $(this).addClass('ui-state-hover'); if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).addClass('ui-timepicker-prev-hover'); if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-timepicker-next-hover'); } }) .end() .find('.' + this._dayOverClass + ' a') .trigger('mouseover') .end() .find('.ui-timepicker-now').bind("click", function(e) { $.timepicker.selectNow(e); }).end() .find('.ui-timepicker-deselect').bind("click",function(e) { $.timepicker.deselectTime(e); }).end() .find('.ui-timepicker-close').bind("click",function(e) { $.timepicker._hideTimepicker(); }).end(); }, /* Generate the HTML for the current state of the time picker. */ _generateHTML: function (inst) { var h, m, row, col, html, hoursHtml, minutesHtml = '', showPeriod = (this._get(inst, 'showPeriod') == true), showPeriodLabels = (this._get(inst, 'showPeriodLabels') == true), showLeadingZero = (this._get(inst, 'showLeadingZero') == true), showHours = (this._get(inst, 'showHours') == true), showMinutes = (this._get(inst, 'showMinutes') == true), amPmText = this._get(inst, 'amPmText'), rows = this._get(inst, 'rows'), amRows = 0, pmRows = 0, amItems = 0, pmItems = 0, amFirstRow = 0, pmFirstRow = 0, hours = Array(), hours_options = this._get(inst, 'hours'), hoursPerRow = null, hourCounter = 0, hourLabel = this._get(inst, 'hourText'), showCloseButton = this._get(inst, 'showCloseButton'), closeButtonText = this._get(inst, 'closeButtonText'), showNowButton = this._get(inst, 'showNowButton'), nowButtonText = this._get(inst, 'nowButtonText'), showDeselectButton = this._get(inst, 'showDeselectButton'), deselectButtonText = this._get(inst, 'deselectButtonText'), showButtonPanel = showCloseButton || showNowButton || showDeselectButton; // prepare all hours and minutes, makes it easier to distribute by rows for (h = hours_options.starts; h <= hours_options.ends; h++) { hours.push (h); } hoursPerRow = Math.ceil(hours.length / rows); // always round up if (showPeriodLabels) { for (hourCounter = 0; hourCounter < hours.length; hourCounter++) { if (hours[hourCounter] < 12) { amItems++; } else { pmItems++; } } hourCounter = 0; amRows = Math.floor(amItems / hours.length * rows); pmRows = Math.floor(pmItems / hours.length * rows); // assign the extra row to the period that is more densely populated if (rows != amRows + pmRows) { // Make sure: AM Has Items and either PM Does Not, AM has no rows yet, or AM is more dense if (amItems && (!pmItems || !amRows || (pmRows && amItems / amRows >= pmItems / pmRows))) { amRows++; } else { pmRows++; } } amFirstRow = Math.min(amRows, 1); pmFirstRow = amRows + 1; if (amRows == 0) { hoursPerRow = Math.ceil(pmItems / pmRows); } else if (pmRows == 0) { hoursPerRow = Math.ceil(amItems / amRows); } else { hoursPerRow = Math.ceil(Math.max(amItems / amRows, pmItems / pmRows)); } } html = ''; if (showHours) { html += ''; // Close the Hour td } if (showMinutes) { html += ''; } html += ''; if (showButtonPanel) { var buttonPanel = ''; } html += '
' + '
' + hourLabel + '
' + ''; for (row = 1; row <= rows; row++) { html += ''; // AM if (row == amFirstRow && showPeriodLabels) { html += ''; } // PM if (row == pmFirstRow && showPeriodLabels) { html += ''; } for (col = 1; col <= hoursPerRow; col++) { if (showPeriodLabels && row < pmFirstRow && hours[hourCounter] >= 12) { html += this._generateHTMLHourCell(inst, undefined, showPeriod, showLeadingZero); } else { html += this._generateHTMLHourCell(inst, hours[hourCounter], showPeriod, showLeadingZero); hourCounter++; } } html += ''; } html += '
' + amPmText[0] + '' + amPmText[1] + '
' + // Close the hours cells table '
'; html += this._generateHTMLMinutes(inst); html += '
'; if (showNowButton) { buttonPanel += ''; } if (showDeselectButton) { buttonPanel += ''; } if (showCloseButton) { buttonPanel += ''; } html += buttonPanel + '
'; return html; }, /* Special function that update the minutes selection in currently visible timepicker * called on hour selection when onMinuteShow is defined */ _updateMinuteDisplay: function (inst) { var newHtml = this._generateHTMLMinutes(inst); inst.tpDiv.find('td.ui-timepicker-minutes').html(newHtml); this._rebindDialogEvents(inst); // after the picker html is appended bind the click & double click events (faster in IE this way // then letting the browser interpret the inline events) // yes I know, duplicate code, sorry /* .find('.ui-timepicker-minute-cell') .bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectMinutes, this)) .bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectMinutes, this)); */ }, /* * Generate the minutes table * This is separated from the _generateHTML function because is can be called separately (when hours changes) */ _generateHTMLMinutes: function (inst) { var m, row, html = '', rows = this._get(inst, 'rows'), minutes = Array(), minutes_options = this._get(inst, 'minutes'), minutesPerRow = null, minuteCounter = 0, showMinutesLeadingZero = (this._get(inst, 'showMinutesLeadingZero') == true), onMinuteShow = this._get(inst, 'onMinuteShow'), minuteLabel = this._get(inst, 'minuteText'); if ( ! minutes_options.starts) { minutes_options.starts = 0; } if ( ! minutes_options.ends) { minutes_options.ends = 59; } if ( ! minutes_options.manual) { minutes_options.manual = []; } for (m = minutes_options.starts; m <= minutes_options.ends; m += minutes_options.interval) { minutes.push(m); } for (i = 0; i < minutes_options.manual.length;i++) { var currMin = minutes_options.manual[i]; // Validate & filter duplicates of manual minute input if (typeof currMin != 'number' || currMin < 0 || currMin > 59 || $.inArray(currMin, minutes) >= 0) { continue; } minutes.push(currMin); } // Sort to get correct order after adding manual minutes // Use compare function to sort by number, instead of string (default) minutes.sort(function(a, b) { return a-b; }); minutesPerRow = Math.round(minutes.length / rows + 0.49); // always round up /* * The minutes table */ // if currently selected minute is not enabled, we have a problem and need to select a new minute. if (onMinuteShow && (onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours , inst.minutes]) == false) ) { // loop minutes and select first available for (minuteCounter = 0; minuteCounter < minutes.length; minuteCounter += 1) { m = minutes[minuteCounter]; if (onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours, m])) { inst.minutes = m; break; } } } html += '
' + minuteLabel + '
' + ''; minuteCounter = 0; for (row = 1; row <= rows; row++) { html += ''; while (minuteCounter < row * minutesPerRow) { var m = minutes[minuteCounter]; var displayText = ''; if (m !== undefined ) { displayText = (m < 10) && showMinutesLeadingZero ? "0" + m.toString() : m.toString(); } html += this._generateHTMLMinuteCell(inst, m, displayText); minuteCounter++; } html += ''; } html += '
'; return html; }, /* Generate the content of a "Hour" cell */ _generateHTMLHourCell: function (inst, hour, showPeriod, showLeadingZero) { var displayHour = hour; if ((hour > 12) && showPeriod) { displayHour = hour - 12; } if ((displayHour == 0) && showPeriod) { displayHour = 12; } if ((displayHour < 10) && showLeadingZero) { displayHour = '0' + displayHour; } var html = ""; var enabled = true; var onHourShow = this._get(inst, 'onHourShow'); //custom callback var maxTime = this._get(inst, 'maxTime'); var minTime = this._get(inst, 'minTime'); if (hour == undefined) { html = ' '; return html; } if (onHourShow) { enabled = onHourShow.apply((inst.input ? inst.input[0] : null), [hour]); } if (enabled) { if ( !isNaN(parseInt(maxTime.hour)) && hour > maxTime.hour ) enabled = false; if ( !isNaN(parseInt(minTime.hour)) && hour < minTime.hour ) enabled = false; } if (enabled) { html = '' + '' + displayHour.toString() + ''; } else { html = '' + '' + displayHour.toString() + '' + ''; } return html; }, /* Generate the content of a "Hour" cell */ _generateHTMLMinuteCell: function (inst, minute, displayText) { var html = ""; var enabled = true; var hour = inst.hours; var onMinuteShow = this._get(inst, 'onMinuteShow'); //custom callback var maxTime = this._get(inst, 'maxTime'); var minTime = this._get(inst, 'minTime'); if (onMinuteShow) { //NEW: 2011-02-03 we should give the hour as a parameter as well! enabled = onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours,minute]); //trigger callback } if (minute == undefined) { html = ' '; return html; } if (enabled && hour !== null) { if ( !isNaN(parseInt(maxTime.hour)) && !isNaN(parseInt(maxTime.minute)) && hour >= maxTime.hour && minute > maxTime.minute ) enabled = false; if ( !isNaN(parseInt(minTime.hour)) && !isNaN(parseInt(minTime.minute)) && hour <= minTime.hour && minute < minTime.minute ) enabled = false; } if (enabled) { html = '' + '' + displayText + ''; } else { html = '' + '' + displayText + '' + ''; } return html; }, /* Detach a timepicker from its control. @param target element - the target input field or division or span */ _destroyTimepicker: function(target) { var $target = $(target); var inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } var nodeName = target.nodeName.toLowerCase(); $.removeData(target, PROP_NAME); if (nodeName == 'input') { inst.append.remove(); inst.trigger.remove(); $target.removeClass(this.markerClassName) .unbind('focus.timepicker', this._showTimepicker) .unbind('click.timepicker', this._adjustZIndex); } else if (nodeName == 'div' || nodeName == 'span') $target.removeClass(this.markerClassName).empty(); }, /* Enable the date picker to a jQuery selection. @param target element - the target input field or division or span */ _enableTimepicker: function(target) { var $target = $(target), target_id = $target.attr('id'), inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } var nodeName = target.nodeName.toLowerCase(); if (nodeName == 'input') { target.disabled = false; var button = this._get(inst, 'button'); $(button).removeClass('ui-state-disabled').disabled = false; inst.trigger.filter('button'). each(function() { this.disabled = false; }).end(); } else if (nodeName == 'div' || nodeName == 'span') { var inline = $target.children('.' + this._inlineClass); inline.children().removeClass('ui-state-disabled'); inline.find('button').each( function() { this.disabled = false } ) } this._disabledInputs = $.map(this._disabledInputs, function(value) { return (value == target_id ? null : value); }); // delete entry }, /* Disable the time picker to a jQuery selection. @param target element - the target input field or division or span */ _disableTimepicker: function(target) { var $target = $(target); var inst = $.data(target, PROP_NAME); if (!$target.hasClass(this.markerClassName)) { return; } var nodeName = target.nodeName.toLowerCase(); if (nodeName == 'input') { var button = this._get(inst, 'button'); $(button).addClass('ui-state-disabled').disabled = true; target.disabled = true; inst.trigger.filter('button'). each(function() { this.disabled = true; }).end(); } else if (nodeName == 'div' || nodeName == 'span') { var inline = $target.children('.' + this._inlineClass); inline.children().addClass('ui-state-disabled'); inline.find('button').each( function() { this.disabled = true } ) } this._disabledInputs = $.map(this._disabledInputs, function(value) { return (value == target ? null : value); }); // delete entry this._disabledInputs[this._disabledInputs.length] = $target.attr('id'); }, /* Is the first field in a jQuery collection disabled as a timepicker? @param target_id element - the target input field or division or span @return boolean - true if disabled, false if enabled */ _isDisabledTimepicker: function (target_id) { if ( ! target_id) { return false; } for (var i = 0; i < this._disabledInputs.length; i++) { if (this._disabledInputs[i] == target_id) { return true; } } return false; }, /* Check positioning to remain on screen. */ _checkOffset: function (inst, offset, isFixed) { var tpWidth = inst.tpDiv.outerWidth(); var tpHeight = inst.tpDiv.outerHeight(); var inputWidth = inst.input ? inst.input.outerWidth() : 0; var inputHeight = inst.input ? inst.input.outerHeight() : 0; var viewWidth = document.documentElement.clientWidth + $(document).scrollLeft(); var viewHeight = document.documentElement.clientHeight + $(document).scrollTop(); offset.left -= (this._get(inst, 'isRTL') ? (tpWidth - inputWidth) : 0); offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0; offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0; // now check if timepicker is showing outside window viewport - move to a better place if so. offset.left -= Math.min(offset.left, (offset.left + tpWidth > viewWidth && viewWidth > tpWidth) ? Math.abs(offset.left + tpWidth - viewWidth) : 0); offset.top -= Math.min(offset.top, (offset.top + tpHeight > viewHeight && viewHeight > tpHeight) ? Math.abs(tpHeight + inputHeight) : 0); return offset; }, /* Find an object's position on the screen. */ _findPos: function (obj) { var inst = this._getInst(obj); var isRTL = this._get(inst, 'isRTL'); while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) { obj = obj[isRTL ? 'previousSibling' : 'nextSibling']; } var position = $(obj).offset(); return [position.left, position.top]; }, /* Retrieve the size of left and top borders for an element. @param elem (jQuery object) the element of interest @return (number[2]) the left and top borders */ _getBorders: function (elem) { var convert = function (value) { return { thin: 1, medium: 2, thick: 3}[value] || value; }; return [parseFloat(convert(elem.css('border-left-width'))), parseFloat(convert(elem.css('border-top-width')))]; }, /* Close time picker if clicked elsewhere. */ _checkExternalClick: function (event) { if (!$.timepicker._curInst) { return; } var $target = $(event.target); if ($target[0].id != $.timepicker._mainDivId && $target.parents('#' + $.timepicker._mainDivId).length == 0 && !$target.hasClass($.timepicker.markerClassName) && !$target.hasClass($.timepicker._triggerClass) && $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI)) $.timepicker._hideTimepicker(); }, /* Hide the time picker from view. @param input element - the input field attached to the time picker */ _hideTimepicker: function (input) { var inst = this._curInst; if (!inst || (input && inst != $.data(input, PROP_NAME))) { return; } if (this._timepickerShowing) { var showAnim = this._get(inst, 'showAnim'); var duration = this._get(inst, 'duration'); var postProcess = function () { $.timepicker._tidyDialog(inst); this._curInst = null; }; if ($.effects && $.effects[showAnim]) { inst.tpDiv.hide(showAnim, $.timepicker._get(inst, 'showOptions'), duration, postProcess); } else { inst.tpDiv[(showAnim == 'slideDown' ? 'slideUp' : (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess); } if (!showAnim) { postProcess(); } this._timepickerShowing = false; this._lastInput = null; if (this._inDialog) { this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' }); if ($.blockUI) { $.unblockUI(); $('body').append(this.tpDiv); } } this._inDialog = false; var onClose = this._get(inst, 'onClose'); if (onClose) { onClose.apply( (inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ''), inst]); // trigger custom callback } } }, /* Tidy up after a dialog display. */ _tidyDialog: function (inst) { inst.tpDiv.removeClass(this._dialogClass).unbind('.ui-timepicker'); }, /* Retrieve the instance data for the target control. @param target element - the target input field or division or span @return object - the associated instance data @throws error if a jQuery problem getting data */ _getInst: function (target) { try { return $.data(target, PROP_NAME); } catch (err) { throw 'Missing instance data for this timepicker'; } }, /* Get a setting value, defaulting if necessary. */ _get: function (inst, name) { return inst.settings[name] !== undefined ? inst.settings[name] : this._defaults[name]; }, /* Parse existing time and initialise time picker. */ _setTimeFromField: function (inst) { if (inst.input.val() == inst.lastVal) { return; } var defaultTime = this._get(inst, 'defaultTime'); var timeToParse = defaultTime == 'now' ? this._getCurrentTimeRounded(inst) : defaultTime; if ((inst.inline == false) && (inst.input.val() != '')) { timeToParse = inst.input.val() } if (timeToParse instanceof Date) { inst.hours = timeToParse.getHours(); inst.minutes = timeToParse.getMinutes(); } else { var timeVal = inst.lastVal = timeToParse; if (timeToParse == '') { inst.hours = -1; inst.minutes = -1; } else { var time = this.parseTime(inst, timeVal); inst.hours = time.hours; inst.minutes = time.minutes; } } $.timepicker._updateTimepicker(inst); }, /* Update or retrieve the settings for an existing time picker. @param target element - the target input field or division or span @param name object - the new settings to update or string - the name of the setting to change or retrieve, when retrieving also 'all' for all instance settings or 'defaults' for all global defaults @param value any - the new value for the setting (omit if above is an object or to retrieve a value) */ _optionTimepicker: function(target, name, value) { var inst = this._getInst(target); if (arguments.length == 2 && typeof name == 'string') { return (name == 'defaults' ? $.extend({}, $.timepicker._defaults) : (inst ? (name == 'all' ? $.extend({}, inst.settings) : this._get(inst, name)) : null)); } var settings = name || {}; if (typeof name == 'string') { settings = {}; settings[name] = value; } if (inst) { extendRemove(inst.settings, settings); if (this._curInst == inst) { this._hideTimepicker(); this._updateTimepicker(inst); } if (inst.inline) { this._updateTimepicker(inst); } } }, /* Set the time for a jQuery selection. @param target element - the target input field or division or span @param time String - the new time */ _setTimeTimepicker: function(target, time) { var inst = this._getInst(target); if (inst) { this._setTime(inst, time); this._updateTimepicker(inst); this._updateAlternate(inst, time); } }, /* Set the time directly. */ _setTime: function(inst, time, noChange) { var origHours = inst.hours; var origMinutes = inst.minutes; if (time instanceof Date) { inst.hours = time.getHours(); inst.minutes = time.getMinutes(); } else { var time = this.parseTime(inst, time); inst.hours = time.hours; inst.minutes = time.minutes; } if ((origHours != inst.hours || origMinutes != inst.minutes) && !noChange) { inst.input.trigger('change'); } this._updateTimepicker(inst); this._updateSelectedValue(inst); }, /* Return the current time, ready to be parsed, rounded to the closest minute by interval */ _getCurrentTimeRounded: function (inst) { var currentTime = new Date(), currentMinutes = currentTime.getMinutes(), minutes_options = this._get(inst, 'minutes'), // round to closest interval adjustedMinutes = Math.round(currentMinutes / minutes_options.interval) * minutes_options.interval; currentTime.setMinutes(adjustedMinutes); return currentTime; }, /* * Parse a time string into hours and minutes */ parseTime: function (inst, timeVal) { var retVal = new Object(); retVal.hours = -1; retVal.minutes = -1; if(!timeVal) return ''; var timeSeparator = this._get(inst, 'timeSeparator'), amPmText = this._get(inst, 'amPmText'), showHours = this._get(inst, 'showHours'), showMinutes = this._get(inst, 'showMinutes'), optionalMinutes = this._get(inst, 'optionalMinutes'), showPeriod = (this._get(inst, 'showPeriod') == true), p = timeVal.indexOf(timeSeparator); // check if time separator found if (p != -1) { retVal.hours = parseInt(timeVal.substr(0, p), 10); retVal.minutes = parseInt(timeVal.substr(p + 1), 10); } // check for hours only else if ( (showHours) && ( !showMinutes || optionalMinutes ) ) { retVal.hours = parseInt(timeVal, 10); } // check for minutes only else if ( ( ! showHours) && (showMinutes) ) { retVal.minutes = parseInt(timeVal, 10); } if (showHours) { var timeValUpper = timeVal.toUpperCase(); if ((retVal.hours < 12) && (showPeriod) && (timeValUpper.indexOf(amPmText[1].toUpperCase()) != -1)) { retVal.hours += 12; } // fix for 12 AM if ((retVal.hours == 12) && (showPeriod) && (timeValUpper.indexOf(amPmText[0].toUpperCase()) != -1)) { retVal.hours = 0; } } return retVal; }, selectNow: function(event) { var id = $(event.target).attr("data-timepicker-instance-id"), $target = $(id), inst = this._getInst($target[0]); //if (!inst || (input && inst != $.data(input, PROP_NAME))) { return; } var currentTime = new Date(); inst.hours = currentTime.getHours(); inst.minutes = currentTime.getMinutes(); this._updateSelectedValue(inst); this._updateTimepicker(inst); this._hideTimepicker(); }, deselectTime: function(event) { var id = $(event.target).attr("data-timepicker-instance-id"), $target = $(id), inst = this._getInst($target[0]); inst.hours = -1; inst.minutes = -1; this._updateSelectedValue(inst); this._hideTimepicker(); }, selectHours: function (event) { var $td = $(event.currentTarget), id = $td.attr("data-timepicker-instance-id"), newHours = parseInt($td.attr("data-hour")), fromDoubleClick = event.data.fromDoubleClick, $target = $(id), inst = this._getInst($target[0]), showMinutes = (this._get(inst, 'showMinutes') == true); // don't select if disabled if ( $.timepicker._isDisabledTimepicker($target.attr('id')) ) { return false } $td.parents('.ui-timepicker-hours:first').find('a').removeClass('ui-state-active'); $td.children('a').addClass('ui-state-active'); inst.hours = newHours; // added for onMinuteShow callback var onMinuteShow = this._get(inst, 'onMinuteShow'), maxTime = this._get(inst, 'maxTime'), minTime = this._get(inst, 'minTime'); if (onMinuteShow || maxTime.minute || minTime.minute) { // this will trigger a callback on selected hour to make sure selected minute is allowed. this._updateMinuteDisplay(inst); } this._updateSelectedValue(inst); inst._hoursClicked = true; if ((inst._minutesClicked) || (fromDoubleClick) || (showMinutes == false)) { $.timepicker._hideTimepicker(); } // return false because if used inline, prevent the url to change to a hashtag return false; }, selectMinutes: function (event) { var $td = $(event.currentTarget), id = $td.attr("data-timepicker-instance-id"), newMinutes = parseInt($td.attr("data-minute")), fromDoubleClick = event.data.fromDoubleClick, $target = $(id), inst = this._getInst($target[0]), showHours = (this._get(inst, 'showHours') == true); // don't select if disabled if ( $.timepicker._isDisabledTimepicker($target.attr('id')) ) { return false } $td.parents('.ui-timepicker-minutes:first').find('a').removeClass('ui-state-active'); $td.children('a').addClass('ui-state-active'); inst.minutes = newMinutes; this._updateSelectedValue(inst); inst._minutesClicked = true; if ((inst._hoursClicked) || (fromDoubleClick) || (showHours == false)) { $.timepicker._hideTimepicker(); // return false because if used inline, prevent the url to change to a hashtag return false; } // return false because if used inline, prevent the url to change to a hashtag return false; }, _updateSelectedValue: function (inst) { var newTime = this._getParsedTime(inst); if (inst.input) { inst.input.val(newTime); inst.input.trigger('change'); } var onSelect = this._get(inst, 'onSelect'); if (onSelect) { onSelect.apply((inst.input ? inst.input[0] : null), [newTime, inst]); } // trigger custom callback this._updateAlternate(inst, newTime); return newTime; }, /* this function process selected time and return it parsed according to instance options */ _getParsedTime: function(inst) { if (inst.hours == -1 && inst.minutes == -1) { return ''; } // default to 0 AM if hours is not valid if ((inst.hours < inst.hours.starts) || (inst.hours > inst.hours.ends )) { inst.hours = 0; } // default to 0 minutes if minute is not valid if ((inst.minutes < inst.minutes.starts) || (inst.minutes > inst.minutes.ends)) { inst.minutes = 0; } var period = "", showPeriod = (this._get(inst, 'showPeriod') == true), showLeadingZero = (this._get(inst, 'showLeadingZero') == true), showHours = (this._get(inst, 'showHours') == true), showMinutes = (this._get(inst, 'showMinutes') == true), optionalMinutes = (this._get(inst, 'optionalMinutes') == true), amPmText = this._get(inst, 'amPmText'), selectedHours = inst.hours ? inst.hours : 0, selectedMinutes = inst.minutes ? inst.minutes : 0, displayHours = selectedHours ? selectedHours : 0, parsedTime = ''; // fix some display problem when hours or minutes are not selected yet if (displayHours == -1) { displayHours = 0 } if (selectedMinutes == -1) { selectedMinutes = 0 } if (showPeriod) { if (inst.hours == 0) { displayHours = 12; } if (inst.hours < 12) { period = amPmText[0]; } else { period = amPmText[1]; if (displayHours > 12) { displayHours -= 12; } } } var h = displayHours.toString(); if (showLeadingZero && (displayHours < 10)) { h = '0' + h; } var m = selectedMinutes.toString(); if (selectedMinutes < 10) { m = '0' + m; } if (showHours) { parsedTime += h; } if (showHours && showMinutes && (!optionalMinutes || m != 0)) { parsedTime += this._get(inst, 'timeSeparator'); } if (showMinutes && (!optionalMinutes || m != 0)) { parsedTime += m; } if (showHours) { if (period.length > 0) { parsedTime += this._get(inst, 'periodSeparator') + period; } } return parsedTime; }, /* Update any alternate field to synchronise with the main field. */ _updateAlternate: function(inst, newTime) { var altField = this._get(inst, 'altField'); if (altField) { // update alternate field too $(altField).each(function(i,e) { $(e).val(newTime); }); } }, _getTimeAsDateTimepicker: function(input) { var inst = this._getInst(input); if (inst.hours == -1 && inst.minutes == -1) { return ''; } // default to 0 AM if hours is not valid if ((inst.hours < inst.hours.starts) || (inst.hours > inst.hours.ends )) { inst.hours = 0; } // default to 0 minutes if minute is not valid if ((inst.minutes < inst.minutes.starts) || (inst.minutes > inst.minutes.ends)) { inst.minutes = 0; } return new Date(0, 0, 0, inst.hours, inst.minutes, 0); }, /* This might look unused but it's called by the $.fn.timepicker function with param getTime */ /* added v 0.2.3 - gitHub issue #5 - Thanks edanuff */ _getTimeTimepicker : function(input) { var inst = this._getInst(input); return this._getParsedTime(inst); }, _getHourTimepicker: function(input) { var inst = this._getInst(input); if ( inst == undefined) { return -1; } return inst.hours; }, _getMinuteTimepicker: function(input) { var inst= this._getInst(input); if ( inst == undefined) { return -1; } return inst.minutes; } }); /* Invoke the timepicker functionality. @param options string - a command, optionally followed by additional parameters or Object - settings for attaching new timepicker functionality @return jQuery object */ $.fn.timepicker = function (options) { /* Initialise the time picker. */ if (!$.timepicker.initialized) { $(document).mousedown($.timepicker._checkExternalClick); $.timepicker.initialized = true; } /* Append timepicker main container to body if not exist. */ if ($("#"+$.timepicker._mainDivId).length === 0) { $('body').append($.timepicker.tpDiv); } var otherArgs = Array.prototype.slice.call(arguments, 1); if (typeof options == 'string' && (options == 'getTime' || options == 'getTimeAsDate' || options == 'getHour' || options == 'getMinute' )) return $.timepicker['_' + options + 'Timepicker']. apply($.timepicker, [this[0]].concat(otherArgs)); if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') return $.timepicker['_' + options + 'Timepicker']. apply($.timepicker, [this[0]].concat(otherArgs)); return this.each(function () { typeof options == 'string' ? $.timepicker['_' + options + 'Timepicker']. apply($.timepicker, [this].concat(otherArgs)) : $.timepicker._attachTimepicker(this, options); }); }; /* jQuery extend now ignores nulls! */ function extendRemove(target, props) { $.extend(target, props); for (var name in props) if (props[name] == null || props[name] == undefined) target[name] = props[name]; return target; }; $.timepicker = new Timepicker(); // singleton instance $.timepicker.initialized = false; $.timepicker.uuid = new Date().getTime(); $.timepicker.version = "0.3.3"; // Workaround for #4055 // Add another global to avoid noConflict issues with inline event handlers window['TP_jQuery_' + tpuuid] = $; })(jQuery);