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=«]').inject(this.header); 668 this.next = new Element('div.next[html=»]').inject(this.header); 669 } else { 670 this.next = new Element('div.previous[html=«]').inject(this.header); 671 this.previous = new Element('div.next[html=»]').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.