jquery-monthpicker-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # jquery-monthpicker-rails
2
+
3
+ This gem packages the jQuery Monthpicker assets for the Rails 3.1+ asset pipeline.
4
+
5
+ # Usage
6
+
7
+ In your Gemfile, add:
8
+
9
+ ```ruby
10
+ gem "jquery-monthpicker-rails"
11
+ ```
12
+
13
+ Then require the javascript in your application.js
14
+
15
+ ```js
16
+ //= require jquery.ui.monthpicker
17
+ ```
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/jquery/monthpicker/rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "jquery-monthpicker-rails"
6
+ s.version = Jquery::Monthpicker::Rails::VERSION
7
+ s.authors = ["Charles Maresh"]
8
+ s.email = ["cmaresh@gmail.com"]
9
+ s.homepage = "https://github.com/cmaresh/jquery-monthpicker-rails"
10
+ s.summary = "jQuery Monthpicker packaged for the Rails asset pipeline"
11
+ s.description = "jQuery Monthpicker's JavaScript files packaged for the Rails 3.1+ asset pipeline"
12
+ s.license = "MIT"
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+
16
+ s.add_dependency "railties", ">= 3.1.0"
17
+ s.add_dependency "jquery-ui-rails", ">= 3.0"
18
+
19
+ s.files = `git ls-files`.split("\n").reject { |f| f =~ /^testapp/ }
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
21
+ s.require_path = 'lib'
22
+ end
@@ -0,0 +1 @@
1
+ require "jquery/monthpicker/rails"
@@ -0,0 +1,2 @@
1
+ require "jquery/monthpicker/rails/engine"
2
+ require "jquery/monthpicker/rails/version"
@@ -0,0 +1,8 @@
1
+ module Jquery
2
+ module Monthpicker
3
+ module Rails
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Jquery
2
+ module Monthpicker
3
+ module Rails
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,1451 @@
1
+ /*!
2
+ * jQuery UI Monthpicker
3
+ *
4
+ * MIT License
5
+ * Copyright (c) 2011, Julien Poumailloux
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software i
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ */
25
+ (function ($) {
26
+
27
+ $.extend($.ui, { monthpicker: { version: "@VERSION" } });
28
+
29
+ var PROP_NAME = 'monthpicker';
30
+ var instActive;
31
+
32
+ /* Month picker manager.
33
+ Use the singleton instance of this class, $.monthpicker, to interact with the date picker.
34
+ Settings for (groups of) month pickers are maintained in an instance object,
35
+ allowing multiple different settings on the same page. */
36
+
37
+ function Monthpicker() {
38
+ this.uuid = 0;
39
+ this._keyEvent = false; // If the last event was a key event
40
+ this._curInst = null; // The current instance in use
41
+ this._disabledInputs = []; // List of date picker inputs that have been disabled
42
+ this._monthpickerShowing = false; // True if the popup picker is showing , false if not
43
+ this._mainDivId = 'ui-monthpicker-div'; // The ID of the main monthpicker division
44
+ this._triggerClass = 'ui-monthpicker-trigger'; // The name of the trigger marker class
45
+ this._dialogClass = 'ui-monthpicker-dialog'; // The name of the dialog marker class
46
+ this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
47
+ this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
48
+ this.regional = []; // Available regional settings, indexed by language code
49
+ this.regional[''] = { // Default regional settings
50
+ closeText: "Done", // Display text for close link
51
+ prevText: 'Prev', // Display text for previous month link
52
+ nextText: 'Next', // Display text for next month link
53
+ currentText: "Current", // Display text for current month link
54
+ monthNames: ['January','February','March','April','May','June',
55
+ 'July','August','September','October','November','December'], // Names of months for drop-down and formatting
56
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting
57
+ dateFormat: 'mm/yy',
58
+ isRTL: false,
59
+ yearSuffix: '' // Additional text to append to the year in the month headers
60
+ };
61
+ this._defaults = { // Global defaults for all the date picker instances
62
+ showOn: 'focus', // 'focus' for popup on focus,
63
+ // 'button' for trigger button, or 'both' for either
64
+ showAnim: 'fadeIn', // Name of jQuery animation for popup
65
+ showButtonPanel: false, // True to show button panel, false to not show it
66
+ appendText: "", // Display text following the input box, e.g. showing the format
67
+ buttonText: '...', // Text for trigger button
68
+ buttonImage: '', // URL for trigger button image
69
+ gotoCurrent: false, // True if today link goes back to current selection instead
70
+ changeYear: false, // True if year can be selected directly, false if only prev/next
71
+ navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
72
+ yearRange: 'c-10:c+10', // Range of years to display in drop-down,
73
+ // either relative to today's year (-nn:+nn), relative to currently displayed year
74
+ // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
75
+ beforeShow: null, // Function that takes an input field and
76
+ // returns a set of custom settings for the date picker
77
+ onSelect: null, // Define a callback function when a date is selected
78
+ onChangeYear: null, // Define a callback function when the year is changed
79
+ onClose: null, // Define a callback function when the monthpicker is closed
80
+ stepYears: 1, // Number of months to step back/forward
81
+ stepBigYears: 3, // Number of months to step back/forward
82
+ defaultDate: null, // Used when field is blank: actual date,
83
+ minDate: null, // The earliest selectable date, or null for no limit
84
+ maxDate: null, // The latest selectable date, or null for no limit
85
+ altField: '', // Selector for an alternate field to store selected dates into
86
+ altFormat: '', // The date format to use for the alternate field
87
+ disabled: false // The initial disabled state
88
+ };
89
+ $.extend(this._defaults, this.regional['']);
90
+ this.dpDiv = bindHover($('<div id="' + this._mainDivId + '" class="ui-monthpicker ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'));
91
+ }
92
+
93
+ $.extend(Monthpicker.prototype, {
94
+ /* Class name added to elements to indicate already configured with a date picker. */
95
+ markerClassName: 'hasMonthpicker',
96
+
97
+ /* Debug logging (if enabled). */
98
+ log: function () {
99
+ if (this.debug)
100
+ console.log.apply('', arguments);
101
+ },
102
+
103
+ // TODO rename to "widget" when switching to widget factory
104
+ _widgetMonthpicker: function() {
105
+ return this.dpDiv;
106
+ },
107
+
108
+ /* Override the default settings for all instances of the date picker.
109
+ * @param settings object - the new settings to use as defaults (anonymous object)
110
+ * @return the manager object
111
+ */
112
+ setDefaults: function(settings) {
113
+ extendRemove(this._defaults, settings || {});
114
+ return this;
115
+ },
116
+
117
+
118
+ /* Retrieve the instance data for the target control.
119
+ @param target element - the target input field or division or span
120
+ @return object - the associated instance data
121
+ @throws error if a jQuery problem getting data */
122
+ _getInst: function(target) {
123
+ try {
124
+ return $.data(target, PROP_NAME);
125
+ }
126
+ catch (err) {
127
+ throw 'Missing instance data for this monthpicker';
128
+ }
129
+ },
130
+
131
+ /* Get a setting value, defaulting if necessary. */
132
+ _get: function(inst, name) {
133
+ return inst.settings[name] !== undefined ?
134
+ inst.settings[name] : this._defaults[name];
135
+ },
136
+
137
+ /* Attach the month picker to a jQuery selection.
138
+ @param target element - the target input field or division or span
139
+ @param settings object - the new settings to use for this month picker instance (anonymous) */
140
+ _attachMonthpicker: function(target, settings) {
141
+ // check for settings on the control itself - in namespace 'month:'
142
+ var inlineSettings = null;
143
+ for (var attrName in this._defaults) {
144
+ var attrValue = target.getAttribute('month:' + attrName);
145
+ if (attrValue) {
146
+ inlineSettings = inlineSettings || {};
147
+ try {
148
+ inlineSettings[attrName] = eval(attrValue);
149
+ } catch (err) {
150
+ inlineSettings[attrName] = attrValue;
151
+ }
152
+ }
153
+ }
154
+ var nodeName = target.nodeName.toLowerCase();
155
+ if (!target.id) {
156
+ this.uuid += 1;
157
+ target.id = 'dp' + this.uuid;
158
+ }
159
+ var inst = this._newInst($(target));
160
+ inst.settings = $.extend({}, settings || {}, inlineSettings || {});
161
+ if (nodeName == 'input') {
162
+ this._connectMonthpicker(target, inst);
163
+ }
164
+ },
165
+
166
+ /* Create a new instance object. */
167
+ _newInst: function(target) {
168
+ var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1'); // escape jQuery meta chars
169
+ return {id: id, input: target, // associated target
170
+ selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
171
+ drawMonth: 0, drawYear: 0, // month being drawn
172
+ dpDiv: this.dpDiv // presentation div
173
+ };
174
+ },
175
+
176
+ /* Attach the date picker to an input field. */
177
+ _connectMonthpicker: function(target, inst) {
178
+ var input = $(target);
179
+ inst.append = $([]);
180
+ inst.trigger = $([]);
181
+ if (input.hasClass(this.markerClassName))
182
+ return;
183
+ this._attachments(input, inst);
184
+ input.addClass(this.markerClassName).keydown(this._doKeyDown).
185
+ keypress(this._doKeyPress).keyup(this._doKeyUp).
186
+ bind("setData.monthpicker", function(event, key, value) {
187
+ inst.settings[key] = value;
188
+ }).bind("getData.monthpicker", function(event, key) {
189
+ return this._get(inst, key);
190
+ });
191
+ //this._autoSize(inst);
192
+ $.data(target, PROP_NAME, inst);
193
+ //If disabled option is true, disable the monthpicker once it has been attached to the input (see ticket #5665)
194
+ if( inst.settings.disabled ) {
195
+ this._disableMonthpicker( target );
196
+ }
197
+ },
198
+
199
+ /* Make attachments based on settings. */
200
+ _attachments: function(input, inst) {
201
+ var appendText = this._get(inst, "appendText"),
202
+ isRTL = this._get(inst, "isRTL");
203
+
204
+ if (appendText) {
205
+ inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>");
206
+ input[isRTL ? "before" : "after"](inst.append);
207
+ }
208
+
209
+ input.unbind('focus', this._showMonthpicker);
210
+ if (inst.trigger)
211
+ inst.trigger.remove();
212
+ var showOn = this._get(inst, 'showOn');
213
+ if (showOn == 'focus' || showOn == 'both') // pop-up month picker when in the marked field
214
+ input.focus(this._showMonthpicker);
215
+ if (showOn === "button" || showOn === "both") { // pop-up month picker when button clicked
216
+ buttonText = this._get(inst, "buttonText");
217
+ buttonImage = this._get(inst, "buttonImage");
218
+ inst.trigger = $(this._get(inst, "buttonImageOnly") ?
219
+ $("<img/>").addClass(this._triggerClass).
220
+ attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
221
+ $("<button type='button'></button>").addClass(this._triggerClass).
222
+ html(!buttonImage ? buttonText : $("<img/>").attr(
223
+ { src:buttonImage, alt:buttonText, title:buttonText })));
224
+ input[isRTL ? "before" : "after"](inst.trigger);
225
+ inst.trigger.click(function() {
226
+ if ($.monthpicker._monthpickerShowing && $.monthpicker._lastInput === input[0]) {
227
+ $.monthpicker._hideMonthpicker();
228
+ } else if ($.monthpicker._monthpickerShowing && $.monthpicker._lastInput !== input[0]) {
229
+ $.monthpicker._hideMonthpicker();
230
+ $.monthpicker._showMonthpicker(input[0]);
231
+ } else {
232
+ $.monthpicker._showMonthpicker(input[0]);
233
+ }
234
+ return false;
235
+ });
236
+ }
237
+ },
238
+
239
+ /* Close month picker if clicked elsewhere. */
240
+ _checkExternalClick: function(event) {
241
+ if (!$.monthpicker._curInst)
242
+ return;
243
+
244
+ var $target = $(event.target),
245
+ inst = $.monthpicker._getInst($target[0]);
246
+
247
+ if ( ( ( $target[0].id != $.monthpicker._mainDivId &&
248
+ $target.parents('#' + $.monthpicker._mainDivId).length == 0 &&
249
+ !$target.hasClass($.monthpicker.markerClassName) &&
250
+ !$target.hasClass($.monthpicker._triggerClass) &&
251
+ $.monthpicker._monthpickerShowing ) ) ||
252
+ ( $target.hasClass($.monthpicker.markerClassName) && $.monthpicker._curInst != inst ) )
253
+ $.monthpicker._hideMonthpicker();
254
+ },
255
+
256
+ /* Pop-up the month picker for a given input field.
257
+ If false returned from beforeShow event handler do not show.
258
+ @param input element - the input field attached to the date picker or
259
+ event - if triggered by focus */
260
+ _showMonthpicker: function(input) {
261
+ input = input.target || input;
262
+ if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger
263
+ input = $('input', input.parentNode)[0];
264
+ if ($.monthpicker._isDisabledMonthpicker(input) || $.monthpicker._lastInput == input) // already here
265
+ return;
266
+ var inst = $.monthpicker._getInst(input);
267
+ if ($.monthpicker._curInst && $.monthpicker._curInst != inst) {
268
+ $.monthpicker._curInst.dpDiv.stop(true, true);
269
+ if ( inst && $.monthpicker._monthpickerShowing ) {
270
+ $.monthpicker._hideMonthpicker( $.monthpicker._curInst.input[0] );
271
+ }
272
+ }
273
+ var beforeShow = $.monthpicker._get(inst, 'beforeShow');
274
+ var beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
275
+ if(beforeShowSettings === false){
276
+ //false
277
+ return;
278
+ }
279
+ extendRemove(inst.settings, beforeShowSettings);
280
+ inst.lastVal = null;
281
+ $.monthpicker._lastInput = input;
282
+ $.monthpicker._setDateFromField(inst);
283
+ if ($.monthpicker._inDialog) // hide cursor
284
+ input.value = '';
285
+ if (!$.monthpicker._pos) { // position below input
286
+ $.monthpicker._pos = $.monthpicker._findPos(input);
287
+ $.monthpicker._pos[1] += input.offsetHeight; // add the height
288
+ }
289
+ var isFixed = false;
290
+ $(input).parents().each(function() {
291
+ isFixed |= $(this).css('position') == 'fixed';
292
+ return !isFixed;
293
+ });
294
+ if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled
295
+ $.monthpicker._pos[0] -= document.documentElement.scrollLeft;
296
+ $.monthpicker._pos[1] -= document.documentElement.scrollTop;
297
+ }
298
+ var offset = {left: $.monthpicker._pos[0], top: $.monthpicker._pos[1]};
299
+ $.monthpicker._pos = null;
300
+ //to avoid flashes on Firefox
301
+ inst.dpDiv.empty();
302
+ // determine sizing offscreen
303
+ inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'});
304
+ $.monthpicker._updateMonthpicker(inst);
305
+ // fix width for dynamic number of date pickers
306
+ // and adjust position before showing
307
+ offset = $.monthpicker._checkOffset(inst, offset, isFixed);
308
+ inst.dpDiv.css({position: ($.monthpicker._inDialog && $.blockUI ?
309
+ 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
310
+ left: offset.left + 'px', top: offset.top + 'px'});
311
+ var showAnim = $.monthpicker._get(inst, 'showAnim');
312
+ var duration = $.monthpicker._get(inst, 'duration');
313
+ var postProcess = function() {
314
+ var cover = inst.dpDiv.find('iframe.ui-monthpicker-cover'); // IE6- only
315
+ if( !! cover.length ){
316
+ var borders = $.monthpicker._getBorders(inst.dpDiv);
317
+ cover.css({left: -borders[0], top: -borders[1],
318
+ width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()});
319
+ }
320
+ };
321
+ inst.dpDiv.zIndex($(input).zIndex()+1);
322
+ $.monthpicker._monthpickerShowing = true;
323
+
324
+ if ($.effects && $.effects[showAnim])
325
+ inst.dpDiv.show(showAnim, $.monthpicker._get(inst, 'showOptions'), duration, postProcess);
326
+ else
327
+ inst.dpDiv[showAnim || 'show']((showAnim ? duration : null), postProcess);
328
+ if (!showAnim || !duration)
329
+ postProcess();
330
+ if (inst.input.is(':visible') && !inst.input.is(':disabled'))
331
+ inst.input.focus();
332
+ $.monthpicker._curInst = inst;
333
+ },
334
+
335
+ /* Generate the date picker content. */
336
+ _updateMonthpicker: function(inst) {
337
+
338
+ var self = this;
339
+ self.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
340
+ var borders = $.monthpicker._getBorders(inst.dpDiv);
341
+ instActive = inst; // for delegate hover events
342
+ inst.dpDiv.empty().append(this._generateHTML(inst));
343
+ this._attachHandlers(inst);
344
+ var cover = inst.dpDiv.find('iframe.ui-monthpicker-cover'); // IE6- only
345
+ if( !!cover.length ){ //avoid call to outerXXXX() when not in IE6
346
+ cover.css({left: -borders[0], top: -borders[1], width: inst.dpDiv.outerWidth(), height: inst.dpDiv.outerHeight()})
347
+ }
348
+ inst.dpDiv.find('.' + this._dayOverClass + ' a').mouseover();
349
+
350
+ if (inst == $.monthpicker._curInst && $.monthpicker._monthpickerShowing && inst.input &&
351
+ // #6694 - don't focus the input if it's already focused
352
+ // this breaks the change event in IE
353
+ inst.input.is(':visible') && !inst.input.is(':disabled') && inst.input[0] != document.activeElement)
354
+ inst.input.focus();
355
+ // deffered render of the years select (to avoid flashes on Firefox)
356
+ if( inst.yearshtml ){
357
+ var origyearshtml = inst.yearshtml;
358
+ setTimeout(function(){
359
+ //assure that inst.yearshtml didn't change.
360
+ if( origyearshtml === inst.yearshtml && inst.yearshtml ){
361
+ inst.dpDiv.find('select.ui-monthpicker-year:first').replaceWith(inst.yearshtml);
362
+ }
363
+ origyearshtml = inst.yearshtml = null;
364
+ }, 0);
365
+ }
366
+ },
367
+
368
+ /* Trigger custom callback of onClose. */
369
+ _triggerOnClose: function(inst) {
370
+ var onClose = this._get(inst, 'onClose');
371
+ if (onClose)
372
+ onClose.apply((inst.input ? inst.input[0] : null),
373
+ [(inst.input ? inst.input.val() : ''), inst]);
374
+ },
375
+
376
+ /* Hide the month picker from view.
377
+ @param input element - the input field attached to the month picker */
378
+ _hideMonthpicker: function(input) {
379
+ var inst = this._curInst;
380
+ if (!inst || (input && inst != $.data(input, PROP_NAME)))
381
+ return;
382
+ if (this._monthpickerShowing) {
383
+ var showAnim = this._get(inst, 'showAnim');
384
+ var duration = this._get(inst, 'duration');
385
+ var postProcess = function() {
386
+ $.monthpicker._tidyDialog(inst);
387
+ $.monthpicker._curInst = null;
388
+ };
389
+
390
+ if ( $.effects && $.effects[ showAnim ] )
391
+ inst.dpDiv.hide(showAnim, $.monthpicker._get(inst, 'showOptions'), duration, postProcess);
392
+ else
393
+ inst.dpDiv[(showAnim == 'slideDown' ? 'slideUp' :
394
+ (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
395
+ if (!showAnim)
396
+ postProcess();
397
+ $.monthpicker._triggerOnClose(inst);
398
+ this._monthpickerShowing = false;
399
+ this._lastInput = null;
400
+ if (this._inDialog) {
401
+ this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' });
402
+ if ($.blockUI) {
403
+ $.unblockUI();
404
+ $('body').append(this.dpDiv);
405
+ }
406
+ }
407
+ this._inDialog = false;
408
+ }
409
+ },
410
+
411
+ /* Handle keystrokes. */
412
+ _doKeyDown: function(event) {
413
+ var onSelect, dateStr, sel,
414
+ inst = $.monthpicker._getInst(event.target),
415
+ handled = true,
416
+ isRTL = inst.dpDiv.is(".ui-datepicker-rtl");
417
+
418
+ inst._keyEvent = true;
419
+ if ($.monthpicker._monthpickerShowing) {
420
+ switch (event.keyCode) {
421
+ case 9: $.monthpicker._hideMonthpicker();
422
+ handled = false;
423
+ break; // hide on tab out
424
+ case 13: sel = $("td." + $.monthpicker._dayOverClass + ":not(." +
425
+ $.monthpicker._currentClass + ")", inst.dpDiv);
426
+
427
+ if (sel[0]) {
428
+ $.monthpicker._selectMonth(event.target, inst.selectedYear, inst.selectedMonth, sel[0]);
429
+ }
430
+
431
+ onSelect = $.monthpicker._get(inst, "onSelect");
432
+ if (onSelect) {
433
+ dateStr = $.monthpicker._formatDate(inst);
434
+
435
+ // trigger custom callback
436
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
437
+ } else {
438
+ $.monthpicker._hideMonthpicker();
439
+ }
440
+
441
+ return false; // don't submit the form
442
+ case 27: $.monthpicker._hideMonthpicker();
443
+ break; // hide on escape
444
+ case 33: $.monthpicker._adjustDate(event.target, (event.ctrlKey ?
445
+ -$.monthpicker._get(inst, "stepBigYears") :
446
+ -$.monthpicker._get(inst, "stepYears")), "Y");
447
+ break; // previous year on page up/+ ctrl
448
+ case 34: $.monthpicker._adjustDate(event.target, (event.ctrlKey ?
449
+ +$.monthpicker._get(inst, "stepBigYears") :
450
+ +$.monthpicker._get(inst, "stepYears")), "Y");
451
+ break; // next year on page down/+ ctrl
452
+ case 35: if (event.ctrlKey || event.metaKey) {
453
+ $.monthpicker._clearDate(event.target);
454
+ }
455
+ handled = event.ctrlKey || event.metaKey;
456
+ break; // clear on ctrl or command +end
457
+ case 36: if (event.ctrlKey || event.metaKey) {
458
+ $.monthpicker._gotoCurrent(event.target);
459
+ }
460
+ handled = event.ctrlKey || event.metaKey;
461
+ break; // current on ctrl or command +home
462
+ case 37: if (event.ctrlKey || event.metaKey) {
463
+ $.monthpicker._adjustDate(event.target, (isRTL ? +1 : -1), "M");
464
+ }
465
+ handled = event.ctrlKey || event.metaKey;
466
+ // -1 month on ctrl or command +left
467
+ if (event.originalEvent.altKey) {
468
+ $.monthpicker._adjustDate(event.target, (event.ctrlKey ?
469
+ -$.monthpicker._get(inst, "stepBigYears") :
470
+ -$.monthpicker._get(inst, "stepYears")), "Y");
471
+ }
472
+ // next year on alt +left on Mac
473
+ break;
474
+ case 38: if (event.ctrlKey || event.metaKey) {
475
+ $.monthpicker._adjustDate(event.target, -3, "M");
476
+ }
477
+ handled = event.ctrlKey || event.metaKey;
478
+ break; // -1 quarter on ctrl or command +up
479
+ case 39: if (event.ctrlKey || event.metaKey) {
480
+ $.monthpicker._adjustDate(event.target, (isRTL ? -1 : +1), "M");
481
+ }
482
+ handled = event.ctrlKey || event.metaKey;
483
+ // +1 month on ctrl or command +right
484
+ if (event.originalEvent.altKey) {
485
+ $.monthpicker._adjustDate(event.target, (event.ctrlKey ?
486
+ +$.monthpicker._get(inst, "stepBigYears") :
487
+ +$.monthpicker._get(inst, "stepYears")), "Y");
488
+ }
489
+ // next year on alt +right
490
+ break;
491
+ case 40: if (event.ctrlKey || event.metaKey) {
492
+ $.monthpicker._adjustDate(event.target, +3, "M");
493
+ }
494
+ handled = event.ctrlKey || event.metaKey;
495
+ break; // +1 quarter on ctrl or command +down
496
+ default: handled = false;
497
+ }
498
+ } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
499
+ $.monthpicker._showMonthpicker(this);
500
+ } else {
501
+ handled = false;
502
+ }
503
+
504
+ if (handled) {
505
+ event.preventDefault();
506
+ event.stopPropagation();
507
+ }
508
+ },
509
+
510
+ /* Is the first field in a jQuery collection disabled as a monthpicker?
511
+ @param target element - the target input field or division or span
512
+ @return boolean - true if disabled, false if enabled */
513
+ _isDisabledMonthpicker: function(target) {
514
+ if (!target) {
515
+ return false;
516
+ }
517
+ for (var i = 0; i < this._disabledInputs.length; i++) {
518
+ if (this._disabledInputs[i] == target)
519
+ return true;
520
+ }
521
+ return false;
522
+ },
523
+
524
+ /* Tidy up after a dialog display. */
525
+ _tidyDialog: function(inst) {
526
+ inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-monthpicker-calendar');
527
+ },
528
+
529
+ /* Retrieve the size of left and top borders for an element.
530
+ @param elem (jQuery object) the element of interest
531
+ @return (number[2]) the left and top borders */
532
+ _getBorders: function(elem) {
533
+ var convert = function(value) {
534
+ return {thin: 1, medium: 2, thick: 3}[value] || value;
535
+ };
536
+ return [parseFloat(convert(elem.css('border-left-width'))),
537
+ parseFloat(convert(elem.css('border-top-width')))];
538
+ },
539
+
540
+ /* Check positioning to remain on screen. */
541
+ _checkOffset: function(inst, offset, isFixed) {
542
+ var dpWidth = inst.dpDiv.outerWidth();
543
+ var dpHeight = inst.dpDiv.outerHeight();
544
+ var inputWidth = inst.input ? inst.input.outerWidth() : 0;
545
+ var inputHeight = inst.input ? inst.input.outerHeight() : 0;
546
+ var viewWidth = document.documentElement.clientWidth + $(document).scrollLeft();
547
+ var viewHeight = document.documentElement.clientHeight + $(document).scrollTop();
548
+
549
+ offset.left -= (isFixed && offset.left == inst.input.offset().left) ? $(document).scrollLeft() : 0;
550
+ offset.top -= (isFixed && offset.top == (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
551
+
552
+ // now check if monthpicker is showing outside window viewport - move to a better place if so.
553
+ offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
554
+ Math.abs(offset.left + dpWidth - viewWidth) : 0);
555
+ offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
556
+ Math.abs(dpHeight + inputHeight) : 0);
557
+
558
+ return offset;
559
+ },
560
+
561
+ /* Find an object's position on the screen. */
562
+ _findPos: function(obj) {
563
+ var inst = this._getInst(obj);
564
+ while (obj && (obj.type == 'hidden' || obj.nodeType != 1 || $.expr.filters.hidden(obj))) {
565
+ obj = obj['nextSibling'];
566
+ }
567
+ var position = $(obj).offset();
568
+ return [position.left, position.top];
569
+ },
570
+
571
+ /* Adjust one of the month sub-fields. */
572
+ _adjustDate: function(id, offset, period) {
573
+ var target = $(id);
574
+ var inst = this._getInst(target[0]);
575
+ if (this._isDisabledMonthpicker(target[0])) {
576
+ return;
577
+ }
578
+ this._adjustInstDate(inst, offset, period);
579
+ this._updateMonthpicker(inst);
580
+ },
581
+
582
+ /* Adjust one of the date sub-fields. */
583
+ _adjustInstDate: function(inst, offset, period) {
584
+ var year = inst.drawYear + (period == 'Y' ? offset : 0);
585
+ var month = Math.min(inst.selectedMonth, 12) + (period === "M" ? offset : 0);
586
+ var date = this._restrictMinMax(inst, new Date(year, month, 1));
587
+ inst.drawYear = inst.selectedYear = date.getFullYear();
588
+ inst.selectedMonth = date.getMonth();
589
+
590
+ if (period == 'Y') {
591
+ this._notifyChange(inst);
592
+ }
593
+ },
594
+
595
+ /* Notify change of month/year. */
596
+ _notifyChange: function(inst) {
597
+ var onChange = this._get(inst, 'onChangeYear');
598
+ if (onChange)
599
+ onChange.apply((inst.input ? inst.input[0] : null), [inst.selectedYear, inst]);
600
+ },
601
+
602
+ /* Detach a datepicker from its control.
603
+ * @param target element - the target input field or division or span
604
+ */
605
+ _destroyMonthpicker: function(target) {
606
+ var nodeName,
607
+ $target = $(target),
608
+ inst = $.data(target, PROP_NAME);
609
+
610
+ if (!$target.hasClass(this.markerClassName)) {
611
+ return;
612
+ }
613
+
614
+ nodeName = target.nodeName.toLowerCase();
615
+ $.removeData(target, PROP_NAME);
616
+ if (nodeName === "input") {
617
+ inst.append.remove();
618
+ inst.trigger.remove();
619
+ $target.removeClass(this.markerClassName).
620
+ unbind("focus", this._showMonthpicker).
621
+ unbind("keydown", this._doKeyDown).
622
+ unbind("keypress", this._doKeyPress).
623
+ unbind("keyup", this._doKeyUp);
624
+ } else if (nodeName === "div" || nodeName === "span") {
625
+ $target.removeClass(this.markerClassName).empty();
626
+ }
627
+ },
628
+
629
+ /* Enable the date picker to a jQuery selection.
630
+ @param target element - the target input field or division or span */
631
+ _enableMonthpicker: function(target) {
632
+ var $target = $(target);
633
+ var inst = $.data(target, PROP_NAME);
634
+ if (!$target.hasClass(this.markerClassName)) {
635
+ return;
636
+ }
637
+ var nodeName = target.nodeName.toLowerCase();
638
+ if (nodeName == 'input') {
639
+ target.disabled = false;
640
+ inst.trigger.filter('button').
641
+ each(function() { this.disabled = false; }).end().
642
+ filter('img').css({opacity: '1.0', cursor: ''});
643
+ }
644
+ this._disabledInputs = $.map(this._disabledInputs,
645
+ function(value) { return (value == target ? null : value); }); // delete entry
646
+ },
647
+
648
+ /* Disable the month picker to a jQuery selection.
649
+ @param target element - the target input field or division or span */
650
+ _disableMonthpicker: function(target) {
651
+ var $target = $(target);
652
+ var inst = $.data(target, PROP_NAME);
653
+ if (!$target.hasClass(this.markerClassName)) {
654
+ return;
655
+ }
656
+ var nodeName = target.nodeName.toLowerCase();
657
+ if (nodeName == 'input') {
658
+ target.disabled = true;
659
+ inst.trigger.filter('button').
660
+ each(function() { this.disabled = true; }).end().
661
+ filter('img').css({opacity: '0.5', cursor: 'default'});
662
+ }
663
+ this._disabledInputs = $.map(this._disabledInputs,
664
+ function(value) { return (value == target ? null : value); }); // delete entry
665
+ this._disabledInputs[this._disabledInputs.length] = target;
666
+ },
667
+
668
+ /* Generate the HTML for the current state of the date picker. */
669
+ _generateHTML: function(inst) {
670
+ var printDate, hideIfNoPrevNext;
671
+
672
+ hideIfNoPrevNext = false;
673
+ var today = new Date();
674
+ today = new Date(today.getFullYear(), today.getMonth(), today.getDate()); // clear time
675
+ var currentDate = (!inst.currentMonth ? new Date(9999, 9, 9) :
676
+ new Date(inst.currentYear, inst.currentMonth, 1));
677
+ var html = '';
678
+ var year = currentDate && currentDate.year ? currentDate.year : 2011;
679
+ var prevText = this._get(inst, 'prevText');
680
+ var nextText = this._get(inst, 'nextText');
681
+ var stepYears = this._get(inst, 'stepYears');
682
+ var monthNames = this._get(inst, 'monthNames');
683
+ var monthNamesShort = this._get(inst, 'monthNamesShort');
684
+ var drawYear = inst.drawYear;
685
+ var showButtonPanel = this._get(inst, "showButtonPanel");
686
+ var isRTL = this._get(inst, "isRTL");
687
+ var defaultDate = this._getDefaultDate(inst);
688
+ var navigationAsDateFormat = this._get(inst, "navigationAsDateFormat");
689
+ defaultDate.setDate(1);
690
+
691
+ prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
692
+ new Date(drawYear - stepYears, 1, 1),
693
+ this._getFormatConfig(inst)));
694
+ nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
695
+ new Date(drawYear + stepYears, 1, 1),
696
+ this._getFormatConfig(inst)));
697
+
698
+ var prev = (this._canAdjustYear(inst, -1, drawYear) ?
699
+ "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
700
+ " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" :
701
+ (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>"));
702
+
703
+ var next = (this._canAdjustYear(inst, +1, drawYear) ?
704
+ "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
705
+ " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" :
706
+ (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>"));
707
+
708
+ html += '<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix ui-corner-all">' +
709
+ prev + next + this._generateYearHeader(inst, drawYear) + // draw year header
710
+ '</div><table class="ui-datepicker-calendar"><tbody>';
711
+
712
+ // draw months table
713
+ for(var month = 0; month <= 11; month++) {
714
+ if (month % 3 === 0) {
715
+ html += '<tr>';
716
+ }
717
+
718
+ printDate = new Date(drawYear, month, 1);
719
+ var selectedDate = new Date(drawYear, inst.selectedMonth, 1);
720
+
721
+ html += '<td class="'
722
+ + (drawYear == inst.currentYear && month == inst.currentMonth ? " " + this._currentClass : "") // highlight selected month
723
+ + ((month === inst.selectedMonth && drawYear === inst.selectedYear && inst._keyEvent) || // user pressed key
724
+ (defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
725
+ // or defaultDate is current printedDate and defaultDate is selectedDate
726
+ " " + this._dayOverClass : "") // highlight selected day
727
+ + '" data-month="' + month + '" data-year="' + drawYear + '" data-handler="selectMonth" data-event="click">'
728
+ + '<a class="ui-state-default'
729
+ + (drawYear == inst.currentYear && month == inst.currentMonth ? ' ui-state-active' : '') // highlight selected month
730
+ + (drawYear == today.getFullYear() && month == today.getMonth() ? ' ui-state-highlight' : '') // highlight today (if different)
731
+ + '" href="#">' + (inst.settings && inst.settings.monthNamesShort ? inst.settings.monthNamesShort[month] : this._defaults.monthNamesShort[month]) + '</a>' + '</td>'; // display selectable date
732
+
733
+ if (month % 3 === 2) {
734
+ html += '</tr>';
735
+ }
736
+ }
737
+ html += '</tbody></table>';
738
+
739
+ var controls = "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
740
+ this._get(inst, "closeText") + "</button>";
741
+
742
+ var currentText = this._get(inst, "currentText");
743
+ var gotoDate = (this._get(inst, "gotoCurrent") && inst.currentMonth ? currentDate : today);
744
+ currentText = (!navigationAsDateFormat ? currentText :
745
+ this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
746
+
747
+ var buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") +
748
+ "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='current' data-event='click'" +
749
+ ">" + currentText + "</button>" + (isRTL ? "" : controls) + "</div>" : "";
750
+
751
+ html += buttonPanel;
752
+
753
+ return html;
754
+ },
755
+
756
+ /* Generate the year header. */
757
+ _generateYearHeader: function(inst, drawYear) {
758
+ var changeYear = this._get(inst, 'changeYear');
759
+ var html = '<div class="ui-datepicker-title">';
760
+ // year selection
761
+ if ( !inst.yearshtml ) {
762
+ inst.yearshtml = '';
763
+
764
+ if (changeYear) {
765
+ // determine range of years to display
766
+ var years = this._get(inst, 'yearRange').split(':');
767
+ var thisYear = new Date().getFullYear();
768
+ var determineYear = function(value) {
769
+ var year = (value.match(/c[+-].*/) ? drawYear + parseInt(value.substring(1), 10) :
770
+ (value.match(/[+-].*/) ? thisYear + parseInt(value, 10) :
771
+ parseInt(value, 10)));
772
+ return (isNaN(year) ? thisYear : year);
773
+ };
774
+ var year = determineYear(years[0]);
775
+ var endYear = Math.max(year, determineYear(years[1] || ''));
776
+
777
+ inst.yearshtml += '<select class="ui-datepicker-year" ' +
778
+ "data-handler='selectYear' data-event='change'>";
779
+
780
+ for (; year <= endYear; year++) {
781
+ inst.yearshtml += '<option value="' + year + '"' +
782
+ (year == drawYear ? ' selected="selected"' : '') +
783
+ '>' + year + '</option>';
784
+ }
785
+ inst.yearshtml += '</select>';
786
+ } else {
787
+ inst.yearshtml += '<span class="ui-datepicker-year">' + drawYear + '</span>';
788
+ }
789
+
790
+ html += inst.yearshtml;
791
+ inst.yearshtml = null;
792
+ }
793
+ html += this._get(inst, 'yearSuffix');
794
+ html += '</div>'; // Close monthpicker_header
795
+ return html;
796
+ },
797
+
798
+ /* Provide the configuration settings for formatting/parsing. */
799
+ _getFormatConfig: function(inst) {
800
+ var shortYearCutoff = this._get(inst, 'shortYearCutoff');
801
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
802
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
803
+ return {shortYearCutoff: shortYearCutoff,
804
+ dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'),
805
+ monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')};
806
+ },
807
+
808
+ /* Parse existing date and initialise date picker. */
809
+ _setDateFromField: function(inst, noDefault) {
810
+ if (inst.input.val() == inst.lastVal) {
811
+ return;
812
+ }
813
+ var dateFormat = this._get(inst, 'dateFormat');
814
+ var dates = inst.lastVal = inst.input ? inst.input.val() : null;
815
+ var date, defaultDate;
816
+ date = defaultDate = this._getDefaultDate(inst);
817
+ var settings = this._getFormatConfig(inst);
818
+ try {
819
+ date = this.parseDate(dateFormat, dates, settings) || defaultDate;
820
+ } catch (event) {
821
+ this.log(event);
822
+ dates = (noDefault ? '' : dates);
823
+ }
824
+ inst.selectedMonth = date.getMonth();
825
+ inst.drawYear = inst.selectedYear = date.getFullYear();
826
+ inst.currentMonth = (dates ? date.getMonth() : 0);
827
+ inst.currentYear = (dates ? date.getFullYear() : 0);
828
+ this._adjustInstDate(inst);
829
+ },
830
+
831
+ /* Retrieve the default date shown on opening. */
832
+ _getDefaultDate: function(inst) {
833
+ return this._restrictMinMax(inst,
834
+ this._determineDate(inst, this._get(inst, 'defaultDate'), new Date()));
835
+ },
836
+
837
+ /* A date may be specified as an exact value or a relative one. */
838
+ _determineDate: function(inst, date, defaultDate) {
839
+ var offsetNumeric = function(offset) {
840
+ var date = new Date();
841
+ date.setDate(1);
842
+ date.setMonth(date.getMonth() + offset);
843
+ return date;
844
+ };
845
+ var offsetString = function(offset) {
846
+ try {
847
+ return $.monthpicker.parseDate($.monthpicker._get(inst, 'dateFormat'),
848
+ offset, $.monthpicker._getFormatConfig(inst));
849
+ }
850
+ catch (e) {
851
+ // Ignore
852
+ }
853
+ var date = (offset.toLowerCase().match(/^c/) ?
854
+ $.monthpicker._getDate(inst) : null) || new Date();
855
+ var year = date.getFullYear();
856
+ var month = date.getMonth();
857
+ var day = 1;
858
+ var pattern = /([+-]?[0-9]+)\s*(m|M|y|Y)?/g;
859
+ var matches = pattern.exec(offset);
860
+ while (matches) {
861
+ switch (matches[2] || 'm') {
862
+ case 'm' : case 'M' :
863
+ month += parseInt(matches[1],10);
864
+ day = 1
865
+ break;
866
+ case 'y': case 'Y' :
867
+ year += parseInt(matches[1],10);
868
+ day = 1
869
+ break;
870
+ }
871
+ matches = pattern.exec(offset);
872
+ }
873
+ return new Date(year, month, day);
874
+ };
875
+ var newDate = (date == null || date === '' ? defaultDate : (typeof date == 'string' ? offsetString(date) :
876
+ (typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
877
+ newDate = (newDate && newDate.toString() == 'Invalid Date' ? defaultDate : newDate);
878
+ if (newDate) {
879
+ newDate.setHours(0);
880
+ newDate.setMinutes(0);
881
+ newDate.setSeconds(0);
882
+ newDate.setMilliseconds(0);
883
+ }
884
+ return newDate;
885
+ },
886
+
887
+ /* Determine the current maximum date - ensure no time components are set. */
888
+ _getMinMaxDate: function(inst, minMax) {
889
+ return this._determineDate(inst, this._get(inst, minMax + 'Date'), null);
890
+ },
891
+
892
+ /* Ensure a date is within any min/max bounds. */
893
+ _restrictMinMax: function(inst, date) {
894
+ var minDate = this._getMinMaxDate(inst, 'min');
895
+ var maxDate = this._getMinMaxDate(inst, 'max');
896
+ var newDate = (minDate && date < minDate ? minDate : date);
897
+ newDate = (maxDate && newDate > maxDate ? maxDate : newDate);
898
+ return newDate;
899
+ },
900
+
901
+ /* Determines if we should allow a "next/prev" year display change. */
902
+ _canAdjustYear: function(inst, offset, curYear) {
903
+ var date = new Date(curYear + offset, 1, 1);
904
+ return this._isInRange(inst, date);
905
+ },
906
+
907
+ /* Is the given date in the accepted range? */
908
+ _isInRange: function(inst, date) {
909
+ var yearSplit, currentYear,
910
+ minDate = this._getMinMaxDate(inst, "min"),
911
+ maxDate = this._getMinMaxDate(inst, "max"),
912
+ minYear = null,
913
+ maxYear = null,
914
+ years = this._get(inst, "yearRange");
915
+ if (years){
916
+ yearSplit = years.split(":");
917
+ currentYear = new Date().getFullYear();
918
+ minYear = parseInt(yearSplit[0], 10);
919
+ maxYear = parseInt(yearSplit[1], 10);
920
+ if ( yearSplit[0].match(/[+\-].*/) ) {
921
+ minYear += currentYear;
922
+ }
923
+ if ( yearSplit[1].match(/[+\-].*/) ) {
924
+ maxYear += currentYear;
925
+ }
926
+ }
927
+
928
+ return ((!minDate || date.getTime() >= minDate.getTime()) &&
929
+ (!maxDate || date.getTime() <= maxDate.getTime()) &&
930
+ (!minYear || date.getFullYear() >= minYear) &&
931
+ (!maxYear || date.getFullYear() <= maxYear));
932
+ },
933
+
934
+ /* Action for selecting a new month/year. */
935
+ _selectYear: function(id, select, period) {
936
+ var target = $(id);
937
+ var inst = this._getInst(target[0]);
938
+ inst['selected' + (period == 'M' ? 'Month' : 'Year')] =
939
+ inst['draw' + (period == 'M' ? 'Month' : 'Year')] =
940
+ parseInt(select.options[select.selectedIndex].value,10);
941
+ this._notifyChange(inst);
942
+ this._adjustDate(target);
943
+ },
944
+
945
+ /* Action for current link. */
946
+ _gotoCurrent: function(id) {
947
+ var date,
948
+ target = $(id),
949
+ inst = this._getInst(target[0]);
950
+
951
+ if (this._get(inst, "gotoCurrent") && inst.currentYear) {
952
+ inst.selectedDay = inst.currentDay;
953
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth;
954
+ inst.drawYear = inst.selectedYear = inst.currentYear;
955
+ } else {
956
+ date = new Date();
957
+ inst.selectedDay = date.getDate();
958
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
959
+ inst.drawYear = inst.selectedYear = date.getFullYear();
960
+ }
961
+ this._notifyChange(inst);
962
+ this._adjustDate(target);
963
+ },
964
+
965
+ /* Action for selecting a month. */
966
+ _selectMonth: function(id, year, month, td) {
967
+ var target = $(id);
968
+ var inst = this._getInst(target[0]);
969
+
970
+ if ($(td).hasClass(this._unselectableClass) || this._isDisabledMonthpicker(target[0])) {
971
+ return;
972
+ }
973
+
974
+ inst.selectedMonth = inst.currentMonth = month;
975
+ inst.selectedYear = inst.currentYear = year;
976
+ this._selectDate(id, this._formatDate(inst, inst.currentMonth, inst.currentYear));
977
+ },
978
+
979
+ /* Parse a string value into a date object.
980
+ See formatDate below for the possible formats.
981
+
982
+ @param format string - the expected format of the date
983
+ @param value string - the date in the above format
984
+ @param settings Object - attributes include:
985
+ shortYearCutoff number - the cutoff year for determining the century (optional)
986
+ monthNamesShort string[12] - abbreviated names of the months (optional)
987
+ monthNames string[12] - names of the months (optional)
988
+ @return Date - the extracted date value or null if value is blank */
989
+ parseDate: function (format, value, settings) {
990
+ if (format == null || value == null)
991
+ throw 'Invalid arguments';
992
+ value = (typeof value == 'object' ? value.toString() : value + '');
993
+ if (value == '')
994
+ return null;
995
+ var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
996
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
997
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
998
+ var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
999
+ var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
1000
+ var year = -1;
1001
+ var month = -1;
1002
+ var literal = false;
1003
+ // Check whether a format character is doubled
1004
+ var lookAhead = function(match) {
1005
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
1006
+ if (matches)
1007
+ iFormat++;
1008
+ return matches;
1009
+ };
1010
+ // Extract a number from the string value
1011
+ var getNumber = function(match) {
1012
+ var isDoubled = lookAhead(match);
1013
+ var size = (match == '@' ? 14 : (match == '!' ? 20 :
1014
+ (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2))));
1015
+ var digits = new RegExp('^\\d{1,' + size + '}');
1016
+ var num = value.substring(iValue).match(digits);
1017
+ if (!num)
1018
+ throw 'Missing number at position ' + iValue;
1019
+ iValue += num[0].length;
1020
+ return parseInt(num[0], 10);
1021
+ };
1022
+ // Extract a name from the string value and convert to an index
1023
+ var getName = function(match, shortNames, longNames) {
1024
+ var names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
1025
+ return [ [k, v] ];
1026
+ }).sort(function (a, b) {
1027
+ return -(a[1].length - b[1].length);
1028
+ });
1029
+ var index = -1;
1030
+ $.each(names, function (i, pair) {
1031
+ var name = pair[1];
1032
+ if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) {
1033
+ index = pair[0];
1034
+ iValue += name.length;
1035
+ return false;
1036
+ }
1037
+ });
1038
+ if (index != -1)
1039
+ return index + 1;
1040
+ else
1041
+ throw 'Unknown name at position ' + iValue;
1042
+ };
1043
+ // Confirm that a literal character matches the string value
1044
+ var checkLiteral = function() {
1045
+ if (value.charAt(iValue) != format.charAt(iFormat))
1046
+ throw 'Unexpected literal at position ' + iValue;
1047
+ iValue++;
1048
+ };
1049
+ var iValue = 0;
1050
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
1051
+ if (literal)
1052
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
1053
+ literal = false;
1054
+ else
1055
+ checkLiteral();
1056
+ else
1057
+ switch (format.charAt(iFormat)) {
1058
+ case 'm':
1059
+ month = getNumber('m');
1060
+ break;
1061
+ case 'M':
1062
+ month = getName('M', monthNamesShort, monthNames);
1063
+ break;
1064
+ case 'y':
1065
+ year = getNumber('y');
1066
+ break;
1067
+ case '@':
1068
+ var date = new Date(getNumber('@'));
1069
+ year = date.getFullYear();
1070
+ month = date.getMonth() + 1;
1071
+ day = date.getDate();
1072
+ break;
1073
+ case '!':
1074
+ var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
1075
+ year = date.getFullYear();
1076
+ month = date.getMonth() + 1;
1077
+ break;
1078
+ case "'":
1079
+ if (lookAhead("'"))
1080
+ checkLiteral();
1081
+ else
1082
+ literal = true;
1083
+ break;
1084
+ default:
1085
+ checkLiteral();
1086
+ }
1087
+ }
1088
+ if (iValue < value.length){
1089
+ throw "Extra/unparsed characters found in date: " + value.substring(iValue);
1090
+ }
1091
+ if (year == -1)
1092
+ year = new Date().getFullYear();
1093
+ else if (year < 100)
1094
+ year += new Date().getFullYear() - new Date().getFullYear() % 100 +
1095
+ (year <= shortYearCutoff ? 0 : -100);
1096
+ var date = new Date(year, month - 1, 1);
1097
+ if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != 1)
1098
+ throw 'Invalid date'; // E.g. 31/02/00
1099
+ return date;
1100
+ },
1101
+
1102
+ /* Format a date object into a string value.
1103
+ The format can be combinations of the following:
1104
+ d - day of month (no leading zero)
1105
+ dd - day of month (two digit)
1106
+ o - day of year (no leading zeros)
1107
+ oo - day of year (three digit)
1108
+ D - day name short
1109
+ DD - day name long
1110
+ m - month of year (no leading zero)
1111
+ mm - month of year (two digit)
1112
+ M - month name short
1113
+ MM - month name long
1114
+ y - year (two digit)
1115
+ yy - year (four digit)
1116
+ @ - Unix timestamp (ms since 01/01/1970)
1117
+ ! - Windows ticks (100ns since 01/01/0001)
1118
+ '...' - literal text
1119
+ '' - single quote
1120
+
1121
+ @param format string - the desired format of the date
1122
+ @param date Date - the date value to format
1123
+ @param settings Object - attributes include:
1124
+ dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
1125
+ dayNames string[7] - names of the days from Sunday (optional)
1126
+ monthNamesShort string[12] - abbreviated names of the months (optional)
1127
+ monthNames string[12] - names of the months (optional)
1128
+ @return string - the date in the above format */
1129
+ formatDate: function (format, date, settings) {
1130
+ if (!date)
1131
+ return '';
1132
+ var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
1133
+ var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
1134
+ // Check whether a format character is doubled
1135
+ var lookAhead = function(match) {
1136
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
1137
+ if (matches)
1138
+ iFormat++;
1139
+ return matches;
1140
+ };
1141
+ // Format a number, with leading zero if necessary
1142
+ var formatNumber = function(match, value, len) {
1143
+ var num = '' + value;
1144
+ if (lookAhead(match))
1145
+ while (num.length < len)
1146
+ num = '0' + num;
1147
+ return num;
1148
+ };
1149
+ // Format a name, short or long as requested
1150
+ var formatName = function(match, value, shortNames, longNames) {
1151
+ return (lookAhead(match) ? longNames[value] : shortNames[value]);
1152
+ };
1153
+ var output = '';
1154
+ var literal = false;
1155
+ if (date)
1156
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
1157
+ if (literal)
1158
+ if (format.charAt(iFormat) == "'" && !lookAhead("'"))
1159
+ literal = false;
1160
+ else
1161
+ output += format.charAt(iFormat);
1162
+ else
1163
+ switch (format.charAt(iFormat)) {
1164
+ case 'm':
1165
+ output += formatNumber('m', date.getMonth() + 1, 2);
1166
+ break;
1167
+ case 'M':
1168
+ output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
1169
+ break;
1170
+ case 'y':
1171
+ output += (lookAhead('y') ? date.getFullYear() :
1172
+ (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
1173
+ break;
1174
+ case '@':
1175
+ output += date.getTime();
1176
+ break;
1177
+ case '!':
1178
+ output += date.getTime() * 10000 + this._ticksTo1970;
1179
+ break;
1180
+ case "'":
1181
+ if (lookAhead("'"))
1182
+ output += "'";
1183
+ else
1184
+ literal = true;
1185
+ break;
1186
+ default:
1187
+ output += format.charAt(iFormat);
1188
+ }
1189
+ }
1190
+ return output;
1191
+ },
1192
+
1193
+ /* Erase the input field and hide the date picker. */
1194
+ _clearDate: function(id) {
1195
+ var target = $(id);
1196
+ this._selectDate(target, "");
1197
+ },
1198
+
1199
+ /* Update the input field with the selected date. */
1200
+ _selectDate: function(id, dateStr) {
1201
+ var target = $(id);
1202
+ var inst = this._getInst(target[0]);
1203
+ dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
1204
+ if (inst.input)
1205
+ inst.input.val(dateStr);
1206
+ this._updateAlternate(inst);
1207
+ var onSelect = this._get(inst, 'onSelect');
1208
+ if (onSelect)
1209
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback
1210
+ else if (inst.input)
1211
+ inst.input.trigger('change'); // fire the change event
1212
+ this._hideMonthpicker();
1213
+ this._lastInput = inst.input[0];
1214
+ if (typeof(inst.input[0]) != 'object')
1215
+ inst.input.focus(); // restore focus
1216
+ this._lastInput = null;
1217
+ },
1218
+
1219
+ /* Update any alternate field to synchronise with the main field. */
1220
+ _updateAlternate: function(inst) {
1221
+ var altField = this._get(inst, 'altField');
1222
+ if (altField) { // update alternate field too
1223
+ var altFormat = this._get(inst, 'altFormat') || this._get(inst, 'dateFormat');
1224
+ var date = this._getDate(inst);
1225
+ var dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
1226
+ $(altField).each(function() { $(this).val(dateStr); });
1227
+ }
1228
+ },
1229
+
1230
+ /* Set the date(s) directly. */
1231
+ _setDate: function(inst, date, noChange) {
1232
+ var clear = !date,
1233
+ origMonth = inst.selectedMonth,
1234
+ origYear = inst.selectedYear,
1235
+ newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
1236
+
1237
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
1238
+ inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
1239
+ if ((origYear !== inst.selectedYear) && !noChange) {
1240
+ this._notifyChange(inst);
1241
+ }
1242
+ this._adjustInstDate(inst);
1243
+ if (inst.input) {
1244
+ inst.input.val(clear ? "" : this._formatDate(inst));
1245
+ }
1246
+ },
1247
+
1248
+ /* Set the dates for a jQuery selection.
1249
+ * @param target element - the target input field or division or span
1250
+ * @param date Date - the new date
1251
+ */
1252
+ _setDateMonthpicker: function(target, date) {
1253
+ var inst = this._getInst(target);
1254
+ if (inst) {
1255
+ this._setDate(inst, date);
1256
+ this._updateMonthpicker(inst);
1257
+ this._updateAlternate(inst);
1258
+ }
1259
+ },
1260
+
1261
+ _getDate: function(inst) {
1262
+ var date = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null :
1263
+ new Date(inst.currentYear, inst.currentMonth, 1));
1264
+
1265
+ return date;
1266
+ },
1267
+
1268
+ /* Attach the onxxx handlers. These are declared statically so
1269
+ * they work with static code transformers like Caja.
1270
+ */
1271
+ _attachHandlers: function(inst) {
1272
+ var stepYears = this._get(inst, "stepYears"),
1273
+ id = "#" + inst.id.replace( /\\\\/g, "\\" );
1274
+ inst.dpDiv.find("[data-handler]").map(function () {
1275
+ var handler = {
1276
+ prev: function () {
1277
+ $.monthpicker._adjustDate(id, -stepYears, "Y");
1278
+ },
1279
+ next: function () {
1280
+ $.monthpicker._adjustDate(id, +stepYears, "Y");
1281
+ },
1282
+ hide: function () {
1283
+ $.monthpicker._hideMonthpicker();
1284
+ },
1285
+ current: function () {
1286
+ $.monthpicker._gotoCurrent(id);
1287
+ },
1288
+ selectMonth: function () {
1289
+ $.monthpicker._selectMonth(id, +this.getAttribute("data-year"), +this.getAttribute("data-month"), this);
1290
+ return false;
1291
+ },
1292
+ selectYear: function () {
1293
+ $.monthpicker._selectYear(id, this, "Y");
1294
+ return false;
1295
+ }
1296
+ };
1297
+ $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]);
1298
+ });
1299
+ },
1300
+
1301
+ /* Update or retrieve the settings for a month picker attached to an input field or division.
1302
+ * @param target element - the target input field or division or span
1303
+ * @param name object - the new settings to update or
1304
+ * string - the name of the setting to change or retrieve,
1305
+ * when retrieving also "all" for all instance settings or
1306
+ * "defaults" for all global defaults
1307
+ * @param value any - the new value for the setting
1308
+ * (omit if above is an object or to retrieve a value)
1309
+ */
1310
+ _optionMonthpicker: function(target, name, value) {
1311
+ var settings, date, minDate, maxDate,
1312
+ inst = this._getInst(target);
1313
+
1314
+ if (arguments.length === 2 && typeof name === "string") {
1315
+ return (name === "defaults" ? $.extend({}, $.monthpicker._defaults) :
1316
+ (inst ? (name === "all" ? $.extend({}, inst.settings) :
1317
+ this._get(inst, name)) : null));
1318
+ }
1319
+
1320
+ settings = name || {};
1321
+ if (typeof name === "string") {
1322
+ settings = {};
1323
+ settings[name] = value;
1324
+ }
1325
+
1326
+ if (inst) {
1327
+ if (this._curInst === inst) {
1328
+ this._hideMonthpicker();
1329
+ }
1330
+
1331
+ date = this._getDateMonthpicker(target, true);
1332
+ minDate = this._getMinMaxDate(inst, "min");
1333
+ maxDate = this._getMinMaxDate(inst, "max");
1334
+ extendRemove(inst.settings, settings);
1335
+ // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
1336
+ if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) {
1337
+ inst.settings.minDate = this._formatDate(inst, minDate);
1338
+ }
1339
+ if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) {
1340
+ inst.settings.maxDate = this._formatDate(inst, maxDate);
1341
+ }
1342
+ if ( "disabled" in settings ) {
1343
+ if ( settings.disabled ) {
1344
+ this._disableMonthpicker(target);
1345
+ } else {
1346
+ this._enableMonthpicker(target);
1347
+ }
1348
+ }
1349
+ this._attachments($(target), inst);
1350
+ this._setDate(inst, date);
1351
+ this._updateAlternate(inst);
1352
+ this._updateMonthpicker(inst);
1353
+ }
1354
+ },
1355
+
1356
+
1357
+ /* Get the date(s) for the first entry in a jQuery selection.
1358
+ * @param target element - the target input field or division or span
1359
+ * @return Date - the current date
1360
+ */
1361
+ _getDateMonthpicker: function(target, noDefault) {
1362
+ var inst = this._getInst(target);
1363
+ if (inst && !inst.inline) {
1364
+ this._setDateFromField(inst, noDefault);
1365
+ }
1366
+ return (inst ? this._getDate(inst) : null);
1367
+ },
1368
+
1369
+ /* Format the given date for display. */
1370
+ _formatDate: function(inst, month, year) {
1371
+ if (!month) {
1372
+ inst.currentMonth = inst.selectedMonth;
1373
+ inst.currentYear = inst.selectedYear;
1374
+ }
1375
+ var date = (month ? (typeof month == 'object' ? month :
1376
+ new Date(year, month, 1)) :
1377
+ new Date(inst.currentYear, inst.currentMonth, 1));
1378
+ return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst));
1379
+ }
1380
+ });
1381
+
1382
+ /* jQuery extend now ignores nulls! */
1383
+ function extendRemove(target, props) {
1384
+ $.extend(target, props);
1385
+ for (var name in props)
1386
+ if (props[name] == null || props[name] == undefined)
1387
+ target[name] = props[name];
1388
+ return target;
1389
+ };
1390
+
1391
+ /*
1392
+ * Bind hover events for monthpicker elements.
1393
+ * Done via delegate so the binding only occurs once in the lifetime of the parent div.
1394
+ * Global instActive, set by _updateMonthpicker allows the handlers to find their way back to the active picker.
1395
+ */
1396
+ function bindHover(dpDiv) {
1397
+ var selector = '.ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a';
1398
+ return dpDiv.delegate(selector, 'mouseout', function() {
1399
+ $(this).removeClass('ui-state-hover');
1400
+ if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).removeClass('ui-datepicker-prev-hover');
1401
+ if (this.className.indexOf('ui-datepicker-next') != -1) $(this).removeClass('ui-datepicker-next-hover');
1402
+ })
1403
+ .delegate(selector, 'mouseover', function(){
1404
+ if (!$.monthpicker._isDisabledMonthpicker( instActive.input[0])) {
1405
+ $(this).parents('.ui-datepicker-calendar').find('a').removeClass('ui-state-hover');
1406
+ $(this).addClass('ui-state-hover');
1407
+ if (this.className.indexOf('ui-datepicker-prev') != -1) $(this).addClass('ui-datepicker-prev-hover');
1408
+ if (this.className.indexOf('ui-datepicker-next') != -1) $(this).addClass('ui-datepicker-next-hover');
1409
+ }
1410
+ });
1411
+ }
1412
+
1413
+ /* Invoke the monthpicker functionality.
1414
+ @param options Object - settings for attaching new monthpicker functionality
1415
+ @return jQuery object */
1416
+ $.fn.monthpicker = function(options){
1417
+
1418
+ /* Verify an empty collection wasn't passed */
1419
+ if ( !this.length ) {
1420
+ return this;
1421
+ }
1422
+
1423
+ /* Initialise the date picker. */
1424
+ if (!$.monthpicker.initialized) {
1425
+ $(document).mousedown($.monthpicker._checkExternalClick).
1426
+ find('body').append($.monthpicker.dpDiv);
1427
+ $.monthpicker.initialized = true;
1428
+ }
1429
+
1430
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
1431
+ if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) {
1432
+ return $.monthpicker["_" + options + "Monthpicker"].
1433
+ apply($.monthpicker, [this[0]].concat(otherArgs));
1434
+ }
1435
+ if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") {
1436
+ return $.monthpicker["_" + options + "Monthpicker"].
1437
+ apply($.monthpicker, [this[0]].concat(otherArgs));
1438
+ }
1439
+
1440
+ return this.each(function() {
1441
+ typeof options === 'string' ?
1442
+ $.monthpicker['_' + options + 'Monthpicker'].
1443
+ apply($.monthpicker, [this].concat(otherArgs)) :
1444
+ $.monthpicker._attachMonthpicker(this, options);
1445
+ });
1446
+ };
1447
+
1448
+ $.monthpicker = new Monthpicker(); // singleton instance
1449
+ $.monthpicker.initialized = false;
1450
+
1451
+ })(jQuery);