Committer: vkurkin
LJSUP-10472: Changing display of setting date/time with creating/editing postA trunk/htdocs/js/jquery/jquery.dateentry.js A trunk/htdocs/js/jquery/jquery.dateentry.min.js U trunk/htdocs/js/jquery/jquery.lj.entryDatePicker.js
Added: trunk/htdocs/js/jquery/jquery.dateentry.js =================================================================== --- trunk/htdocs/js/jquery/jquery.dateentry.js (rev 0) +++ trunk/htdocs/js/jquery/jquery.dateentry.js 2011-11-25 13:36:04 UTC (rev 20627) @@ -0,0 +1,984 @@ +/* http://keith-wood.name/dateEntry.html + Date entry for jQuery v1.0.6. + Written by Keith Wood (kbwood{at}iinet.com.au) March 2009. + Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and + MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Please attribute the author if you use it. */ + +/* Turn an input field into an entry point for a date value. + The date can be entered via directly typing the value, + via the arrow keys, or via spinner buttons. + It is configurable to reorder the fields, to enforce a minimum + and/or maximum date, and to change the spinner image. + Attach it with $('input selector').dateEntry(); for default settings, + or configure it with options like: + $('input selector').dateEntry( + {spinnerImage: 'spinnerSquare.png', spinnerSize: [20, 20, 0]}); */ + +(function($) { // Hide scope, no $ conflict + + /* DateEntry manager. + Use the singleton instance of this class, $.dateEntry, to interact with the date + entry functionality. Settings for fields are maintained in an instance object, + allowing multiple different settings on the same page. */ + function DateEntry() { + this._disabledInputs = []; // List of date inputs that have been disabled + this.regional = []; // Available regional settings, indexed by language code + this.regional[''] = { // Default regional settings + dateFormat: 'mdy/', // The format of the date text: + // first three fields in order ('y' for year, 'Y' for two-digit year, + // 'm' for month, 'n' for abbreviated month name, 'N' for full month name, + // 'd' for day, 'w' for abbreviated day name and number, + // 'W' for full day name and number), followed by separator(s) + monthNames: ['January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December'], // Names of the months + monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // Abbreviated names of the months + dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + // Names of the days + dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // Abbreviated names of the days + spinnerTexts: ['Today', 'Previous field', 'Next field', 'Increment', 'Decrement'] + // The popup texts for the spinner image areas + }; + this._defaults = { + appendText: '', // Display text following the input box, e.g. showing the format + initialField: 0, // The field to highlight initially + useMouseWheel: true, // True to use mouse wheel for increment/decrement if possible, + // false to never use it + defaultDate: null, // The date to use if none has been set, leave at null for now + minDate: null, // The earliest selectable date, or null for no limit + maxDate: null, // The latest selectable date, or null for no limit + spinnerImage: 'spinnerDefault.png', // The URL of the images to use for the date spinner + // Seven images packed horizontally for normal, each button pressed, and disabled + spinnerSize: [20, 20, 8], // The width and height of the spinner image, + // and size of centre button for current date + spinnerBigImage: '', // The URL of the images to use for the expanded date spinner + // Seven images packed horizontally for normal, each button pressed, and disabled + spinnerBigSize: [40, 40, 16], // The width and height of the expanded spinner image, + // and size of centre button for current date + spinnerIncDecOnly: false, // True for increment/decrement buttons only, false for all + spinnerRepeat: [500, 250], // Initial and subsequent waits in milliseconds + // for repeats on the spinner buttons + beforeShow: null, // Function that takes an input field and + // returns a set of custom settings for the date entry + altField: null, // Selector, element or jQuery object for an alternate field to keep synchronised + altFormat: null // A separate format for the alternate field + }; + $.extend(this._defaults, this.regional['']); + } + + var PROP_NAME = 'dateEntry'; + + $.extend(DateEntry.prototype, { + /* Class name added to elements to indicate already configured with date entry. */ + markerClassName: 'hasDateEntry', + + /* Override the default settings for all instances of the date entry. + @param options (object) the new settings to use as defaults (anonymous object) + @return (DateEntry) this object */ + setDefaults: function(options) { + extendRemove(this._defaults, options || {}); + return this; + }, + + /* Attach the date entry handler to an input field. + @param target (element) the field to attach to + @param options (object) custom settings for this instance */ + _connectDateEntry: function(target, options) { + var input = $(target); + if (input.hasClass(this.markerClassName)) { + return; + } + var inst = {}; + inst.options = $.extend({}, options); + inst._selectedYear = 0; // The currently selected year + inst._selectedMonth = 0; // The currently selected month + inst._selectedDay = 0; // The currently selected day + inst._field = 0; // The selected subfield + inst.input = $(target); // The attached input field + $.data(target, PROP_NAME, inst); + var spinnerImage = this._get(inst, 'spinnerImage'); + var spinnerText = this._get(inst, 'spinnerText'); + var spinnerSize = this._get(inst, 'spinnerSize'); + var appendText = this._get(inst, 'appendText'); + var spinner = (!spinnerImage ? null : $('<span class="dateEntry_control" style="display: inline-block; ' + 'background: url(\'' + spinnerImage + '\') 0 0 no-repeat; ' + 'width: ' + spinnerSize[0] + 'px; height: ' + spinnerSize[1] + 'px;' + ($.browser.mozilla && $.browser.version < '1.9' ? // FF 2- (Win) + ' padding-left: ' + spinnerSize[0] + 'px; padding-bottom: ' + (spinnerSize[1] - 18) + 'px;' : '') + '"></span>')); + input.wrap('<span class="dateEntry_wrap"></span>').after(appendText ? '<span class="dateEntry_append">' + appendText + '</span>' : '').after(spinner || ''); + input.addClass(this.markerClassName).bind('focus.dateEntry', this._doFocus).bind('blur.dateEntry', this._doBlur).bind('click.dateEntry', this._doClick).bind('keydown.dateEntry', this._doKeyDown).bind('keypress.dateEntry', this._doKeyPress); + // Check pastes + if ($.browser.mozilla) { + input.bind('input.dateEntry', function(event) { + $.dateEntry._parseDate(inst); + }); + } + if ($.browser.msie) { + input.bind('paste.dateEntry', function(event) { + setTimeout(function() { + $.dateEntry._parseDate(inst); + }, 1); + }); + } + // Allow mouse wheel usage + if (this._get(inst, 'useMouseWheel') && $.fn.mousewheel) { + input.mousewheel(this._doMouseWheel); + } + if (spinner) { + spinner.mousedown(this._handleSpinner).mouseup(this._endSpinner).mouseover(this._expandSpinner).mouseout(this._endSpinner).mousemove(this._describeSpinner); + } + }, + + /* Enable a date entry input and any associated spinner. + @param input (element) single input field */ + _enableDateEntry: function(input) { + this._enableDisable(input, false); + }, + + /* Disable a date entry input and any associated spinner. + @param input (element) single input field */ + _disableDateEntry: function(input) { + this._enableDisable(input, true); + }, + + /* Enable or disable a date entry input and any associated spinner. + @param input (element) single input field + @param disable (boolean) true to disable, false to enable */ + _enableDisable: function(input, disable) { + var inst = $.data(input, PROP_NAME); + if (!inst) { + return; + } + input.disabled = disable; + if (input.nextSibling && input.nextSibling.nodeName.toLowerCase() == 'span') { + $.dateEntry._changeSpinner(inst, input.nextSibling, (disable ? 5 : -1)); + } + $.dateEntry._disabledInputs = $.map($.dateEntry._disabledInputs, function(value) { + return (value == input ? null : value); + }); // Delete entry + if (disable) { + $.dateEntry._disabledInputs.push(input); + } + }, + + /* Check whether an input field has been disabled. + @param input (element) input field to check + @return (boolean) true if this field has been disabled, false if it is enabled */ + _isDisabledDateEntry: function(input) { + return $.inArray(input, this._disabledInputs) > -1; + }, + + /* Reconfigure the settings for a date entry field. + @param input (element) input field to change + @param name (object) new settings to add or + (string) the option name + @param value (any, optional) the option value */ + _changeDateEntry: function(input, name, value) { + var inst = $.data(input, PROP_NAME); + if (inst) { + var options = name; + if (typeof name == 'string') { + options = {}; + options[name] = value; + } + var currentDate = this._extractDate(inst.input.val(), inst); + extendRemove(inst.options, options || {}); + if (currentDate) { + this._setDate(inst, currentDate); + } + } + $.data(input, PROP_NAME, inst); + }, + + /* Remove the date entry functionality from an input. + @param input (element) input field to affect */ + _destroyDateEntry: function(input) { + $input = $(input); + if (!$input.hasClass(this.markerClassName)) { + return; + } + $input.removeClass(this.markerClassName).unbind('.dateEntry'); + if ($.fn.mousewheel) { + $input.unmousewheel(); + } + this._disabledInputs = $.map(this._disabledInputs, function(value) { + return (value == input ? null : value); + }); // Delete entry + $input.parent().replaceWith($input); + $.removeData(input, PROP_NAME); + }, + + /* Initialise the current date for a date entry input field. + @param input (element) input field to update + @param date (Date) the new date or null for now */ + _setDateDateEntry: function(input, date) { + var inst = $.data(input, PROP_NAME); + if (inst) { + if (date === null || date === '') { + inst.input.val(''); + } else { + this._setDate(inst, date ? (typeof date == 'object' ? new Date(date.getTime()) : date) : null); + } + } + }, + + /* Retrieve the current date for a date entry input field. + @param input (element) input field to update + @return (Date) current date or null if none */ + _getDateDateEntry: function(input) { + var inst = $.data(input, PROP_NAME); + return (inst ? this._extractDate(inst.input.val(), inst) : null); + }, + + /* Initialise date entry. + @param target (element) the input field or + (event) the focus event */ + _doFocus: function(target) { + var input = (target.nodeName && target.nodeName.toLowerCase() == 'input' ? target : this); + if ($.dateEntry._lastInput == input || $.dateEntry._isDisabledDateEntry(input)) { + $.dateEntry._focussed = false; + return; + } + var inst = $.data(input, PROP_NAME); + $.dateEntry._focussed = true; + $.dateEntry._lastInput = input; + $.dateEntry._blurredInput = null; + var beforeShow = $.dateEntry._get(inst, 'beforeShow'); + extendRemove(inst.options, (beforeShow ? beforeShow.apply(input, [input]) : {})); + $.data(input, PROP_NAME, inst); + $.dateEntry._parseDate(inst); + setTimeout(function() { + $.dateEntry._showField(inst); + }, 10); + }, + + /* Note that the field has been exited. + @param event (event) the blur event */ + _doBlur: function(event) { + $.dateEntry._blurredInput = $.dateEntry._lastInput; + $.dateEntry._lastInput = null; + }, + + /* Select appropriate field portion on click, if already in the field. + @param event (event) the click event */ + _doClick: function(event) { + var input = event.target; + var inst = $.data(input, PROP_NAME); + if (!$.dateEntry._focussed) { + var dateFormat = $.dateEntry._get(inst, 'dateFormat'); + inst._field = 0; + if (input.selectionStart != null) { // Use input select range + var end = 0; + for (var field = 0; field < 3; field++) { + end += $.dateEntry._fieldLength(inst, field, dateFormat) + 1; + inst._field = field; + if (input.selectionStart < end) { + break; + } + } + } else if (input.createTextRange) { // Check against bounding boxes + var src = $(event.srcElement); + var range = input.createTextRange(); + var convert = function(value) { + return {thin: 2, medium: 4, thick: 6}[value] || value; + }; + var offsetX = event.clientX + document.documentElement.scrollLeft - (src.offset().left + parseInt(convert(src.css('border-left-width')), 10)) - range.offsetLeft; // Position - left edge - alignment + var end = 0; + for (var field = 0; field < 3; field++) { + end += $.dateEntry._fieldLength(inst, field, dateFormat) + 1; + range.collapse(); + range.moveEnd('character', end); + inst._field = field; + if (offsetX < range.boundingWidth) { // And compare + break; + } + } + } + } + $.data(input, PROP_NAME, inst); + $.dateEntry._showField(inst); + $.dateEntry._focussed = false; + }, + + /* Handle keystrokes in the field. + @param event (event) the keydown event + @return (boolean) true to continue, false to stop processing */ + _doKeyDown: function(event) { + if (event.keyCode >= 48) { // >= '0' + return true; + } + var inst = $.data(event.target, PROP_NAME); + switch (event.keyCode) { + case 9: + return (event.shiftKey ? // Move to previous date field, or out if at the beginning + $.dateEntry._changeField(inst, -1, true) : // Move to next date field, or out if at the end + $.dateEntry._changeField(inst, +1, true)); + case 35: + if (event.ctrlKey) { // Clear date on ctrl+end + $.dateEntry._setValue(inst, ''); + } else { // Last field on end + inst._field = 2; + $.dateEntry._adjustField(inst, 0); + } + break; + case 36: + if (event.ctrlKey) { // Current date on ctrl+home + $.dateEntry._setDate(inst); + } else { // First field on home + inst._field = 0; + $.dateEntry._adjustField(inst, 0); + } + break; + case 37: + $.dateEntry._changeField(inst, -1, false); + break; // Previous field on left + case 38: + $.dateEntry._adjustField(inst, +1); + break; // Increment date field on up + case 39: + $.dateEntry._changeField(inst, +1, false); + break; // Next field on right + case 40: + $.dateEntry._adjustField(inst, -1); + break; // Decrement date field on down + case 46: + $.dateEntry._setValue(inst, ''); + break; // Clear date on delete + } + return false; + }, + + /* Disallow unwanted characters. + @param event (event) the keypress event + @return (boolean) true to continue, false to stop processing */ + _doKeyPress: function(event) { + var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode); + if (chr < ' ') { + return true; + } + var inst = $.data(event.target, PROP_NAME); + $.dateEntry._handleKeyPress(inst, chr); + return false; + }, + + /* Increment/decrement on mouse wheel activity. + @param event (event) the mouse wheel event + @param delta (number) the amount of change */ + _doMouseWheel: function(event, delta) { + if ($.dateEntry._isDisabledDateEntry(event.target)) { + return; + } + delta = ($.browser.opera ? -delta / Math.abs(delta) : ($.browser.safari ? delta / Math.abs(delta) : delta)); + var inst = $.data(event.target, PROP_NAME); + inst.input.focus(); + if (!inst.input.val()) { + $.dateEntry._parseDate(inst); + } + $.dateEntry._adjustField(inst, delta); + event.preventDefault(); + }, + + /* Expand the spinner, if possible, to make it easier to use. + @param event (event) the mouse over event */ + _expandSpinner: function(event) { + var spinner = $.dateEntry._getSpinnerTarget(event); + var inst = $.data($.dateEntry._getInput(spinner), PROP_NAME); + if ($.dateEntry._isDisabledDateEntry(inst.input[0])) { + return; + } + var spinnerBigImage = $.dateEntry._get(inst, 'spinnerBigImage'); + if (spinnerBigImage) { + inst._expanded = true; + var offset = $(spinner).offset(); + var relative = null; + $(spinner).parents().each(function() { + var parent = $(this); + if (parent.css('position') == 'relative' || parent.css('position') == 'absolute') { + relative = parent.offset(); + } + return !relative; + }); + var spinnerSize = $.dateEntry._get(inst, 'spinnerSize'); + var spinnerBigSize = $.dateEntry._get(inst, 'spinnerBigSize'); + $('<div class="dateEntry_expand" style="position: absolute; left: ' + (offset.left - (spinnerBigSize[0] - spinnerSize[0]) / 2 - (relative ? relative.left : 0)) + 'px; top: ' + (offset.top - (spinnerBigSize[1] - spinnerSize[1]) / 2 - (relative ? relative.top : 0)) + 'px; width: ' + spinnerBigSize[0] + 'px; height: ' + spinnerBigSize[1] + 'px; background: transparent url(' + spinnerBigImage + ') no-repeat 0px 0px; z-index: 10;"></div>').mousedown($.dateEntry._handleSpinner).mouseup($.dateEntry._endSpinner).mouseout($.dateEntry._endExpand).mousemove($.dateEntry._describeSpinner).insertAfter(spinner); + } + }, + + /* Locate the actual input field from the spinner. + @param spinner (element) the current spinner + @return (element) the corresponding input */ + _getInput: function(spinner) { + return $(spinner).siblings('.' + $.dateEntry.markerClassName)[0]; + }, + + /* Change the title based on position within the spinner. + @param event (event) the mouse move event */ + _describeSpinner: function(event) { + var spinner = $.dateEntry._getSpinnerTarget(event); + var inst = $.data($.dateEntry._getInput(spinner), PROP_NAME); + spinner.title = $.dateEntry._get(inst, 'spinnerTexts') + [$.dateEntry._getSpinnerRegion(inst, event)]; + }, + + /* Handle a click on the spinner. + @param event (event) the mouse click event */ + _handleSpinner: function(event) { + var spinner = $.dateEntry._getSpinnerTarget(event); + var input = $.dateEntry._getInput(spinner); + if ($.dateEntry._isDisabledDateEntry(input)) { + return; + } + if (input == $.dateEntry._blurredInput) { + $.dateEntry._lastInput = input; + $.dateEntry._blurredInput = null; + } + var inst = $.data(input, PROP_NAME); + $.dateEntry._doFocus(input); + var region = $.dateEntry._getSpinnerRegion(inst, event); + $.dateEntry._changeSpinner(inst, spinner, region); + $.dateEntry._actionSpinner(inst, region); + $.dateEntry._timer = null; + $.dateEntry._handlingSpinner = true; + var spinnerRepeat = $.dateEntry._get(inst, 'spinnerRepeat'); + if (region >= 3 && spinnerRepeat[0]) { // Repeat increment/decrement + $.dateEntry._timer = setTimeout(function() { + $.dateEntry._repeatSpinner(inst, region); + }, spinnerRepeat[0]); + $(spinner).one('mouseout', $.dateEntry._releaseSpinner).one('mouseup', $.dateEntry._releaseSpinner); + } + }, + + /* Action a click on the spinner. + @param inst (object) the instance settings + @param region (number) the spinner "button" */ + _actionSpinner: function(inst, region) { + if (!inst.input.val()) { + $.dateEntry._parseDate(inst); + } + switch (region) { + case 0: + this._setDate(inst); + break; + case 1: + this._changeField(inst, -1, false); + break; + case 2: + this._changeField(inst, +1, false); + break; + case 3: + this._adjustField(inst, +1); + break; + case 4: + this._adjustField(inst, -1); + break; + } + }, + + /* Repeat a click on the spinner. + @param inst (object) the instance settings + @param region (number) the spinner "button" */ + _repeatSpinner: function(inst, region) { + if (!$.dateEntry._timer) { + return; + } + $.dateEntry._lastInput = $.dateEntry._blurredInput; + this._actionSpinner(inst, region); + this._timer = setTimeout(function() { + $.dateEntry._repeatSpinner(inst, region); + }, this._get(inst, 'spinnerRepeat')[1]); + }, + + /* Stop a spinner repeat. + @param event (event) the mouse event */ + _releaseSpinner: function(event) { + clearTimeout($.dateEntry._timer); + $.dateEntry._timer = null; + }, + + /* Tidy up after an expanded spinner. + @param event (event) the mouse event */ + _endExpand: function(event) { + $.dateEntry._timer = null; + var spinner = $.dateEntry._getSpinnerTarget(event); + var input = $.dateEntry._getInput(spinner); + var inst = $.data(input, PROP_NAME); + $(spinner).remove(); + inst._expanded = false; + }, + + /* Tidy up after a spinner click. + @param event (event) the mouse event */ + _endSpinner: function(event) { + $.dateEntry._timer = null; + var spinner = $.dateEntry._getSpinnerTarget(event); + var input = $.dateEntry._getInput(spinner); + var inst = $.data(input, PROP_NAME); + if (!$.dateEntry._isDisabledDateEntry(input)) { + $.dateEntry._changeSpinner(inst, spinner, -1); + } + if ($.dateEntry._handlingSpinner) { + $.dateEntry._lastInput = $.dateEntry._blurredInput; + } + if ($.dateEntry._lastInput && $.dateEntry._handlingSpinner) { + $.dateEntry._showField(inst); + } + $.dateEntry._handlingSpinner = false; + }, + + /* Retrieve the spinner from the event. + @param event (event) the mouse click event + @return (element) the target field */ + _getSpinnerTarget: function(event) { + return event.target || event.srcElement; + }, + + /* Determine which "button" within the spinner was clicked. + @param inst (object) the instance settings + @param event (event) the mouse event + @return (number) the spinner "button" number */ + _getSpinnerRegion: function(inst, event) { + var spinner = this._getSpinnerTarget(event); + var pos = ($.browser.opera || $.browser.safari ? $.dateEntry._findPos(spinner) : $(spinner).offset()); + var scrolled = ($.browser.safari ? $.dateEntry._findScroll(spinner) : [document.documentElement.scrollLeft || document.body.scrollLeft, + document.documentElement.scrollTop || document.body.scrollTop]); + var spinnerIncDecOnly = this._get(inst, 'spinnerIncDecOnly'); + var left = (spinnerIncDecOnly ? 99 : event.clientX + scrolled[0] - pos.left - ($.browser.msie ? 2 : 0)); + var top = event.clientY + scrolled[1] - pos.top - ($.browser.msie ? 2 : 0); + var spinnerSize = this._get(inst, (inst._expanded ? 'spinnerBigSize' : 'spinnerSize')); + var right = (spinnerIncDecOnly ? 99 : spinnerSize[0] - 1 - left); + var bottom = spinnerSize[1] - 1 - top; + if (spinnerSize[2] > 0 && Math.abs(left - right) <= spinnerSize[2] && Math.abs(top - bottom) <= spinnerSize[2]) { + return 0; // Centre button + } + var min = Math.min(left, top, right, bottom); + return (min == left ? 1 : (min == right ? 2 : (min == top ? 3 : 4))); // Nearest edge + }, + + /* Change the spinner image depending on button clicked. + @param inst (object) the instance settings + @param spinner (element) the spinner control + @param region (number) the spinner "button" */ + _changeSpinner: function(inst, spinner, region) { + $(spinner).css('background-position', '-' + ((region + 1) * this._get(inst, (inst._expanded ? 'spinnerBigSize' : 'spinnerSize'))[0]) + 'px 0px'); + }, + + /* Find an object's position on the screen. + @param obj (element) the control + @return (object) position as .left and .top */ + _findPos: function(obj) { + var curLeft = curTop = 0; + if (obj.offsetParent) { + curLeft = obj.offsetLeft; + curTop = obj.offsetTop; + while (obj = obj.offsetParent) { + var origCurLeft = curLeft; + curLeft += obj.offsetLeft; + if (curLeft < 0) { + curLeft = origCurLeft; + } + curTop += obj.offsetTop; + } + } + return {left: curLeft, top: curTop}; + }, + + /* Find an object's scroll offset on the screen. + @param obj (element) the control + @return (number[]) offset as [left, top] */ + _findScroll: function(obj) { + var isFixed = false; + $(obj).parents().each(function() { + isFixed |= $(this).css('position') == 'fixed'; + }); + if (isFixed) { + return [0, 0]; + } + var scrollLeft = obj.scrollLeft; + var scrollTop = obj.scrollTop; + while (obj = obj.parentNode) { + scrollLeft += obj.scrollLeft || 0; + scrollTop += obj.scrollTop || 0; + } + return [scrollLeft, scrollTop]; + }, + + /* Get a setting value, defaulting if necessary. + @param inst (object) the instance settings + @param name (string) the setting name + @return (any) the setting value */ + _get: function(inst, name) { + return (inst.options[name] != null ? inst.options[name] : $.dateEntry._defaults[name]); + }, + + /* Extract the date value from the input field, or default to now. + @param inst (object) the instance settings */ + _parseDate: function(inst) { + var currentDate = this._extractDate(inst.input.val(), inst) || this._normaliseDate(this._determineDate(this._get(inst, 'defaultDate'), inst) || new Date()); + inst._selectedYear = currentDate.getFullYear(); + inst._selectedMonth = currentDate.getMonth(); + inst._selectedDay = currentDate.getDate(); + inst._lastChr = ''; + inst._field = Math.max(0, Math.min(2, this._get(inst, 'initialField'))); + if (inst.input.val() != '') { + this._showDate(inst); + } + }, + + /* Extract the date value from a string. + @param value (string) the date text + @param inst (object) the instance settings + @return (Date) the retrieved date or null if no value */ + _extractDate: function(value, inst) { + var dateFormat = this._get(inst, 'dateFormat'); + var values = value.split(new RegExp('[\\' + dateFormat.substr(-1).split('').join('\\') + ']')); + var year = inst._selectedYear; + var month = inst._selectedMonth + 1; + var day = inst._selectedDay; + for (var i = 0, l = values.length; i < l; i++) { + var num = parseInt(values[i], 10); + num = (isNaN(num) ? 0 : num); + var field = dateFormat.charAt(i); + switch (field) { + case 'y': + year = num; + break; + case 'Y': + year = (num % 100) + (new Date().getFullYear() - new Date().getFullYear() % 100); + break; + case 'm': + month = num; + break; + case 'n': + case 'N': + month = $.inArray(values[i], this._get(inst, (field == 'N' ? 'monthNames' : 'monthNamesShort'))) + 1; + break; + case 'w': + case 'W': + if (dateFormat.charAt(3) == ' ') { + values.splice(i, 1); + num = parseInt(values[i], 10); + } else { + num = parseInt(values[i].substr(this._get(inst, (field == 'W' ? 'dayNames' : 'dayNamesShort'))[0].length + 1), 10); + } + num = (isNaN(num) ? 0 : num); // Fall through + case 'd': + day = num; + break; + } + } + + return new Date(year, month - 1, day, 12); + }, + + /* Set the selected date into the input field. + @param inst (object) the instance settings */ + _showDate: function(inst) { + this._setValue(inst, this._formatDate(inst, this._get(inst, 'dateFormat'))); + this._showField(inst); + }, + + /* Format a date as requested. + @param inst (object) the instance settings + @param format (string) the date format to use + @return (string) the formatted date */ + _formatDate: function(inst, format) { + var currentDate = ''; + for (var i = 0, l = format.length - 1; i < l; i++) { + currentDate += (i == 0 ? '' : format.charAt(format.length - 1)); + var field = format.charAt(i); + switch (field) { + case 'y': + currentDate += this._formatNumber(inst._selectedYear); + break; + case 'Y': + currentDate += this._formatNumber(inst._selectedYear % 100); + break; + case 'm': + currentDate += this._formatNumber(inst._selectedMonth + 1); + break; + case 'n': + case 'N': + currentDate += this._get(inst, (field == 'N' ? 'monthNames' : 'monthNamesShort'))[inst._selectedMonth]; + break; + case 'd': + currentDate += this._formatNumber(inst._selectedDay); + break; + case 'w': + case 'W': + currentDate += this._get(inst, (field == 'W' ? 'dayNames' : 'dayNamesShort'))[ + new Date(inst._selectedYear, inst._selectedMonth, inst._selectedDay, 12).getDay()] + ' ' + this._formatNumber(inst._selectedDay); + break; + } + } + return currentDate; + }, + + /* Highlight the current date field. + @param inst (object) the instance settings */ + _showField: function(inst) { + var input = inst.input[0]; + if (inst.input.is(':hidden') || $.dateEntry._lastInput != input) { + return; + } + var dateFormat = this._get(inst, 'dateFormat'); + var start = 0; + for (var i = 0; i < inst._field; i++) { + start += this._fieldLength(inst, i, dateFormat) + 1; + } + var end = start + this._fieldLength(inst, i, dateFormat); + if (input.setSelectionRange) { // Mozilla + input.setSelectionRange(start, end); + } else if (input.createTextRange) { // IE + var range = input.createTextRange(); + range.moveStart('character', start); + range.moveEnd('character', end - inst.input.val().length); + range.select(); + } + if (!input.disabled) { + input.focus(); + } + }, + + /* Calculate the field length. + @param inst (object) the instance settings + @param field (number) the field number (0-2) + @param dateFormat (string) the format for the date display + @return (number) the length of this subfield */ + _fieldLength: function(inst, field, dateFormat) { + field = dateFormat.charAt(field); + switch (field) { + case 'y': + return 4; + case 'n': + case 'N': + return this._get(inst, (field == 'N' ? 'monthNames' : 'monthNamesShort')) + [inst._selectedMonth].length; + case 'w': + case 'W': + return this._get(inst, (field == 'W' ? 'dayNames' : 'dayNamesShort')) + [new Date(inst._selectedYear, inst._selectedMonth, inst._selectedDay, 12).getDay()].length + 3; + default: + return 2; + } + }, + + /* Ensure displayed single number has a leading zero. + @param value (number) current value + @return (string) number with at least two digits */ + _formatNumber: function(value) { + return (value < 10 ? '0' : '') + value; + }, + + /* Update the input field and notify listeners. + @param inst (object) the instance settings + @param value (string) the new value */ + _setValue: function(inst, value) { + if (value != inst.input.val()) { + var altField = this._get(inst, 'altField'); + if (altField) { + $(altField).val(!value ? '' : this._formatDate(inst, this._get(inst, 'altFormat') || this._get(inst, 'dateFormat'))); + } + inst.input.val(value).trigger('change'); + } + }, + + /* Move to previous/next field, or out of field altogether if appropriate. + @param inst (object) the instance settings + @param offset (number) the direction of change (-1, +1) + @param moveOut (boolean) true if can move out of the field + @return (boolean) true if exitting the field, false if not */ + _changeField: function(inst, offset, moveOut) { + var atFirstLast = (inst.input.val() == '' || inst._field == (offset == -1 ? 0 : 2)); + if (!atFirstLast) { + inst._field += offset; + } + this._showField(inst); + inst._lastChr = ''; + $.data(inst.input[0], PROP_NAME, inst); + return (atFirstLast && moveOut); + }, + + /* Update the current field in the direction indicated. + @param inst (object) the instance settings + @param offset (number) the amount to change by */ + _adjustField: function(inst, offset) { + if (inst.input.val() == '') { + offset = 0; + } + var field = this._get(inst, 'dateFormat').charAt(inst._field); + var year = inst._selectedYear + (field == 'y' || field == 'Y' ? offset : 0); + var month = inst._selectedMonth + (field == 'm' || field == 'n' || field == 'N' ? offset : 0); + var day = (field == 'd' || field == 'w' || field == 'W' ? inst._selectedDay + offset : Math.min(inst._selectedDay, this._getDaysInMonth(year, month))); + this._setDate(inst, new Date(year, month, day, 12)); + }, + + /* Find the number of days in a given month. + @param year (number) the full year + @param month (number) the month (0 to 11) + @return (number) the number of days in this month */ + _getDaysInMonth: function(year, month) { + return new Date(year, month + 1, 0, 12).getDate(); + }, + + /* Check against minimum/maximum and display date. + @param inst (object) the instance settings + @param date (Date) an actual date or + (number) offset in days from now or + (string) units and periods of offsets from now */ + _setDate: function(inst, date) { + // Normalise to base time + date = this._normaliseDate(this._determineDate(date || this._get(inst, 'defaultDate'), inst) || new Date()); + var minDate = this._normaliseDate(this._determineDate(this._get(inst, 'minDate'), inst)); + var maxDate = this._normaliseDate(this._determineDate(this._get(inst, 'maxDate'), inst)); + // Ensure it is within the bounds set + date = (minDate && date < minDate ? minDate : (maxDate && date > maxDate ? maxDate : date)); + inst._selectedYear = date.getFullYear(); + inst._selectedMonth = date.getMonth(); + inst._selectedDay = date.getDate(); + this._showDate(inst); + $.data(inst.input[0], PROP_NAME, inst); + }, + + /* A date may be specified as an exact value or a relative one. + @param setting (Date) an actual date or + (string) date in current format + (number) offset in days from now or + (string) units and periods of offsets from now + @param inst (object) the instance settings + @return (Date) the calculated date */ + _determineDate: function(setting, inst) { + var offsetNumeric = function(offset) { // E.g. +300, -2 + var date = $.dateEntry._normaliseDate(new Date()); + date.setDate(date.getDate() + offset); + return date; + }; + var offsetString = function(offset) { // E.g. '+2m', '-1w', '+3m +10d' + var date = $.dateEntry._extractDate(offset, inst); + if (date) { + return date; + } + offset = offset.toLowerCase(); + date = $.dateEntry._normaliseDate(new Date()); + var year = date.getFullYear(); + var month = date.getMonth(); + var day = date.getDate(); + var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g; + var matches = pattern.exec(offset); + while (matches) { + switch (matches[2] || 'd') { + case 'd': + day += parseInt(matches[1], 10); + break; + case 'w': + day += parseInt(matches[1], 10) * 7; + break; + case 'm': + month += parseInt(matches[1], 10); + break; + case 'y': + year += parseInt(matches[1], 10); + break; + } + matches = pattern.exec(offset); + } + return new Date(year, month, day, 12); + }; + return (setting ? (typeof setting == 'string' ? offsetString(setting) : (typeof setting == 'number' ? offsetNumeric(setting) : setting)) : null); + }, + + /* Normalise date object to a common time. + @param date (Date) the original date + @return (Date) the normalised date */ + _normaliseDate: function(date) { + if (date) { + date.setHours(12, 0, 0, 0); + } + return date; + }, + + /* Update date based on keystroke entered. + @param inst (object) the instance settings + @param chr (ch) the new character */ + _handleKeyPress: function(inst, chr) { + var dateFormat = this._get(inst, 'dateFormat'); + if (dateFormat.substring(3).indexOf(chr) > -1) { + this._changeField(inst, +1, false); + } else if (chr >= '0' && chr <= '9') { // Allow direct entry of date + var field = dateFormat.charAt(inst._field); + var key = parseInt(chr, 10); + var value = parseInt((inst._lastChr || '') + chr, 10); + var year = (field != 'y' && field != 'Y' ? inst._selectedYear : value); + var month = (field != 'm' && field != 'n' && field != 'N' ? inst._selectedMonth + 1 : (value >= 1 && value <= 12 ? value : (key > 0 ? key : inst._selectedMonth + 1))); + var day = (field != 'd' && field != 'w' && field != 'W' ? inst._selectedDay : (value >= 1 && value <= this._getDaysInMonth(year, month - 1) ? value : (key > 0 ? key : inst._selectedDay))); + this._setDate(inst, new Date(year, month - 1, day, 12)); + inst._lastChr = (field != 'y' ? '' : inst._lastChr.substr(Math.max(0, inst._lastChr.length - 2))) + chr; + } else { // Allow text entry by month name + var field = dateFormat.charAt(inst._field); + if (field == 'n' || field == 'N') { + inst._lastChr += chr.toLowerCase(); + var names = this._get(inst, (field == 'n' ? 'monthNamesShort' : 'monthNames')); + var findMonth = function() { + for (var i = 0; i < names.length; i++) { + if (names[i].toLowerCase().substring(0, inst._lastChr.length) == inst._lastChr) { + return i; + break; + } + } + return -1; + }; + var month = findMonth(); + if (month == -1) { + inst._lastChr = chr.toLowerCase(); + month = findMonth(); + } + if (month == -1) { + inst._lastChr = ''; + } else { + var year = inst._selectedYear; + var day = Math.min(inst._selectedDay, this._getDaysInMonth(year, month)); + this._setDate(inst, new Date(year, month, day, 12)); + } + } + } + } + }); + + /* jQuery extend now ignores nulls! + @param target (object) the object to update + @param props (object) the new settings + @return (object) the updated object */ + function extendRemove(target, props) { + $.extend(target, props); + for (var name in props) { + if (props[name] == null) { + target[name] = null; + } + } + return target; + } + + /* Attach the date entry functionality to a jQuery selection. + @param command (string) the command to run (optional, default 'attach') + @param options (object) the new settings to use for these countdown instances (optional) + @return (jQuery) for chaining further calls */ + $.fn.dateEntry = function(options) { + var otherArgs = Array.prototype.slice.call(arguments, 1); + if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate')) { + return $.dateEntry['_' + options + 'DateEntry'].apply($.dateEntry, [this[0]].concat(otherArgs)); + } + return this.each(function() { + var nodeName = this.nodeName.toLowerCase(); + if (nodeName == 'input') { + if (typeof options == 'string') { + $.dateEntry['_' + options + 'DateEntry'].apply($.dateEntry, [this].concat(otherArgs)); + } else { + // Check for settings on the control itself + var inlineSettings = ($.fn.metadata ? $(this).metadata() : {}); + $.dateEntry._connectDateEntry(this, $.extend(inlineSettings, options)); + } + } + }); + }; + + /* Initialise the date entry functionality. */ + $.dateEntry = new DateEntry(); // Singleton instance + +})(jQuery); Added: trunk/htdocs/js/jquery/jquery.dateentry.min.js =================================================================== --- trunk/htdocs/js/jquery/jquery.dateentry.min.js (rev 0) +++ trunk/htdocs/js/jquery/jquery.dateentry.min.js 2011-11-25 13:36:04 UTC (rev 20627) @@ -0,0 +1,7 @@ +/* http://keith-wood.name/dateEntry.html + Date entry for jQuery v1.0.6. + Written by Keith Wood (kbwood{at}iinet.com.au) March 2009. + Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and + MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. + Please attribute the author if you use it. */ +(function(b){function m(){this._disabledInputs=[];this.regional=[];this.regional[""]={dateFormat:"mdy/",monthNames:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),monthNamesShort:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),dayNames:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),dayNamesShort:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),spinnerTexts:["Today","Previous field","Next field","Increment","Decrement"]}; this._defaults={appendText:"",initialField:0,useMouseWheel:!0,defaultDate:null,minDate:null,maxDate:null,spinnerImage:"spinnerDefault.png",spinnerSize:[20,20,8],spinnerBigImage:"",spinnerBigSize:[40,40,16],spinnerIncDecOnly:!1,spinnerRepeat:[500,250],beforeShow:null,altField:null,altFormat:null};b.extend(this._defaults,this.regional[""])}function l(a,c){b.extend(a,c);for(var d in c)null==c[d]&&(a[d]=null);return a}b.extend(m.prototype,{markerClassName:"hasDateEntry",setDefaults:function(a){l(this._defaults, a||{});return this},_connectDateEntry:function(a,c){var d=b(a);if(!d.hasClass(this.markerClassName)){var e={};e.options=b.extend({},c);e._selectedYear=0;e._selectedMonth=0;e._selectedDay=0;e._field=0;e.input=b(a);b.data(a,"dateEntry",e);var f=this._get(e,"spinnerImage");this._get(e,"spinnerText");var g=this._get(e,"spinnerSize"),h=this._get(e,"appendText"),f=!f?null:b('<span class="dateEntry_control" style="display: inline-block; background: url(\''+f+"') 0 0 no-repeat; width: "+g[0]+"px; height: "+ g[1]+"px;"+(b.browser.mozilla&&"1.9">b.browser.version?" padding-left: "+g[0]+"px; padding-bottom: "+(g[1]-18)+"px;":"")+'"></span>');d.wrap('<span class="dateEntry_wrap"></span>').after(h?'<span class="dateEntry_append">'+h+"</span>":"").after(f||"");d.addClass(this.markerClassName).bind("focus.dateEntry",this._doFocus).bind("blur.dateEntry",this._doBlur).bind("click.dateEntry",this._doClick).bind("keydown.dateEntry",this._doKeyDown).bind("keypress.dateEntry",this._doKeyPress);b.browser.mozilla&& d.bind("input.dateEntry",function(){b.dateEntry._parseDate(e)});b.browser.msie&&d.bind("paste.dateEntry",function(){setTimeout(function(){b.dateEntry._parseDate(e)},1)});this._get(e,"useMouseWheel")&&b.fn.mousewheel&&d.mousewheel(this._doMouseWheel);f&&f.mousedown(this._handleSpinner).mouseup(this._endSpinner).mouseover(this._expandSpinner).mouseout(this._endSpinner).mousemove(this._describeSpinner)}},_enableDateEntry:function(a){this._enableDisable(a,!1)},_disableDateEntry:function(a){this._enableDisable(a, !0)},_enableDisable:function(a,c){var d=b.data(a,"dateEntry");if(d)a.disabled=c,a.nextSibling&&"span"==a.nextSibling.nodeName.toLowerCase()&&b.dateEntry._changeSpinner(d,a.nextSibling,c?5:-1),b.dateEntry._disabledInputs=b.map(b.dateEntry._disabledInputs,function(c){return c==a?null:c}),c&&b.dateEntry._disabledInputs.push(a)},_isDisabledDateEntry:function(a){return-1<b.inArray(a,this._disabledInputs)},_changeDateEntry:function(a,c,d){var e=b.data(a,"dateEntry");if(e){var f=c;"string"==typeof c&&(f= {},f[c]=d);c=this._extractDate(e.input.val(),e);l(e.options,f||{});c&&this._setDate(e,c)}b.data(a,"dateEntry",e)},_destroyDateEntry:function(a){$input=b(a);if($input.hasClass(this.markerClassName))$input.removeClass(this.markerClassName).unbind(".dateEntry"),b.fn.mousewheel&&$input.unmousewheel(),this._disabledInputs=b.map(this._disabledInputs,function(c){return c==a?null:c}),$input.parent().replaceWith($input),b.removeData(a,"dateEntry")},_setDateDateEntry:function(a,c){var d=b.data(a,"dateEntry"); d&&(null===c||""===c?d.input.val(""):this._setDate(d,c?"object"==typeof c?new Date(c.getTime()):c:null))},_getDateDateEntry:function(a){return(a=b.data(a,"dateEntry"))?this._extractDate(a.input.val(),a):null},_doFocus:function(a){a=a.nodeName&&"input"==a.nodeName.toLowerCase()?a:this;if(b.dateEntry._lastInput==a||b.dateEntry._isDisabledDateEntry(a))b.dateEntry._focussed=!1;else{var c=b.data(a,"dateEntry");b.dateEntry._focussed=!0;b.dateEntry._lastInput=a;b.dateEntry._blurredInput=null;var d=b.dateEntry._get(c, "beforeShow");l(c.options,d?d.apply(a,[a]):{});b.data(a,"dateEntry",c);b.dateEntry._parseDate(c);setTimeout(function(){b.dateEntry._showField(c)},10)}},_doBlur:function(){b.dateEntry._blurredInput=b.dateEntry._lastInput;b.dateEntry._lastInput=null},_doClick:function(a){var c=a.target,d=b.data(c,"dateEntry");if(!b.dateEntry._focussed){var e=b.dateEntry._get(d,"dateFormat");d._field=0;if(null!=c.selectionStart)for(var f=0,a=0;3>a&&!(f+=b.dateEntry._fieldLength(d,a,e)+1,d._field=a,c.selectionStart<f);a++); else if(c.createTextRange)for(var f=b(a.srcElement),g=c.createTextRange(),a=a.clientX+document.documentElement.scrollLeft,h=f.offset().left,i=parseInt,f=f.css("border-left-width"),h=a-(h+i({thin:2,medium:4,thick:6}[f]||f,10))-g.offsetLeft,a=f=0;3>a&&!(f+=b.dateEntry._fieldLength(d,a,e)+1,g.collapse(),g.moveEnd("character",f),d._field=a,h<g.boundingWidth);a++);}b.data(c,"dateEntry",d);b.dateEntry._showField(d);b.dateEntry._focussed=!1},_doKeyDown:function(a){if(48<=a.keyCode)return!0;var c=b.data(a.target, "dateEntry");switch(a.keyCode){case 9:return a.shiftKey?b.dateEntry._changeField(c,-1,!0):b.dateEntry._changeField(c,1,!0);case 35:a.ctrlKey?b.dateEntry._setValue(c,""):(c._field=2,b.dateEntry._adjustField(c,0));break;case 36:a.ctrlKey?b.dateEntry._setDate(c):(c._field=0,b.dateEntry._adjustField(c,0));break;case 37:b.dateEntry._changeField(c,-1,!1);break;case 38:b.dateEntry._adjustField(c,1);break;case 39:b.dateEntry._changeField(c,1,!1);break;case 40:b.dateEntry._adjustField(c,-1);break;case 46:b.dateEntry._setValue(c, "")}return!1},_doKeyPress:function(a){var c=String.fromCharCode(void 0==a.charCode?a.keyCode:a.charCode);if(" ">c)return!0;a=b.data(a.target,"dateEntry");b.dateEntry._handleKeyPress(a,c);return!1},_doMouseWheel:function(a,c){if(!b.dateEntry._isDisabledDateEntry(a.target)){var c=b.browser.opera?-c/Math.abs(c):b.browser.safari?c/Math.abs(c):c,d=b.data(a.target,"dateEntry");d.input.focus();d.input.val()||b.dateEntry._parseDate(d);b.dateEntry._adjustField(d,c);a.preventDefault()}},_expandSpinner:function(a){var a= b.dateEntry._getSpinnerTarget(a),c=b.data(b.dateEntry._getInput(a),"dateEntry");if(!b.dateEntry._isDisabledDateEntry(c.input[0])){var d=b.dateEntry._get(c,"spinnerBigImage");if(d){c._expanded=!0;var e=b(a).offset(),f=null;b(a).parents().each(function(){var a=b(this);if("relative"==a.css("position")||"absolute"==a.css("position"))f=a.offset();return!f});var g=b.dateEntry._get(c,"spinnerSize"),c=b.dateEntry._get(c,"spinnerBigSize");b('<div class="dateEntry_expand" style="position: absolute; left: '+ (e.left-(c[0]-g[0])/2-(f?f.left:0))+"px; top: "+(e.top-(c[1]-g[1])/2-(f?f.top:0))+"px; width: "+c[0]+"px; height: "+c[1]+"px; background: transparent url("+d+') no-repeat 0px 0px; z-index: 10;"></div>').mousedown(b.dateEntry._handleSpinner).mouseup(b.dateEntry._endSpinner).mouseout(b.dateEntry._endExpand).mousemove(b.dateEntry._describeSpinner).insertAfter(a)}}},_getInput:function(a){return b(a).siblings("."+b.dateEntry.markerClassName)[0]},_describeSpinner:function(a){var c=b.dateEntry._getSpinnerTarget(a), d=b.data(b.dateEntry._getInput(c),"dateEntry");c.title=b.dateEntry._get(d,"spinnerTexts")[b.dateEntry._getSpinnerRegion(d,a)]},_handleSpinner:function(a){var c=b.dateEntry._getSpinnerTarget(a),d=b.dateEntry._getInput(c);if(!b.dateEntry._isDisabledDateEntry(d)){if(d==b.dateEntry._blurredInput)b.dateEntry._lastInput=d,b.dateEntry._blurredInput=null;var e=b.data(d,"dateEntry");b.dateEntry._doFocus(d);var f=b.dateEntry._getSpinnerRegion(e,a);b.dateEntry._changeSpinner(e,c,f);b.dateEntry._actionSpinner(e, f);b.dateEntry._timer=null;b.dateEntry._handlingSpinner=!0;a=b.dateEntry._get(e,"spinnerRepeat");if(3<=f&&a[0])b.dateEntry._timer=setTimeout(function(){b.dateEntry._repeatSpinner(e,f)},a[0]),b(c).one("mouseout",b.dateEntry._releaseSpinner).one("mouseup",b.dateEntry._releaseSpinner)}},_actionSpinner:function(a,c){a.input.val()||b.dateEntry._parseDate(a);switch(c){case 0:this._setDate(a);break;case 1:this._changeField(a,-1,!1);break;case 2:this._changeField(a,1,!1);break;case 3:this._adjustField(a, 1);break;case 4:this._adjustField(a,-1)}},_repeatSpinner:function(a,c){if(b.dateEntry._timer)b.dateEntry._lastInput=b.dateEntry._blurredInput,this._actionSpinner(a,c),this._timer=setTimeout(function(){b.dateEntry._repeatSpinner(a,c)},this._get(a,"spinnerRepeat")[1])},_releaseSpinner:function(){clearTimeout(b.dateEntry._timer);b.dateEntry._timer=null},_endExpand:function(a){b.dateEntry._timer=null;var a=b.dateEntry._getSpinnerTarget(a),c=b.dateEntry._getInput(a),c=b.data(c,"dateEntry");b(a).remove(); c._expanded=!1},_endSpinner:function(a){b.dateEntry._timer=null;var a=b.dateEntry._getSpinnerTarget(a),c=b.dateEntry._getInput(a),d=b.data(c,"dateEntry");b.dateEntry._isDisabledDateEntry(c)||b.dateEntry._changeSpinner(d,a,-1);if(b.dateEntry._handlingSpinner)b.dateEntry._lastInput=b.dateEntry._blurredInput;b.dateEntry._lastInput&&b.dateEntry._handlingSpinner&&b.dateEntry._showField(d);b.dateEntry._handlingSpinner=!1},_getSpinnerTarget:function(a){return a.target||a.srcElement},_getSpinnerRegion:function(a, c){var d=this._getSpinnerTarget(c),e=b.browser.opera||b.browser.safari?b.dateEntry._findPos(d):b(d).offset(),f=b.browser.safari?b.dateEntry._findScroll(d):[document.documentElement.scrollLeft||document.body.scrollLeft,document.documentElement.scrollTop||document.body.scrollTop],g=this._get(a,"spinnerIncDecOnly"),d=g?99:c.clientX+f[0]-e.left-(b.browser.msie?2:0),e=c.clientY+f[1]-e.top-(b.browser.msie?2:0),f=this._get(a,a._expanded?"spinnerBigSize":"spinnerSize"),g=g?99:f[0]-1-d,h=f[1]-1-e;if(0<f[2]&& Math.abs(d-g)<=f[2]&&Math.abs(e-h)<=f[2])return 0;f=Math.min(d,e,g,h);return f==d?1:f==g?2:f==e?3:4},_changeSpinner:function(a,c,d){b(c).css("background-position","-"+(d+1)*this._get(a,a._expanded?"spinnerBigSize":"spinnerSize")[0]+"px 0px")},_findPos:function(a){var c=curTop=0;if(a.offsetParent){c=a.offsetLeft;for(curTop=a.offsetTop;a=a.offsetParent;){var b=c,c=c+a.offsetLeft;0>c&&(c=b);curTop+=a.offsetTop}}return{left:c,top:curTop}},_findScroll:function(a){var c=!1;b(a).parents().each(function(){c|= "fixed"==b(this).css("position")});if(c)return[0,0];for(var d=a.scrollLeft,e=a.scrollTop;a=a.parentNode;)d+=a.scrollLeft||0,e+=a.scrollTop||0;return[d,e]},_get:function(a,c){return null!=a.options[c]?a.options[c]:b.dateEntry._defaults[c]},_parseDate:function(a){var c=this._extractDate(a.input.val(),a)||this._normaliseDate(this._determineDate(this._get(a,"defaultDate"),a)||new Date);a._selectedYear=c.getFullYear();a._selectedMonth=c.getMonth();a._selectedDay=c.getDate();a._lastChr="";a._field=Math.max(0, Math.min(2,this._get(a,"initialField")));""!=a.input.val()&&this._showDate(a)},_extractDate:function(a,c){for(var d=this._get(c,"dateFormat"),e=a.split(RegExp("[\\"+d.substr(-1).split("").join("\\")+"]")),f=c._selectedYear,g=c._selectedMonth+1,h=c._selectedDay,i=0,k=e.length;i<k;i++){var j=parseInt(e[i],10),j=isNaN(j)?0:j,l=d.charAt(i);switch(l){case "y":f=j;break;case "Y":f=j%100+((new Date).getFullYear()-(new Date).getFullYear()%100);break;case "m":g=j;break;case "n":case "N":g=b.inArray(e[i],this._get(c, "N"==l?"monthNames":"monthNamesShort"))+1;break;case "w":case "W":" "==d.charAt(3)?(e.splice(i,1),j=parseInt(e[i],10)):j=parseInt(e[i].substr(this._get(c,"W"==l?"dayNames":"dayNamesShort")[0].length+1),10),j=isNaN(j)?0:j;case "d":h=j}}return new Date(f,g-1,h,12)},_showDate:function(a){this._setValue(a,this._formatDate(a,this._get(a,"dateFormat")));this._showField(a)},_formatDate:function(a,c){for(var b="",e=0,f=c.length-1;e<f;e++){var b=b+(0==e?"":c.charAt(c.length-1)),g=c.charAt(e);switch(g){case "y":b+= this._formatNumber(a._selectedYear);break;case "Y":b+=this._formatNumber(a._selectedYear%100);break;case "m":b+=this._formatNumber(a._selectedMonth+1);break;case "n":case "N":b+=this._get(a,"N"==g?"monthNames":"monthNamesShort")[a._selectedMonth];break;case "d":b+=this._formatNumber(a._selectedDay);break;case "w":case "W":b+=this._get(a,"W"==g?"dayNames":"dayNamesShort")[(new Date(a._selectedYear,a._selectedMonth,a._selectedDay,12)).getDay()]+" "+this._formatNumber(a._selectedDay)}}return b},_showField:function(a){var c= a.input[0];if(!(a.input.is(":hidden")||b.dateEntry._lastInput!=c)){for(var d=this._get(a,"dateFormat"),e=0,f=0;f<a._field;f++)e+=this._fieldLength(a,f,d)+1;d=e+this._fieldLength(a,f,d);c.setSelectionRange?c.setSelectionRange(e,d):c.createTextRange&&(f=c.createTextRange(),f.moveStart("character",e),f.moveEnd("character",d-a.input.val().length),f.select());c.disabled||c.focus()}},_fieldLength:function(a,b,d){b=d.charAt(b);switch(b){case "y":return 4;case "n":case "N":return this._get(a,"N"==b?"monthNames": "monthNamesShort")[a._selectedMonth].length;case "w":case "W":return this._get(a,"W"==b?"dayNames":"dayNamesShort")[(new Date(a._selectedYear,a._selectedMonth,a._selectedDay,12)).getDay()].length+3;default:return 2}},_formatNumber:function(a){return(10>a?"0":"")+a},_setValue:function(a,c){if(c!=a.input.val()){var d=this._get(a,"altField");d&&b(d).val(!c?"":this._formatDate(a,this._get(a,"altFormat")||this._get(a,"dateFormat")));a.input.val(c).trigger("change")}},_changeField:function(a,c,d){var e= ""==a.input.val()||a._field==(-1==c?0:2);e||(a._field+=c);this._showField(a);a._lastChr="";b.data(a.input[0],"dateEntry",a);return e&&d},_adjustField:function(a,b){""==a.input.val()&&(b=0);var d=this._get(a,"dateFormat").charAt(a._field),e=a._selectedYear+("y"==d||"Y"==d?b:0),f=a._selectedMonth+("m"==d||"n"==d||"N"==d?b:0),d="d"==d||"w"==d||"W"==d?a._selectedDay+b:Math.min(a._selectedDay,this._getDaysInMonth(e,f));this._setDate(a,new Date(e,f,d,12))},_getDaysInMonth:function(a,b){return(new Date(a, b+1,0,12)).getDate()},_setDate:function(a,c){var c=this._normaliseDate(this._determineDate(c||this._get(a,"defaultDate"),a)||new Date),d=this._normaliseDate(this._determineDate(this._get(a,"minDate"),a)),e=this._normaliseDate(this._determineDate(this._get(a,"maxDate"),a)),c=d&&c<d?d:e&&c>e?e:c;a._selectedYear=c.getFullYear();a._selectedMonth=c.getMonth();a._selectedDay=c.getDate();this._showDate(a);b.data(a.input[0],"dateEntry",a)},_determineDate:function(a,c){var d=function(a){var c=b.dateEntry._normaliseDate(new Date); c.setDate(c.getDate()+a);return c};return a?"string"==typeof a?function(a){var d=b.dateEntry._extractDate(a,c);if(d)return d;for(var a=a.toLowerCase(),d=b.dateEntry._normaliseDate(new Date),g=d.getFullYear(),h=d.getMonth(),d=d.getDate(),i=/([+-]?[0-9]+)\s*(d|w|m|y)?/g,k=i.exec(a);k;){switch(k[2]||"d"){case "d":d+=parseInt(k[1],10);break;case "w":d+=7*parseInt(k[1],10);break;case "m":h+=parseInt(k[1],10);break;case "y":g+=parseInt(k[1],10)}k=i.exec(a)}return new Date(g,h,d,12)}(a):"number"==typeof a? d(a):a:null},_normaliseDate:function(a){a&&a.setHours(12,0,0,0);return a},_handleKeyPress:function(a,b){var d=this._get(a,"dateFormat");if(-1<d.substring(3).indexOf(b))this._changeField(a,1,!1);else if("0"<=b&&"9">=b){var e=d.charAt(a._field),f=parseInt(b,10),g=parseInt((a._lastChr||"")+b,10),h="y"!=e&&"Y"!=e?a._selectedYear:g,d="m"!=e&&"n"!=e&&"N"!=e?a._selectedMonth+1:1<=g&&12>=g?g:0<f?f:a._selectedMonth+1,f="d"!=e&&"w"!=e&&"W"!=e?a._selectedDay:1<=g&&g<=this._getDaysInMonth(h,d-1)?g:0<f?f:a._selectedDay; this._setDate(a,new Date(h,d-1,f,12));a._lastChr=("y"!=e?"":a._lastChr.substr(Math.max(0,a._lastChr.length-2)))+b}else if(e=d.charAt(a._field),"n"==e||"N"==e){a._lastChr+=b.toLowerCase();var i=this._get(a,"n"==e?"monthNamesShort":"monthNames"),e=function(){for(var b=0;b<i.length;b++)if(i[b].toLowerCase().substring(0,a._lastChr.length)==a._lastChr)return b;return-1},d=e();if(-1==d)a._lastChr=b.toLowerCase(),d=e();-1==d?a._lastChr="":(h=a._selectedYear,f=Math.min(a._selectedDay,this._getDaysInMonth(h, d)),this._setDate(a,new Date(h,d,f,12)))}}});b.fn.dateEntry=function(a){var c=Array.prototype.slice.call(arguments,1);return"string"==typeof a&&("isDisabled"==a||"getDate"==a)?b.dateEntry["_"+a+"DateEntry"].apply(b.dateEntry,[this[0]].concat(c)):this.each(function(){if("input"==this.nodeName.toLowerCase())if("string"==typeof a)b.dateEntry["_"+a+"DateEntry"].apply(b.dateEntry,[this].concat(c));else{var d=b.fn.metadata?b(this).metadata():{};b.dateEntry._connectDateEntry(this,b.extend(d,a))}})};b.dateEntry= new m})(jQuery); \ No newline at end of file Modified: trunk/htdocs/js/jquery/jquery.lj.entryDatePicker.js =================================================================== --- trunk/htdocs/js/jquery/jquery.lj.entryDatePicker.js 2011-11-25 13:26:20 UTC (rev 20626) +++ trunk/htdocs/js/jquery/jquery.lj.entryDatePicker.js 2011-11-25 13:36:04 UTC (rev 20627) @@ -1,7 +1,7 @@ /*! * LiveJournal datePicker for edit entries. * - * Copyright 2011, dmitry.petrov@sup.com + * Copyright 2011, dmitry.petrov@sup.com & vkurkin@sup.com * * http://docs.jquery.com/UI * @@ -9,6 +9,9 @@ * jquery.ui.core.js * jquery.ui.widget.js * jquery.lj.basicWidget.js + * jquery.lj.inlineCalendar.js + * jquery.dateentry.js + * jquery.timeentry.js * * @overview Widget represents a date picker on update.bml and editjournal.bml pages. * @@ -16,13 +19,61 @@ * reset Return widget to the initial state * */ -(function($,window) { +(function($, window) { + var listeners = { + onStartEdit: function(evt) { + evt.data._setState('edit'); + evt.data._isCalendarOpen = true; + evt.preventDefault(); + }, + onChangeMonth: function(evt) { + var self = evt.data, + month = this.selectedIndex, + newDate = self.currentDate; + + if (self._isEvent === false) { + return; + } + + newDate.setMonth(month); + if(newDate.getMonth() != month) { + newDate = new Date(self.currentDate.getFullYear(), month + 1, 0, newDate.getHours(), newDate.getMinutes()); + } + + self._setEditDate(newDate); + }, + onChangeTime: function(evt) { + var newDate = $(this).timeEntry('getTime'), + self = evt.data; + + if (self._isEvent === false) { + return; + } + newDate.setFullYear(self.currentDate.getFullYear()); + newDate.setMonth(self.currentDate.getMonth()); + newDate.setDate(self.currentDate.getDate()); + self._setEditDate(newDate); + }, + onChangeDate: function(evt) { + var newDate = $(this).dateEntry('getDate'), + self = evt.data; + + if (self._isEvent === false) { + return; + } + newDate.setHours(self.currentDate.getHours()); + newDate.setMinutes(self.currentDate.getMinutes()); + self._setEditDate(newDate); + } + }; + $.widget('lj.entryDatePicker', jQuery.lj.basicWidget, { options: { state: 'default', //when the widget is in inedit or infutureedit states, the timers are paused. states: ['default', 'edit', 'inedit', 'infutureedit', 'future'], - updateDate: true, + monthNames: Site.ml_text['month.names.long'] || ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + updateDate: !Site.is_edit, //if true, widget sets custom_time flag if user clicks on edit link. Otherwise it //does so only on real time change from user. disableOnEdit: false, @@ -31,26 +82,26 @@ 'edit': 'entrydate-changeit', 'inedit': 'entrydate-changeit', 'infutureedit': 'entrydate-until', - 'future': 'entrydate-until', + 'future': Site.hasOwnProperty('is_delayed_post') && Site.is_delayed_post === 0 ? 'entrydate-changeit' : 'entrydate-until', 'delayed': 'entrydate-delayed' }, selectors: { dateInputs: 'input, select', - calendar: '.wrap-calendar', + calendar: '.wrap-calendar .i-calendar', editLink: '#currentdate-edit', - currentDate: '#currentdate-date' - }, - //this input was located outside the widget markup - customTimeFlag: jQuery() + monthSelect: '#_mm', + dateString: '.entrydate-string' + } }, _create: function() { - var self = this; - var states = this.options.states; + var self = this, + states = this.options.states, + state = states.length; this._totalStateClassNames = ''; - for (var i in states) if (states.hasOwnProperty(i)) { - this._totalStateClassNames += ' ' + this.options.classNames[states[i]]; + while (state) { + this._totalStateClassNames += ' ' + this.options.classNames[states[--state]]; } this._dateInputs = {}; @@ -60,19 +111,27 @@ } }); - this._currentDate = this.element.find(this.options.selectors.currentDate); + this._dateString = this.element.find(this.options.selectors.dateString); + $.lj.basicWidget.prototype._create.apply(this); - this._initialUpdateDate = this.options.updateDate; - //if delayed posts are disabled we should get old bejavior - this.options.disableOnEdit = !this.element.hasClass(this.options.classNames.delayed); - this._updateTimer = null; - this._bindControls(); + if (this._initialUp... (truncated)