PageSourceSearch

https://bgsocial.com/application/modules/Ynevent/externals/scripts/yncalendar.js?c=21838

js bgsocial.com collected 2026-10-07 20:34:50 UTC 33,526 bytes, 1,199 lines download raw bytes

1/*
2---
3name: Picker
4description: Creates a Picker, which can be used for anything
5authors: Arian Stolwijk
6requires: [Core/Element.Dimensions, Core/Fx.Tween, Core/Fx.Transitions]
7provides: Picker
8...
9*/
10
11
12var Picker = new Class({
13
14	Implements: [Options, Events],
15
16	options: {
17		pickerClass: 'datepicker',
18		inject: null,
19		animationDuration: 400,
20		useFadeInOut: true,
21		positionOffset: {x: 0, y: 0},
22		pickerPosition: 'bottom',
23		
24		draggable: true,
25		showOnInit: true,
26		columns: 1,
27		footer: false
28	},
29
30	initialize: function(options){
31		this.setOptions(options);
32		this.constructPicker();
33		if (this.options.showOnInit) this.show();
34	},
35
36	constructPicker: function(){
37		var options = this.options;
38
39		var picker = this.picker = new Element('div', {
40			'class': options.pickerClass,
41			styles: {
42				left: 0,
43				top: 0,
44				display: 'none',
45				opacity: 0
46			}
47		}).inject(options.inject || document.body);
48		picker.addClass('column_' + options.columns);
49
50		if (options.useFadeInOut){
51			picker.set('tween', {
52				duration: options.animationDuration,
53				link: 'cancel'
54			});
55		}
56
57		// Build the header
58		var header = this.header = new Element('div.header').inject(picker);
59
60		var title = this.title = new Element('div.title').inject(header);
61		var titleID = this.titleID = 'pickertitle-' + String.uniqueID();
62		this.titleText = new Element('div', {
63			'role': 'heading',
64			'class': 'titleText',
65			'id': titleID,
66			'aria-live': 'assertive',
67			'aria-atomic': 'true'
68		}).inject(title);
69
70		this.closeButton = new Element('div.closeButton[text=x][role=button]')
71			.addEvent('click', this.close.pass(false, this))
72			.inject(header);
73
74		// Build the body of the picker
75		var body = this.body = new Element('div.body').inject(picker);
76
77		if (options.footer){
78			this.footer = new Element('div.footer').inject(picker);
79			picker.addClass('footer');
80		}
81
82		// oldContents and newContents are used to slide from the old content to a new one.
83		var slider = this.slider = new Element('div.slider', {
84			styles: {
85				position: 'absolute',
86				top: 0,
87				left: 0
88			}
89		}).set('tween', {
90			duration: options.animationDuration,
91			transition: Fx.Transitions.Quad.easeInOut
92		}).inject(body);
93
94		this.newContents = new Element('div', {
95			styles: {
96				position: 'absolute',
97				top: 0,
98				left: 0
99			}
100		}).inject(slider);
101
102		this.oldContents = new Element('div', {
103			styles: {
104				position: 'absolute',
105				top: 0
106			}
107		}).inject(slider);
108
109		this.originalColumns = options.columns;
110		this.setColumns(options.columns);
111
112		// IFrameShim for select fields in IE
113		var shim = this.shim = window['IframeShim'] ? new IframeShim(picker) : null;
114
115		// Dragging
116		if (options.draggable && typeOf(picker.makeDraggable) == 'function'){
117			this.dragger = picker.makeDraggable(shim ? {
118				onDrag: shim.position.bind(shim)
119			} : null);
120			picker.setStyle('cursor', 'move');
121		}
122	},
123
124	open: function(noFx){
125		if (this.opened == true) return this;
126		this.opened = true;
127		var picker = this.picker.setStyle('display', 'block').set('aria-hidden', 'false')
128		if (this.shim) this.shim.show();
129		this.fireEvent('open');
130		if (this.options.useFadeInOut && !noFx){
131			picker.fade('in').get('tween').chain(this.fireEvent.pass('show', this));
132		} else {
133			picker.setStyle('opacity', 1);
134			this.fireEvent('show');
135		}
136		return this;
137	},
138
139	show: function(){
140		return this.open(true);
141	},
142
143	close: function(noFx){
144		if (this.opened == false) return this;
145		this.opened = false;
146		this.fireEvent('close');
147		var self = this, picker = this.picker, hide = function(){
148			picker.setStyle('display', 'none').set('aria-hidden', 'true');
149			if (self.shim) self.shim.hide();
150			self.fireEvent('hide');
151		};
152		if (this.options.useFadeInOut && !noFx){
153			picker.fade('out').get('tween').chain(hide);
154		} else {
155			picker.setStyle('opacity', 0);
156			hide();
157		}
158		return this;
159	},
160
161	hide: function(){
162		return this.close(true);
163	},
164
165	toggle: function(){
166		return this[this.opened == true ? 'close' : 'open']();
167	},
168
169	destroy: function(){
170		this.picker.destroy();
171		if (this.shim) this.shim.destroy();
172	},
173
174	position: function(x, y){
175		var offset = this.options.positionOffset,
176			scroll = document.getScroll(),
177			size = document.getSize(),
178			pickersize = this.picker.getSize();
179
180		if (typeOf(x) == 'element'){
181			var element = x,
182				where = y || this.options.pickerPosition;
183
184			var elementCoords = element.getCoordinates();
185
186			x = (where == 'left') ? elementCoords.left - pickersize.x
187				: (where == 'bottom' || where == 'top') ? elementCoords.left
188				: elementCoords.right
189			y = (where == 'bottom') ? elementCoords.bottom
190				: (where == 'top') ? elementCoords.top - pickersize.y
191				: elementCoords.top;
192		}
193
194		x += offset.x * ((where && where == 'left') ? -1 : 1);
195		y += offset.y * ((where && where == 'top') ? -1: 1);
196
197		if ((x + pickersize.x) > (size.x + scroll.x)) x = (size.x + scroll.x) - pickersize.x;
198		if ((y + pickersize.y) > (size.y + scroll.y)) y = (size.y + scroll.y) - pickersize.y;
199		if (x < 0) x = 0;
200		if (y < 0) y = 0;
201		x = x - 124;
202		this.picker.setStyles({
203			left: x,
204			top: y
205		});
206		if (this.shim) this.shim.position();
207		return this;
208	},
209
210	setBodySize: function(){
211		var bodysize = this.bodysize = this.body.getSize();
212
213		this.slider.setStyles({
214			width: 2 * bodysize.x,
215			height: bodysize.y
216		});
217		this.oldContents.setStyles({
218			left: bodysize.x,
219			width: bodysize.x,
220			height: bodysize.y
221		});
222		this.newContents.setStyles({
223			width: bodysize.x,
224			height: bodysize.y
225		});
226	},
227
228	setColumnContent: function(column, content){
229		var columnElement = this.columns[column];
230		if (!columnElement) return this;
231
232		var type = typeOf(content);
233		if (['string', 'number'].contains(type)) columnElement.set('text', content);
234		else columnElement.empty().adopt(content);
235
236		return this;
237	},
238
239	setColumnsContent: function(content, fx){
240		var old = this.columns;
241		this.columns = this.newColumns;
242		this.newColumns = old;
243
244		content.forEach(function(_content, i){
245			this.setColumnContent(i, _content);
246		}, this);
247		return this.setContent(null, fx);
248	},
249
250	setColumns: function(columns){
251		var _columns = this.columns = new Elements, _newColumns = this.newColumns = new Elements;
252		for (var i = columns; i--;){
253			_columns.push(new Element('div.column').addClass('column_' + (columns - i)));
254			_newColumns.push(new Element('div.column').addClass('column_' + (columns - i)));
255		}
256
257		var oldClass = 'column_' + this.options.columns, newClass = 'column_' + columns;
258		this.picker.removeClass(oldClass).addClass(newClass);
259
260		this.options.columns = columns;
261		return this;
262	},
263
264	setContent: function(content, fx){
265		if (content) return this.setColumnsContent([content], fx);
266
267		// swap contents so we can fill the newContents again and animate
268		var old = this.oldContents;
269		this.oldContents = this.newContents;
270		this.newContents = old;
271		this.newContents.empty();
272
273		this.newContents.adopt(this.columns);
274
275		this.setBodySize();
276
277		if (fx){
278			this.fx(fx);
279		} else {
280			this.slider.setStyle('left', 0);
281			this.oldContents.setStyles({left: 0, opacity: 0});
282			this.newContents.setStyles({left: 0, opacity: 1});
283		}
284		return this;
285	},
286
287	fx: function(fx){
288		var oldContents = this.oldContents,
289			newContents = this.newContents,
290			slider = this.slider,
291			bodysize = this.bodysize;
292		if (fx == 'right'){
293			oldContents.setStyles({left: 0, opacity: 1});
294			newContents.setStyles({left: bodysize.x, opacity: 1});
295			slider.setStyle('left', 0).tween('left', 0, -bodysize.x);
296		} else if (fx == 'left'){
297			oldContents.setStyles({left: bodysize.x, opacity: 1});
298			newContents.setStyles({left: 0, opacity: 1});
299			slider.setStyle('left', -bodysize.x).tween('left', -bodysize.x, 0);
300		} else if (fx == 'fade'){
301			slider.setStyle('left', 0);
302			oldContents.setStyle('left', 0).set('tween', {
303				duration: this.options.animationDuration / 2
304			}).tween('opacity', 1, 0).get('tween').chain(function(){
305				oldContents.setStyle('left', bodysize.x);
306			});
307			newContents.setStyles({opacity: 0, left: 0}).set('tween', {
308				duration: this.options.animationDuration
309			}).tween('opacity', 0, 1);
310		}
311	},
312
313	toElement: function(){
314		return this.picker;
315	},
316
317	setTitle: function(content, fn){
318		if (!fn) fn = Function.from;
319		this.titleText.empty().adopt(
320			Array.from(content).map(function(item, i){
321				return typeOf(item) == 'element'
322					? item
323					: new Element('div.column', {text: fn(item, this.options)}).addClass('column_' + (i + 1));
324			}, this)
325		);
326		return this;
327	},
328
329	setTitleEvent: function(fn){
330		this.titleText.removeEvents('click');
331		if (fn) this.titleText.addEvent('click', fn);
332		this.titleText.setStyle('cursor', fn ? 'pointer' : '');
333		return this;
334	}
335
336});
337
338
339/*
340---
341name: Picker.Attach
342description: Adds attach and detach methods to the Picker, to attach it to element events
343authors: Arian Stolwijk
344requires: [Picker, Core/Element.Event]
345provides: Picker.Attach
346...
347*/
348
349
350Picker.Attach = new Class({
351
352	Extends: Picker,
353
354	options: {/*
355		onAttached: function(event){},
356
357		toggleElements: null, // deprecated
358		toggle: null, // When set it deactivate toggling by clicking on the input */
359		togglesOnly: true, // set to false to always make calendar popup on input element, if true, it depends on the toggles elements set.
360		showOnInit: false, // overrides the Picker option
361		blockKeydown: true
362	},
363
364	initialize: function(attachTo, options){
365		this.parent(options);
366
367		this.attachedEvents = [];
368		this.attachedElements = [];
369		this.toggles = [];
370		this.inputs = [];
371
372		var documentEvent = function(event){
373			if (this.attachedElements.contains(event.target)) return;
374			this.close();
375		}.bind(this);
376		var document = this.picker.getDocument().addEvent('click', documentEvent);
377
378		var preventPickerClick = function(event){
379			event.stopPropagation();
380			return false;
381		};
382		this.picker.addEvent('click', preventPickerClick);
383
384		// Support for deprecated toggleElements
385		if (this.options.toggleElements) this.options.toggle = document.getElements(this.options.toggleElements);
386
387		this.attach(attachTo, this.options.toggle);
388	},
389
390	attach: function(attachTo, toggle){
391		if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo);
392		if (typeOf(toggle) == 'string') toggle = document.id(toggle);
393
394		var elements = Array.from(attachTo),
395			toggles = Array.from(toggle),
396			allElements = [].append(elements).combine(toggles),
397			self = this;
398
399		var closeEvent = function(event){
400			var stopInput = self.options.blockKeydown
401					&& event.type == 'keydown'
402					&& !(['tab', 'esc'].contains(event.key)),
403				isCloseKey = event.type == 'keydown'
404					&& (['tab', 'esc'].contains(event.key)),
405				isA = event.target.get('tag') == 'a';
406
407			if (stopInput || isA) event.preventDefault();
408			if (isCloseKey || isA) self.close();
409		};
410
411		var getOpenEvent = function(element){
412			return function(event){
413				var tag = event.target.get('tag');
414				if (tag == 'input' && event.type == 'click' && !element.match(':focus') || (self.opened && self.input == element)) return;
415				if (tag == 'a') event.stop();
416				self.position(element);
417				self.open();
418				self.fireEvent('attached', [event, element]);
419			};
420		};
421
422		var getToggleEvent = function(open, close){
423			return function(event){
424				if (self.opened) close(event);
425				else open(event);
426			};
427		};
428
429		allElements.each(function(element){
430
431			// The events are already attached!
432			if (self.attachedElements.contains(element)) return;
433
434			var events = {},
435				tag = element.get('tag'),
436				openEvent = getOpenEvent(element),
437				// closeEvent does not have a depency on element
438				toggleEvent = getToggleEvent(openEvent, closeEvent);
439
440			if (tag == 'input'){
441				// Fix in order to use togglers only
442				if (!self.options.togglesOnly || !toggles.length){
443					events = {
444						focus: openEvent,
445						click: openEvent,
446						keydown: closeEvent
447					};
448				}
449				self.inputs.push(element);
450			} else {
451				if (toggles.contains(element)){
452					self.toggles.push(element);
453					events.click = toggleEvent
454				} else {
455					events.click = openEvent;
456				}
457			}
458			element.addEvents(events);
459			self.attachedElements.push(element);
460			self.attachedEvents.push(events);
461		});
462		return this;
463	},
464
465	detach: function(attachTo, toggle){
466		if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo);
467		if (typeOf(toggle) == 'string') toggle = document.id(toggle);
468
469		var elements = Array.from(attachTo),
470			toggles = Array.from(toggle),
471			allElements = [].append(elements).combine(toggles),
472			self = this;
473
474		if (!allElements.length) allElements = self.attachedElements;
475
476		allElements.each(function(element){
477			var i = self.attachedElements.indexOf(element);
478			if (i < 0) return;
479
480			var events = self.attachedEvents[i];
481			element.removeEvents(events);
482			delete self.attachedEvents[i];
483			delete self.attachedElements[i];
484
485			var toggleIndex = self.toggles.indexOf(element);
486			if (toggleIndex != -1) delete self.toggles[toggleIndex];
487
488			var inputIndex = self.inputs.indexOf(element);
489			if (toggleIndex != -1) delete self.inputs[inputIndex];
490		});
491		return this;
492	},
493
494	destroy: function(){
495		this.detach();
496		return this.parent();
497	}
498
499});
500
501/*
502---
503name: Picker.Date
504description: Creates a DatePicker, can be used for picking years/months/days and time, or all of them
505authors: Arian Stolwijk
506requires: [Picker, Picker.Attach, Locale.en-US.DatePicker, More/Locale, More/Date]
507provides: Picker.Date
508...
509*/
510
511String.prototype.leftpad = function(padString, length) {
512	var str = this;
513    while (str.length < length)
514        str = padString + str;
515    return str;
516}
517
518function getShortDateString(d){
519	return d.getFullYear() + '-'+(d.getMonth()+1) +'-'+d.getDate();
520}
521
522(function(){
523
524this.DatePicker = Picker.Date = new Class({
525
526	Extends: Picker.Attach,
527
528	options: {/*
529		onSelect: function(date){},
530
531		minDate: new Date('3/4/2010'), // Date object or a string
532		maxDate: new Date('3/4/2011'), // same as minDate
533		availableDates: {}, //
534		invertAvailable: false,
535		*/
536		format: '%m-%d-%Y',
537
538		timePicker: false,
539		timePickerOnly: false, // deprecated, use onlyView = 'time'
540		timeWheelStep: 1, // 10,15,20,30
541
542		yearPicker: true,
543		yearsPerPage: 20,
544		fnAfterRender:false,
545
546		startDay: 0, // Sunday (0) through Saturday (6) - be aware that this may affect your layout, since the days on the right might have a different margin
547		rtl: (en4.orientation == 'rtl'),
548
549		startView: 'days', // allowed values: {time, days, months, years}
550		openLastView: false,
551		pickOnly: false, // 'years', 'months', 'days', 'time'
552		canAlwaysGoUp: ['months', 'days'],
553		updateAll : false, //whether or not to update all inputs when selecting a date
554
555		weeknumbers: false,
556
557		// if you like to use your own translations
558		months_abbr: null,
559		days_abbr: null,
560		years_title: function(date, options){
561			var year = date.get('year');
562			return year + '-' + (year + options.yearsPerPage - 1);
563		},
564		months_title: function(date, options){
565			return date.get('year');
566		},
567		days_title: function(date, options){
568			return date.format('%b %Y');
569		},
570		time_title: function(date, options){
571			return (options.pickOnly == 'time') ? en4.core.language.translate('select a time') : date.format('%d %B, %Y');
572		}
573	},
574
575	initialize: function(attachTo, options){
576		this.parent(attachTo, options);
577
578		this.setOptions(options);
579		options = this.options;		
580		// transt
581	
582		this.options.days_abbr = [
583		en4.core.language.translate('Sun'),
584		en4.core.language.translate('Mon'),
585		en4.core.language.translate('Tue'),
586		en4.core.language.translate('Wed'),
587		en4.core.language.translate('Thu'),
588		en4.core.language.translate('Fri'),
589		en4.core.language.translate('Sat')];
590
591		// If we only want to use one picker / backwards compatibility
592		['year', 'month', 'day', 'time'].some(function(what){
593			if (options[what + 'PickerOnly']){
594				options.pickOnly = what;
595				return true;
596			}
597			return false;
598		});
599		if (options.pickOnly){
600			options[options.pickOnly + 'Picker'] = true;
601			options.startView = options.pickOnly;
602		}
603
604		// backward compatibility for startView
605		var newViews = ['days', 'months', 'years'];
606		['month', 'year', 'decades'].some(function(what, i){
607			return (options.startView == what) && (options.startView = newViews[i]);
608		});
609
610		options.canAlwaysGoUp = options.canAlwaysGoUp ? Array.from(options.canAlwaysGoUp) : [];
611
612		// Set the min and max dates as Date objects
613		if (options.minDate){
614			if (!(options.minDate instanceof Date)) options.minDate = Date.parse(options.minDate);
615			options.minDate.clearTime();
616		}
617		if (options.maxDate){
618			if (!(options.maxDate instanceof Date)) options.maxDate = Date.parse(options.maxDate);
619			options.maxDate.clearTime();
620		}
621
622		if (!options.format){
623			options.format = (options.pickOnly != 'time') ? Locale.get('Date.shortDate') : '';
624			if (options.timePicker) options.format = (options.format) + (options.format ? ' ' : '') + Locale.get('Date.shortTime');
625		}
626		
627
628		// Some link or input has fired an event!
629		this.addEvent('attached', function(event, element){
630
631			// This is where we store the selected date
632			if (!this.currentView || !options.openLastView) this.currentView = options.startView;
633
634			this.date = limitDate(new Date(), options.minDate, options.maxDate);
635			var tag = element.get('tag'), input;
636			if (tag == 'input') input = element;
637			else {
638				var index = this.toggles.indexOf(element);
639				if (this.inputs[index]) input = this.inputs[index];
640			}
641			this.getInputDate(input);
642			this.input = input;
643			this.setColumns(this.originalColumns);
644		}.bind(this), true);
645
646	},
647
648	getInputDate: function(input){
649		this.date = new Date();
650		if (!input) return;
651		var date = Date.parse(input.get('value'));
652		
653		if (date == null || !date.isValid()){
654			var storeDate = input.retrieve('datepicker:value');
655			if (storeDate) date = Date.parse(storeDate);
656		}
657		
658		if (date != null && date.isValid()) this.date = date;
659	},
660
661	// Control the previous and next elements
662
663	constructPicker: function(){
664		this.parent();
665
666		if (!this.options.rtl){
667			this.previous = new Element('div.previous[html=&#171;]').inject(this.header);
668			this.next = new Element('div.next[html=&#187;]').inject(this.header);
669		} else {
670			this.next = new Element('div.previous[html=&#171;]').inject(this.header);
671			this.previous = new Element('div.next[html=&#187;]').inject(this.header);
672		}
673	},
674
675	hidePrevious: function(_next, _show){
676		this[_next ? 'next' : 'previous'].setStyle('display', _show ? 'block' : 'none');
677		return this;
678	},
679
680	showPrevious: function(_next){
681		return this.hidePrevious(_next, true);
682	},
683
684	setPreviousEvent: function(fn, _next){
685		this[_next ? 'next' : 'previous'].removeEvents('click');
686		if (fn) this[_next ? 'next' : 'previous'].addEvent('click', fn);
687		return this;
688	},
689
690	hideNext: function(){
691		return this.hidePrevious(true);
692	},
693
694	showNext: function(){
695		return this.showPrevious(true);
696	},
697
698	setNextEvent: function(fn){
699		return this.setPreviousEvent(fn, true);
700	},
701
702	setColumns: function(columns, view, date, viewFx){
703		var ret = this.parent(columns), method;
704
705		if ((view || this.currentView)
706			&& (method = 'render' + (view || this.currentView).capitalize())
707			&& this[method]
708		) this[method](date || this.date.clone(), viewFx);
709
710		return ret;
711	},
712
713	// Render the Pickers
714
715	renderYears: function(date, fx){
716		var options = this.options, pages = options.columns, perPage = options.yearsPerPage,
717			_columns = [], _dates = [];
718		this.dateElements = [];
719
720		// start neatly at interval (eg. 1980 instead of 1987)
721		date = date.clone().decrement('year', date.get('year') % perPage);
722	
723		var iterateDate = date.clone().decrement('year', Math.floor((pages - 1) / 2) * perPage);
724
725		for (var i = pages; i--;){
726			var _date = iterateDate.clone();
727			_dates.push(_date);
728			_columns.push(renderers.years(
729				timesSelectors.years(options, _date.clone()),
730				options,
731				this.date.clone(),
732				this.dateElements,
733				function(date){
734					if (options.pickOnly == 'years') this.select(date);
735					else this.renderMonths(date, 'fade');
736					this.date = date;
737				}.bind(this)
738			));
739			iterateDate.increment('year', perPage);
740		}
741
742		this.setColumnsContent(_columns, fx);
743		this.setTitle(_dates, options.years_title);
744
745		// Set limits
746		var limitLeft = (options.minDate && date.get('year') <= options.minDate.get('year')),
747			limitRight = (options.maxDate && (date.get('year') + options.yearsPerPage) >= options.maxDate.get('year'));
748		this[(limitLeft ? 'hide' : 'show') + 'Previous']();
749		this[(limitRight ? 'hide' : 'show') + 'Next']();
750
751		this.setPreviousEvent(function(){
752			this.renderYears(date.decrement('year', perPage), 'left');
753		}.bind(this));
754
755		this.setNextEvent(function(){
756			this.renderYears(date.increment('year', perPage), 'right');
757		}.bind(this));
758
759		// We can't go up!
760		this.setTitleEvent(null);
761
762		this.currentView = 'years';
763	},
764
765	renderMonths: function(date, fx){
766		var options = this.options, years = options.columns, _columns = [], _dates = [],
767			iterateDate = date.clone().decrement('year', Math.floor((years - 1) / 2));
768		this.dateElements = [];
769
770		for (var i = years; i--;){
771			var _date = iterateDate.clone();
772			_dates.push(_date);
773			_columns.push(renderers.months(
774				timesSelectors.months(options, _date.clone()),
775				options,
776				this.date.clone(),
777				this.dateElements,
778				function(date){
779					if (options.pickOnly == 'months') this.select(date);
780					else this.renderDays(date, 'fade');
781					this.date = date;
782				}.bind(this)
783			));
784			iterateDate.increment('year', 1);
785		}
786
787		this.setColumnsContent(_columns, fx);
788		this.setTitle(_dates, options.months_title);
789
790		// Set limits
791		var year = date.get('year'),
792			limitLeft = (options.minDate && year <= options.minDate.get('year')),
793			limitRight = (options.maxDate && year >= options.maxDate.get('year'));
794		this[(limitLeft ? 'hide' : 'show') + 'Previous']();
795		this[(limitRight ? 'hide' : 'show') + 'Next']();
796
797		this.setPreviousEvent(function(){
798			this.renderMonths(date.decrement('year', years), 'left');
799		}.bind(this));
800
801		this.setNextEvent(function(){
802			this.renderMonths(date.increment('year', years), 'right');
803		}.bind(this));
804
805		var canGoUp = options.yearPicker && (options.pickOnly != 'months' || options.canAlwaysGoUp.contains('months'));
806		var titleEvent = (canGoUp) ? function(){
807			this.renderYears(date, 'fade');
808		}.bind(this) : null;
809		this.setTitleEvent(titleEvent);
810
811		this.currentView = 'months';
812		
813	},
814
815	renderDays: function(date, fx){
816		
817		var options = this.options, months = options.columns, _columns = [], _dates = [],
818			iterateDate = date.clone().decrement('month', Math.floor((months - 1) / 2));
819		this.dateElements = [];
820
821		for (var i = months; i--;){
822			_date = iterateDate.clone();
823			_dates.push(_date);
824			_columns.push(renderers.days(
825				timesSelectors.days(options, _date.clone()),
826				options,
827				this.date.clone(),
828				this.dateElements,
829				function(date){
830					if (options.pickOnly == 'days' || !options.timePicker) this.select(date)
831					else this.renderTime(date, 'fade');
832					this.date = date;
833				}.bind(this)
834			));
835			iterateDate.increment('month', 1);
836		}
837
838		this.setColumnsContent(_columns, fx);
839		this.setTitle(_dates, options.days_title);
840
841		var yearmonth = date.format('%Y%m').toInt(),
842			limitLeft = (options.minDate && yearmonth <= options.minDate.format('%Y%m')),
843			limitRight = (options.maxDate && yearmonth >= options.maxDate.format('%Y%m'));
844		this[(limitLeft ? 'hide' : 'show') + 'Previous']();
845		this[(limitRight ? 'hide' : 'show') + 'Next']();
846
847		this.setPreviousEvent(function(){
848			this.renderDays(date.decrement('month', months), 'left');
849		}.bind(this));
850
851		this.setNextEvent(function(){
852			this.renderDays(date.increment('month', months), 'right');
853		}.bind(this));
854
855		var canGoUp = options.pickOnly != 'days' || options.canAlwaysGoUp.contains('days');
856		var titleEvent = (canGoUp) ? function(){
857			this.renderMonths(date, 'fade');
858		}.bind(this) : null;
859		this.setTitleEvent(titleEvent);
860
861		this.currentView = 'days';
862		if(this.options.fnAfterRender){
863			this.options.fnAfterRender(date);
864		}
865	},
866
867	renderTime: function(date, fx){
868		var options = this.options;
869		this.setTitle(date, options.time_title);
870
871		var originalColumns = this.originalColumns = options.columns;
872		this.currentView = null; // otherwise you'd get crazy recursion
873		if (originalColumns != 1) this.setColumns(1);
874
875		this.setContent(renderers.time(
876			options,
877			date.clone(),
878			function(date){
879				this.select(date);
880			}.bind(this)
881		), fx);
882
883		// Hide « and » buttons
884		this.hidePrevious()
885			.hideNext()
886			.setPreviousEvent(null)
887			.setNextEvent(null);
888
889		var canGoUp = options.pickOnly != 'time' || options.canAlwaysGoUp.contains('time');
890		var titleEvent = (canGoUp) ? function(){
891			this.setColumns(originalColumns, 'days', date, 'fade');
892		}.bind(this) : null;
893		this.setTitleEvent(titleEvent);
894
895		this.currentView = 'time';
896	},
897
898	select: function(date, all){
899		this.date = date;
900		var formatted = date.format(this.options.format),
901			time = date.strftime(),
902			inputs = (!this.options.updateAll && !all && this.input) ? [this.input] : this.inputs;
903
904		inputs.each(function(input){
905			input.set('value', formatted).store('datepicker:value', time).fireEvent('change');
906		}, this);
907
908		this.fireEvent('select', [date].concat(inputs));
909		this.close();
910		return this;
911	}
912
913});
914
915
916// Renderers only output elements and calculate the limits!
917
918var timesSelectors = {
919
920	years: function(options, date){
921		var times = [];
922		for (var i = 0; i < options.yearsPerPage; i++){
923			times.push(+date);
924			date.increment('year', 1);
925		}
926		return times;
927	},
928
929	months: function(options, date){
930		var times = [];
931		date.set('month', 0);
932		for (var i = 0; i <= 11; i++){
933			times.push(+date);
934			date.increment('month', 1);
935		}
936		return times;
937	},
938
939	days: function(options, date){
940		var times = [];
941		date.set('date', 1);
942		while (date.get('day') != options.startDay) date.set('date', date.get('date') - 1);
943		for (var i = 0; i < 42; i++){
944			times.push(+date);
945			date.increment('day',  1);
946		}
947		return times;
948	}
949
950};
951
952var renderers = {
953
954	years: function(years, options, currentDate, dateElements, fn){
955		var container = new Element('div.years'),
956			today = new Date(), element, classes;
957
958		years.each(function(_year, i){
959			var date = new Date(_year), year = date.get('year');
960
961			classes = '.year.year' + i;
962			if (year == today.get('year')) classes += '.today';
963			if (year == currentDate.get('year')) classes += '.selected';
964			element = new Element('div' + classes, {text: year}).inject(container);
965
966			dateElements.push({element: element, time: _year});
967
968			if (isUnavailable('year', date, options)) element.addClass('unavailable');
969			else element.addEvent('click', fn.pass(date));
970		});
971
972		return container;
973	},
974
975	months: function(months, options, currentDate, dateElements, fn){
976		
977		var today = new Date(),
978			month = today.get('month'),
979			thisyear = today.get('year'),
980			selectedyear = currentDate.get('year'),
981			container = new Element('div.months'),
982			monthsAbbr = options.months_abbr || Locale.get('Date.months_abbr'),
983			element, classes;
984
985		months.each(function(_month, i){
986			var date = new Date(_month), year = date.get('year');
987
988			classes = '.month.month' + (i + 1);
989			if (i == month && year == thisyear) classes += '.today';
990			if (i == currentDate.get('month') && year == selectedyear) classes += '.selected';
991			element = new Element('div' + classes, {text: monthsAbbr[i]}).inject(container);
992
993			dateElements.push({element: element, time: _month});
994
995			if (isUnavailable('month', date, options)) element.addClass('unavailable');
996			else element.addEvent('click', fn.pass(date));
997		});
998
999		return container;
1000	},
1001
1002	days: function(days, options, currentDate, dateElements, fn){
1003		var month = new Date(days[14]).get('month'),
1004			todayString = new Date().toDateString(),
1005			currentString = currentDate.toDateString(),
1006			weeknumbers = options.weeknumbers,
1007			container = new Element('table.days' + (weeknumbers ? '.weeknumbers' : ''), {
1008				role: 'grid', 'aria-labelledby': this.titleID
1009			}),
1010			header = new Element('thead').inject(container),
1011			body = new Element('tbody').inject(container),
1012			titles = new Element('tr.titles').inject(header),
1013			localeDaysShort = options.days_abbr,
1014			day, classes, element, weekcontainer, dateString,
1015			where = options.rtl ? 'top' : 'bottom';
1016
1017		if (weeknumbers) new Element('th.title.day.weeknumber', {
1018			text: Locale.get('DatePicker.week')
1019		}).inject(titles);
1020
1021		for (day = options.startDay; day < (options.startDay + 7); day++){
1022			new Element('th.title.day.day' + (day % 7), {
1023				text: localeDaysShort[(day % 7)],
1024				role: 'columnheader'
1025			}).inject(titles, where);
1026		}
1027
1028		days.each(function(_date, i){
1029			var date = new Date(_date);
1030
1031			if (i % 7 == 0){
1032				weekcontainer = new Element('tr.week.week' + (Math.floor(i / 7))).set('role', 'row').inject(body);
1033				if (weeknumbers) new Element('th.day.weeknumber', {text: date.get('week'), scope: 'row', role: 'rowheader'}).inject(weekcontainer);
1034			}
1035
1036			dateString = date.toDateString();
1037			classes = '.day.ynevent-cal-day.day' + date.get('day');
1038			if (dateString == todayString) classes += '.today';
1039			if (date.get('month') != month) classes += '.otherMonth';
1040			element = new Element('td' + classes, {text: date.getDate(), role: 'gridcell'}).inject(weekcontainer, where);
1041
1042			if (dateString == currentString) element.addClass('selected').set('aria-selected', 'true');
1043			else element.set('aria-selected', 'false');
1044			element.set('id','yndc-'+getShortDateString(date));
1045
1046			dateElements.push({element: element, time: _date});
1047
1048			if (isUnavailable('date', date, options)) element.addClass('unavailable');
1049			else element.addEvent('click', fn.pass(date.clone()));
1050		});
1051
1052		return container;
1053	},
1054
1055	time: function(options, date, fn){
1056		var container = new Element('div.time'),
1057			// make sure that the minutes are timeWheelStep * k
1058			initMinutes = (date.get('minutes') / options.timeWheelStep).round() * options.timeWheelStep
1059
1060		if (initMinutes >= 60) initMinutes = 0;
1061		date.set('minutes', initMinutes);
1062
1063		var hoursInput = new Element('input.hour[type=text]', {
1064			title: Locale.get('DatePicker.use_mouse_wheel'),
1065			value: date.format('%H'),
1066			events: {
1067				click: function(event){
1068					event.target.focus();
1069					event.stop();
1070				},
1071				mousewheel: function(event){
1072					event.stop();
1073					hoursInput.focus();
1074					var value = hoursInput.get('value').toInt();
1075					value = (event.wheel > 0) ? ((value < 23) ? value + 1 : 0)
1076						: ((value > 0) ? value - 1 : 23)
1077					date.set('hours', value);
1078					hoursInput.set('value', date.format('%H'));
1079				}.bind(this)
1080			},
1081			maxlength: 2
1082		}).inject(container);
1083
1084		var minutesInput = new Element('input.minutes[type=text]', {
1085			title: Locale.get('DatePicker.use_mouse_wheel'),
1086			value: date.format('%M'),
1087			events: {
1088				click: function(event){
1089					event.target.focus();
1090					event.stop();
1091				},
1092				mousewheel: function(event){
1093					event.stop();
1094					minutesInput.focus();
1095					var value = minutesInput.get('value').toInt();
1096					value = (event.wheel > 0) ? ((value < 59) ? (value + options.timeWheelStep) : 0)
1097						: ((value > 0) ? (value - options.timeWheelStep) : (60 - options.timeWheelStep));
1098					if (value >= 60) value = 0;
1099					date.set('minutes', value);
1100					minutesInput.set('value', date.format('%M'));
1101				}.bind(this)
1102			},
1103			maxlength: 2
1104		}).inject(container);
1105
1106		new Element('div.separator[text=:]').inject(container);
1107
1108		new Element('input.ok[type=submit]', {
1109			value: Locale.get('DatePicker.time_confirm_button'),
1110			events: {click: function(event){
1111				event.stop();
1112				date.set({
1113					hours: hoursInput.get('value').toInt(),
1114					minutes: minutesInput.get('value').toInt()
1115				});
1116				fn(date.clone());
1117			}}
1118		}).inject(container);
1119
1120		return container;
1121	}
1122
1123};
1124
1125
1126Picker.Date.defineRenderer = function(name, fn){
1127	renderers[name] = fn;
1128	return this;
1129};
1130
1131var limitDate = function(date, min, max){
1132	if (min && date < min) return min;
1133	if (max && date > max) return max;
1134	return date;
1135};
1136
1137var isUnavailable = function(type, date, options){
1138	var minDate = options.minDate,
1139		maxDate = options.maxDate,
1140		availableDates = options.availableDates,
1141		year, month, day, ms;
1142
1143	if (!minDate && !maxDate && !availableDates) return false;
1144	date.clearTime();
1145
1146	if (type == 'year'){
1147		year = date.get('year');
1148		return (
1149			(minDate && year < minDate.get('year')) ||
1150			(maxDate && year > maxDate.get('year')) ||
1151			(
1152				(availableDates != null &&  !options.invertAvailable) && (
1153					availableDates[year] == null ||
1154					Object.getLength(availableDates[year]) == 0 ||
1155					Object.getLength(
1156						Object.filter(availableDates[year], function(days){
1157							return (days.length > 0);
1158						})
1159					) == 0
1160				)
1161			)
1162		);
1163	}
1164
1165	if (type == 'month'){
1166		year = date.get('year');
1167		month = date.get('month') + 1;
1168		ms = date.format('%Y%m').toInt();
1169		return (
1170			(minDate && ms < minDate.format('%Y%m').toInt()) ||
1171			(maxDate && ms > maxDate.format('%Y%m').toInt()) ||
1172			(
1173				(availableDates != null && !options.invertAvailable) && (
1174					availableDates[year] == null ||
1175					availableDates[year][month] == null ||
1176					availableDates[year][month].length == 0
1177				)
1178			)
1179		);
1180	}
1181
1182	// type == 'date'
1183	year = date.get('year');
1184	month = date.get('month') + 1;
1185	day = date.get('date');
1186
1187	var dateAllow = (minDate && date < minDate) || (minDate && date > maxDate);
1188	if (availableDates != null){
1189		dateAllow = dateAllow
1190			|| availableDates[year] == null
1191			|| availableDates[year][month] == null
1192			|| !availableDates[year][month].contains(day);
1193		if (options.invertAvailable) dateAllow = !dateAllow;
1194	}
1195
1196	return dateAllow;
1197};
1198
1199})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.