zebra-datepicker-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2878 @@
1
+ /**
2
+ * Zebra_DatePicker
3
+ *
4
+ * Zebra_DatePicker is a small, compact and highly configurable date picker plugin for jQuery
5
+ *
6
+ * Visit {@link http://stefangabos.ro/jquery/zebra-datepicker/} for more information.
7
+ *
8
+ * For more resources visit {@link http://stefangabos.ro/}
9
+ *
10
+ * @author Stefan Gabos <contact@stefangabos.ro>
11
+ * @version 1.8.4 (last revision: August 11, 2013)
12
+ * @copyright (c) 2011 - 2013 Stefan Gabos
13
+ * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE
14
+ * @package Zebra_DatePicker
15
+ */
16
+ ;(function($) {
17
+
18
+ $.Zebra_DatePicker = function(element, options) {
19
+
20
+ var defaults = {
21
+
22
+ // setting this property to a jQuery element, will result in the date picker being always visible, the indicated
23
+ // element being the date picker's container;
24
+ always_visible: false,
25
+
26
+ // days of the week; Sunday to Saturday
27
+ days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
28
+
29
+ // by default, the abbreviated name of a day consists of the first 2 letters from the day's full name;
30
+ // while this is common for most languages, there are also exceptions for languages like Thai, Loa, Myanmar,
31
+ // etc. where this is not correct; for these cases, specify an array with the abbreviations to be used for
32
+ // the 7 days of the week; leave it FALSE to use the first 2 letters of a day's name as the abbreviation.
33
+ //
34
+ // default is FALSE
35
+ days_abbr: false,
36
+
37
+ // direction of the calendar
38
+ //
39
+ // a positive or negative integer: n (a positive integer) creates a future-only calendar beginning at n days
40
+ // after today; -n (a negative integer); if n is 0, the calendar has no restrictions. use boolean true for
41
+ // a future-only calendar starting with today and use boolean false for a past-only calendar ending today.
42
+ //
43
+ // you may also set this property to an array with two elements in the following combinations:
44
+ //
45
+ // - first item is boolean TRUE (calendar starts today), an integer > 0 (calendar starts n days after
46
+ // today), or a valid date given in the format defined by the "format" attribute, using English for
47
+ // month names (calendar starts at the specified date), and the second item is boolean FALSE (the calendar
48
+ // has no ending date), an integer > 0 (calendar ends n days after the starting date), or a valid date
49
+ // given in the format defined by the "format" attribute, using English for month names, and which occurs
50
+ // after the starting date (calendar ends at the specified date)
51
+ //
52
+ // - first item is boolean FALSE (calendar ends today), an integer < 0 (calendar ends n days before today),
53
+ // or a valid date given in the format defined by the "format" attribute, using English for month names
54
+ // (calendar ends at the specified date), and the second item is an integer > 0 (calendar ends n days
55
+ // before the ending date), or a valid date given in the format defined by the "format" attribute, using
56
+ // English for month names and which occurs before the starting date (calendar starts at the specified
57
+ // date)
58
+ //
59
+ // [1, 7] - calendar starts tomorrow and ends seven days after that
60
+ // [true, 7] - calendar starts today and ends seven days after that
61
+ // ['2013-01-01', false] - calendar starts on January 1st 2013 and has no ending date ("format" is YYYY-MM-DD)
62
+ // [false, '2012-01-01'] - calendar ends today and starts on January 1st 2012 ("format" is YYYY-MM-DD)
63
+ //
64
+ // note that "disabled_dates" property will still apply!
65
+ //
66
+ // default is 0 (no restrictions)
67
+ direction: 0,
68
+
69
+ // an array of disabled dates in the following format: 'day month year weekday' where "weekday" is optional
70
+ // and can be 0-6 (Saturday to Sunday); the syntax is similar to cron's syntax: the values are separated by
71
+ // spaces and may contain * (asterisk) - (dash) and , (comma) delimiters:
72
+ //
73
+ // ['1 1 2012'] would disable January 1, 2012;
74
+ // ['* 1 2012'] would disable all days in January 2012;
75
+ // ['1-10 1 2012'] would disable January 1 through 10 in 2012;
76
+ // ['1,10 1 2012'] would disable January 1 and 10 in 2012;
77
+ // ['1-10,20,22,24 1-3 *'] would disable 1 through 10, plus the 22nd and 24th of January through March for every year;
78
+ // ['* * * 0,6'] would disable all Saturdays and Sundays;
79
+ // ['01 07 2012', '02 07 2012', '* 08 2012'] would disable 1st and 2nd of July 2012, and all of August of 2012
80
+ //
81
+ // default is FALSE, no disabled dates
82
+ disabled_dates: false,
83
+
84
+ // an array of enabled dates in the same format as required for "disabled_dates" property.
85
+ // to be used together with the "disabled_dates" property by first setting the "disabled_dates" property to
86
+ // something like "[* * * *]" (which will disable everything) and the setting the "enabled_dates" property to,
87
+ // say, "[* * * 0,6]" to enable just weekends.
88
+ enabled_dates: false,
89
+
90
+ // week's starting day
91
+ //
92
+ // valid values are 0 to 6, Sunday to Saturday
93
+ //
94
+ // default is 1, Monday
95
+ first_day_of_week: 1,
96
+
97
+ // format of the returned date
98
+ //
99
+ // accepts the following characters for date formatting: d, D, j, l, N, w, S, F, m, M, n, Y, y borrowing
100
+ // syntax from (PHP's date function)
101
+ //
102
+ // note that when setting a date format without days ('d', 'j'), the users will be able to select only years
103
+ // and months, and when setting a format without months and days ('F', 'm', 'M', 'n', 'd', 'j'), the
104
+ // users will be able to select only years; likewise, when setting a date format with just months ('F', 'm',
105
+ // 'M', 'n') or just years ('Y', 'y'), users will be able to select only months and years, respectively.
106
+ //
107
+ // also note that the value of the "view" property (see below) may be overridden if it is the case: a value of
108
+ // "days" for the "view" property makes no sense if the date format doesn't allow the selection of days.
109
+ //
110
+ // default is Y-m-d
111
+ format: 'Y-m-d',
112
+
113
+ // should the icon for opening the datepicker be inside the element?
114
+ // if set to FALSE, the icon will be placed to the right of the parent element, while if set to TRUE it will
115
+ // be placed to the right of the parent element, but *inside* the element itself
116
+ //
117
+ // default is TRUE
118
+ inside: true,
119
+
120
+ // the caption for the "Clear" button
121
+ lang_clear_date: 'Clear date',
122
+
123
+ // months names
124
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
125
+
126
+ // by default, the abbreviated name of a month consists of the first 3 letters from the month's full name;
127
+ // while this is common for most languages, there are also exceptions for languages like Thai, Loa, Myanmar,
128
+ // etc. where this is not correct; for these cases, specify an array with the abbreviations to be used for
129
+ // the months of the year; leave it FALSE to use the first 3 letters of a month's name as the abbreviation.
130
+ //
131
+ // default is FALSE
132
+ months_abbr: false,
133
+
134
+ // the offset, in pixels (x, y), to shift the date picker's position relative to the top-right of the icon
135
+ // that toggles the date picker or, if the icon is disabled, relative to the top-right corner of the element
136
+ // the plugin is attached to.
137
+ //
138
+ // note that this only applies if the position of element relative to the browser's viewport doesn't require
139
+ // the date picker to be placed automatically so that it is visible!
140
+ //
141
+ // default is [5, -5]
142
+ offset: [5, -5],
143
+
144
+ // if set as a jQuery element with a Zebra_DatePicker attached, that particular date picker will use the
145
+ // current date picker's value as starting date
146
+ // note that the rules set in the "direction" property will still apply, only that the reference date will
147
+ // not be the current system date but the value selected in the current date picker
148
+ // default is FALSE (not paired with another date picker)
149
+ pair: false,
150
+
151
+ // should the element the calendar is attached to, be read-only?
152
+ // if set to TRUE, a date can be set only through the date picker and cannot be entered manually
153
+ //
154
+ // default is TRUE
155
+ readonly_element: true,
156
+
157
+ // should days from previous and/or next month be selectable when visible?
158
+ // note that if the value of this property is set to TRUE, the value of "show_other_months" will be considered
159
+ // TRUE regardless of the actual value!
160
+ //
161
+ // default is FALSE
162
+ select_other_months: false,
163
+
164
+ // should the "Clear date" button be visible?
165
+ //
166
+ // accepted values are:
167
+ //
168
+ // - 0 (zero) - the button for clearing a previously selected date is shown only if a previously selected date
169
+ // already exists; this means that if the input the date picker is attached to is empty, and the user selects
170
+ // a date for the first time, this button will not be visible; once the user picked a date and opens the date
171
+ // picker again, this time the button will be visible.
172
+ //
173
+ // - TRUE will make the button visible all the time
174
+ //
175
+ // - FALSE will disable the button
176
+ //
177
+ // default is "0" (without quotes)
178
+ show_clear_date: 0,
179
+
180
+ // should a calendar icon be added to the elements the plugin is attached to?
181
+ //
182
+ // default is TRUE
183
+ show_icon: true,
184
+
185
+ // should days from previous and/or next month be visible?
186
+ //
187
+ // default is TRUE
188
+ show_other_months: true,
189
+
190
+ // should the "Today" button be visible?
191
+ // setting it to anything but boolean FALSE will enable the button and will use the property's value as
192
+ // caption for the button; setting it to FALSE will disable the button
193
+ //
194
+ // default is "Today"
195
+ show_select_today: 'Today',
196
+
197
+ // should an extra column be shown, showing the number of each week?
198
+ // anything other than FALSE will enable this feature, and use the given value as column title
199
+ // i.e. show_week_number: 'Wk' would enable this feature and have "Wk" as the column's title
200
+ //
201
+ // default is FALSE
202
+ show_week_number: false,
203
+
204
+ // a default date to start the date picker with
205
+ // must be specified in the format defined by the "format" property, or it will be ignored!
206
+ // note that this value is used only if there is no value in the field the date picker is attached to!
207
+ start_date: false,
208
+
209
+ // how should the date picker start; valid values are "days", "months" and "years"
210
+ // note that the date picker is always cycling days-months-years when clicking in the date picker's header,
211
+ // and years-months-days when selecting dates (unless one or more of the views are missing due to the date's
212
+ // format)
213
+ //
214
+ // also note that the value of the "view" property may be overridden if the date's format requires so! (i.e.
215
+ // "days" for the "view" property makes no sense if the date format doesn't allow the selection of days)
216
+ //
217
+ // default is "days"
218
+ view: 'days',
219
+
220
+ // days of the week that are considered "weekend days"
221
+ // valid values are 0 to 6, Sunday to Saturday
222
+ //
223
+ // default values are 0 and 6 (Saturday and Sunday)
224
+ weekend_days: [0, 6],
225
+
226
+ // when set to TRUE, day numbers < 10 will be prefixed with 0; set to FALSE if you don't want that
227
+ //
228
+ // default is TRUE
229
+ zero_pad: false,
230
+
231
+ // callback function to be executed whenever the user changes the view (days/months/years), as well as when
232
+ // the user navigates by clicking on the "next"/"previous" icons in any of the views;
233
+ //
234
+ // the callback function called by this event takes 3 arguments - the first argument represents the current
235
+ // view (can be "days", "months" or "years"), the second argument represents an array containing the "active"
236
+ // elements (not disabled) from the view, as jQuery elements, allowing for easy customization and interaction
237
+ // with particular cells in the date picker's view, while the third argument is a reference to the element
238
+ // the date picker is attached to, as a jQuery object
239
+ //
240
+ // for simplifying searching for particular dates, each element in the second argument will also have a
241
+ // "date" data attribute whose format depends on the value of the "view" argument:
242
+ // - YYYY-MM-DD for elements in the "days" view
243
+ // - YYYY-MM for elements in the "months" view
244
+ // - YYYY for elements in the "years" view
245
+ onChange: null,
246
+
247
+ // callback function to be executed when the user clicks the "Clear" button
248
+ // the callback function takes a single argument:
249
+ // - a reference to the element the date picker is attached to, as a jQuery object
250
+ onClear: null,
251
+
252
+ // callback function to be executed when a date is selected
253
+ // the callback function takes 4 arguments:
254
+ // - the date in the format specified by the "format" attribute;
255
+ // - the date in YYYY-MM-DD format
256
+ // - the date as a JavaScript Date object
257
+ // - a reference to the element the date picker is attached to, as a jQuery object
258
+ onSelect: null
259
+
260
+ };
261
+
262
+ // private properties
263
+ var view, datepicker, icon, header, daypicker, monthpicker, yearpicker, cleardate, current_system_month, current_system_year,
264
+ current_system_day, first_selectable_month, first_selectable_year, first_selectable_day, selected_month, selected_year,
265
+ default_day, default_month, default_year, enabled_dates, disabled_dates, shim, start_date, end_date, last_selectable_day,
266
+ last_selectable_year, last_selectable_month, daypicker_cells, monthpicker_cells, yearpicker_cells, views, clickables,
267
+ selecttoday, footer, show_select_today, timeout;
268
+
269
+ var plugin = this;
270
+
271
+ plugin.settings = {};
272
+
273
+ // the jQuery version of the element
274
+ // "element" (without the $) will point to the DOM element
275
+ var $element = $(element);
276
+
277
+ /**
278
+ * Constructor method. Initializes the date picker.
279
+ *
280
+ * @return void
281
+ */
282
+ var init = function(update) {
283
+
284
+ // merge default settings with user-settings (unless we're just updating settings)
285
+ if (!update) plugin.settings = $.extend({}, defaults, options);
286
+
287
+ // if the element should be read-only, set the "readonly" attribute
288
+ if (plugin.settings.readonly_element) $element.attr('readonly', 'readonly');
289
+
290
+ // determine the views the user can cycle through, depending on the format
291
+ // that is, if the format doesn't contain the day, the user will be able to cycle only through years and months,
292
+ // whereas if the format doesn't contain months nor days, the user will only be able to select years
293
+
294
+ var
295
+
296
+ // the characters that may be present in the date format and that represent days, months and years
297
+ date_chars = {
298
+ days: ['d', 'j', 'D'],
299
+ months: ['F', 'm', 'M', 'n', 't'],
300
+ years: ['o', 'Y', 'y']
301
+ },
302
+
303
+ // some defaults
304
+ has_days = false,
305
+ has_months = false,
306
+ has_years = false,
307
+ type = null;
308
+
309
+ // iterate through all the character blocks
310
+ for (type in date_chars)
311
+
312
+ // iterate through the characters of each block
313
+ $.each(date_chars[type], function(index, character) {
314
+
315
+ // if current character exists in the "format" property
316
+ if (plugin.settings.format.indexOf(character) > -1)
317
+
318
+ // set to TRUE the appropriate flag
319
+ if (type == 'days') has_days = true;
320
+ else if (type == 'months') has_months = true;
321
+ else if (type == 'years') has_years = true;
322
+
323
+ });
324
+
325
+ // if user can cycle through all the views, set the flag accordingly
326
+ if (has_days && has_months && has_years) views = ['years', 'months', 'days'];
327
+
328
+ // if user can cycle only through year and months, set the flag accordingly
329
+ else if (!has_days && has_months && has_years) views = ['years', 'months'];
330
+
331
+ // if user can only see the year picker, set the flag accordingly
332
+ else if (!has_days && !has_months && has_years) views = ['years'];
333
+
334
+ // if user can only see the month picker, set the flag accordingly
335
+ else if (!has_days && has_months && !has_years) views = ['months'];
336
+
337
+ // if invalid format (no days, no months, no years) use the default where the user is able to cycle through
338
+ // all the views
339
+ else views = ['years', 'months', 'days'];
340
+
341
+ // if the starting view is not amongst the views the user can cycle through, set the correct starting view
342
+ if ($.inArray(plugin.settings.view, views) == -1) plugin.settings.view = views[views.length - 1];
343
+
344
+ // parse the rules for disabling dates and turn them into arrays of arrays
345
+
346
+ // array that will hold the rules for enabling/disabling dates
347
+ disabled_dates = []; enabled_dates = [];
348
+
349
+ var dates;
350
+
351
+ // it's the same logic for preparing the enabled/disable dates...
352
+ for (var l = 0; l < 2; l++) {
353
+
354
+ // first time we're doing disabled dates,
355
+ if (l === 0) dates = plugin.settings.disabled_dates;
356
+
357
+ // second time we're doing enabled_dates
358
+ else dates = plugin.settings.enabled_dates;
359
+
360
+ // if we have a non-empty array
361
+ if ($.isArray(dates) && dates.length > 0)
362
+
363
+ // iterate through the rules
364
+ $.each(dates, function() {
365
+
366
+ // split the values in rule by white space
367
+ var rules = this.split(' ');
368
+
369
+ // there can be a maximum of 4 rules (days, months, years and, optionally, day of the week)
370
+ for (var i = 0; i < 4; i++) {
371
+
372
+ // if one of the values is not available
373
+ // replace it with a * (wildcard)
374
+ if (!rules[i]) rules[i] = '*';
375
+
376
+ // if rule contains a comma, create a new array by splitting the rule by commas
377
+ // if there are no commas create an array containing the rule's string
378
+ rules[i] = (rules[i].indexOf(',') > -1 ? rules[i].split(',') : new Array(rules[i]));
379
+
380
+ // iterate through the items in the rule
381
+ for (var j = 0; j < rules[i].length; j++)
382
+
383
+ // if item contains a dash (defining a range)
384
+ if (rules[i][j].indexOf('-') > -1) {
385
+
386
+ // get the lower and upper limits of the range
387
+ var limits = rules[i][j].match(/^([0-9]+)\-([0-9]+)/);
388
+
389
+ // if range is valid
390
+ if (null !== limits) {
391
+
392
+ // iterate through the range
393
+ for (var k = to_int(limits[1]); k <= to_int(limits[2]); k++)
394
+
395
+ // if value is not already among the values of the rule
396
+ // add it to the rule
397
+ if ($.inArray(k, rules[i]) == -1) rules[i].push(k + '');
398
+
399
+ // remove the range indicator
400
+ rules[i].splice(j, 1);
401
+
402
+ }
403
+
404
+ }
405
+
406
+ // iterate through the items in the rule
407
+ // and make sure that numbers are numbers
408
+ for (j = 0; j < rules[i].length; j++) rules[i][j] = (isNaN(to_int(rules[i][j])) ? rules[i][j] : to_int(rules[i][j]));
409
+
410
+ }
411
+
412
+ // add to the correct list of processed rules
413
+ // first time we're doing disabled dates,
414
+ if (l === 0) disabled_dates.push(rules);
415
+
416
+ // second time we're doing enabled_dates
417
+ else enabled_dates.push(rules);
418
+
419
+ });
420
+
421
+ }
422
+
423
+ var
424
+
425
+ // cache the current system date
426
+ date = new Date(),
427
+
428
+ // when the date picker's starting date depends on the value of another date picker, this value will be
429
+ // set by the other date picker
430
+ // this value will be used as base for all calculations (if not set, will be the same as the current
431
+ // system date)
432
+ reference_date = (!plugin.settings.reference_date ? ($element.data('zdp_reference_date') && undefined !== $element.data('zdp_reference_date') ? $element.data('zdp_reference_date') : date) : plugin.settings.reference_date),
433
+
434
+ tmp_start_date, tmp_end_date;
435
+
436
+ // reset these values here as this method might be called more than once during a date picker's lifetime
437
+ // (when the selectable dates depend on the values from another date picker)
438
+ start_date = undefined; end_date = undefined;
439
+
440
+ // extract the date parts
441
+ // also, save the current system month/day/year - we'll use them to highlight the current system date
442
+ first_selectable_month = reference_date.getMonth();
443
+ current_system_month = date.getMonth();
444
+ first_selectable_year = reference_date.getFullYear();
445
+ current_system_year = date.getFullYear();
446
+ first_selectable_day = reference_date.getDate();
447
+ current_system_day = date.getDate();
448
+
449
+ // check if the calendar has any restrictions
450
+
451
+ // calendar is future-only, starting today
452
+ // it means we have a starting date (the current system date), but no ending date
453
+ if (plugin.settings.direction === true) start_date = reference_date;
454
+
455
+ // calendar is past only, ending today
456
+ else if (plugin.settings.direction === false) {
457
+
458
+ // it means we have an ending date (the reference date), but no starting date
459
+ end_date = reference_date;
460
+
461
+ // extract the date parts
462
+ last_selectable_month = end_date.getMonth();
463
+ last_selectable_year = end_date.getFullYear();
464
+ last_selectable_day = end_date.getDate();
465
+
466
+ } else if (
467
+
468
+ // if direction is not given as an array and the value is an integer > 0
469
+ (!$.isArray(plugin.settings.direction) && is_integer(plugin.settings.direction) && to_int(plugin.settings.direction) > 0) ||
470
+
471
+ // or direction is given as an array
472
+ ($.isArray(plugin.settings.direction) && (
473
+
474
+ // and first entry is a valid date
475
+ (tmp_start_date = check_date(plugin.settings.direction[0])) ||
476
+ // or a boolean TRUE
477
+ plugin.settings.direction[0] === true ||
478
+ // or an integer > 0
479
+ (is_integer(plugin.settings.direction[0]) && plugin.settings.direction[0] > 0)
480
+
481
+ ) && (
482
+
483
+ // and second entry is a valid date
484
+ (tmp_end_date = check_date(plugin.settings.direction[1])) ||
485
+ // or a boolean FALSE
486
+ plugin.settings.direction[1] === false ||
487
+ // or integer >= 0
488
+ (is_integer(plugin.settings.direction[1]) && plugin.settings.direction[1] >= 0)
489
+
490
+ ))
491
+
492
+ ) {
493
+
494
+ // if an exact starting date was given, use that as a starting date
495
+ if (tmp_start_date) start_date = tmp_start_date;
496
+
497
+ // otherwise
498
+ else
499
+
500
+ // figure out the starting date
501
+ // use the Date object to normalize the date
502
+ // for example, 2011 05 33 will be transformed to 2011 06 02
503
+ start_date = new Date(
504
+ first_selectable_year,
505
+ first_selectable_month,
506
+ first_selectable_day + (!$.isArray(plugin.settings.direction) ? to_int(plugin.settings.direction) : to_int(plugin.settings.direction[0] === true ? 0 : plugin.settings.direction[0]))
507
+ );
508
+
509
+ // re-extract the date parts
510
+ first_selectable_month = start_date.getMonth();
511
+ first_selectable_year = start_date.getFullYear();
512
+ first_selectable_day = start_date.getDate();
513
+
514
+ // if an exact ending date was given and the date is after the starting date, use that as a ending date
515
+ if (tmp_end_date && +tmp_end_date >= +start_date) end_date = tmp_end_date;
516
+
517
+ // if have information about the ending date
518
+ else if (!tmp_end_date && plugin.settings.direction[1] !== false && $.isArray(plugin.settings.direction))
519
+
520
+ // figure out the ending date
521
+ // use the Date object to normalize the date
522
+ // for example, 2011 05 33 will be transformed to 2011 06 02
523
+ end_date = new Date(
524
+ first_selectable_year,
525
+ first_selectable_month,
526
+ first_selectable_day + to_int(plugin.settings.direction[1])
527
+ );
528
+
529
+ // if a valid ending date exists
530
+ if (end_date) {
531
+
532
+ // extract the date parts
533
+ last_selectable_month = end_date.getMonth();
534
+ last_selectable_year = end_date.getFullYear();
535
+ last_selectable_day = end_date.getDate();
536
+
537
+ }
538
+
539
+ } else if (
540
+
541
+ // if direction is not given as an array and the value is an integer < 0
542
+ (!$.isArray(plugin.settings.direction) && is_integer(plugin.settings.direction) && to_int(plugin.settings.direction) < 0) ||
543
+
544
+ // or direction is given as an array
545
+ ($.isArray(plugin.settings.direction) && (
546
+
547
+ // and first entry is boolean FALSE
548
+ plugin.settings.direction[0] === false ||
549
+ // or an integer < 0
550
+ (is_integer(plugin.settings.direction[0]) && plugin.settings.direction[0] < 0)
551
+
552
+ ) && (
553
+
554
+ // and second entry is a valid date
555
+ (tmp_start_date = check_date(plugin.settings.direction[1])) ||
556
+ // or an integer >= 0
557
+ (is_integer(plugin.settings.direction[1]) && plugin.settings.direction[1] >= 0)
558
+
559
+ ))
560
+
561
+ ) {
562
+
563
+ // figure out the ending date
564
+ // use the Date object to normalize the date
565
+ // for example, 2011 05 33 will be transformed to 2011 06 02
566
+ end_date = new Date(
567
+ first_selectable_year,
568
+ first_selectable_month,
569
+ first_selectable_day + (!$.isArray(plugin.settings.direction) ? to_int(plugin.settings.direction) : to_int(plugin.settings.direction[0] === false ? 0 : plugin.settings.direction[0]))
570
+ );
571
+
572
+ // re-extract the date parts
573
+ last_selectable_month = end_date.getMonth();
574
+ last_selectable_year = end_date.getFullYear();
575
+ last_selectable_day = end_date.getDate();
576
+
577
+ // if an exact starting date was given, and the date is before the ending date, use that as a starting date
578
+ if (tmp_start_date && +tmp_start_date < +end_date) start_date = tmp_start_date;
579
+
580
+ // if have information about the starting date
581
+ else if (!tmp_start_date && $.isArray(plugin.settings.direction))
582
+
583
+ // figure out the staring date
584
+ // use the Date object to normalize the date
585
+ // for example, 2011 05 33 will be transformed to 2011 06 02
586
+ start_date = new Date(
587
+ last_selectable_year,
588
+ last_selectable_month,
589
+ last_selectable_day - to_int(plugin.settings.direction[1])
590
+ );
591
+
592
+ // if a valid starting date exists
593
+ if (start_date) {
594
+
595
+ // extract the date parts
596
+ first_selectable_month = start_date.getMonth();
597
+ first_selectable_year = start_date.getFullYear();
598
+ first_selectable_day = start_date.getDate();
599
+
600
+ }
601
+
602
+ // if there are disabled dates
603
+ } else if ($.isArray(plugin.settings.disabled_dates) && plugin.settings.disabled_dates.length > 0)
604
+
605
+ // iterate through the rules for disabling dates
606
+ for (var interval in disabled_dates)
607
+
608
+ // only if there is a rule that disables *everything*
609
+ if (disabled_dates[interval][0] == '*' && disabled_dates[interval][1] == '*' && disabled_dates[interval][2] == '*' && disabled_dates[interval][3] == '*') {
610
+
611
+ var tmpDates = [];
612
+
613
+ // iterate through the rules for enabling dates
614
+ // looking for the minimum/maximum selectable date (if it's the case)
615
+ $.each(enabled_dates, function() {
616
+
617
+ var rule = this;
618
+
619
+ // if the rule doesn't apply to all years
620
+ if (rule[2][0] != '*')
621
+
622
+ // format date and store it in our stack
623
+ tmpDates.push(parseInt(
624
+ rule[2][0] +
625
+ (rule[1][0] == '*' ? '12' : str_pad(rule[1][0], 2)) +
626
+ (rule[0][0] == '*' ? (rule[1][0] == '*' ? '31' : new Date(rule[2][0], rule[1][0], 0).getDate()) : str_pad(rule[0][0], 2)), 10));
627
+
628
+ });
629
+
630
+ // sort dates ascending
631
+ tmpDates.sort();
632
+
633
+ // if we have any rules
634
+ if (tmpDates.length > 0) {
635
+
636
+ // get date parts
637
+ var matches = (tmpDates[0] + '').match(/([0-9]{4})([0-9]{2})([0-9]{2})/);
638
+
639
+ // assign the date parts to the appropriate variables
640
+ first_selectable_year = parseInt(matches[1], 10);
641
+ first_selectable_month = parseInt(matches[2], 10) - 1;
642
+ first_selectable_day = parseInt(matches[3], 10);
643
+
644
+ }
645
+
646
+ // don't look further
647
+ break;
648
+
649
+ }
650
+
651
+ // if first selectable date exists but is disabled, find the actual first selectable date
652
+ if (is_disabled(first_selectable_year, first_selectable_month, first_selectable_day)) {
653
+
654
+ // loop until we find the first selectable year
655
+ while (is_disabled(first_selectable_year)) {
656
+
657
+ // if calendar is past-only,
658
+ if (!start_date) {
659
+
660
+ // decrement the year
661
+ first_selectable_year--;
662
+
663
+ // because we've changed years, reset the month to December
664
+ first_selectable_month = 11;
665
+
666
+ // otherwise
667
+ } else {
668
+
669
+ // increment the year
670
+ first_selectable_year++;
671
+
672
+ // because we've changed years, reset the month to January
673
+ first_selectable_month = 0;
674
+
675
+ }
676
+
677
+ }
678
+
679
+ // loop until we find the first selectable month
680
+ while (is_disabled(first_selectable_year, first_selectable_month)) {
681
+
682
+ // if calendar is past-only
683
+ if (!start_date) {
684
+
685
+ // decrement the month
686
+ first_selectable_month--;
687
+
688
+ // because we've changed months, reset the day to the last day of the month
689
+ first_selectable_day = new Date(first_selectable_year, first_selectable_month + 1, 0).getDate();
690
+
691
+ // otherwise
692
+ } else {
693
+
694
+ // increment the month
695
+ first_selectable_month++;
696
+
697
+ // because we've changed months, reset the day to the first day of the month
698
+ first_selectable_day = 1;
699
+
700
+ }
701
+
702
+ // if we moved to a following year
703
+ if (first_selectable_month > 11) {
704
+
705
+ // increment the year
706
+ first_selectable_year++;
707
+
708
+ // reset the month to January
709
+ first_selectable_month = 0;
710
+
711
+ // because we've changed months, reset the day to the first day of the month
712
+ first_selectable_day = 1;
713
+
714
+ // if we moved to a previous year
715
+ } else if (first_selectable_month < 0) {
716
+
717
+ // decrement the year
718
+ first_selectable_year--;
719
+
720
+ // reset the month to December
721
+ first_selectable_month = 11;
722
+
723
+ // because we've changed months, reset the day to the last day of the month
724
+ first_selectable_day = new Date(first_selectable_year, first_selectable_month + 1, 0).getDate();
725
+
726
+ }
727
+
728
+ }
729
+
730
+ // loop until we find the first selectable day
731
+ while (is_disabled(first_selectable_year, first_selectable_month, first_selectable_day)) {
732
+
733
+ // if calendar is past-only, decrement the day
734
+ if (!start_date) first_selectable_day--;
735
+
736
+ // otherwise, increment the day
737
+ else first_selectable_day++;
738
+
739
+ // use the Date object to normalize the date
740
+ // for example, 2011 05 33 will be transformed to 2011 06 02
741
+ date = new Date(first_selectable_year, first_selectable_month, first_selectable_day);
742
+
743
+ // re-extract date parts from the normalized date
744
+ // as we use them in the current loop
745
+ first_selectable_year = date.getFullYear();
746
+ first_selectable_month = date.getMonth();
747
+ first_selectable_day = date.getDate();
748
+
749
+ }
750
+
751
+ // use the Date object to normalize the date
752
+ // for example, 2011 05 33 will be transformed to 2011 06 02
753
+ date = new Date(first_selectable_year, first_selectable_month, first_selectable_day);
754
+
755
+ // re-extract date parts from the normalized date
756
+ // as we use them in the current loop
757
+ first_selectable_year = date.getFullYear();
758
+ first_selectable_month = date.getMonth();
759
+ first_selectable_day = date.getDate();
760
+
761
+ }
762
+
763
+ // get the default date, from the element, and check if it represents a valid date, according to the required format
764
+ var default_date = check_date($element.val() || (plugin.settings.start_date ? plugin.settings.start_date : ''));
765
+
766
+ // if there is a default date but it is disabled
767
+ if (default_date && is_disabled(default_date.getFullYear(), default_date.getMonth(), default_date.getDate()))
768
+
769
+ // clear the value of the parent element
770
+ $element.val('');
771
+
772
+ // updates value for the date picker whose starting date depends on the selected date (if any)
773
+ update_dependent(default_date);
774
+
775
+ // if date picker is not always visible
776
+ if (!plugin.settings.always_visible) {
777
+
778
+ // if we're just creating the date picker
779
+ if (!update) {
780
+
781
+ // if a calendar icon should be added to the element the plugin is attached to, create the icon now
782
+ if (plugin.settings.show_icon) {
783
+
784
+ // strangely, in Firefox 21+ (or maybe even earlier) input elements have their "display" property
785
+ // set to "inline" instead of "inline-block" as do all the other browsers.
786
+ // because this behavior brakes the positioning of the icon, we'll set the "display" property to
787
+ // "inline-block" before anything else;
788
+ if (browser.name == 'firefox' && $element.is('input[type="text"]') && $element.css('display') == 'inline') $element.css('display', 'inline-block');
789
+
790
+ // we create a wrapper for the parent element so that we can later position the icon
791
+ // also, make sure the wrapper inherits some important css properties of the parent element
792
+ var icon_wrapper = jQuery('<span class="Zebra_DatePicker_Icon_Wrapper"></span>').css({
793
+ 'display': $element.css('display'),
794
+ 'position': $element.css('position') == 'static' ? 'relative' : $element.css('position'),
795
+ 'float': $element.css('float'),
796
+ 'top': $element.css('top'),
797
+ 'right': $element.css('right'),
798
+ 'bottom': $element.css('bottom'),
799
+ 'left': $element.css('left')
800
+ });
801
+
802
+ // put wrapper around the element
803
+ // also, make sure we set some important css properties for it
804
+ $element.wrap(icon_wrapper).css({
805
+ 'position': 'relative',
806
+ 'top': 'auto',
807
+ 'right': 'auto',
808
+ 'bottom': 'auto',
809
+ 'left': 'auto'
810
+ });
811
+
812
+ // create the actual calendar icon (show a disabled icon if the element is disabled)
813
+ icon = jQuery('<button type="button" class="Zebra_DatePicker_Icon' + ($element.attr('disabled') == 'disabled' ? ' Zebra_DatePicker_Icon_Disabled' : '') + '">Pick a date</button>');
814
+
815
+ // a reference to the icon, as a global property
816
+ plugin.icon = icon;
817
+
818
+ // the date picker will open when clicking both the icon and the element the plugin is attached to
819
+ clickables = icon.add($element);
820
+
821
+ // if calendar icon is not visible, the date picker will open when clicking the element
822
+ } else clickables = $element;
823
+
824
+ // attach the click event to the clickable elements (icon and/or element)
825
+ clickables.bind('click', function(e) {
826
+
827
+ e.preventDefault();
828
+
829
+ // if element is not disabled
830
+ if (!$element.attr('disabled'))
831
+
832
+ // if the date picker is visible, hide it
833
+ if (datepicker.css('display') != 'none') plugin.hide();
834
+
835
+ // if the date picker is not visible, show it
836
+ else plugin.show();
837
+
838
+ });
839
+
840
+ // if icon exists, inject it into the DOM, right after the parent element (and inside the wrapper)
841
+ if (undefined !== icon) icon.insertAfter($element);
842
+
843
+ }
844
+
845
+ // if calendar icon exists
846
+ if (undefined !== icon) {
847
+
848
+ // needed when updating: remove any inline style set previously by library,
849
+ // so we get the right values below
850
+ icon.attr('style', '');
851
+
852
+ // if calendar icon is to be placed *inside* the element
853
+ // add an extra class to the icon
854
+ if (plugin.settings.inside) icon.addClass('Zebra_DatePicker_Icon_Inside');
855
+
856
+ var
857
+
858
+ // get element' width and height (including margins)
859
+ element_width = $element.outerWidth(),
860
+ element_height = $element.outerHeight(),
861
+ element_margin_left = parseInt($element.css('marginLeft'), 10) || 0,
862
+ element_margin_top = parseInt($element.css('marginTop'), 10) || 0,
863
+
864
+ // get icon's width, height and margins
865
+ icon_width = icon.outerWidth(),
866
+ icon_height = icon.outerHeight(),
867
+ icon_margin_left = parseInt(icon.css('marginLeft'), 10) || 0,
868
+ icon_margin_right = parseInt(icon.css('marginRight'), 10) || 0;
869
+
870
+ // if icon is to be placed *inside* the element
871
+ // position the icon accordingly
872
+ if (plugin.settings.inside)
873
+
874
+ icon.css({
875
+ 'top': element_margin_top + ((element_height - icon_height) / 2),
876
+ 'left': element_margin_left + (element_width - icon_width - icon_margin_right)
877
+ });
878
+
879
+ // if icon is to be placed to the right of the element
880
+ // position the icon accordingly
881
+ else
882
+
883
+ icon.css({
884
+ 'top': element_margin_top + ((element_height - icon_height) / 2),
885
+ 'left': element_margin_left + element_width + icon_margin_left
886
+ });
887
+
888
+ }
889
+
890
+ }
891
+
892
+ // if calendar icon exists (there's no icon if the date picker is always visible or it is specifically hidden)
893
+ if (undefined !== icon)
894
+
895
+ // if parent element is not visible (has display: none, width and height are explicitly set to 0, an ancestor
896
+ // element is hidden, so the element is not shown on the page), hide the icon, or show it otherwise
897
+ if (!($element.is(':visible'))) icon.hide(); else icon.show();
898
+
899
+ // if the "Today" button is to be shown and it makes sense to be shown
900
+ // (the "days" view is available and "today" is not a disabled date)
901
+ show_select_today = (plugin.settings.show_select_today !== false && $.inArray('days', views) > -1 && !is_disabled(current_system_year, current_system_month, current_system_day) ? plugin.settings.show_select_today : false);
902
+
903
+ // if we just needed to recompute the things above, return now
904
+ if (update) return;
905
+
906
+ // if icon exists, update its position when the page is resized
907
+ if (icon) $(window).bind('resize', _resize);
908
+
909
+ // generate the container that will hold everything
910
+ var html = '' +
911
+ '<div class="Zebra_DatePicker">' +
912
+ '<table class="dp_header">' +
913
+ '<tr>' +
914
+ '<td class="dp_previous">&#171;</td>' +
915
+ '<td class="dp_caption">&#032;</td>' +
916
+ '<td class="dp_next">&#187;</td>' +
917
+ '</tr>' +
918
+ '</table>' +
919
+ '<table class="dp_daypicker"></table>' +
920
+ '<table class="dp_monthpicker"></table>' +
921
+ '<table class="dp_yearpicker"></table>' +
922
+ '<table class="dp_footer"><tr>' +
923
+ '<td class="dp_today"' + (plugin.settings.show_clear_date !== false ? ' style="width:50%"' : '') + '>' + show_select_today + '</td>' +
924
+ '<td class="dp_clear"' + (show_select_today !== false ? ' style="width:50%"' : '') + '>' + plugin.settings.lang_clear_date + '</td>' +
925
+ '</tr></table>' +
926
+ '</div>';
927
+
928
+ // create a jQuery object out of the HTML above and create a reference to it
929
+ datepicker = $(html);
930
+
931
+ // a reference to the calendar, as a global property
932
+ plugin.datepicker = datepicker;
933
+
934
+ // create references to the different parts of the date picker
935
+ header = $('table.dp_header', datepicker);
936
+ daypicker = $('table.dp_daypicker', datepicker);
937
+ monthpicker = $('table.dp_monthpicker', datepicker);
938
+ yearpicker = $('table.dp_yearpicker', datepicker);
939
+ footer = $('table.dp_footer', datepicker);
940
+ selecttoday = $('td.dp_today', footer);
941
+ cleardate = $('td.dp_clear', footer);
942
+
943
+ // if date picker is not always visible
944
+ if (!plugin.settings.always_visible)
945
+
946
+ // inject the container into the DOM
947
+ $('body').append(datepicker);
948
+
949
+ // otherwise, if element is not disabled
950
+ else if (!$element.attr('disabled')) {
951
+
952
+ // inject the date picker into the designated container element
953
+ plugin.settings.always_visible.append(datepicker);
954
+
955
+ // and make it visible right away
956
+ plugin.show();
957
+
958
+ }
959
+
960
+ // add the mouseover/mousevents to all to the date picker's cells
961
+ // except those that are not selectable
962
+ datepicker.
963
+ delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked, .dp_week_number)', 'mouseover', function() {
964
+ $(this).addClass('dp_hover');
965
+ }).
966
+ delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked, .dp_week_number)', 'mouseout', function() {
967
+ $(this).removeClass('dp_hover');
968
+ });
969
+
970
+ // prevent text highlighting for the text in the header
971
+ // (for the case when user keeps clicking the "next" and "previous" buttons)
972
+ disable_text_select($('td', header));
973
+
974
+ // event for when clicking the "previous" button
975
+ $('.dp_previous', header).bind('click', function() {
976
+
977
+ // if button is not disabled
978
+ if (!$(this).hasClass('dp_blocked')) {
979
+
980
+ // if view is "months"
981
+ // decrement year by one
982
+ if (view == 'months') selected_year--;
983
+
984
+ // if view is "years"
985
+ // decrement years by 12
986
+ else if (view == 'years') selected_year -= 12;
987
+
988
+ // if view is "days"
989
+ // decrement the month and
990
+ // if month is out of range
991
+ else if (--selected_month < 0) {
992
+
993
+ // go to the last month of the previous year
994
+ selected_month = 11;
995
+ selected_year--;
996
+
997
+ }
998
+
999
+ // generate the appropriate view
1000
+ manage_views();
1001
+
1002
+ }
1003
+
1004
+ });
1005
+
1006
+ // attach a click event to the caption in header
1007
+ $('.dp_caption', header).bind('click', function() {
1008
+
1009
+ // if current view is "days", take the user to the next view, depending on the format
1010
+ if (view == 'days') view = ($.inArray('months', views) > -1 ? 'months' : ($.inArray('years', views) > -1 ? 'years' : 'days'));
1011
+
1012
+ // if current view is "months", take the user to the next view, depending on the format
1013
+ else if (view == 'months') view = ($.inArray('years', views) > -1 ? 'years' : ($.inArray('days', views) > -1 ? 'days' : 'months'));
1014
+
1015
+ // if current view is "years", take the user to the next view, depending on the format
1016
+ else view = ($.inArray('days', views) > -1 ? 'days' : ($.inArray('months', views) > -1 ? 'months' : 'years'));
1017
+
1018
+ // generate the appropriate view
1019
+ manage_views();
1020
+
1021
+ });
1022
+
1023
+ // event for when clicking the "next" button
1024
+ $('.dp_next', header).bind('click', function() {
1025
+
1026
+ // if button is not disabled
1027
+ if (!$(this).hasClass('dp_blocked')) {
1028
+
1029
+ // if view is "months"
1030
+ // increment year by 1
1031
+ if (view == 'months') selected_year++;
1032
+
1033
+ // if view is "years"
1034
+ // increment years by 12
1035
+ else if (view == 'years') selected_year += 12;
1036
+
1037
+ // if view is "days"
1038
+ // increment the month and
1039
+ // if month is out of range
1040
+ else if (++selected_month == 12) {
1041
+
1042
+ // go to the first month of the next year
1043
+ selected_month = 0;
1044
+ selected_year++;
1045
+
1046
+ }
1047
+
1048
+ // generate the appropriate view
1049
+ manage_views();
1050
+
1051
+ }
1052
+
1053
+ });
1054
+
1055
+ // attach a click event for the cells in the day picker
1056
+ daypicker.delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_week_number)', 'click', function() {
1057
+
1058
+ // if other months are selectable and currently clicked cell contains a class with the cell's date
1059
+ if (plugin.settings.select_other_months && null !== (matches = $(this).attr('class').match(/date\_([0-9]{4})(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])/)))
1060
+
1061
+ // use the stored date
1062
+ select_date(matches[1], matches[2], matches[3], 'days', $(this));
1063
+
1064
+ // put selected date in the element the plugin is attached to, and hide the date picker
1065
+ else select_date(selected_year, selected_month, to_int($(this).html()), 'days', $(this));
1066
+
1067
+ });
1068
+
1069
+ // attach a click event for the cells in the month picker
1070
+ monthpicker.delegate('td:not(.dp_disabled)', 'click', function() {
1071
+
1072
+ // get the month we've clicked on
1073
+ var matches = $(this).attr('class').match(/dp\_month\_([0-9]+)/);
1074
+
1075
+ // set the selected month
1076
+ selected_month = to_int(matches[1]);
1077
+
1078
+ // if user can select only years and months
1079
+ if ($.inArray('days', views) == -1)
1080
+
1081
+ // put selected date in the element the plugin is attached to, and hide the date picker
1082
+ select_date(selected_year, selected_month, 1, 'months', $(this));
1083
+
1084
+ else {
1085
+
1086
+ // direct the user to the "days" view
1087
+ view = 'days';
1088
+
1089
+ // if date picker is always visible
1090
+ // empty the value in the text box the date picker is attached to
1091
+ if (plugin.settings.always_visible) $element.val('');
1092
+
1093
+ // generate the appropriate view
1094
+ manage_views();
1095
+
1096
+ }
1097
+
1098
+ });
1099
+
1100
+ // attach a click event for the cells in the year picker
1101
+ yearpicker.delegate('td:not(.dp_disabled)', 'click', function() {
1102
+
1103
+ // set the selected year
1104
+ selected_year = to_int($(this).html());
1105
+
1106
+ // if user can select only years
1107
+ if ($.inArray('months', views) == -1)
1108
+
1109
+ // put selected date in the element the plugin is attached to, and hide the date picker
1110
+ select_date(selected_year, 1, 1, 'years', $(this));
1111
+
1112
+ else {
1113
+
1114
+ // direct the user to the "months" view
1115
+ view = 'months';
1116
+
1117
+ // if date picker is always visible
1118
+ // empty the value in the text box the date picker is attached to
1119
+ if (plugin.settings.always_visible) $element.val('');
1120
+
1121
+ // generate the appropriate view
1122
+ manage_views();
1123
+
1124
+ }
1125
+
1126
+ });
1127
+
1128
+ // function to execute when the "Today" button is clicked
1129
+ $(selecttoday).bind('click', function(e) {
1130
+
1131
+ e.preventDefault();
1132
+
1133
+ // select the current date
1134
+ select_date(current_system_year, current_system_month, current_system_day, 'days', $('.dp_current', daypicker));
1135
+
1136
+ // if date picker is always visible
1137
+ if (plugin.settings.always_visible)
1138
+
1139
+ // repaint the datepicker so it centers on the currently selected date
1140
+ plugin.show();
1141
+
1142
+ // hide the date picker
1143
+ plugin.hide();
1144
+
1145
+ });
1146
+
1147
+ // function to execute when the "Clear" button is clicked
1148
+ $(cleardate).bind('click', function(e) {
1149
+
1150
+ e.preventDefault();
1151
+
1152
+ // clear the element's value
1153
+ $element.val('');
1154
+
1155
+ // if date picker is not always visible
1156
+ if (!plugin.settings.always_visible) {
1157
+
1158
+ // reset these values
1159
+ default_day = null; default_month = null; default_year = null; selected_month = null; selected_year = null;
1160
+
1161
+ }
1162
+
1163
+ // hide the date picker
1164
+ plugin.hide();
1165
+
1166
+ // if a callback function exists for when clearing a date
1167
+ if (plugin.settings.onClear && typeof plugin.settings.onClear == 'function')
1168
+
1169
+ // execute the callback function and pass as argument the element the plugin is attached to
1170
+ plugin.settings.onClear($element);
1171
+
1172
+ });
1173
+
1174
+ // if date picker is not always visible
1175
+ if (!plugin.settings.always_visible)
1176
+
1177
+ // bind some events to the document
1178
+ $(document).bind({
1179
+
1180
+ //whenever anything is clicked on the page or a key is pressed
1181
+ 'mousedown': _mousedown,
1182
+ 'keyup': _keyup
1183
+
1184
+ });
1185
+
1186
+ // last thing is to pre-render some of the date picker right away
1187
+ manage_views();
1188
+
1189
+ };
1190
+
1191
+ /**
1192
+ * Destroys the date picker.
1193
+ *
1194
+ * @return void
1195
+ */
1196
+ plugin.destroy = function() {
1197
+
1198
+ // remove the attached icon (if it exists)...
1199
+ if (undefined !== plugin.icon) plugin.icon.remove();
1200
+
1201
+ // ...and the calendar
1202
+ plugin.datepicker.remove();
1203
+
1204
+ // remove associated event handlers from the document
1205
+ $(document).unbind('keyup', _keyup);
1206
+ $(document).unbind('mousedown', _mousedown);
1207
+ $(window).unbind('resize', _resize);
1208
+
1209
+ // remove association with the element
1210
+ $element.removeData('Zebra_DatePicker');
1211
+
1212
+ // completely delete object
1213
+ delete plugin;
1214
+
1215
+ };
1216
+
1217
+ /**
1218
+ * Hides the date picker.
1219
+ *
1220
+ * @return void
1221
+ */
1222
+ plugin.hide = function() {
1223
+
1224
+ // if date picker is not always visible
1225
+ if (!plugin.settings.always_visible) {
1226
+
1227
+ // hide the iFrameShim in Internet Explorer 6
1228
+ iframeShim('hide');
1229
+
1230
+ // hide the date picker
1231
+ datepicker.hide();
1232
+
1233
+ }
1234
+
1235
+ };
1236
+
1237
+ /**
1238
+ * Shows the date picker.
1239
+ *
1240
+ * @return void
1241
+ */
1242
+ plugin.show = function() {
1243
+
1244
+ // always show the view defined in settings
1245
+ view = plugin.settings.view;
1246
+
1247
+ // get the default date, from the element, and check if it represents a valid date, according to the required format
1248
+ var default_date = check_date($element.val() || (plugin.settings.start_date ? plugin.settings.start_date : ''));
1249
+
1250
+ // if the value represents a valid date
1251
+ if (default_date) {
1252
+
1253
+ // extract the date parts
1254
+ // we'll use these to highlight the default date in the date picker and as starting point to
1255
+ // what year and month to start the date picker with
1256
+ // why separate values? because selected_* will change as user navigates within the date picker
1257
+ default_month = default_date.getMonth();
1258
+ selected_month = default_date.getMonth();
1259
+ default_year = default_date.getFullYear();
1260
+ selected_year = default_date.getFullYear();
1261
+ default_day = default_date.getDate();
1262
+
1263
+ // if the default date represents a disabled date
1264
+ if (is_disabled(default_year, default_month, default_day)) {
1265
+
1266
+ // clear the value of the parent element
1267
+ $element.val('');
1268
+
1269
+ // the calendar will start with the first selectable year/month
1270
+ selected_month = first_selectable_month;
1271
+ selected_year = first_selectable_year;
1272
+
1273
+ }
1274
+
1275
+ // if a default value is not available, or value does not represent a valid date
1276
+ } else {
1277
+
1278
+ // the calendar will start with the first selectable year/month
1279
+ selected_month = first_selectable_month;
1280
+ selected_year = first_selectable_year;
1281
+
1282
+ }
1283
+
1284
+ // generate the appropriate view
1285
+ manage_views();
1286
+
1287
+ // if date picker is not always visible and the calendar icon is visible
1288
+ if (!plugin.settings.always_visible) {
1289
+
1290
+ var
1291
+
1292
+ // get the date picker width and height
1293
+ datepicker_width = datepicker.outerWidth(),
1294
+ datepicker_height = datepicker.outerHeight(),
1295
+
1296
+ // compute the date picker's default left and top
1297
+ // this will be computed relative to the icon's top-right corner (if the calendar icon exists), or
1298
+ // relative to the element's top-right corner otherwise, to which the offsets given at initialization
1299
+ // are added/subtracted
1300
+ left = (undefined !== icon ? icon.offset().left + icon.outerWidth(true) : $element.offset().left + $element.outerWidth(true)) + plugin.settings.offset[0],
1301
+ top = (undefined !== icon ? icon.offset().top : $element.offset().top) - datepicker_height + plugin.settings.offset[1],
1302
+
1303
+ // get browser window's width and height
1304
+ window_width = $(window).width(),
1305
+ window_height = $(window).height(),
1306
+
1307
+ // get browser window's horizontal and vertical scroll offsets
1308
+ window_scroll_top = $(window).scrollTop(),
1309
+ window_scroll_left = $(window).scrollLeft();
1310
+
1311
+ // if date picker is outside the viewport, adjust its position so that it is visible
1312
+ if (left + datepicker_width > window_scroll_left + window_width) left = window_scroll_left + window_width - datepicker_width;
1313
+ if (left < window_scroll_left) left = window_scroll_left;
1314
+ if (top + datepicker_height > window_scroll_top + window_height) top = window_scroll_top + window_height - datepicker_height;
1315
+ if (top < window_scroll_top) top = window_scroll_top;
1316
+
1317
+ // make the date picker visible
1318
+ datepicker.css({
1319
+ 'left': left,
1320
+ 'top': top
1321
+ });
1322
+
1323
+ // fade-in the date picker
1324
+ // for Internet Explorer < 9 show the date picker instantly or fading alters the font's weight
1325
+ datepicker.fadeIn(browser.name == 'explorer' && browser.version < 9 ? 0 : 150, 'linear');
1326
+
1327
+ // show the iFrameShim in Internet Explorer 6
1328
+ iframeShim();
1329
+
1330
+ // if date picker is always visible, show it
1331
+ } else datepicker.show();
1332
+
1333
+ };
1334
+
1335
+ /**
1336
+ * Updates the configuration options given as argument
1337
+ *
1338
+ * @param object values An object containing any number of configuration options to be updated
1339
+ *
1340
+ * @return void
1341
+ */
1342
+ plugin.update = function(values) {
1343
+
1344
+ // if original direction not saved, save it now
1345
+ if (plugin.original_direction) plugin.original_direction = plugin.direction;
1346
+
1347
+ // update configuration options
1348
+ plugin.settings = $.extend(plugin.settings, values);
1349
+
1350
+ // reinitialize the object with the new options
1351
+ init(true);
1352
+
1353
+ };
1354
+
1355
+ /**
1356
+ * Checks if a string represents a valid date according to the format defined by the "format" property.
1357
+ *
1358
+ * @param string str_date A string representing a date, formatted accordingly to the "format" property.
1359
+ * For example, if "format" is "Y-m-d" the string should look like "2011-06-01"
1360
+ *
1361
+ * @return mixed Returns a JavaScript Date object if string represents a valid date according
1362
+ * formatted according to the "format" property, or FALSE otherwise.
1363
+ *
1364
+ * @access private
1365
+ */
1366
+ var check_date = function(str_date) {
1367
+
1368
+ // treat argument as a string
1369
+ str_date += '';
1370
+
1371
+ // if value is given
1372
+ if ($.trim(str_date) !== '') {
1373
+
1374
+ var
1375
+
1376
+ // prepare the format by removing white space from it
1377
+ // and also escape characters that could have special meaning in a regular expression
1378
+ format = escape_regexp(plugin.settings.format),
1379
+
1380
+ // allowed characters in date's format
1381
+ format_chars = ['d','D','j','l','N','S','w','F','m','M','n','Y','y'],
1382
+
1383
+ // "matches" will contain the characters defining the date's format
1384
+ matches = [],
1385
+
1386
+ // "regexp" will contain the regular expression built for each of the characters used in the date's format
1387
+ regexp = [],
1388
+
1389
+ // "position" will contain the position of the caracter found in the date's format
1390
+ position = null,
1391
+
1392
+ // "segments" will contain the matches of the regular expression
1393
+ segments = null;
1394
+
1395
+ // iterate through the allowed characters in date's format
1396
+ for (var i = 0; i < format_chars.length; i++)
1397
+
1398
+ // if character is found in the date's format
1399
+ if ((position = format.indexOf(format_chars[i])) > -1)
1400
+
1401
+ // save it, alongside the character's position
1402
+ matches.push({character: format_chars[i], position: position});
1403
+
1404
+ // sort characters defining the date's format based on their position, ascending
1405
+ matches.sort(function(a, b){ return a.position - b.position; });
1406
+
1407
+ // iterate through the characters defining the date's format
1408
+ $.each(matches, function(index, match) {
1409
+
1410
+ // add to the array of regular expressions, based on the character
1411
+ switch (match.character) {
1412
+
1413
+ case 'd': regexp.push('0[1-9]|[12][0-9]|3[01]'); break;
1414
+ case 'D': regexp.push('[a-z]{3}'); break;
1415
+ case 'j': regexp.push('[1-9]|[12][0-9]|3[01]'); break;
1416
+ case 'l': regexp.push('[a-z]+'); break;
1417
+ case 'N': regexp.push('[1-7]'); break;
1418
+ case 'S': regexp.push('st|nd|rd|th'); break;
1419
+ case 'w': regexp.push('[0-6]'); break;
1420
+ case 'F': regexp.push('[a-z]+'); break;
1421
+ case 'm': regexp.push('0[1-9]|1[012]+'); break;
1422
+ case 'M': regexp.push('[a-z]{3}'); break;
1423
+ case 'n': regexp.push('[1-9]|1[012]'); break;
1424
+ case 'Y': regexp.push('[0-9]{4}'); break;
1425
+ case 'y': regexp.push('[0-9]{2}'); break;
1426
+
1427
+ }
1428
+
1429
+ });
1430
+
1431
+ // if we have an array of regular expressions
1432
+ if (regexp.length) {
1433
+
1434
+ // we will replace characters in the date's format in reversed order
1435
+ matches.reverse();
1436
+
1437
+ // iterate through the characters in date's format
1438
+ $.each(matches, function(index, match) {
1439
+
1440
+ // replace each character with the appropriate regular expression
1441
+ format = format.replace(match.character, '(' + regexp[regexp.length - index - 1] + ')');
1442
+
1443
+ });
1444
+
1445
+ // the final regular expression
1446
+ regexp = new RegExp('^' + format + '$', 'ig');
1447
+
1448
+ // if regular expression was matched
1449
+ if ((segments = regexp.exec(str_date))) {
1450
+
1451
+ // check if date is a valid date (i.e. there's no February 31)
1452
+
1453
+ var tmpdate = new Date(),
1454
+ original_day = tmpdate.getDate(),
1455
+ original_month = tmpdate.getMonth() + 1,
1456
+ original_year = tmpdate.getFullYear(),
1457
+ english_days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
1458
+ english_months = ['January','February','March','April','May','June','July','August','September','October','November','December'],
1459
+ iterable,
1460
+
1461
+ // by default, we assume the date is valid
1462
+ valid = true;
1463
+
1464
+ // reverse back the characters in the date's format
1465
+ matches.reverse();
1466
+
1467
+ // iterate through the characters in the date's format
1468
+ $.each(matches, function(index, match) {
1469
+
1470
+ // if the date is not valid, don't look further
1471
+ if (!valid) return true;
1472
+
1473
+ // based on the character
1474
+ switch (match.character) {
1475
+
1476
+ case 'm':
1477
+ case 'n':
1478
+
1479
+ // extract the month from the value entered by the user
1480
+ original_month = to_int(segments[index + 1]);
1481
+
1482
+ break;
1483
+
1484
+ case 'd':
1485
+ case 'j':
1486
+
1487
+ // extract the day from the value entered by the user
1488
+ original_day = to_int(segments[index + 1]);
1489
+
1490
+ break;
1491
+
1492
+ case 'D':
1493
+ case 'l':
1494
+ case 'F':
1495
+ case 'M':
1496
+
1497
+ // if day is given as day name, we'll check against the names in the used language
1498
+ if (match.character == 'D' || match.character == 'l') iterable = plugin.settings.days;
1499
+
1500
+ // if month is given as month name, we'll check against the names in the used language
1501
+ else iterable = plugin.settings.months;
1502
+
1503
+ // by default, we assume the day or month was not entered correctly
1504
+ valid = false;
1505
+
1506
+ // iterate through the month/days in the used language
1507
+ $.each(iterable, function(key, value) {
1508
+
1509
+ // if month/day was entered correctly, don't look further
1510
+ if (valid) return true;
1511
+
1512
+ // if month/day was entered correctly
1513
+ if (segments[index + 1].toLowerCase() == value.substring(0, (match.character == 'D' || match.character == 'M' ? 3 : value.length)).toLowerCase()) {
1514
+
1515
+ // extract the day/month from the value entered by the user
1516
+ switch (match.character) {
1517
+
1518
+ case 'D': segments[index + 1] = english_days[key].substring(0, 3); break;
1519
+ case 'l': segments[index + 1] = english_days[key]; break;
1520
+ case 'F': segments[index + 1] = english_months[key]; original_month = key + 1; break;
1521
+ case 'M': segments[index + 1] = english_months[key].substring(0, 3); original_month = key + 1; break;
1522
+
1523
+ }
1524
+
1525
+ // day/month value is valid
1526
+ valid = true;
1527
+
1528
+ }
1529
+
1530
+ });
1531
+
1532
+ break;
1533
+
1534
+ case 'Y':
1535
+
1536
+ // extract the year from the value entered by the user
1537
+ original_year = to_int(segments[index + 1]);
1538
+
1539
+ break;
1540
+
1541
+ case 'y':
1542
+
1543
+ // extract the year from the value entered by the user
1544
+ original_year = '19' + to_int(segments[index + 1]);
1545
+
1546
+ break;
1547
+
1548
+ }
1549
+ });
1550
+
1551
+ // if everything is ok so far
1552
+ if (valid) {
1553
+
1554
+ // generate a Date object using the values entered by the user
1555
+ // (handle also the case when original_month and/or original_day are undefined - i.e date format is "Y-m" or "Y")
1556
+ var date = new Date(original_year, (original_month || 1) - 1, original_day || 1);
1557
+
1558
+ // if, after that, the date is the same as the date entered by the user
1559
+ if (date.getFullYear() == original_year && date.getDate() == (original_day || 1) && date.getMonth() == ((original_month || 1) - 1))
1560
+
1561
+ // return the date as JavaScript date object
1562
+ return date;
1563
+
1564
+ }
1565
+
1566
+ }
1567
+
1568
+ }
1569
+
1570
+ // if script gets this far, return false as something must've went wrong
1571
+ return false;
1572
+
1573
+ }
1574
+
1575
+ };
1576
+
1577
+ /**
1578
+ * Prevents the possibility of selecting text on a given element. Used on the "previous" and "next" buttons
1579
+ * where text might get accidentally selected when user quickly clicks on the buttons.
1580
+ *
1581
+ * Code by http://chris-barr.com/index.php/entry/disable_text_selection_with_jquery/
1582
+ *
1583
+ * @param jQuery Element el A jQuery element on which to prevents text selection.
1584
+ *
1585
+ * @return void
1586
+ *
1587
+ * @access private
1588
+ */
1589
+ var disable_text_select = function(el) {
1590
+
1591
+ // if browser is Firefox
1592
+ if (browser.name == 'firefox') el.css('MozUserSelect', 'none');
1593
+
1594
+ // if browser is Internet Explorer
1595
+ else if (browser.name == 'explorer') el.bind('selectstart', function() { return false; });
1596
+
1597
+ // for the other browsers
1598
+ else el.mousedown(function() { return false; });
1599
+
1600
+ };
1601
+
1602
+ /**
1603
+ * Escapes special characters in a string, preparing it for use in a regular expression.
1604
+ *
1605
+ * @param string str The string in which special characters should be escaped.
1606
+ *
1607
+ * @return string Returns the string with escaped special characters.
1608
+ *
1609
+ * @access private
1610
+ */
1611
+ var escape_regexp = function(str) {
1612
+
1613
+ // return string with special characters escaped
1614
+ return str.replace(/([-.,*+?^${}()|[\]\/\\])/g, '\\$1');
1615
+
1616
+ };
1617
+
1618
+ /**
1619
+ * Formats a JavaScript date object to the format specified by the "format" property.
1620
+ * Code taken from http://electricprism.com/aeron/calendar/
1621
+ *
1622
+ * @param date date A valid JavaScript date object
1623
+ *
1624
+ * @return string Returns a string containing the formatted date
1625
+ *
1626
+ * @access private
1627
+ */
1628
+ var format = function(date) {
1629
+
1630
+ var result = '',
1631
+
1632
+ // extract parts of the date:
1633
+ // day number, 1 - 31
1634
+ j = date.getDate(),
1635
+
1636
+ // day of the week, 0 - 6, Sunday - Saturday
1637
+ w = date.getDay(),
1638
+
1639
+ // the name of the day of the week Sunday - Saturday
1640
+ l = plugin.settings.days[w],
1641
+
1642
+ // the month number, 1 - 12
1643
+ n = date.getMonth() + 1,
1644
+
1645
+ // the month name, January - December
1646
+ f = plugin.settings.months[n - 1],
1647
+
1648
+ // the year (as a string)
1649
+ y = date.getFullYear() + '';
1650
+
1651
+ // iterate through the characters in the format
1652
+ for (var i = 0; i < plugin.settings.format.length; i++) {
1653
+
1654
+ // extract the current character
1655
+ var chr = plugin.settings.format.charAt(i);
1656
+
1657
+ // see what character it is
1658
+ switch(chr) {
1659
+
1660
+ // year as two digits
1661
+ case 'y': y = y.substr(2);
1662
+
1663
+ // year as four digits
1664
+ case 'Y': result += y; break;
1665
+
1666
+ // month number, prefixed with 0
1667
+ case 'm': n = str_pad(n, 2);
1668
+
1669
+ // month number, not prefixed with 0
1670
+ case 'n': result += n; break;
1671
+
1672
+ // month name, three letters
1673
+ case 'M': f = ($.isArray(plugin.settings.months_abbr) && undefined !== plugin.settings.months_abbr[n - 1] ? plugin.settings.months_abbr[n - 1] : plugin.settings.months[n - 1].substr(0, 3));
1674
+
1675
+ // full month name
1676
+ case 'F': result += f; break;
1677
+
1678
+ // day number, prefixed with 0
1679
+ case 'd': j = str_pad(j, 2);
1680
+
1681
+ // day number not prefixed with 0
1682
+ case 'j': result += j; break;
1683
+
1684
+ // day name, three letters
1685
+ case 'D': l = ($.isArray(plugin.settings.days_abbr) && undefined !== plugin.settings.days_abbr[w] ? plugin.settings.days_abbr[w] : plugin.settings.days[w].substr(0, 3));
1686
+
1687
+ // full day name
1688
+ case 'l': result += l; break;
1689
+
1690
+ // ISO-8601 numeric representation of the day of the week, 1 - 7
1691
+ case 'N': w++;
1692
+
1693
+ // day of the week, 0 - 6
1694
+ case 'w': result += w; break;
1695
+
1696
+ // English ordinal suffix for the day of the month, 2 characters
1697
+ // (st, nd, rd or th (works well with j))
1698
+ case 'S':
1699
+
1700
+ if (j % 10 == 1 && j != '11') result += 'st';
1701
+
1702
+ else if (j % 10 == 2 && j != '12') result += 'nd';
1703
+
1704
+ else if (j % 10 == 3 && j != '13') result += 'rd';
1705
+
1706
+ else result += 'th';
1707
+
1708
+ break;
1709
+
1710
+ // this is probably the separator
1711
+ default: result += chr;
1712
+
1713
+ }
1714
+
1715
+ }
1716
+
1717
+ // return formated date
1718
+ return result;
1719
+
1720
+ };
1721
+
1722
+ /**
1723
+ * Generates the day picker view, and displays it
1724
+ *
1725
+ * @return void
1726
+ *
1727
+ * @access private
1728
+ */
1729
+ var generate_daypicker = function() {
1730
+
1731
+ var
1732
+
1733
+ // get the number of days in the selected month
1734
+ days_in_month = new Date(selected_year, selected_month + 1, 0).getDate(),
1735
+
1736
+ // get the selected month's starting day (from 0 to 6)
1737
+ first_day = new Date(selected_year, selected_month, 1).getDay(),
1738
+
1739
+ // how many days are there in the previous month
1740
+ days_in_previous_month = new Date(selected_year, selected_month, 0).getDate(),
1741
+
1742
+ // how many days are there to be shown from the previous month
1743
+ days_from_previous_month = first_day - plugin.settings.first_day_of_week;
1744
+
1745
+ // the final value of how many days are there to be shown from the previous month
1746
+ days_from_previous_month = days_from_previous_month < 0 ? 7 + days_from_previous_month : days_from_previous_month;
1747
+
1748
+ // manage header caption and enable/disable navigation buttons if necessary
1749
+ manage_header(plugin.settings.months[selected_month] + ', ' + selected_year);
1750
+
1751
+ // start generating the HTML
1752
+ var html = '<tr>';
1753
+
1754
+ // if a column featuring the number of the week is to be shown
1755
+ if (plugin.settings.show_week_number)
1756
+
1757
+ // column title
1758
+ html += '<th>' + plugin.settings.show_week_number + '</th>';
1759
+
1760
+ // name of week days
1761
+ // show the abbreviated day names (or only the first two letters of the full name if no abbreviations are specified)
1762
+ // and also, take in account the value of the "first_day_of_week" property
1763
+ for (var i = 0; i < 7; i++)
1764
+
1765
+ html += '<th>' + ($.isArray(plugin.settings.days_abbr) && undefined !== plugin.settings.days_abbr[(plugin.settings.first_day_of_week + i) % 7] ? plugin.settings.days_abbr[(plugin.settings.first_day_of_week + i) % 7] : plugin.settings.days[(plugin.settings.first_day_of_week + i) % 7].substr(0, 2)) + '</th>';
1766
+
1767
+ html += '</tr><tr>';
1768
+
1769
+ // the calendar shows a total of 42 days
1770
+ for (i = 0; i < 42; i++) {
1771
+
1772
+ // seven days per row
1773
+ if (i > 0 && i % 7 === 0) html += '</tr><tr>';
1774
+
1775
+ // if week number is to be shown
1776
+ if (i % 7 === 0 && plugin.settings.show_week_number)
1777
+
1778
+ // show ISO 8601 week number
1779
+ html += '<td class="dp_week_number">' + getWeekNumber(new Date(selected_year, selected_month, (i - days_from_previous_month + 1))) + '</td>';
1780
+
1781
+ // the number of the day in month
1782
+ var day = (i - days_from_previous_month + 1);
1783
+
1784
+ // if dates in previous/next month can be selected, and this is one of those days
1785
+ if (plugin.settings.select_other_months && (i < days_from_previous_month || day > days_in_month)) {
1786
+
1787
+ // use the Date object to normalize the date
1788
+ // for example, 2011 05 33 will be transformed to 2011 06 02
1789
+ var real_date = new Date(selected_year, selected_month, day),
1790
+ real_year = real_date.getFullYear(),
1791
+ real_month = real_date.getMonth(),
1792
+ real_day = real_date.getDate();
1793
+
1794
+ // extract normalized date parts and merge them
1795
+ real_date = real_year + str_pad(real_month, 2) + str_pad(real_day, 2);
1796
+
1797
+ }
1798
+
1799
+ // if this is a day from the previous month
1800
+ if (i < days_from_previous_month)
1801
+
1802
+ html += '<td class="' + (plugin.settings.select_other_months && !is_disabled(real_year, real_month, real_day) ? 'dp_not_in_month_selectable date_' + real_date : 'dp_not_in_month') + '">' + (plugin.settings.select_other_months || plugin.settings.show_other_months ? str_pad(days_in_previous_month - days_from_previous_month + i + 1, plugin.settings.zero_pad ? 2 : 0) : '&nbsp;') + '</td>';
1803
+
1804
+ // if this is a day from the next month
1805
+ else if (day > days_in_month)
1806
+
1807
+ html += '<td class="' + (plugin.settings.select_other_months && !is_disabled(real_year, real_month, real_day) ? 'dp_not_in_month_selectable date_' + real_date : 'dp_not_in_month') + '">' + (plugin.settings.select_other_months || plugin.settings.show_other_months ? str_pad(day - days_in_month, plugin.settings.zero_pad ? 2 : 0) : '&nbsp;') + '</td>';
1808
+
1809
+ // if this is a day from the current month
1810
+ else {
1811
+
1812
+ var
1813
+
1814
+ // get the week day (0 to 6, Sunday to Saturday)
1815
+ weekday = (plugin.settings.first_day_of_week + i) % 7,
1816
+
1817
+ class_name = '';
1818
+
1819
+ // if date needs to be disabled
1820
+ if (is_disabled(selected_year, selected_month, day)) {
1821
+
1822
+ // if day is in weekend
1823
+ if ($.inArray(weekday, plugin.settings.weekend_days) > -1) class_name = 'dp_weekend_disabled';
1824
+
1825
+ // if work day
1826
+ else class_name += ' dp_disabled';
1827
+
1828
+ // highlight the current system date
1829
+ if (selected_month == current_system_month && selected_year == current_system_year && current_system_day == day) class_name += ' dp_disabled_current';
1830
+
1831
+ // if there are no restrictions
1832
+ } else {
1833
+
1834
+ // if day is in weekend
1835
+ if ($.inArray(weekday, plugin.settings.weekend_days) > -1) class_name = 'dp_weekend';
1836
+
1837
+ // highlight the currently selected date
1838
+ if (selected_month == default_month && selected_year == default_year && default_day == day) class_name += ' dp_selected';
1839
+
1840
+ // highlight the current system date
1841
+ if (selected_month == current_system_month && selected_year == current_system_year && current_system_day == day) class_name += ' dp_current';
1842
+
1843
+ }
1844
+
1845
+ // print the day of the month
1846
+ html += '<td' + (class_name !== '' ? ' class="' + $.trim(class_name) + '"' : '') + '>' + (plugin.settings.zero_pad ? str_pad(day, 2) : day) + '</td>';
1847
+
1848
+ }
1849
+
1850
+ }
1851
+
1852
+ // wrap up generating the day picker
1853
+ html += '</tr>';
1854
+
1855
+ // inject the day picker into the DOM
1856
+ daypicker.html($(html));
1857
+
1858
+ // if date picker is always visible
1859
+ if (plugin.settings.always_visible)
1860
+
1861
+ // cache all the cells
1862
+ // (we need them so that we can easily remove the "dp_selected" class from all of them when user selects a date)
1863
+ daypicker_cells = $('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked, .dp_week_number)', daypicker);
1864
+
1865
+ // make the day picker visible
1866
+ daypicker.show();
1867
+
1868
+ };
1869
+
1870
+ /**
1871
+ * Generates the month picker view, and displays it
1872
+ *
1873
+ * @return void
1874
+ *
1875
+ * @access private
1876
+ */
1877
+ var generate_monthpicker = function() {
1878
+
1879
+ // manage header caption and enable/disable navigation buttons if necessary
1880
+ manage_header(selected_year);
1881
+
1882
+ // start generating the HTML
1883
+ var html = '<tr>';
1884
+
1885
+ // iterate through all the months
1886
+ for (var i = 0; i < 12; i++) {
1887
+
1888
+ // three month per row
1889
+ if (i > 0 && i % 3 === 0) html += '</tr><tr>';
1890
+
1891
+ var class_name = 'dp_month_' + i;
1892
+
1893
+ // if month needs to be disabled
1894
+ if (is_disabled(selected_year, i)) class_name += ' dp_disabled';
1895
+
1896
+ // else, if a date is already selected and this is that particular month, highlight it
1897
+ else if (default_month !== false && default_month == i) class_name += ' dp_selected';
1898
+
1899
+ // else, if this the current system month, highlight it
1900
+ else if (current_system_month == i && current_system_year == selected_year) class_name += ' dp_current';
1901
+
1902
+ // first three letters of the month's name
1903
+ html += '<td class="' + $.trim(class_name) + '">' + ($.isArray(plugin.settings.months_abbr) && undefined !== plugin.settings.months_abbr[i] ? plugin.settings.months_abbr[i] : plugin.settings.months[i].substr(0, 3)) + '</td>';
1904
+
1905
+ }
1906
+
1907
+ // wrap up
1908
+ html += '</tr>';
1909
+
1910
+ // inject into the DOM
1911
+ monthpicker.html($(html));
1912
+
1913
+ // if date picker is always visible
1914
+ if (plugin.settings.always_visible)
1915
+
1916
+ // cache all the cells
1917
+ // (we need them so that we can easily remove the "dp_selected" class from all of them when user selects a month)
1918
+ monthpicker_cells = $('td:not(.dp_disabled)', monthpicker);
1919
+
1920
+ // make the month picker visible
1921
+ monthpicker.show();
1922
+
1923
+ };
1924
+
1925
+ /**
1926
+ * Generates the year picker view, and displays it
1927
+ *
1928
+ * @return void
1929
+ *
1930
+ * @access private
1931
+ */
1932
+ var generate_yearpicker = function() {
1933
+
1934
+ // manage header caption and enable/disable navigation buttons if necessary
1935
+ manage_header(selected_year - 7 + ' - ' + (selected_year + 4));
1936
+
1937
+ // start generating the HTML
1938
+ var html = '<tr>';
1939
+
1940
+ // we're showing 9 years at a time, current year in the middle
1941
+ for (var i = 0; i < 12; i++) {
1942
+
1943
+ // three years per row
1944
+ if (i > 0 && i % 3 === 0) html += '</tr><tr>';
1945
+
1946
+ var class_name = '';
1947
+
1948
+ // if year needs to be disabled
1949
+ if (is_disabled(selected_year - 7 + i)) class_name += ' dp_disabled';
1950
+
1951
+ // else, if a date is already selected and this is that particular year, highlight it
1952
+ else if (default_year && default_year == selected_year - 7 + i) class_name += ' dp_selected';
1953
+
1954
+ // else, if this is the current system year, highlight it
1955
+ else if (current_system_year == (selected_year - 7 + i)) class_name += ' dp_current';
1956
+
1957
+ // first three letters of the month's name
1958
+ html += '<td' + ($.trim(class_name) !== '' ? ' class="' + $.trim(class_name) + '"' : '') + '>' + (selected_year - 7 + i) + '</td>';
1959
+
1960
+ }
1961
+
1962
+ // wrap up
1963
+ html += '</tr>';
1964
+
1965
+ // inject into the DOM
1966
+ yearpicker.html($(html));
1967
+
1968
+ // if date picker is always visible
1969
+ if (plugin.settings.always_visible)
1970
+
1971
+ // cache all the cells
1972
+ // (we need them so that we can easily remove the "dp_selected" class from all of them when user selects a year)
1973
+ yearpicker_cells = $('td:not(.dp_disabled)', yearpicker);
1974
+
1975
+ // make the year picker visible
1976
+ yearpicker.show();
1977
+
1978
+ };
1979
+
1980
+ /**
1981
+ * Generates an iFrame shim in Internet Explorer 6 so that the date picker appears above select boxes.
1982
+ *
1983
+ * @return void
1984
+ *
1985
+ * @access private
1986
+ */
1987
+ var iframeShim = function(action) {
1988
+
1989
+ // this is necessary only if browser is Internet Explorer 6
1990
+ if (browser.name == 'explorer' && browser.version == 6) {
1991
+
1992
+ // if the iFrame was not yet created
1993
+ // "undefined" evaluates as FALSE
1994
+ if (!shim) {
1995
+
1996
+ // the iFrame has to have the element's zIndex minus 1
1997
+ var zIndex = to_int(datepicker.css('zIndex')) - 1;
1998
+
1999
+ // create the iFrame
2000
+ shim = jQuery('<iframe>', {
2001
+ 'src': 'javascript:document.write("")',
2002
+ 'scrolling': 'no',
2003
+ 'frameborder': 0,
2004
+ 'allowtransparency': 'true',
2005
+ css: {
2006
+ 'zIndex': zIndex,
2007
+ 'position': 'absolute',
2008
+ 'top': -1000,
2009
+ 'left': -1000,
2010
+ 'width': datepicker.outerWidth(),
2011
+ 'height': datepicker.outerHeight(),
2012
+ 'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)',
2013
+ 'display': 'none'
2014
+ }
2015
+ });
2016
+
2017
+ // inject iFrame into DOM
2018
+ $('body').append(shim);
2019
+
2020
+ }
2021
+
2022
+ // what do we need to do
2023
+ switch (action) {
2024
+
2025
+ // hide the iFrame?
2026
+ case 'hide':
2027
+
2028
+ // set the iFrame's display property to "none"
2029
+ shim.hide();
2030
+
2031
+ break;
2032
+
2033
+ // show the iFrame?
2034
+ default:
2035
+
2036
+ // get date picker top and left position
2037
+ var offset = datepicker.offset();
2038
+
2039
+ // position the iFrame shim right underneath the date picker
2040
+ // and set its display to "block"
2041
+ shim.css({
2042
+ 'top': offset.top,
2043
+ 'left': offset.left,
2044
+ 'display': 'block'
2045
+ });
2046
+
2047
+ }
2048
+
2049
+ }
2050
+
2051
+ };
2052
+
2053
+ /**
2054
+ * Checks if, according to the restrictions of the calendar and/or the values defined by the "disabled_dates"
2055
+ * property, a day, a month or a year needs to be disabled.
2056
+ *
2057
+ * @param integer year The year to check
2058
+ * @param integer month The month to check
2059
+ * @param integer day The day to check
2060
+ *
2061
+ * @return boolean Returns TRUE if the given value is not disabled or FALSE otherwise
2062
+ *
2063
+ * @access private
2064
+ */
2065
+ var is_disabled = function(year, month, day) {
2066
+
2067
+ // don't check bogus values
2068
+ if ((undefined === year || isNaN(year)) && (undefined === month || isNaN(month)) && (undefined === day || isNaN(day))) return false;
2069
+
2070
+ // if calendar has direction restrictions
2071
+ if (!(!$.isArray(plugin.settings.direction) && to_int(plugin.settings.direction) === 0)) {
2072
+
2073
+ var
2074
+ // normalize and merge arguments then transform the result to an integer
2075
+ now = to_int(str_concat(year, (typeof month != 'undefined' ? str_pad(month, 2) : ''), (typeof day != 'undefined' ? str_pad(day, 2) : ''))),
2076
+
2077
+ // get the length of the argument
2078
+ len = (now + '').length;
2079
+
2080
+ // if we're checking days
2081
+ if (len == 8 && (
2082
+
2083
+ // day is before the first selectable date
2084
+ (typeof start_date != 'undefined' && now < to_int(str_concat(first_selectable_year, str_pad(first_selectable_month, 2), str_pad(first_selectable_day, 2)))) ||
2085
+
2086
+ // or day is after the last selectable date
2087
+ (typeof end_date != 'undefined' && now > to_int(str_concat(last_selectable_year, str_pad(last_selectable_month, 2), str_pad(last_selectable_day, 2))))
2088
+
2089
+ // day needs to be disabled
2090
+ )) return true;
2091
+
2092
+ // if we're checking months
2093
+ else if (len == 6 && (
2094
+
2095
+ // month is before the first selectable month
2096
+ (typeof start_date != 'undefined' && now < to_int(str_concat(first_selectable_year, str_pad(first_selectable_month, 2)))) ||
2097
+
2098
+ // or day is after the last selectable date
2099
+ (typeof end_date != 'undefined' && now > to_int(str_concat(last_selectable_year, str_pad(last_selectable_month, 2))))
2100
+
2101
+ // month needs to be disabled
2102
+ )) return true;
2103
+
2104
+ // if we're checking years
2105
+ else if (len == 4 && (
2106
+
2107
+ // year is before the first selectable year
2108
+ (typeof start_date != 'undefined' && now < first_selectable_year) ||
2109
+
2110
+ // or day is after the last selectable date
2111
+ (typeof end_date != 'undefined' && now > last_selectable_year)
2112
+
2113
+ // year needs to be disabled
2114
+ )) return true;
2115
+
2116
+ }
2117
+
2118
+ // if month is given as argument, increment it (as JavaScript uses 0 for January, 1 for February...)
2119
+ if (typeof month != 'undefined') month = month + 1;
2120
+
2121
+ // by default, we assume the day/month/year is not enabled nor disabled
2122
+ var disabled = false, enabled = false;
2123
+
2124
+ // if there are rules for disabling dates
2125
+ if (disabled_dates)
2126
+
2127
+ // iterate through the rules for disabling dates
2128
+ $.each(disabled_dates, function() {
2129
+
2130
+ // if the date is to be disabled, don't look any further
2131
+ if (disabled) return;
2132
+
2133
+ var rule = this;
2134
+
2135
+ // if the rules apply for the current year
2136
+ if ($.inArray(year, rule[2]) > -1 || $.inArray('*', rule[2]) > -1)
2137
+
2138
+ // if the rules apply for the current month
2139
+ if ((typeof month != 'undefined' && $.inArray(month, rule[1]) > -1) || $.inArray('*', rule[1]) > -1)
2140
+
2141
+ // if the rules apply for the current day
2142
+ if ((typeof day != 'undefined' && $.inArray(day, rule[0]) > -1) || $.inArray('*', rule[0]) > -1) {
2143
+
2144
+ // if day is to be disabled whatever the day
2145
+ // don't look any further
2146
+ if (rule[3] == '*') return (disabled = true);
2147
+
2148
+ // get the weekday
2149
+ var weekday = new Date(year, month - 1, day).getDay();
2150
+
2151
+ // if weekday is to be disabled
2152
+ // don't look any further
2153
+ if ($.inArray(weekday, rule[3]) > -1) return (disabled = true);
2154
+
2155
+ }
2156
+
2157
+ });
2158
+
2159
+ // if there are rules that explicitly enable dates
2160
+ if (enabled_dates)
2161
+
2162
+ // iterate through the rules for enabling dates
2163
+ $.each(enabled_dates, function() {
2164
+
2165
+ // if the date is to be enabled, don't look any further
2166
+ if (enabled) return;
2167
+
2168
+ var rule = this;
2169
+
2170
+ // if the rules apply for the current year
2171
+ if ($.inArray(year, rule[2]) > -1 || $.inArray('*', rule[2]) > -1) {
2172
+
2173
+ // the year is enabled
2174
+ enabled = true;
2175
+
2176
+ // if we're also checking months
2177
+ if (typeof month != 'undefined') {
2178
+
2179
+ // we assume the month is enabled
2180
+ enabled = true;
2181
+
2182
+ // if the rules apply for the current month
2183
+ if ($.inArray(month, rule[1]) > -1 || $.inArray('*', rule[1]) > -1) {
2184
+
2185
+ // if we're also checking days
2186
+ if (typeof day != 'undefined') {
2187
+
2188
+ // we assume the day is enabled
2189
+ enabled = true;
2190
+
2191
+ // if the rules apply for the current day
2192
+ if ($.inArray(day, rule[0]) > -1 || $.inArray('*', rule[0]) > -1) {
2193
+
2194
+ // if day is to be enabled whatever the day
2195
+ // don't look any further
2196
+ if (rule[3] == '*') return (enabled = true);
2197
+
2198
+ // get the weekday
2199
+ var weekday = new Date(year, month - 1, day).getDay();
2200
+
2201
+ // if weekday is to be enabled
2202
+ // don't look any further
2203
+ if ($.inArray(weekday, rule[3]) > -1) return (enabled = true);
2204
+
2205
+ // if we get this far, it means the day is not enabled
2206
+ enabled = false;
2207
+
2208
+ // if day is not enabled
2209
+ } else enabled = false;
2210
+
2211
+ }
2212
+
2213
+ // if month is not enabled
2214
+ } else enabled = false;
2215
+
2216
+ }
2217
+
2218
+ }
2219
+
2220
+ });
2221
+
2222
+ // if checked date is enabled, return false
2223
+ if (enabled_dates && enabled) return false;
2224
+
2225
+ // if checked date is disabled return false
2226
+ else if (disabled_dates && disabled) return true;
2227
+
2228
+ // if script gets this far it means that the day/month/year doesn't need to be disabled
2229
+ return false;
2230
+
2231
+ };
2232
+
2233
+ /**
2234
+ * Checks whether a value is an integer number.
2235
+ *
2236
+ * @param mixed value Value to check
2237
+ *
2238
+ * @return Returns TRUE if the value represents an integer number, or FALSE otherwise
2239
+ *
2240
+ * @access private
2241
+ */
2242
+ var is_integer = function(value) {
2243
+
2244
+ // return TRUE if value represents an integer number, or FALSE otherwise
2245
+ return (value + '').match(/^\-?[0-9]+$/) ? true : false;
2246
+
2247
+ };
2248
+
2249
+ /**
2250
+ * Sets the caption in the header of the date picker and enables or disables navigation buttons when necessary.
2251
+ *
2252
+ * @param string caption String that needs to be displayed in the header
2253
+ *
2254
+ * @return void
2255
+ *
2256
+ * @access private
2257
+ */
2258
+ var manage_header = function(caption) {
2259
+
2260
+ // update the caption in the header
2261
+ $('.dp_caption', header).html(caption);
2262
+
2263
+ // if calendar has direction restrictions or we're looking only at months
2264
+ if (!(!$.isArray(plugin.settings.direction) && to_int(plugin.settings.direction) === 0) || (views.length == 1 && views[0] == 'months')) {
2265
+
2266
+ // get the current year and month
2267
+ var year = selected_year,
2268
+ month = selected_month,
2269
+ next, previous;
2270
+
2271
+ // if current view is showing days
2272
+ if (view == 'days') {
2273
+
2274
+ // check if we can click on the "previous" button
2275
+ previous = !is_disabled(month - 1 < 0 ? str_concat(year - 1, '11') : str_concat(year, str_pad(month - 1, 2)));
2276
+
2277
+ // check if we can click on the "next" button
2278
+ next = !is_disabled(month + 1 > 11 ? str_concat(year + 1, '00') : str_concat(year, str_pad(month + 1, 2)));
2279
+
2280
+ // if current view is showing months
2281
+ } else if (view == 'months') {
2282
+
2283
+ // check if we can click on the "previous" button
2284
+ if (!start_date || start_date.getFullYear() <= year - 1) previous = true;
2285
+
2286
+ // check if we can click on the "next" button
2287
+ if (!end_date || end_date.getFullYear() >= year + 1) next = true;
2288
+
2289
+ // if current view is showing years
2290
+ } else if (view == 'years') {
2291
+
2292
+ // check if we can click on the "previous" button
2293
+ if (!start_date || start_date.getFullYear() < year - 7) previous = true;
2294
+
2295
+ // check if we can click on the "next" button
2296
+ if (!end_date || end_date.getFullYear() > year + 4) next = true;
2297
+
2298
+ }
2299
+
2300
+ // if we cannot click on the "previous" button
2301
+ if (!previous) {
2302
+
2303
+ // disable the "previous" button
2304
+ $('.dp_previous', header).addClass('dp_blocked');
2305
+ $('.dp_previous', header).removeClass('dp_hover');
2306
+
2307
+ // otherwise enable the "previous" button
2308
+ } else $('.dp_previous', header).removeClass('dp_blocked');
2309
+
2310
+ // if we cannot click on the "next" button
2311
+ if (!next) {
2312
+
2313
+ // disable the "next" button
2314
+ $('.dp_next', header).addClass('dp_blocked');
2315
+ $('.dp_next', header).removeClass('dp_hover');
2316
+
2317
+ // otherwise enable the "next" button
2318
+ } else $('.dp_next', header).removeClass('dp_blocked');
2319
+
2320
+ }
2321
+
2322
+ };
2323
+
2324
+ /**
2325
+ * Shows the appropriate view (days, months or years) according to the current value of the "view" property.
2326
+ *
2327
+ * @return void
2328
+ *
2329
+ * @access private
2330
+ */
2331
+ var manage_views = function() {
2332
+
2333
+ // if the day picker was not yet generated
2334
+ if (daypicker.text() === '' || view == 'days') {
2335
+
2336
+ // if the day picker was not yet generated
2337
+ if (daypicker.text() === '') {
2338
+
2339
+ // if date picker is not always visible
2340
+ if (!plugin.settings.always_visible)
2341
+
2342
+ // temporarily set the date picker's left outside of view
2343
+ // so that we can later grab its width and height
2344
+ datepicker.css('left', -1000);
2345
+
2346
+ // temporarily make the date picker visible
2347
+ // so that we can later grab its width and height
2348
+ datepicker.show();
2349
+
2350
+ // generate the day picker
2351
+ generate_daypicker();
2352
+
2353
+ // get the day picker's width and height
2354
+ var width = daypicker.outerWidth(),
2355
+ height = daypicker.outerHeight();
2356
+
2357
+ // make the month picker have the same size as the day picker
2358
+ monthpicker.css({
2359
+ 'width': width,
2360
+ 'height': height
2361
+ });
2362
+
2363
+ // make the year picker have the same size as the day picker
2364
+ yearpicker.css({
2365
+ 'width': width,
2366
+ 'height': height
2367
+ });
2368
+
2369
+ // make the header and the footer have the same size as the day picker
2370
+ header.css('width', width);
2371
+ footer.css('width', width);
2372
+
2373
+ // hide the date picker again
2374
+ datepicker.hide();
2375
+
2376
+ // if the day picker was previously generated at least once
2377
+ // generate the day picker
2378
+ } else generate_daypicker();
2379
+
2380
+ // hide the year and the month pickers
2381
+ monthpicker.hide();
2382
+ yearpicker.hide();
2383
+
2384
+ // if the view is "months"
2385
+ } else if (view == 'months') {
2386
+
2387
+ // generate the month picker
2388
+ generate_monthpicker();
2389
+
2390
+ // hide the day and the year pickers
2391
+ daypicker.hide();
2392
+ yearpicker.hide();
2393
+
2394
+ // if the view is "years"
2395
+ } else if (view == 'years') {
2396
+
2397
+ // generate the year picker
2398
+ generate_yearpicker();
2399
+
2400
+ // hide the day and the month pickers
2401
+ daypicker.hide();
2402
+ monthpicker.hide();
2403
+
2404
+ }
2405
+
2406
+ // if a callback function exists for when navigating through months/years
2407
+ if (plugin.settings.onChange && typeof plugin.settings.onChange == 'function' && undefined !== view) {
2408
+
2409
+ // get the "active" elements in the view (ignoring the disabled ones)
2410
+ var elements = (view == 'days' ?
2411
+ daypicker.find('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked)') :
2412
+ (view == 'months' ?
2413
+ monthpicker.find('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked)') :
2414
+ yearpicker.find('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked)')));
2415
+
2416
+ // iterate through the active elements
2417
+ // and attach a "date" data attribute to each element in the form of
2418
+ // YYYY-MM-DD if the view is "days"
2419
+ // YYYY-MM if the view is "months"
2420
+ // YYYY if the view is "years"
2421
+ // so it's easy to identify elements in the list
2422
+ elements.each(function() {
2423
+
2424
+ // if view is "days"
2425
+ if (view == 'days')
2426
+
2427
+ // attach a "date" data attribute to each element in the form of of YYYY-MM-DD for easily identifying sought elements
2428
+ $(this).data('date', selected_year + '-' + str_pad(selected_month + 1, 2) + '-' + str_pad(to_int($(this).text()), 2));
2429
+
2430
+ // if view is "months"
2431
+ else if (view == 'months') {
2432
+
2433
+ // get the month's number for the element's class
2434
+ var matches = $(this).attr('class').match(/dp\_month\_([0-9]+)/);
2435
+
2436
+ // attach a "date" data attribute to each element in the form of of YYYY-MM for easily identifying sought elements
2437
+ $(this).data('date', selected_year + '-' + str_pad(to_int(matches[1]) + 1, 2));
2438
+
2439
+ // if view is "years"
2440
+ } else
2441
+
2442
+ // attach a "date" data attribute to each element in the form of of YYYY for easily identifying sought elements
2443
+ $(this).data('date', to_int($(this).text()));
2444
+
2445
+ });
2446
+
2447
+ // execute the callback function and send as arguments the current view, the elements in the view, and
2448
+ // the element the plugin is attached to
2449
+ plugin.settings.onChange(view, elements, $element);
2450
+
2451
+ }
2452
+
2453
+ // assume the footer is visible
2454
+ footer.show();
2455
+
2456
+ // if the button for clearing a previously selected date needs to be visible all the time,
2457
+ // or the "Clear" button needs to be shown only when a date was previously selected, and now it's the case,
2458
+ // or the date picker is always visible and the "Clear" button was not explicitly disabled
2459
+ if (
2460
+ plugin.settings.show_clear_date === true ||
2461
+ (plugin.settings.show_clear_date === 0 && $element.val() !== '') ||
2462
+ (plugin.settings.always_visible && plugin.settings.show_clear_date !== false)
2463
+ ) {
2464
+
2465
+ // show the "Clear" button
2466
+ cleardate.show();
2467
+
2468
+ // if the "Today" button is visible
2469
+ if (show_select_today) {
2470
+
2471
+ // show it, and set it's width to 50% of the available space
2472
+ selecttoday.css('width', '50%');
2473
+
2474
+ // the "Clear date" button only takes up 50% of the available space
2475
+ cleardate.css('width', '50%');
2476
+
2477
+ // if the "Today" button is not visible
2478
+ } else {
2479
+
2480
+ // hide the "Today" button
2481
+ selecttoday.hide();
2482
+
2483
+ // the "Clear date" button takes up 100% of the available space
2484
+ cleardate.css('width', '100%');
2485
+
2486
+ }
2487
+
2488
+ // otherwise
2489
+ } else {
2490
+
2491
+ // hide the "Clear" button
2492
+ cleardate.hide();
2493
+
2494
+ // if the "Today" button is visible, it will now take up all the available space
2495
+ if (show_select_today) selecttoday.show().css('width', '100%');
2496
+
2497
+ // if the "Today" button is also not visible, hide the footer entirely
2498
+ else footer.hide();
2499
+
2500
+ }
2501
+
2502
+
2503
+ };
2504
+
2505
+ /**
2506
+ * Puts the specified date in the element the plugin is attached to, and hides the date picker.
2507
+ *
2508
+ * @param integer year The year
2509
+ *
2510
+ * @param integer month The month
2511
+ *
2512
+ * @param integer day The day
2513
+ *
2514
+ * @param string view The view from where the method was called
2515
+ *
2516
+ * @param object cell The element that was clicked
2517
+ *
2518
+ * @return void
2519
+ *
2520
+ * @access private
2521
+ */
2522
+ var select_date = function(year, month, day, view, cell) {
2523
+
2524
+ var
2525
+
2526
+ // construct a new date object from the arguments
2527
+ default_date = new Date(year, month, day, 12, 0, 0),
2528
+
2529
+ // pointer to the cells in the current view
2530
+ view_cells = (view == 'days' ? daypicker_cells : (view == 'months' ? monthpicker_cells : yearpicker_cells)),
2531
+
2532
+ // the selected date, formatted correctly
2533
+ selected_value = format(default_date);
2534
+
2535
+ // set the currently selected and formated date as the value of the element the plugin is attached to
2536
+ $element.val(selected_value);
2537
+
2538
+ // if date picker is always visible
2539
+ if (plugin.settings.always_visible) {
2540
+
2541
+ // extract the date parts and reassign values to these variables
2542
+ // so that everything will be correctly highlighted
2543
+ default_month = default_date.getMonth();
2544
+ selected_month = default_date.getMonth();
2545
+ default_year = default_date.getFullYear();
2546
+ selected_year = default_date.getFullYear();
2547
+ default_day = default_date.getDate();
2548
+
2549
+ // remove the "selected" class from all cells in the current view
2550
+ view_cells.removeClass('dp_selected');
2551
+
2552
+ // add the "selected" class to the currently selected cell
2553
+ cell.addClass('dp_selected');
2554
+
2555
+ }
2556
+
2557
+ // hide the date picker
2558
+ plugin.hide();
2559
+
2560
+ // updates value for the date picker whose starting date depends on the selected date (if any)
2561
+ update_dependent(default_date);
2562
+
2563
+ // if a callback function exists for when selecting a date
2564
+ if (plugin.settings.onSelect && typeof plugin.settings.onSelect == 'function')
2565
+
2566
+ // execute the callback function
2567
+ plugin.settings.onSelect(selected_value, year + '-' + str_pad(month + 1, 2) + '-' + str_pad(day, 2), default_date, $element);
2568
+
2569
+ // move focus to the element the plugin is attached to
2570
+ $element.focus();
2571
+
2572
+ };
2573
+
2574
+ /**
2575
+ * Concatenates any number of arguments and returns them as string.
2576
+ *
2577
+ * @return string Returns the concatenated values.
2578
+ *
2579
+ * @access private
2580
+ */
2581
+ var str_concat = function() {
2582
+
2583
+ var str = '';
2584
+
2585
+ // concatenate as string
2586
+ for (var i = 0; i < arguments.length; i++) str += (arguments[i] + '');
2587
+
2588
+ // return the concatenated values
2589
+ return str;
2590
+
2591
+ };
2592
+
2593
+ /**
2594
+ * Left-pad a string to a certain length with zeroes.
2595
+ *
2596
+ * @param string str The string to be padded.
2597
+ *
2598
+ * @param integer len The length to which the string must be padded
2599
+ *
2600
+ * @return string Returns the string left-padded with leading zeroes
2601
+ *
2602
+ * @access private
2603
+ */
2604
+ var str_pad = function(str, len) {
2605
+
2606
+ // make sure argument is a string
2607
+ str += '';
2608
+
2609
+ // pad with leading zeroes until we get to the desired length
2610
+ while (str.length < len) str = '0' + str;
2611
+
2612
+ // return padded string
2613
+ return str;
2614
+
2615
+ };
2616
+
2617
+ /**
2618
+ * Returns the integer representation of a string
2619
+ *
2620
+ * @return int Returns the integer representation of the string given as argument
2621
+ *
2622
+ * @access private
2623
+ */
2624
+ var to_int = function(str) {
2625
+
2626
+ // return the integer representation of the string given as argument
2627
+ return parseInt(str , 10);
2628
+
2629
+ };
2630
+
2631
+ /**
2632
+ * Updates the paired date picker (whose starting date depends on the value of the current date picker)
2633
+ *
2634
+ * @param date date A JavaScript date object representing the currently selected date
2635
+ *
2636
+ * @return void
2637
+ *
2638
+ * @access private
2639
+ */
2640
+ var update_dependent = function(date) {
2641
+
2642
+ // if the pair element exists
2643
+ if (plugin.settings.pair) {
2644
+
2645
+ // iterate through the pair elements (as there may be more than just one)
2646
+ $.each(plugin.settings.pair, function() {
2647
+
2648
+ var $pair = $(this);
2649
+
2650
+ // chances are that in the beginning the pair element doesn't have the Zebra_DatePicker attached to it yet
2651
+ // (as the "start" element is usually created before the "end" element)
2652
+ // so we'll have to rely on "data" to send the starting date to the pair element
2653
+
2654
+ // therefore, if Zebra_DatePicker is not yet attached
2655
+ if (!($pair.data && $pair.data('Zebra_DatePicker')))
2656
+
2657
+ // set the starting date like this
2658
+ $pair.data('zdp_reference_date', date);
2659
+
2660
+ // if Zebra_DatePicker is attached to the pair element
2661
+ else {
2662
+
2663
+ // reference the date picker object attached to the other element
2664
+ var dp = $pair.data('Zebra_DatePicker');
2665
+
2666
+ // update the other date picker's starting date
2667
+ // the value depends on the original value of the "direction" attribute
2668
+ // (also, if the pair date picker does not have a direction, set it to 1)
2669
+ dp.update({
2670
+ 'reference_date': date,
2671
+ 'direction': dp.settings.direction === 0 ? 1 : dp.settings.direction
2672
+ });
2673
+
2674
+ // if the other date picker is always visible, update the visuals now
2675
+ if (dp.settings.always_visible) dp.show();
2676
+
2677
+ }
2678
+
2679
+ });
2680
+
2681
+ }
2682
+
2683
+ };
2684
+
2685
+ /**
2686
+ * Calculate the ISO 8601 week number for a given date.
2687
+ *
2688
+ * Code is based on the algorithm at http://www.tondering.dk/claus/cal/week.php#calcweekno
2689
+ */
2690
+ var getWeekNumber = function(date) {
2691
+
2692
+ var y = date.getFullYear(),
2693
+ m = date.getMonth() + 1,
2694
+ d = date.getDate(),
2695
+ a, b, c, s, e, f, g, n, w;
2696
+
2697
+ // If month jan. or feb.
2698
+ if (m < 3) {
2699
+
2700
+ a = y - 1;
2701
+ b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
2702
+ c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
2703
+ s = b - c;
2704
+ e = 0;
2705
+ f = d - 1 + 31 * (m - 1);
2706
+
2707
+ // If month mar. through dec.
2708
+ } else {
2709
+
2710
+ a = y;
2711
+ b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
2712
+ c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
2713
+ s = b - c;
2714
+ e = s + 1;
2715
+ f = d + ((153 * (m - 3) + 2) / 5 | 0) + 58 + s;
2716
+
2717
+ }
2718
+
2719
+ g = (a + b) % 7;
2720
+ // ISO Weekday (0 is monday, 1 is tuesday etc.)
2721
+ d = (f + g - e) % 7;
2722
+ n = f + 3 - d;
2723
+
2724
+ if (n < 0) w = 53 - ((g - s) / 5 | 0);
2725
+
2726
+ else if (n > 364 + s) w = 1;
2727
+
2728
+ else w = (n / 7 | 0) + 1;
2729
+
2730
+ return w;
2731
+
2732
+ };
2733
+
2734
+ /**
2735
+ * Function to be called when the "onKeyUp" event occurs
2736
+ *
2737
+ * Why as a separate function and not inline when binding the event? Because only this way we can "unbind" it
2738
+ * if the date picker is destroyed
2739
+ *
2740
+ * @return void
2741
+ *
2742
+ * @access private
2743
+ */
2744
+ var _keyup = function(e) {
2745
+
2746
+ // if the date picker is visible
2747
+ // and the pressed key is ESC
2748
+ // hide the date picker
2749
+ if (datepicker.css('display') == 'block' || e.which == 27) plugin.hide();
2750
+
2751
+ };
2752
+
2753
+ /**
2754
+ * Function to be called when the "onMouseDown" event occurs
2755
+ *
2756
+ * Why as a separate function and not inline when binding the event? Because only this way we can "unbind" it
2757
+ * if the date picker is destroyed
2758
+ *
2759
+ * @return void
2760
+ *
2761
+ * @access private
2762
+ */
2763
+ var _mousedown = function(e) {
2764
+
2765
+ // if the date picker is visible
2766
+ if (datepicker.css('display') == 'block') {
2767
+
2768
+ // if the calendar icon is visible and we clicked it, let the onClick event of the icon to handle the event
2769
+ // (we want it to toggle the date picker)
2770
+ if (plugin.settings.show_icon && $(e.target).get(0) === icon.get(0)) return true;
2771
+
2772
+ // if what's clicked is not inside the date picker
2773
+ // hide the date picker
2774
+ if ($(e.target).parents().filter('.Zebra_DatePicker').length === 0) plugin.hide();
2775
+
2776
+ }
2777
+
2778
+ };
2779
+
2780
+ /**
2781
+ * Function to be called when the "onResize" event occurs
2782
+ *
2783
+ * Why as a separate function and not inline when binding the event? Because only this way we can "unbind" it
2784
+ * if the date picker is destroyed
2785
+ *
2786
+ * @return void
2787
+ *
2788
+ * @access private
2789
+ */
2790
+ var _resize = function() {
2791
+
2792
+ // hide the date picker
2793
+ plugin.hide();
2794
+
2795
+ // we use timeouts so that we do not call the "update" method on *every* step of the resize event
2796
+
2797
+ // clear a previously set timeout
2798
+ clearTimeout(timeout);
2799
+
2800
+ // set timeout again
2801
+ timeout = setTimeout(function() {
2802
+
2803
+ // update the date picker
2804
+ plugin.update();
2805
+
2806
+ }, 100);
2807
+
2808
+ };
2809
+
2810
+ // since with jQuery 1.9.0 the $.browser object was removed, we rely on this piece of code from
2811
+ // http://www.quirksmode.org/js/detect.html to detect the browser
2812
+ var browser = {
2813
+ init: function () {
2814
+ this.name = this.searchString(this.dataBrowser) || '';
2815
+ this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || '';
2816
+ },
2817
+ searchString: function (data) {
2818
+ for (var i=0;i<data.length;i++) {
2819
+ var dataString = data[i].string;
2820
+ var dataProp = data[i].prop;
2821
+ this.versionSearchString = data[i].versionSearch || data[i].identity;
2822
+ if (dataString) {
2823
+ if (dataString.indexOf(data[i].subString) != -1)
2824
+ return data[i].identity;
2825
+ }
2826
+ else if (dataProp)
2827
+ return data[i].identity;
2828
+ }
2829
+ },
2830
+ searchVersion: function (dataString) {
2831
+ var index = dataString.indexOf(this.versionSearchString);
2832
+ if (index == -1) return;
2833
+ return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
2834
+ },
2835
+ dataBrowser: [
2836
+ {
2837
+ string: navigator.userAgent,
2838
+ subString: 'Firefox',
2839
+ identity: 'firefox'
2840
+ },
2841
+ {
2842
+ string: navigator.userAgent,
2843
+ subString: 'MSIE',
2844
+ identity: 'explorer',
2845
+ versionSearch: 'MSIE'
2846
+ }
2847
+ ]
2848
+ };
2849
+
2850
+ browser.init();
2851
+
2852
+ // initialize the plugin
2853
+ init();
2854
+
2855
+ };
2856
+
2857
+ $.fn.Zebra_DatePicker = function(options) {
2858
+
2859
+ // iterate through all the elements to which we need to attach the date picker to
2860
+ return this.each(function() {
2861
+
2862
+ // if element has a date picker already attached
2863
+ if (undefined !== $(this).data('Zebra_DatePicker'))
2864
+
2865
+ // remove the attached date picker
2866
+ $(this).data('Zebra_DatePicker').destroy();
2867
+
2868
+ // create an instance of the plugin
2869
+ var plugin = new $.Zebra_DatePicker(this, options);
2870
+
2871
+ // save a reference to the newly created object
2872
+ $(this).data('Zebra_DatePicker', plugin);
2873
+
2874
+ });
2875
+
2876
+ };
2877
+
2878
+ })(jQuery);