bhf 0.4.4 → 0.4.5

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,161 @@
1
+ /*
2
+ ---
3
+ name: Picker.Attach
4
+ description: Adds attach and detach methods to the Picker, to attach it to element events
5
+ authors: Arian Stolwijk
6
+ requires: [Picker, Core/Element.Event]
7
+ provides: Picker.Attach
8
+ ...
9
+ */
10
+
11
+
12
+ Picker.Attach = new Class({
13
+
14
+ Extends: Picker,
15
+
16
+ options: {/*
17
+ onAttached: function(event){},
18
+
19
+ toggleElements: null, // deprecated
20
+ toggle: null, // When set it deactivate toggling by clicking on the input */
21
+ togglesOnly: true, // set to false to always make calendar popup on input element, if true, it depends on the toggles elements set.
22
+ showOnInit: false, // overrides the Picker option
23
+ blockKeydown: true
24
+ },
25
+
26
+ initialize: function(attachTo, options){
27
+ this.parent(options);
28
+
29
+ this.attachedEvents = [];
30
+ this.attachedElements = [];
31
+ this.toggles = [];
32
+ this.inputs = [];
33
+
34
+ var documentEvent = function(event){
35
+ if (this.attachedElements.contains(event.target)) return;
36
+ this.close();
37
+ }.bind(this);
38
+ var document = this.picker.getDocument().addEvent('click', documentEvent);
39
+
40
+ var preventPickerClick = function(event){
41
+ event.stopPropagation();
42
+ return false;
43
+ };
44
+ this.picker.addEvent('click', preventPickerClick);
45
+
46
+ // Support for deprecated toggleElements
47
+ if (this.options.toggleElements) this.options.toggle = document.getElements(this.options.toggleElements);
48
+
49
+ this.attach(attachTo, this.options.toggle);
50
+ },
51
+
52
+ attach: function(attachTo, toggle){
53
+ if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo);
54
+ if (typeOf(toggle) == 'string') toggle = document.id(toggle);
55
+
56
+ var elements = Array.from(attachTo),
57
+ toggles = Array.from(toggle),
58
+ allElements = [].append(elements).combine(toggles),
59
+ self = this;
60
+
61
+ var closeEvent = function(event){
62
+ var stopInput = self.options.blockKeydown
63
+ && event.type == 'keydown'
64
+ && !(['tab', 'esc'].contains(event.key)),
65
+ isCloseKey = event.type == 'keydown'
66
+ && (['tab', 'esc'].contains(event.key)),
67
+ isA = event.target.get('tag') == 'a';
68
+
69
+ if (stopInput || isA) event.preventDefault();
70
+ if (isCloseKey || isA) self.close();
71
+ };
72
+
73
+ var getOpenEvent = function(element){
74
+ return function(event){
75
+ var tag = event.target.get('tag');
76
+ if (tag == 'input' && event.type == 'click' && !element.match(':focus') || (self.opened && self.input == element)) return;
77
+ if (tag == 'a') event.stop();
78
+ self.position(element);
79
+ self.open();
80
+ self.fireEvent('attached', [event, element]);
81
+ };
82
+ };
83
+
84
+ var getToggleEvent = function(open, close){
85
+ return function(event){
86
+ if (self.opened) close(event);
87
+ else open(event);
88
+ };
89
+ };
90
+
91
+ allElements.each(function(element){
92
+
93
+ // The events are already attached!
94
+ if (self.attachedElements.contains(element)) return;
95
+
96
+ var events = {},
97
+ tag = element.get('tag'),
98
+ openEvent = getOpenEvent(element),
99
+ // closeEvent does not have a depency on element
100
+ toggleEvent = getToggleEvent(openEvent, closeEvent);
101
+
102
+ if (tag == 'input'){
103
+ // Fix in order to use togglers only
104
+ if (!self.options.togglesOnly || !toggles.length){
105
+ events = {
106
+ focus: openEvent,
107
+ click: openEvent,
108
+ keydown: closeEvent
109
+ };
110
+ }
111
+ self.inputs.push(element);
112
+ } else {
113
+ if (toggles.contains(element)){
114
+ self.toggles.push(element);
115
+ events.click = toggleEvent
116
+ } else {
117
+ events.click = openEvent;
118
+ }
119
+ }
120
+ element.addEvents(events);
121
+ self.attachedElements.push(element);
122
+ self.attachedEvents.push(events);
123
+ });
124
+ return this;
125
+ },
126
+
127
+ detach: function(attachTo, toggle){
128
+ if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo);
129
+ if (typeOf(toggle) == 'string') toggle = document.id(toggle);
130
+
131
+ var elements = Array.from(attachTo),
132
+ toggles = Array.from(toggle),
133
+ allElements = [].append(elements).combine(toggles),
134
+ self = this;
135
+
136
+ if (!allElements.length) allElements = self.attachedElements;
137
+
138
+ allElements.each(function(element){
139
+ var i = self.attachedElements.indexOf(element);
140
+ if (i < 0) return;
141
+
142
+ var events = self.attachedEvents[i];
143
+ element.removeEvents(events);
144
+ delete self.attachedEvents[i];
145
+ delete self.attachedElements[i];
146
+
147
+ var toggleIndex = self.toggles.indexOf(element);
148
+ if (toggleIndex != -1) delete self.toggles[toggleIndex];
149
+
150
+ var inputIndex = self.inputs.indexOf(element);
151
+ if (toggleIndex != -1) delete self.inputs[inputIndex];
152
+ });
153
+ return this;
154
+ },
155
+
156
+ destroy: function(){
157
+ this.detach();
158
+ return this.parent();
159
+ }
160
+
161
+ });
@@ -0,0 +1,668 @@
1
+ /*
2
+ ---
3
+ name: Picker.Date
4
+ description: Creates a DatePicker, can be used for picking years/months/days and time, or all of them
5
+ authors: Arian Stolwijk
6
+ requires: [Picker, Picker.Attach, Locale.en-US.DatePicker, More/Locale, More/Date]
7
+ provides: Picker.Date
8
+ ...
9
+ */
10
+
11
+
12
+ (function(){
13
+
14
+ this.DatePicker = Picker.Date = new Class({
15
+
16
+ Extends: Picker.Attach,
17
+
18
+ options: {/*
19
+ onSelect: function(date){},
20
+
21
+ minDate: new Date('3/4/2010'), // Date object or a string
22
+ maxDate: new Date('3/4/2011'), // same as minDate
23
+ availableDates: {}, //
24
+ invertAvailable: false,
25
+
26
+ format: null,*/
27
+
28
+ timePicker: false,
29
+ timePickerOnly: false, // deprecated, use onlyView = 'time'
30
+ timeWheelStep: 1, // 10,15,20,30
31
+
32
+ yearPicker: true,
33
+ yearsPerPage: 20,
34
+
35
+ startDay: 1, // Sunday (0) through Saturday (6) - be aware that this may affect your layout, since the days on the right might have a different margin
36
+ rtl: false,
37
+
38
+ startView: 'days', // allowed values: {time, days, months, years}
39
+ openLastView: false,
40
+ pickOnly: false, // 'years', 'months', 'days', 'time'
41
+ canAlwaysGoUp: ['months', 'days'],
42
+ updateAll : false, //whether or not to update all inputs when selecting a date
43
+
44
+ weeknumbers: false,
45
+
46
+ // if you like to use your own translations
47
+ months_abbr: null,
48
+ days_abbr: null,
49
+ years_title: function(date, options){
50
+ var year = date.get('year');
51
+ return year + '-' + (year + options.yearsPerPage - 1);
52
+ },
53
+ months_title: function(date, options){
54
+ return date.get('year');
55
+ },
56
+ days_title: function(date, options){
57
+ return date.format('%b %Y');
58
+ },
59
+ time_title: function(date, options){
60
+ return (options.pickOnly == 'time') ? Locale.get('DatePicker.select_a_time') : date.format('%d %B, %Y');
61
+ }
62
+ },
63
+
64
+ initialize: function(attachTo, options){
65
+ this.parent(attachTo, options);
66
+
67
+ this.setOptions(options);
68
+ options = this.options;
69
+
70
+ // If we only want to use one picker / backwards compatibility
71
+ ['year', 'month', 'day', 'time'].some(function(what){
72
+ if (options[what + 'PickerOnly']){
73
+ options.pickOnly = what;
74
+ return true;
75
+ }
76
+ return false;
77
+ });
78
+ if (options.pickOnly){
79
+ options[options.pickOnly + 'Picker'] = true;
80
+ options.startView = options.pickOnly;
81
+ }
82
+
83
+ // backward compatibility for startView
84
+ var newViews = ['days', 'months', 'years'];
85
+ ['month', 'year', 'decades'].some(function(what, i){
86
+ return (options.startView == what) && (options.startView = newViews[i]);
87
+ });
88
+
89
+ options.canAlwaysGoUp = options.canAlwaysGoUp ? Array.from(options.canAlwaysGoUp) : [];
90
+
91
+ // Set the min and max dates as Date objects
92
+ if (options.minDate){
93
+ if (!(options.minDate instanceof Date)) options.minDate = Date.parse(options.minDate);
94
+ options.minDate.clearTime();
95
+ }
96
+ if (options.maxDate){
97
+ if (!(options.maxDate instanceof Date)) options.maxDate = Date.parse(options.maxDate);
98
+ options.maxDate.clearTime();
99
+ }
100
+
101
+ if (!options.format){
102
+ options.format = (options.pickOnly != 'time') ? Locale.get('Date.shortDate') : '';
103
+ if (options.timePicker) options.format = (options.format) + (options.format ? ' ' : '') + Locale.get('Date.shortTime');
104
+ }
105
+
106
+ // Some link or input has fired an event!
107
+ this.addEvent('attached', function(event, element){
108
+
109
+ // This is where we store the selected date
110
+ if (!this.currentView || !options.openLastView) this.currentView = options.startView;
111
+
112
+ this.date = limitDate(new Date(), options.minDate, options.maxDate);
113
+ var tag = element.get('tag'), input;
114
+ if (tag == 'input') input = element;
115
+ else {
116
+ var index = this.toggles.indexOf(element);
117
+ if (this.inputs[index]) input = this.inputs[index];
118
+ }
119
+ this.getInputDate(input);
120
+ this.input = input;
121
+ this.setColumns(this.originalColumns);
122
+ }.bind(this), true);
123
+
124
+ },
125
+
126
+ getInputDate: function(input){
127
+ this.date = new Date();
128
+ if (!input) return;
129
+ var date = Date.parse(input.get('value'));
130
+ if (!date || !date.isValid()){
131
+ var storeDate = input.retrieve('datepicker:value');
132
+ if (storeDate) date = Date.parse(storeDate);
133
+ }
134
+ if (date && date != null && date.isValid()) this.date = date;
135
+ },
136
+
137
+ // Control the previous and next elements
138
+
139
+ constructPicker: function(){
140
+ this.parent();
141
+
142
+ if (!this.options.rtl){
143
+ this.previous = new Element('div.previous[html=&#171;]').inject(this.header);
144
+ this.next = new Element('div.next[html=&#187;]').inject(this.header);
145
+ } else {
146
+ this.next = new Element('div.previous[html=&#171;]').inject(this.header);
147
+ this.previous = new Element('div.next[html=&#187;]').inject(this.header);
148
+ }
149
+ },
150
+
151
+ hidePrevious: function(_next, _show){
152
+ this[_next ? 'next' : 'previous'].setStyle('display', _show ? 'block' : 'none');
153
+ return this;
154
+ },
155
+
156
+ showPrevious: function(_next){
157
+ return this.hidePrevious(_next, true);
158
+ },
159
+
160
+ setPreviousEvent: function(fn, _next){
161
+ this[_next ? 'next' : 'previous'].removeEvents('click');
162
+ if (fn) this[_next ? 'next' : 'previous'].addEvent('click', fn);
163
+ return this;
164
+ },
165
+
166
+ hideNext: function(){
167
+ return this.hidePrevious(true);
168
+ },
169
+
170
+ showNext: function(){
171
+ return this.showPrevious(true);
172
+ },
173
+
174
+ setNextEvent: function(fn){
175
+ return this.setPreviousEvent(fn, true);
176
+ },
177
+
178
+ setColumns: function(columns, view, date, viewFx){
179
+ var ret = this.parent(columns), method;
180
+
181
+ if ((view || this.currentView)
182
+ && (method = 'render' + (view || this.currentView).capitalize())
183
+ && this[method]
184
+ ) this[method](date || this.date.clone(), viewFx);
185
+
186
+ return ret;
187
+ },
188
+
189
+ // Render the Pickers
190
+
191
+ renderYears: function(date, fx){
192
+ var options = this.options, pages = options.columns, perPage = options.yearsPerPage,
193
+ _columns = [], _dates = [];
194
+ this.dateElements = [];
195
+
196
+ // start neatly at interval (eg. 1980 instead of 1987)
197
+ date = date.clone().decrement('year', date.get('year') % perPage);
198
+
199
+ var iterateDate = date.clone().decrement('year', Math.floor((pages - 1) / 2) * perPage);
200
+
201
+ for (var i = pages; i--;){
202
+ var _date = iterateDate.clone();
203
+ _dates.push(_date);
204
+ _columns.push(renderers.years(
205
+ timesSelectors.years(options, _date.clone()),
206
+ options,
207
+ this.date.clone(),
208
+ this.dateElements,
209
+ function(date){
210
+ if (options.pickOnly == 'years') this.select(date);
211
+ else this.renderMonths(date, 'fade');
212
+ this.date = date;
213
+ }.bind(this)
214
+ ));
215
+ iterateDate.increment('year', perPage);
216
+ }
217
+
218
+ this.setColumnsContent(_columns, fx);
219
+ this.setTitle(_dates, options.years_title);
220
+
221
+ // Set limits
222
+ var limitLeft = (options.minDate && date.get('year') <= options.minDate.get('year')),
223
+ limitRight = (options.maxDate && (date.get('year') + options.yearsPerPage) >= options.maxDate.get('year'));
224
+ this[(limitLeft ? 'hide' : 'show') + 'Previous']();
225
+ this[(limitRight ? 'hide' : 'show') + 'Next']();
226
+
227
+ this.setPreviousEvent(function(){
228
+ this.renderYears(date.decrement('year', perPage), 'left');
229
+ }.bind(this));
230
+
231
+ this.setNextEvent(function(){
232
+ this.renderYears(date.increment('year', perPage), 'right');
233
+ }.bind(this));
234
+
235
+ // We can't go up!
236
+ this.setTitleEvent(null);
237
+
238
+ this.currentView = 'years';
239
+ },
240
+
241
+ renderMonths: function(date, fx){
242
+ var options = this.options, years = options.columns, _columns = [], _dates = [],
243
+ iterateDate = date.clone().decrement('year', Math.floor((years - 1) / 2));
244
+ this.dateElements = [];
245
+
246
+ for (var i = years; i--;){
247
+ var _date = iterateDate.clone();
248
+ _dates.push(_date);
249
+ _columns.push(renderers.months(
250
+ timesSelectors.months(options, _date.clone()),
251
+ options,
252
+ this.date.clone(),
253
+ this.dateElements,
254
+ function(date){
255
+ if (options.pickOnly == 'months') this.select(date);
256
+ else this.renderDays(date, 'fade');
257
+ this.date = date;
258
+ }.bind(this)
259
+ ));
260
+ iterateDate.increment('year', 1);
261
+ }
262
+
263
+ this.setColumnsContent(_columns, fx);
264
+ this.setTitle(_dates, options.months_title);
265
+
266
+ // Set limits
267
+ var year = date.get('year'),
268
+ limitLeft = (options.minDate && year <= options.minDate.get('year')),
269
+ limitRight = (options.maxDate && year >= options.maxDate.get('year'));
270
+ this[(limitLeft ? 'hide' : 'show') + 'Previous']();
271
+ this[(limitRight ? 'hide' : 'show') + 'Next']();
272
+
273
+ this.setPreviousEvent(function(){
274
+ this.renderMonths(date.decrement('year', years), 'left');
275
+ }.bind(this));
276
+
277
+ this.setNextEvent(function(){
278
+ this.renderMonths(date.increment('year', years), 'right');
279
+ }.bind(this));
280
+
281
+ var canGoUp = options.yearPicker && (options.pickOnly != 'months' || options.canAlwaysGoUp.contains('months'));
282
+ var titleEvent = (canGoUp) ? function(){
283
+ this.renderYears(date, 'fade');
284
+ }.bind(this) : null;
285
+ this.setTitleEvent(titleEvent);
286
+
287
+ this.currentView = 'months';
288
+ },
289
+
290
+ renderDays: function(date, fx){
291
+ var options = this.options, months = options.columns, _columns = [], _dates = [],
292
+ iterateDate = date.clone().decrement('month', Math.floor((months - 1) / 2));
293
+ this.dateElements = [];
294
+
295
+ for (var i = months; i--;){
296
+ _date = iterateDate.clone();
297
+ _dates.push(_date);
298
+ _columns.push(renderers.days(
299
+ timesSelectors.days(options, _date.clone()),
300
+ options,
301
+ this.date.clone(),
302
+ this.dateElements,
303
+ function(date){
304
+ if (options.pickOnly == 'days' || !options.timePicker) this.select(date)
305
+ else this.renderTime(date, 'fade');
306
+ this.date = date;
307
+ }.bind(this)
308
+ ));
309
+ iterateDate.increment('month', 1);
310
+ }
311
+
312
+ this.setColumnsContent(_columns, fx);
313
+ this.setTitle(_dates, options.days_title);
314
+
315
+ var yearmonth = date.format('%Y%m').toInt(),
316
+ limitLeft = (options.minDate && yearmonth <= options.minDate.format('%Y%m')),
317
+ limitRight = (options.maxDate && yearmonth >= options.maxDate.format('%Y%m'));
318
+ this[(limitLeft ? 'hide' : 'show') + 'Previous']();
319
+ this[(limitRight ? 'hide' : 'show') + 'Next']();
320
+
321
+ this.setPreviousEvent(function(){
322
+ this.renderDays(date.decrement('month', months), 'left');
323
+ }.bind(this));
324
+
325
+ this.setNextEvent(function(){
326
+ this.renderDays(date.increment('month', months), 'right');
327
+ }.bind(this));
328
+
329
+ var canGoUp = options.pickOnly != 'days' || options.canAlwaysGoUp.contains('days');
330
+ var titleEvent = (canGoUp) ? function(){
331
+ this.renderMonths(date, 'fade');
332
+ }.bind(this) : null;
333
+ this.setTitleEvent(titleEvent);
334
+
335
+ this.currentView = 'days';
336
+ },
337
+
338
+ renderTime: function(date, fx){
339
+ var options = this.options;
340
+ this.setTitle(date, options.time_title);
341
+
342
+ var originalColumns = this.originalColumns = options.columns;
343
+ this.currentView = null; // otherwise you'd get crazy recursion
344
+ if (originalColumns != 1) this.setColumns(1);
345
+
346
+ this.setContent(renderers.time(
347
+ options,
348
+ date.clone(),
349
+ function(date){
350
+ this.select(date);
351
+ }.bind(this)
352
+ ), fx);
353
+
354
+ // Hide « and » buttons
355
+ this.hidePrevious()
356
+ .hideNext()
357
+ .setPreviousEvent(null)
358
+ .setNextEvent(null);
359
+
360
+ var canGoUp = options.pickOnly != 'time' || options.canAlwaysGoUp.contains('time');
361
+ var titleEvent = (canGoUp) ? function(){
362
+ this.setColumns(originalColumns, 'days', date, 'fade');
363
+ }.bind(this) : null;
364
+ this.setTitleEvent(titleEvent);
365
+
366
+ this.currentView = 'time';
367
+ },
368
+
369
+ select: function(date, all){
370
+ this.date = date;
371
+ var formatted = date.format(this.options.format),
372
+ time = date.strftime(),
373
+ inputs = (!this.options.updateAll && !all && this.input) ? [this.input] : this.inputs;
374
+
375
+ inputs.each(function(input){
376
+ input.set('value', formatted).store('datepicker:value', time).fireEvent('change');
377
+ }, this);
378
+
379
+ this.fireEvent('select', [date].concat(inputs));
380
+ this.close();
381
+ return this;
382
+ }
383
+
384
+ });
385
+
386
+
387
+ // Renderers only output elements and calculate the limits!
388
+
389
+ var timesSelectors = {
390
+
391
+ years: function(options, date){
392
+ var times = [];
393
+ for (var i = 0; i < options.yearsPerPage; i++){
394
+ times.push(+date);
395
+ date.increment('year', 1);
396
+ }
397
+ return times;
398
+ },
399
+
400
+ months: function(options, date){
401
+ var times = [];
402
+ date.set('month', 0);
403
+ for (var i = 0; i <= 11; i++){
404
+ times.push(+date);
405
+ date.increment('month', 1);
406
+ }
407
+ return times;
408
+ },
409
+
410
+ days: function(options, date){
411
+ var times = [];
412
+ date.set('date', 1);
413
+ while (date.get('day') != options.startDay) date.set('date', date.get('date') - 1);
414
+ for (var i = 0; i < 42; i++){
415
+ times.push(+date);
416
+ date.increment('day', 1);
417
+ }
418
+ return times;
419
+ }
420
+
421
+ };
422
+
423
+ var renderers = {
424
+
425
+ years: function(years, options, currentDate, dateElements, fn){
426
+ var container = new Element('div.years'),
427
+ today = new Date(), element, classes;
428
+
429
+ years.each(function(_year, i){
430
+ var date = new Date(_year), year = date.get('year');
431
+
432
+ classes = '.year.year' + i;
433
+ if (year == today.get('year')) classes += '.today';
434
+ if (year == currentDate.get('year')) classes += '.selected';
435
+ element = new Element('div' + classes, {text: year}).inject(container);
436
+
437
+ dateElements.push({element: element, time: _year});
438
+
439
+ if (isUnavailable('year', date, options)) element.addClass('unavailable');
440
+ else element.addEvent('click', fn.pass(date));
441
+ });
442
+
443
+ return container;
444
+ },
445
+
446
+ months: function(months, options, currentDate, dateElements, fn){
447
+ var today = new Date(),
448
+ month = today.get('month'),
449
+ thisyear = today.get('year'),
450
+ selectedyear = currentDate.get('year'),
451
+ container = new Element('div.months'),
452
+ monthsAbbr = options.months_abbr || Locale.get('Date.months_abbr'),
453
+ element, classes;
454
+
455
+ months.each(function(_month, i){
456
+ var date = new Date(_month), year = date.get('year');
457
+
458
+ classes = '.month.month' + (i + 1);
459
+ if (i == month && year == thisyear) classes += '.today';
460
+ if (i == currentDate.get('month') && year == selectedyear) classes += '.selected';
461
+ element = new Element('div' + classes, {text: monthsAbbr[i]}).inject(container);
462
+
463
+ dateElements.push({element: element, time: _month});
464
+
465
+ if (isUnavailable('month', date, options)) element.addClass('unavailable');
466
+ else element.addEvent('click', fn.pass(date));
467
+ });
468
+
469
+ return container;
470
+ },
471
+
472
+ days: function(days, options, currentDate, dateElements, fn){
473
+ var month = new Date(days[14]).get('month'),
474
+ todayString = new Date().toDateString(),
475
+ currentString = currentDate.toDateString(),
476
+ weeknumbers = options.weeknumbers,
477
+ container = new Element('table.days' + (weeknumbers ? '.weeknumbers' : ''), {
478
+ role: 'grid', 'aria-labelledby': this.titleID
479
+ }),
480
+ header = new Element('thead').inject(container),
481
+ body = new Element('tbody').inject(container),
482
+ titles = new Element('tr.titles').inject(header),
483
+ localeDaysShort = options.days_abbr || Locale.get('Date.days_abbr'),
484
+ day, classes, element, weekcontainer, dateString,
485
+ where = options.rtl ? 'top' : 'bottom';
486
+
487
+ if (weeknumbers) new Element('th.title.day.weeknumber', {
488
+ text: Locale.get('DatePicker.week')
489
+ }).inject(titles);
490
+
491
+ for (day = options.startDay; day < (options.startDay + 7); day++){
492
+ new Element('th.title.day.day' + (day % 7), {
493
+ text: localeDaysShort[(day % 7)],
494
+ role: 'columnheader'
495
+ }).inject(titles, where);
496
+ }
497
+
498
+ days.each(function(_date, i){
499
+ var date = new Date(_date);
500
+
501
+ if (i % 7 == 0){
502
+ weekcontainer = new Element('tr.week.week' + (Math.floor(i / 7))).set('role', 'row').inject(body);
503
+ if (weeknumbers) new Element('th.day.weeknumber', {text: date.get('week'), scope: 'row', role: 'rowheader'}).inject(weekcontainer);
504
+ }
505
+
506
+ dateString = date.toDateString();
507
+ classes = '.day.day' + date.get('day');
508
+ if (dateString == todayString) classes += '.today';
509
+ if (date.get('month') != month) classes += '.otherMonth';
510
+ element = new Element('td' + classes, {text: date.getDate(), role: 'gridcell'}).inject(weekcontainer, where);
511
+
512
+ if (dateString == currentString) element.addClass('selected').set('aria-selected', 'true');
513
+ else element.set('aria-selected', 'false');
514
+
515
+ dateElements.push({element: element, time: _date});
516
+
517
+ if (isUnavailable('date', date, options)) element.addClass('unavailable');
518
+ else element.addEvent('click', fn.pass(date.clone()));
519
+ });
520
+
521
+ return container;
522
+ },
523
+
524
+ time: function(options, date, fn){
525
+ var container = new Element('div.time'),
526
+ // make sure that the minutes are timeWheelStep * k
527
+ initMinutes = (date.get('minutes') / options.timeWheelStep).round() * options.timeWheelStep
528
+
529
+ if (initMinutes >= 60) initMinutes = 0;
530
+ date.set('minutes', initMinutes);
531
+
532
+ var hoursInput = new Element('input.hour[type=text]', {
533
+ title: Locale.get('DatePicker.use_mouse_wheel'),
534
+ value: date.format('%H'),
535
+ events: {
536
+ click: function(event){
537
+ event.target.focus();
538
+ event.stop();
539
+ },
540
+ mousewheel: function(event){
541
+ event.stop();
542
+ hoursInput.focus();
543
+ var value = hoursInput.get('value').toInt();
544
+ value = (event.wheel > 0) ? ((value < 23) ? value + 1 : 0)
545
+ : ((value > 0) ? value - 1 : 23)
546
+ date.set('hours', value);
547
+ hoursInput.set('value', date.format('%H'));
548
+ }.bind(this)
549
+ },
550
+ maxlength: 2
551
+ }).inject(container);
552
+
553
+ var minutesInput = new Element('input.minutes[type=text]', {
554
+ title: Locale.get('DatePicker.use_mouse_wheel'),
555
+ value: date.format('%M'),
556
+ events: {
557
+ click: function(event){
558
+ event.target.focus();
559
+ event.stop();
560
+ },
561
+ mousewheel: function(event){
562
+ event.stop();
563
+ minutesInput.focus();
564
+ var value = minutesInput.get('value').toInt();
565
+ value = (event.wheel > 0) ? ((value < 59) ? (value + options.timeWheelStep) : 0)
566
+ : ((value > 0) ? (value - options.timeWheelStep) : (60 - options.timeWheelStep));
567
+ if (value >= 60) value = 0;
568
+ date.set('minutes', value);
569
+ minutesInput.set('value', date.format('%M'));
570
+ }.bind(this)
571
+ },
572
+ maxlength: 2
573
+ }).inject(container);
574
+
575
+ new Element('div.separator[text=:]').inject(container);
576
+
577
+ new Element('input.ok[type=submit]', {
578
+ value: Locale.get('DatePicker.time_confirm_button'),
579
+ events: {click: function(event){
580
+ event.stop();
581
+ date.set({
582
+ hours: hoursInput.get('value').toInt(),
583
+ minutes: minutesInput.get('value').toInt()
584
+ });
585
+ fn(date.clone());
586
+ }}
587
+ }).inject(container);
588
+
589
+ return container;
590
+ }
591
+
592
+ };
593
+
594
+
595
+ Picker.Date.defineRenderer = function(name, fn){
596
+ renderers[name] = fn;
597
+ return this;
598
+ };
599
+
600
+ var limitDate = function(date, min, max){
601
+ if (min && date < min) return min;
602
+ if (max && date > max) return max;
603
+ return date;
604
+ };
605
+
606
+ var isUnavailable = function(type, date, options){
607
+ var minDate = options.minDate,
608
+ maxDate = options.maxDate,
609
+ availableDates = options.availableDates,
610
+ year, month, day, ms;
611
+
612
+ if (!minDate && !maxDate && !availableDates) return false;
613
+ date.clearTime();
614
+
615
+ if (type == 'year'){
616
+ year = date.get('year');
617
+ return (
618
+ (minDate && year < minDate.get('year')) ||
619
+ (maxDate && year > maxDate.get('year')) ||
620
+ (
621
+ (availableDates != null && !options.invertAvailable) && (
622
+ availableDates[year] == null ||
623
+ Object.getLength(availableDates[year]) == 0 ||
624
+ Object.getLength(
625
+ Object.filter(availableDates[year], function(days){
626
+ return (days.length > 0);
627
+ })
628
+ ) == 0
629
+ )
630
+ )
631
+ );
632
+ }
633
+
634
+ if (type == 'month'){
635
+ year = date.get('year');
636
+ month = date.get('month') + 1;
637
+ ms = date.format('%Y%m').toInt();
638
+ return (
639
+ (minDate && ms < minDate.format('%Y%m').toInt()) ||
640
+ (maxDate && ms > maxDate.format('%Y%m').toInt()) ||
641
+ (
642
+ (availableDates != null && !options.invertAvailable) && (
643
+ availableDates[year] == null ||
644
+ availableDates[year][month] == null ||
645
+ availableDates[year][month].length == 0
646
+ )
647
+ )
648
+ );
649
+ }
650
+
651
+ // type == 'date'
652
+ year = date.get('year');
653
+ month = date.get('month') + 1;
654
+ day = date.get('date');
655
+
656
+ var dateAllow = (minDate && date < minDate) || (maxDate && date > maxDate);
657
+ if (availableDates != null){
658
+ dateAllow = dateAllow
659
+ || availableDates[year] == null
660
+ || availableDates[year][month] == null
661
+ || !availableDates[year][month].contains(day);
662
+ if (options.invertAvailable) dateAllow = !dateAllow;
663
+ }
664
+
665
+ return dateAllow;
666
+ };
667
+
668
+ })();