pikaday-gem 1.0.0.0

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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 UC Berkeley - ETS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Pikaday Gem
2
+ [![Gem Version](https://badge.fury.io/rb/pikaday-gem.png)](http://badge.fury.io/rb/pikaday-gem) [![Dependency Status](https://gemnasium.com/ets-berkeley-edu/pikaday-gem.png)](https://gemnasium.com/ets-berkeley-edu/pikaday-gem) [![Code Climate](https://codeclimate.com/github/ets-berkeley-edu/pikaday-gem.png)](https://codeclimate.com/github/ets-berkeley-edu/pikaday-gem)
3
+
4
+ [Pikaday datepicker][pikaday] as a Ruby gem.
5
+
6
+ ## Getting Started
7
+
8
+ Add the gem to your Gemfile:
9
+
10
+ ```ruby
11
+ gem "pikaday-gem"
12
+ ```
13
+
14
+ And run
15
+
16
+ ```bash
17
+ bundle install
18
+ ```
19
+ in the terminal to download the resources.
20
+
21
+ ### Adding the files to your projects
22
+
23
+ In order for the files to load, you'll need to do add them.
24
+
25
+ `application.js`:
26
+
27
+ ```javascript
28
+ //= require pikaday
29
+ ```
30
+
31
+ `application.css`:
32
+
33
+ ```css
34
+ *= require pikaday
35
+ ```
36
+
37
+ and you should be good to go.
38
+
39
+ ## Updating this plug-in
40
+
41
+ If you would like to update this gem you should take the following steps:
42
+
43
+ 1. `rake download VERSION=X.X.X`. If you don't specify the version, it will get the latest one.
44
+ 1. `rake tag VERSION=X.X.X` will tag the version you've specified as the standard version.
45
+ 1. Make a Pull request
46
+
47
+ Then the maintainer of the gem will need to do the following steps:
48
+
49
+ 1. Update the version [lib/pikaday-gem/version.rb](lib/pikaday-gem/version.rb)
50
+ 1. Run ``gem build pikaday-gem.gemspec`` to package the gem
51
+ 1. Once satisfied, push the gem up to RubyGems.org with ``gem push pikaday-gem-<VERSION>.gem``
52
+
53
+ [pikaday]: https://github.com/dbushell/Pikaday
data/Rakefile ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ Bundler::GemHelper.install_tasks
9
+
10
+ require 'rake'
11
+ require 'open-uri'
12
+ require 'json'
13
+
14
+ dir_assets = 'vendor/assets/'
15
+ dir_js = dir_assets + 'javascripts'
16
+ dir_css = dir_assets + 'stylesheets'
17
+
18
+ desc 'Downloads the pikaday CSS and JavaScript files from GitHub'
19
+ task :download do |t|
20
+
21
+ def create_dir dir, version
22
+ dir_name = File.join(dir, version)
23
+ Dir.mkdir(dir_name) unless Dir.exist?(dir_name)
24
+ end
25
+
26
+ def download_file url, dir, filename, version
27
+ Dir.chdir(File.join(dir, version)) do
28
+ if File.exists?(filename)
29
+ puts " #{dir + '/' + version + '/' + filename} already exists"
30
+ next
31
+ end
32
+ puts " #{url}"
33
+ open(filename, "w") { |file| file.write(open(url).read)}
34
+ end
35
+ end
36
+
37
+ # Specify which version you want
38
+ version = ENV['VERSION']
39
+ version ||= 'latest'
40
+ puts "Target version: #{version.chomp('/')}"
41
+
42
+ # Get the different versions
43
+ tags_url = 'https://api.github.com/repos/dbushell/Pikaday/tags'
44
+ result = JSON.parse(open(tags_url).read)
45
+ versions = result.map { |item| item['name'] }
46
+
47
+ puts "Available versions: #{versions.inspect}"
48
+
49
+ # Figuring out which version to get
50
+ if versions.include? version
51
+ get_version = version
52
+ else
53
+ get_version = versions.first
54
+
55
+ if !(versions.include? version) && version != 'latest'
56
+ puts "Warning: The version you've specified: '#{version}' wasn't found, using the latest version instead: '#{get_version}'"
57
+ end
58
+ end
59
+
60
+ # Get the right commit
61
+ commit = result.select { |item| item['name'] == get_version }.first['commit']['sha']
62
+ puts "We'll use the following commit to get the files: #{commit}"
63
+
64
+ # Creating the necessary directories
65
+ create_dir dir_js, get_version
66
+ create_dir dir_css, get_version
67
+
68
+ # Download the right files
69
+ url_root = 'https://raw.github.com/dbushell/Pikaday/' + commit + '/'
70
+ url_js = url_root + 'pikaday.js'
71
+ url_css = url_root + '/css/pikaday.css'
72
+
73
+ puts "Downloading... \n"
74
+ download_file url_js, dir_js, 'pikaday.js', get_version
75
+ download_file url_css, dir_css, 'pikaday.css', get_version
76
+
77
+ end
78
+
79
+ desc 'Tag the default file versions for asset helpers'
80
+ task :tag do |t|
81
+ Rake::Task['tag_default'].invoke
82
+ end
83
+
84
+ task :tag_default do |t|
85
+
86
+ def copy_files dir, version
87
+ Dir.entries(File.join(dir, version)).each do |file|
88
+ file_source = File.join(dir, version, file)
89
+ file_destination = File.join(dir, file)
90
+ if File.file?(file_source)
91
+ FileUtils.cp file_source, file_destination, { verbose: true }
92
+ end
93
+ end
94
+ end
95
+
96
+ version = ENV['VERSION']
97
+ version ||= 'latest'
98
+
99
+ puts "Target version: #{version.chomp('/')}"
100
+
101
+ Dir.chdir(dir_js) do
102
+ version_directories = Dir.glob("*").select { |fn| File.directory?(fn) }.sort.reverse
103
+
104
+ puts "Available versions: #{version_directories.inspect}"
105
+ if !(version_directories.include? version)
106
+ if version != 'latest'
107
+ puts "WARN: Specified version='#{version}' not found, setting to latest version: '#{version_directories.first}'"
108
+ end
109
+ version = version_directories.first
110
+ end
111
+ end
112
+
113
+ copy_files dir_js, version
114
+ copy_files dir_css, version
115
+ end
@@ -0,0 +1,6 @@
1
+ require "rails/engine"
2
+
3
+ module PikadayGem
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module PikadayGem
2
+ VERSION = "1.0.0.0"
3
+ end
@@ -0,0 +1,816 @@
1
+ /*!
2
+ * Pikaday
3
+ * Copyright © 2012 David Bushell | BSD & MIT license | http://dbushell.com/
4
+ */
5
+
6
+ (function(window, document, undefined)
7
+ {
8
+ 'use strict';
9
+
10
+ /**
11
+ * feature detection and helper functions
12
+ */
13
+ var hasMoment = typeof window.moment === 'function',
14
+
15
+ hasEventListeners = !!window.addEventListener,
16
+
17
+ sto = window.setTimeout,
18
+
19
+ addEvent = function(el, e, callback, capture)
20
+ {
21
+ if (hasEventListeners) {
22
+ el.addEventListener(e, callback, !!capture);
23
+ } else {
24
+ el.attachEvent('on' + e, callback);
25
+ }
26
+ },
27
+
28
+ removeEvent = function(el, e, callback, capture)
29
+ {
30
+ if (hasEventListeners) {
31
+ el.removeEventListener(e, callback, !!capture);
32
+ } else {
33
+ el.detachEvent('on' + e, callback);
34
+ }
35
+ },
36
+
37
+ fireEvent = function(el, eventName, data)
38
+ {
39
+ var ev;
40
+
41
+ if (document.createEvent) {
42
+ ev = document.createEvent('HTMLEvents');
43
+ ev.initEvent(eventName, true, false);
44
+ ev = extend(ev, data);
45
+ el.dispatchEvent(ev);
46
+ } else if (document.createEventObject) {
47
+ ev = document.createEventObject();
48
+ ev = extend(ev, data);
49
+ el.fireEvent('on' + eventName, ev);
50
+ }
51
+ },
52
+
53
+ trim = function(str)
54
+ {
55
+ return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g,'');
56
+ },
57
+
58
+ hasClass = function(el, cn)
59
+ {
60
+ return (' ' + el.className + ' ').indexOf(' ' + cn + ' ') !== -1;
61
+ },
62
+
63
+ addClass = function(el, cn)
64
+ {
65
+ if (!hasClass(el, cn)) {
66
+ el.className = (el.className === '') ? cn : el.className + ' ' + cn;
67
+ }
68
+ },
69
+
70
+ removeClass = function(el, cn)
71
+ {
72
+ el.className = trim((' ' + el.className + ' ').replace(' ' + cn + ' ', ' '));
73
+ },
74
+
75
+ isArray = function(obj)
76
+ {
77
+ return (/Array/).test(Object.prototype.toString.call(obj));
78
+ },
79
+
80
+ isDate = function(obj)
81
+ {
82
+ return (/Date/).test(Object.prototype.toString.call(obj)) && !isNaN(obj.getTime());
83
+ },
84
+
85
+ isLeapYear = function(year)
86
+ {
87
+ // solution by Matti Virkkunen: http://stackoverflow.com/a/4881951
88
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
89
+ },
90
+
91
+ getDaysInMonth = function(year, month)
92
+ {
93
+ return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
94
+ },
95
+
96
+ setToStartOfDay = function(date)
97
+ {
98
+ if (isDate(date)) date.setHours(0,0,0,0);
99
+ },
100
+
101
+ compareDates = function(a,b)
102
+ {
103
+ // weak date comparison (use setToStartOfDay(date) to ensure correct result)
104
+ return a.getTime() === b.getTime();
105
+ },
106
+
107
+ extend = function(to, from, overwrite)
108
+ {
109
+ var prop, hasProp;
110
+ for (prop in from) {
111
+ hasProp = to[prop] !== undefined;
112
+ if (hasProp && typeof from[prop] === 'object' && from[prop].nodeName === undefined) {
113
+ if (isDate(from[prop])) {
114
+ if (overwrite) {
115
+ to[prop] = new Date(from[prop].getTime());
116
+ }
117
+ }
118
+ else if (isArray(from[prop])) {
119
+ if (overwrite) {
120
+ to[prop] = from[prop].slice(0);
121
+ }
122
+ } else {
123
+ to[prop] = extend({}, from[prop], overwrite);
124
+ }
125
+ } else if (overwrite || !hasProp) {
126
+ to[prop] = from[prop];
127
+ }
128
+ }
129
+ return to;
130
+ },
131
+
132
+
133
+ /**
134
+ * defaults and localisation
135
+ */
136
+ defaults = {
137
+
138
+ // bind the picker to a form field
139
+ field: null,
140
+
141
+ // automatically show/hide the picker on `field` focus (default `true` if `field` is set)
142
+ bound: undefined,
143
+
144
+ // the default output format for `.toString()` and `field` value
145
+ format: 'YYYY-MM-DD',
146
+
147
+ // the initial date to view when first opened
148
+ defaultDate: null,
149
+
150
+ // make the `defaultDate` the initial selected value
151
+ setDefaultDate: false,
152
+
153
+ // first day of week (0: Sunday, 1: Monday etc)
154
+ firstDay: 0,
155
+
156
+ // the minimum/earliest date that can be selected
157
+ minDate: null,
158
+ // the maximum/latest date that can be selected
159
+ maxDate: null,
160
+
161
+ // number of years either side, or array of upper/lower range
162
+ yearRange: 10,
163
+
164
+ // used internally (don't config outside)
165
+ minYear: 0,
166
+ maxYear: 9999,
167
+ minMonth: undefined,
168
+ maxMonth: undefined,
169
+
170
+ isRTL: false,
171
+
172
+ // how many months are visible (not implemented yet)
173
+ numberOfMonths: 1,
174
+
175
+ // internationalization
176
+ i18n: {
177
+ previousMonth : 'Previous Month',
178
+ nextMonth : 'Next Month',
179
+ months : ['January','February','March','April','May','June','July','August','September','October','November','December'],
180
+ //monthsShort : ['Jan_Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
181
+ weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
182
+ weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
183
+ },
184
+
185
+ // callback function
186
+ onSelect: null,
187
+ onOpen: null,
188
+ onClose: null,
189
+ onDraw: null
190
+ },
191
+
192
+
193
+ /**
194
+ * templating functions to abstract HTML rendering
195
+ */
196
+ renderDayName = function(opts, day, abbr)
197
+ {
198
+ day += opts.firstDay;
199
+ while (day >= 7) {
200
+ day -= 7;
201
+ }
202
+ return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day];
203
+ },
204
+
205
+ renderDay = function(i, isSelected, isToday, isDisabled, isEmpty)
206
+ {
207
+ if (isEmpty) {
208
+ return '<td class="is-empty"></td>';
209
+ }
210
+ var arr = [];
211
+ if (isDisabled) {
212
+ arr.push('is-disabled');
213
+ }
214
+ if (isToday) {
215
+ arr.push('is-today');
216
+ }
217
+ if (isSelected) {
218
+ arr.push('is-selected');
219
+ }
220
+ return '<td data-day="' + i + '" class="' + arr.join(' ') + '"><button class="pika-button" type="button">' + i + '</button>' + '</td>';
221
+ },
222
+
223
+ renderRow = function(days, isRTL)
224
+ {
225
+ return '<tr>' + (isRTL ? days.reverse() : days).join('') + '</tr>';
226
+ },
227
+
228
+ renderBody = function(rows)
229
+ {
230
+ return '<tbody>' + rows.join('') + '</tbody>';
231
+ },
232
+
233
+ renderHead = function(opts)
234
+ {
235
+ var i, arr = [];
236
+ for (i = 0; i < 7; i++) {
237
+ arr.push('<th scope="col"><abbr title="' + renderDayName(opts, i) + '">' + renderDayName(opts, i, true) + '</abbr></th>');
238
+ }
239
+ return '<thead>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</thead>';
240
+ },
241
+
242
+ renderTitle = function(instance)
243
+ {
244
+ var i, j, arr,
245
+ opts = instance._o,
246
+ month = instance._m,
247
+ year = instance._y,
248
+ isMinYear = year === opts.minYear,
249
+ isMaxYear = year === opts.maxYear,
250
+ html = '<div class="pika-title">',
251
+ prev = true,
252
+ next = true;
253
+
254
+ for (arr = [], i = 0; i < 12; i++) {
255
+ arr.push('<option value="' + i + '"' +
256
+ (i === month ? ' selected': '') +
257
+ ((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled' : '') + '>' +
258
+ opts.i18n.months[i] + '</option>');
259
+ }
260
+ html += '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month">' + arr.join('') + '</select></div>';
261
+
262
+ if (isArray(opts.yearRange)) {
263
+ i = opts.yearRange[0];
264
+ j = opts.yearRange[1] + 1;
265
+ } else {
266
+ i = year - opts.yearRange;
267
+ j = 1 + year + opts.yearRange;
268
+ }
269
+
270
+ for (arr = []; i < j && i <= opts.maxYear; i++) {
271
+ if (i >= opts.minYear) {
272
+ arr.push('<option value="' + i + '"' + (i === year ? ' selected': '') + '>' + (i) + '</option>');
273
+ }
274
+ }
275
+ html += '<div class="pika-label">' + year + '<select class="pika-select pika-select-year">' + arr.join('') + '</select></div>';
276
+
277
+ if (isMinYear && (month === 0 || opts.minMonth >= month)) {
278
+ prev = false;
279
+ }
280
+
281
+ if (isMaxYear && (month === 11 || opts.maxMonth <= month)) {
282
+ next = false;
283
+ }
284
+
285
+ html += '<button class="pika-prev' + (prev ? '' : ' is-disabled') + '" type="button">' + opts.i18n.previousMonth + '</button>';
286
+ html += '<button class="pika-next' + (next ? '' : ' is-disabled') + '" type="button">' + opts.i18n.nextMonth + '</button>';
287
+
288
+ return html += '</div>';
289
+ },
290
+
291
+ renderTable = function(opts, data)
292
+ {
293
+ return '<table cellpadding="0" cellspacing="0" class="pika-table">' + renderHead(opts) + renderBody(data) + '</table>';
294
+ };
295
+
296
+
297
+ /**
298
+ * Pikaday constructor
299
+ */
300
+ window.Pikaday = function(options)
301
+ {
302
+ var self = this,
303
+ opts = self.config(options);
304
+
305
+ self._onMouseDown = function(e)
306
+ {
307
+ if (!self._v) {
308
+ return;
309
+ }
310
+ e = e || window.event;
311
+ var target = e.target || e.srcElement;
312
+ if (!target) {
313
+ return;
314
+ }
315
+
316
+ if (!hasClass(target, 'is-disabled')) {
317
+ if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty')) {
318
+ self.setDate(new Date(self._y, self._m, parseInt(target.innerHTML, 10)));
319
+ if (opts.bound) {
320
+ sto(function() {
321
+ self.hide();
322
+ }, 100);
323
+ }
324
+ return;
325
+ }
326
+ else if (hasClass(target, 'pika-prev')) {
327
+ self.prevMonth();
328
+ }
329
+ else if (hasClass(target, 'pika-next')) {
330
+ self.nextMonth();
331
+ }
332
+ }
333
+ if (!hasClass(target, 'pika-select')) {
334
+ if (e.preventDefault) {
335
+ e.preventDefault();
336
+ } else {
337
+ return e.returnValue = false;
338
+ }
339
+ } else {
340
+ self._c = true;
341
+ }
342
+ };
343
+
344
+ self._onChange = function(e)
345
+ {
346
+ e = e || window.event;
347
+ var target = e.target || e.srcElement;
348
+ if (!target) {
349
+ return;
350
+ }
351
+ if (hasClass(target, 'pika-select-month')) {
352
+ self.gotoMonth(target.value);
353
+ }
354
+ else if (hasClass(target, 'pika-select-year')) {
355
+ self.gotoYear(target.value);
356
+ }
357
+ };
358
+
359
+ self._onInputChange = function(e)
360
+ {
361
+ var date;
362
+
363
+ if (e.firedBy === self) {
364
+ return;
365
+ }
366
+ if (hasMoment) {
367
+ date = window.moment(opts.field.value, opts.format);
368
+ date = date ? date.toDate() : null;
369
+ }
370
+ else {
371
+ date = new Date(Date.parse(opts.field.value));
372
+ }
373
+ self.setDate(isDate(date) ? date : null);
374
+ if (!self._v) {
375
+ self.show();
376
+ }
377
+ };
378
+
379
+ self._onInputFocus = function(e)
380
+ {
381
+ self.show();
382
+ };
383
+
384
+ self._onInputClick = function(e)
385
+ {
386
+ self.show();
387
+ };
388
+
389
+ self._onInputBlur = function(e)
390
+ {
391
+ if (!self._c) {
392
+ self._b = sto(function() {
393
+ self.hide();
394
+ }, 50);
395
+ }
396
+ self._c = false;
397
+ };
398
+
399
+ self._onClick = function(e)
400
+ {
401
+ e = e || window.event;
402
+ var target = e.target || e.srcElement,
403
+ pEl = target;
404
+ if (!target) {
405
+ return;
406
+ }
407
+ if (!hasEventListeners && hasClass(target, 'pika-select')) {
408
+ if (!target.onchange) {
409
+ target.setAttribute('onchange', 'return;');
410
+ addEvent(target, 'change', self._onChange);
411
+ }
412
+ }
413
+ do {
414
+ if (hasClass(pEl, 'pika-single')) {
415
+ return;
416
+ }
417
+ }
418
+ while ((pEl = pEl.parentNode));
419
+ if (self._v && target !== opts.field) {
420
+ self.hide();
421
+ }
422
+ };
423
+
424
+ self.el = document.createElement('div');
425
+ self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '');
426
+
427
+ addEvent(self.el, 'mousedown', self._onMouseDown, true);
428
+ addEvent(self.el, 'change', self._onChange);
429
+
430
+ if (opts.field) {
431
+ if (opts.bound) {
432
+ document.body.appendChild(self.el);
433
+ } else {
434
+ opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling);
435
+ }
436
+ addEvent(opts.field, 'change', self._onInputChange);
437
+
438
+ if (!opts.defaultDate) {
439
+ if (hasMoment && opts.field.value) {
440
+ opts.defaultDate = window.moment(opts.field.value, opts.format).toDate();
441
+ } else {
442
+ opts.defaultDate = new Date(Date.parse(opts.field.value));
443
+ }
444
+ opts.setDefaultDate = true;
445
+ }
446
+ }
447
+
448
+ var defDate = opts.defaultDate;
449
+
450
+ if (isDate(defDate)) {
451
+ if (opts.setDefaultDate) {
452
+ self.setDate(defDate, true);
453
+ } else {
454
+ self.gotoDate(defDate);
455
+ }
456
+ } else {
457
+ self.gotoDate(new Date());
458
+ }
459
+
460
+ if (opts.bound) {
461
+ this.hide();
462
+ self.el.className += ' is-bound';
463
+ addEvent(opts.field, 'click', self._onInputClick);
464
+ addEvent(opts.field, 'focus', self._onInputFocus);
465
+ addEvent(opts.field, 'blur', self._onInputBlur);
466
+ } else {
467
+ this.show();
468
+ }
469
+
470
+ };
471
+
472
+
473
+ /**
474
+ * public Pikaday API
475
+ */
476
+ window.Pikaday.prototype = {
477
+
478
+
479
+ /**
480
+ * configure functionality
481
+ */
482
+ config: function(options)
483
+ {
484
+ if (!this._o) {
485
+ this._o = extend({}, defaults, true);
486
+ }
487
+
488
+ var opts = extend(this._o, options, true);
489
+
490
+ opts.isRTL = !!opts.isRTL;
491
+
492
+ opts.field = (opts.field && opts.field.nodeName) ? opts.field : null;
493
+
494
+ opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field);
495
+
496
+ var nom = parseInt(opts.numberOfMonths, 10) || 1;
497
+ opts.numberOfMonths = nom > 4 ? 4 : nom;
498
+
499
+ if (!isDate(opts.minDate)) {
500
+ opts.minDate = false;
501
+ }
502
+ if (!isDate(opts.maxDate)) {
503
+ opts.maxDate = false;
504
+ }
505
+ if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) {
506
+ opts.maxDate = opts.minDate = false;
507
+ }
508
+ if (opts.minDate) {
509
+ setToStartOfDay(opts.minDate);
510
+ opts.minYear = opts.minDate.getFullYear();
511
+ opts.minMonth = opts.minDate.getMonth();
512
+ }
513
+ if (opts.maxDate) {
514
+ setToStartOfDay(opts.maxDate);
515
+ opts.maxYear = opts.maxDate.getFullYear();
516
+ opts.maxMonth = opts.maxDate.getMonth();
517
+ }
518
+
519
+ if (isArray(opts.yearRange)) {
520
+ var fallback = new Date().getFullYear() - 10;
521
+ opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback;
522
+ opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback;
523
+ } else {
524
+ opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange;
525
+ if (opts.yearRange > 100) {
526
+ opts.yearRange = 100;
527
+ }
528
+ }
529
+
530
+ return opts;
531
+ },
532
+
533
+ /**
534
+ * return a formatted string of the current selection (using Moment.js if available)
535
+ */
536
+ toString: function(format)
537
+ {
538
+ return !isDate(this._d) ? '' : hasMoment ? window.moment(this._d).format(format || this._o.format) : this._d.toDateString();
539
+ },
540
+
541
+ /**
542
+ * return a Moment.js object of the current selection (if available)
543
+ */
544
+ getMoment: function()
545
+ {
546
+ return hasMoment ? window.moment(this._d) : null;
547
+ },
548
+
549
+ /**
550
+ * set the current selection from a Moment.js object (if available)
551
+ */
552
+ setMoment: function(date)
553
+ {
554
+ if (hasMoment && window.moment.isMoment(date)) {
555
+ this.setDate(date.toDate());
556
+ }
557
+ },
558
+
559
+ /**
560
+ * return a Date object of the current selection
561
+ */
562
+ getDate: function()
563
+ {
564
+ return isDate(this._d) ? new Date(this._d.getTime()) : null;
565
+ },
566
+
567
+ /**
568
+ * set the current selection
569
+ */
570
+ setDate: function(date, preventOnSelect)
571
+ {
572
+ if (!date) {
573
+ this._d = null;
574
+ return this.draw();
575
+ }
576
+ if (typeof date === 'string') {
577
+ date = new Date(Date.parse(date));
578
+ }
579
+ if (!isDate(date)) {
580
+ return;
581
+ }
582
+
583
+ var min = this._o.minDate,
584
+ max = this._o.maxDate;
585
+
586
+ if (isDate(min) && date < min) {
587
+ date = min;
588
+ } else if (isDate(max) && date > max) {
589
+ date = max;
590
+ }
591
+
592
+ this._d = new Date(date.getTime());
593
+ setToStartOfDay(this._d);
594
+ this.gotoDate(this._d);
595
+
596
+ if (this._o.field) {
597
+ this._o.field.value = this.toString();
598
+ fireEvent(this._o.field, "change", { firedBy: this });
599
+ }
600
+ if (!preventOnSelect && typeof this._o.onSelect === 'function') {
601
+ this._o.onSelect.call(this, this.getDate());
602
+ }
603
+ },
604
+
605
+ /**
606
+ * change view to a specific date
607
+ */
608
+ gotoDate: function(date)
609
+ {
610
+ if (!isDate(date)) {
611
+ return;
612
+ }
613
+ this._y = date.getFullYear();
614
+ this._m = date.getMonth();
615
+ this.draw();
616
+ },
617
+
618
+ gotoToday: function()
619
+ {
620
+ this.gotoDate(new Date());
621
+ },
622
+
623
+ /**
624
+ * change view to a specific month (zero-index, e.g. 0: January)
625
+ */
626
+ gotoMonth: function(month)
627
+ {
628
+ if (!isNaN( (month = parseInt(month, 10)) )) {
629
+ this._m = month < 0 ? 0 : month > 11 ? 11 : month;
630
+ this.draw();
631
+ }
632
+ },
633
+
634
+ nextMonth: function()
635
+ {
636
+ if (++this._m > 11) {
637
+ this._m = 0;
638
+ this._y++;
639
+ }
640
+ this.draw();
641
+ },
642
+
643
+ prevMonth: function()
644
+ {
645
+ if (--this._m < 0) {
646
+ this._m = 11;
647
+ this._y--;
648
+ }
649
+ this.draw();
650
+ },
651
+
652
+ /**
653
+ * change view to a specific full year (e.g. "2012")
654
+ */
655
+ gotoYear: function(year)
656
+ {
657
+ if (!isNaN(year)) {
658
+ this._y = parseInt(year, 10);
659
+ this.draw();
660
+ }
661
+ },
662
+
663
+ /**
664
+ * refresh the HTML
665
+ */
666
+ draw: function(force)
667
+ {
668
+ if (!this._v && !force) {
669
+ return;
670
+ }
671
+ var opts = this._o,
672
+ minYear = opts.minYear,
673
+ maxYear = opts.maxYear,
674
+ minMonth = opts.minMonth,
675
+ maxMonth = opts.maxMonth;
676
+
677
+ if (this._y <= minYear) {
678
+ this._y = minYear;
679
+ if (!isNaN(minMonth) && this._m < minMonth) {
680
+ this._m = minMonth;
681
+ }
682
+ }
683
+ if (this._y >= maxYear) {
684
+ this._y = maxYear;
685
+ if (!isNaN(maxMonth) && this._m > maxMonth) {
686
+ this._m = maxMonth;
687
+ }
688
+ }
689
+
690
+ this.el.innerHTML = renderTitle(this) + this.render(this._y, this._m);
691
+
692
+ if (opts.bound) {
693
+ var pEl = opts.field,
694
+ left = pEl.offsetLeft,
695
+ top = pEl.offsetTop + pEl.offsetHeight;
696
+ while((pEl = pEl.offsetParent)) {
697
+ left += pEl.offsetLeft;
698
+ top += pEl.offsetTop;
699
+ }
700
+ this.el.style.cssText = 'position:absolute;left:' + left + 'px;top:' + top + 'px;';
701
+ sto(function() {
702
+ opts.field.focus();
703
+ }, 1);
704
+ }
705
+
706
+ if (typeof this._o.onDraw === 'function') {
707
+ var self = this;
708
+ sto(function() {
709
+ self._o.onDraw.call(self);
710
+ }, 0);
711
+ }
712
+ },
713
+
714
+ /**
715
+ * render HTML for a particular month
716
+ */
717
+ render: function(year, month)
718
+ {
719
+ var opts = this._o,
720
+ now = new Date(),
721
+ days = getDaysInMonth(year, month),
722
+ before = new Date(year, month, 1).getDay(),
723
+ data = [],
724
+ row = [];
725
+ setToStartOfDay(now);
726
+ if (opts.firstDay > 0) {
727
+ before -= opts.firstDay;
728
+ if (before < 0) {
729
+ before += 7;
730
+ }
731
+ }
732
+ var cells = days + before,
733
+ after = cells;
734
+ while(after > 7) {
735
+ after -= 7;
736
+ }
737
+ cells += 7 - after;
738
+ for (var i = 0, r = 0; i < cells; i++)
739
+ {
740
+ var day = new Date(year, month, 1 + (i - before)),
741
+ isDisabled = (opts.minDate && day < opts.minDate) || (opts.maxDate && day > opts.maxDate),
742
+ isSelected = isDate(this._d) ? compareDates(day, this._d) : false,
743
+ isToday = compareDates(day, now),
744
+ isEmpty = i < before || i >= (days + before);
745
+
746
+ row.push(renderDay(1 + (i - before), isSelected, isToday, isDisabled, isEmpty));
747
+
748
+ if (++r === 7) {
749
+ data.push(renderRow(row, opts.isRTL));
750
+ row = [];
751
+ r = 0;
752
+ }
753
+ }
754
+ return renderTable(opts, data);
755
+ },
756
+
757
+ isVisible: function()
758
+ {
759
+ return this._v;
760
+ },
761
+
762
+ show: function()
763
+ {
764
+ if (!this._v) {
765
+ if (this._o.bound) {
766
+ addEvent(document, 'click', this._onClick);
767
+ }
768
+ removeClass(this.el, 'is-hidden');
769
+ this._v = true;
770
+ this.draw();
771
+ if (typeof this._o.onOpen === 'function') {
772
+ this._o.onOpen.call(this);
773
+ }
774
+ }
775
+ },
776
+
777
+ hide: function()
778
+ {
779
+ var v = this._v;
780
+ if (v !== false) {
781
+ if (this._o.bound) {
782
+ removeEvent(document, 'click', this._onClick);
783
+ }
784
+ this.el.style.cssText = '';
785
+ addClass(this.el, 'is-hidden');
786
+ this._v = false;
787
+ if (v !== undefined && typeof this._o.onClose === 'function') {
788
+ this._o.onClose.call(this);
789
+ }
790
+ }
791
+ },
792
+
793
+ /**
794
+ * GAME OVER
795
+ */
796
+ destroy: function()
797
+ {
798
+ this.hide();
799
+ removeEvent(this.el, 'mousedown', this._onMouseDown, true);
800
+ removeEvent(this.el, 'change', this._onChange);
801
+ if (this._o.field) {
802
+ removeEvent(this._o.field, 'change', this._onInputChange);
803
+ if (this._o.bound) {
804
+ removeEvent(this._o.field, 'click', this._onInputClick);
805
+ removeEvent(this._o.field, 'focus', this._onInputFocus);
806
+ removeEvent(this._o.field, 'blur', this._onInputBlur);
807
+ }
808
+ }
809
+ if (this.el.parentNode) {
810
+ this.el.parentNode.removeChild(this.el);
811
+ }
812
+ }
813
+
814
+ };
815
+
816
+ })(window, window.document);