/** * Add date selection from calendar handling for input elements. * * The `.uiDatepicker()` method add validate handling to a input elements. * * The `.uiDatepicker()` method is a validation input also and using the validation type ''date'' from * the ep.validate module. Have a look at the additional options, some options are only available for * a special validation types. * * ### Examples * Apply .uiDatepicker() to input element named date. * * JavaScript: * * ep(':input[name=date]') * .uiDatepicker({ * type: 'date', * format: 'd', * time: false * }); * * HTML: * * <form> * <input type="text" name="date" /> * </form> * * * @class jQuery.ui.uiDatepicker * @extends jQuery.ui.uiValidate * * @uses ep * @uses ep.date * @uses ep.sprite * @uses ep.fn.busy * @uses ep.ui.spinner * @uses jQuery.tmpl * @since 6.11.0 */ /** * Call up a previously attached date picker. * * @method show * @member jQuery.ui.uiDatepicker * * @since 6.11.0 */ /** * Close a previously opened date picker. * * @method hide * @member jQuery.ui.uiDatepicker * * @since 6.11.0 */ /** * @cfg {String} type The value respectively validation type must be 'date'. */ /** * @cfg {String} format A date formatter [look at the jQuery.i18n plugin]. */ /** * @cfg {Integer} min A local timestamp (not UTC) as minimum valid date. */ /** * @cfg {Integer} max A local timestamp (not UTC) as maximum valid date. */ /** * @cfg {String} showOn A handle indication, how to show the datepicker. Supported handles: 'focus', 'button', 'both' (button and focus) or 'inline'. */ /** * @cfg {Integer,String} showOn A duration for show and hide effect. [look at jQuery.show() or jQuery.hide() for supported durations]. */ /** * @cfg {String} show One off the supported show effects: 'show', 'fadeIn' or 'slideIn'. */ /** * @cfg {String} hide One off the supported hide effects: 'hide', 'fadeOut' or 'slideOut'. */ /** * @cfg {Array} weekDaysAvailable An array of 7 weekdays with indications wether a day is available. Week starts on sunday. */ /** * @cfg {Integer} numberOfMonths Set how many months to show at once. */ /** * @cfg {Boolean} showOtherMonth Display dates in other months (non-selectable) at the start or end of the current month. To make these days selectable use selectOtherMonths. */ /** * @cfg {Boolean} selectOtherMonth When true days in other months shown before or after the current month are selectable. This only applies if showOtherMonths is also true. */ /** * @cfg {Boolean} changeMonth Allows you to change the month by selecting from a drop-down list. You can enable this feature by setting the attribute to true. */ /** * @cfg {Boolean} changeYear Allows you to change the year by selecting from a drop-down list. You can enable this feature by setting the attribute to true. Use the yearRange option to control which years are made available for selection. */ /** * @cfg {Boolean} time Allows you to select time also. Requires a format which support time. */ /** * @cfg {Boolean} timeStep A difference to next/previous step in minutes. */ /** * See `jQuery.ui.uiDatepicker` for details. * * @param {Object} [options] A map of additional options pass to the method. * @param {String} type The value respectively validation type must be 'date'. * @param {String} format A date formatter [look at the jQuery.i18n plugin]. * @param {Integer} min A local timestamp (not UTC) as minimum valid date. * @param {Integer} max A local timestamp (not UTC) as maximum valid date. * @param {String} showOn A handle indication, how to show the datepicker. Supported handles: 'focus', 'button', 'both' (button and focus) or 'inline'. * @param {Integer,String} showOn A duration for show and hide effect. [look at jQuery.show() or jQuery.hide() for supported durations]. * @param {String} show One off the supported show effects: 'show', 'fadeIn' or 'slideIn'. * @param {String} hide One off the supported hide effects: 'hide', 'fadeOut' or 'slideOut'. * @param {Array} weekDaysAvailable An array of 7 weekdays with indications wether a day is available. Week starts on sunday. * @param {Integer} numberOfMonths Set how many months to show at once. * @param {Boolean} showOtherMonth Display dates in other months (non-selectable) at the start or end of the current month. To make these days selectable use selectOtherMonths. * @param {Boolean} selectOtherMonth When true days in other months shown before or after the current month are selectable. This only applies if showOtherMonths is also true. * @param {Boolean} changeMonth Allows you to change the month by selecting from a drop-down list. You can enable this feature by setting the attribute to true. * @param {Boolean} changeYear Allows you to change the year by selecting from a drop-down list. You can enable this feature by setting the attribute to true. Use the yearRange option to control which years are made available for selection. * @param {Boolean} time Allows you to select time also. Requires a format which support time. * @param {Boolean} timeStep A difference to next/previous step in minutes. * * @method uiDatepicker * @member jQuery * * @since 6.11.0 */ /* * @copyright © Copyright 2006-2010, epages GmbH, All Rights Reserved. * * @module ep.ui.datepicker * * @revision $Revision: 1.11 $ */ define("ep/ui/datepicker", [ "jquery", "ep", "$dict!ep/dict", "$tmpl!ep/ui/datepicker", "ep/date", "ep/sprite", "ep/fn/busy", "ep/ui/spinner" ], function ($, ep, epDict, tmplDatepicker) { /* * @dictionary ep.dict * * @translation {Today} * {Done} */ $.widget('ui.uiDatepicker',$.ui.uiValidate,{ options: { // ep.ui.Input // big: true, // autofocus: false, // placeholder: 'text', // info: 'text', // ep.ui.Validate // accept: '', // min: 1, // max: 20, // minlength: 1, // maxlength: 4, // pattern: [\w]+, // required: true, // valid: true, // type: 'basic', // format: '', // ep.ui.Datepicker // onAfterSetValue: undefined // callback called after value was set type: 'date', format: 'l', // format for i18n showOn: 'button', // show datepicker on 'focus' || 'button' || 'both' or 'inline' duration: 'normal', // duration for show and hide effect show: 'fadeIn', // show effect 'show' || 'fadeIn' || 'slideIn' hide: 'fadeOut', // hide effect 'hide' || 'fadeOut' || 'slideOut' weekDaysAvailable: [1,1,1,1,1,1,1], // which days ar avaible boolean in array numberOfMonths: 1, // display number of month showOtherMonth: true, // show days from other month selectOtherMonth: false, // days from other month ar clickable changeMonth: true, // enable select month changeYear: true, // enable select year region: $.i18n.settings.region, // region for i18n time: true, // use as date and time picker timeStep: 15, // step for time spinner in minutes min: undefined, // min for range miliseconds || dateObj max: undefined // max for range miliseconds || dateObj }, _create: function(){ this._superApply(arguments); // get instance of dict this.dict = epDict; var currentDate = this.options.currentDate, date; date = currentDate ? new ep.Date(currentDate.year, currentDate.month, currentDate.day) : new ep.Date(); date .setMinutes(30) .setSeconds(0) .setMilliseconds(0); this._date = date.clone().setHours(0,0,0,0); this._dateNow = this._date.clone(); this._dateSelected = this._date.clone(); this._timeSelected = date.clone().setFullYear(0,0,0); this.domCache = $('<div>'); this.cal = $.i18n.regions[ this.options.region ].calendar; this.elem.on( 'changeValue.uiDatepicker', $.proxy(this, '_changeElemVal') ); this.showButton = $('<span>') .attr({ 'class': 'ep-uiInput ep-uiInput-base ep-uiInput-button ep-uiDatepicker-showButton' }) .html( ep.sprite('calendar','s').addClass('ep-uiInput-buttonSpriteOnly') ) .on( 'click.uiDatepicker', $.proxy(this, 'show') ) .appendTo( this.domCache ); this.stack.push( this.domCache[0], this.showButton[0] ); this._changeElemVal(); }, _init: function(){ this._superApply(arguments); var o = this.options; if( (/^(focus|both)$/).test(o.showOn) ){ this.elem .off('focus.uiDatepicker') .on( 'focus.uiDatepicker', $.proxy(this, 'show') ); } if( (/^(button|both)$/).test(o.showOn) ){ this.showButton .insertAfter( this.placeholder || this.elem ); } else{ this.showButton .appendTo( this.domCache ); } if( this.container ){ this._initCalCache(); if( o.time && !this.timeBar ){ this._createTimeBar(); } } this._tooltipInit(); if( o.showOn === 'inline' ){ this.show(); } }, _initCalCache: function(){ this._calCache = { weekDays: arrayRotate( $.merge([],this.cal.days.namesShort), this.cal.firstDay), months: $.merge([],this.cal.months.names) }; }, _tooltipInit: function(){ this._superApply(arguments); if( this.tooltip && (/^(button|both)$/).test(this.options.showOn) ){ this.tooltip.uiTooltip( 'option', 'offsetAdjust', [30,-1] ); } else if( this.tooltip ){ this.tooltip.uiTooltip( 'option', 'offsetAdjust', [3,-2] ); } }, _createBase: function(){ var self = this; // the datepicker this.container = ep('<form>') .addClass('epDialog ep-uiDatepicker-container ui-dialog ui-front') .hide(); // go to previous month this.prevButton = ep('<button type="button">') .append( ep.sprite('arrow-l','s').addClass('ep-uiInput-buttonSpriteOnly') ) .addClass('ep-uiDatepicker-prev') .on( 'click.uiDatepicker', function(){ self._changeMonth(-1); }); // go to next month this.nextButton = ep('<button type="button">') .append( ep.sprite('arrow-r','s').addClass('ep-uiInput-buttonSpriteOnly') ) .addClass('ep-uiDatepicker-next') .on( 'click.uiDatepicker', function(){ self._changeMonth(1); }); // bar on the bottom this.buttonBar = $('<div>') .addClass('ep-uiDatepicker-buttonBar') .appendTo( this.container ); // select the current day this.todayButton = ep('<button type="button">') .text( this.dict.translate('Today') ) .appendTo( this.buttonBar ) .addClass('ep-uiDatepicker-today') .on( 'click.uiDatepicker', function(){ self._date = self._dateNow.clone().setHours(0,0,0,0); self._changeDay( self._date.getTime() ); self._createCalendars(); }); // close the datepicker this.doneButton = ep('<button type="button">') .text( this.dict.translate('Done') ) .appendTo( this.buttonBar ) .addClass('ep-uiDatepicker-done') .on( 'click.uiDatepicker', $.proxy(this, 'hide') ); // live click handler to select a day on click this.container .on( 'click', 'a.ep-uiDatepicker-dayClick', $.proxy(this, '_selectDay') ) .on( 'change', 'select.ep-uiDatepicker-monthSelect,select.ep-uiDatepicker-yearSelect', function(){ self._changeMonth(parseInt(this.value,10)); }); // stack for calendars this.calendars = $(); // reinit this._init(); // add to DOM this.container.appendTo('body'); // save in stack this.stack.push( this.container[0], this.prevButton[0], this.nextButton[0], this.doneButton[0], this.todayButton[0], this.buttonBar[0] ); }, _createTimeBar: function(){ this.timeBar = $('<div>') .addClass('ep-uiDatepicker-timeBar') .insertBefore( this.buttonBar ); this.stack.push( this.timeBar[0] ); this._createTimeSelect(); }, _createTimeSelect: function(){ this.timeSelect = ep('<input>') .attr({ 'class': 'epWidth100', 'placeholder': '00:00' }) .appendTo( this.timeBar ) .uiSpinner({ type: 'date', step: 15, stepType: 'minutes', format: 't', region: this.options.region }) .uiInput('addClass','ep-uiDatepicker-time') .on( 'changeValue.uiDatepicker', $.proxy(this, '_changeTimeVal') ); this._setTimeVal(); this.stack.push( this.timeSelect[0] ); }, _createCalendars: function(){ // set busy layer this.container.busy('show'); // vars var o = this.options, calendarsData = this._calendarData(), calendars; // build weeks $.each( calendarsData, function( i, calendarData){ while( calendarData.days.length >= 7 ){ calendarData.weeks.push({days:calendarData.days.splice(0,7)}); } }); // build calendars calendars = tmplDatepicker(calendarsData); calendars .not(':first,:last') .addClass('ep-uiDatepicker-calendarMiddle'); calendars .not(':last') .first() .addClass('ep-uiDatepicker-calendarFirst'); calendars .not(':first') .last() .addClass('ep-uiDatepicker-calendarLast'); // place prev button to new calendars and handle range this.prevButton .attr( 'disabled', !inRange( o.min, this._date.clone().setFirstMonthDay().setHours(0,0,0,-1).getTime() ) ) .prependTo( calendars.filter(':first').find('.ep-uiDatepicker-titleBar') ); // place next button to new calendars and handle range this.nextButton .attr( 'disabled', !inRange( undefined, this._date.clone().setLastMonthDay().setHours(0,0,0,0).addDate(1).getTime(), o.max ) ) .appendTo( calendars.filter(':last').find('.ep-uiDatepicker-titleBar') ); // remove old calendars this.calendars .html('') .remove(); // show new calendars in datepicker this.container.prepend(calendars) .busy('hide'); // set new calendars this.calendars = calendars; }, _calendarData: function(){ var o = this.options, baseDate = this._date.clone().addMonth( - Math.ceil(o.numberOfMonths/2) ), nowTime = this._dateNow.getTime(), selectedTime = this._dateSelected.getTime(), region = {region: this.options.region}, baseMonth = this._date.getMonth(), changeMonth = null, changeYear = null, data = [], i; if( o.changeMonth ){ changeMonth = []; $.each( this._calCache.months, function( i, name ){ if( name ){ changeMonth.push({ value: (baseMonth-i)*-1, name: name, currentMonth: baseMonth===i }); } }); } if( o.changeYear ){ var yDate = this._date.clone(); changeYear = [{ value: 0, name: yDate.getFormat( "yyyy", region ), currentYear: true }]; for( i=1 ; i<=10 ; i++ ){ yDate.addFullYear(-1); changeYear.unshift({ value: i*-12, name: yDate.getFormat( "yyyy", region ), currentYear: false }); } yDate.addFullYear(10); for( i=1 ; i<=10 ; i++ ){ yDate.addFullYear(1); changeYear.push({ value: i*12, name: yDate.getFormat( "yyyy", region ), currentYear: false }); } } for( i=0,iLength=o.numberOfMonths ; i<iLength ; i++ ){ var date = baseDate.addMonth(1), iDate = date.clone().setDate(0), iFirstDate = date.clone().setFirstMonthDay(), iFirstTime = iFirstDate.getTime(), iLastTime = date.clone().setLastMonthDay().getTime(), iDays = iFirstDate.getMonthLength(), iWeekDay = iFirstDate.getDay() - this.cal.firstDay, add = { year: iFirstDate.getFormat( "yyyy", region ), years: changeYear, month: iFirstDate.getFormat( "MMMM", region ), months: changeMonth, weekDays: this._calCache.weekDays, weeks: [], days: [] }; if( iWeekDay<0 ){ iWeekDay = iWeekDay+7; } iDate.addDate( - iWeekDay ); for( var j=1,jLength=(Math.ceil( (iWeekDay+iDays) / 7) * 7 ) ; j<=jLength ; j++ ){ var jDate = iDate.addDate(1), jTime = jDate.getTime(), currentMonth = inRange( iFirstTime, jTime, iLastTime ), clickable = inRange( o.min, jTime, o.max ) && o.weekDaysAvailable[ jDate.getDay() ]; add.days.push({ 'date': jDate.clone(), 'tstamp': jTime, 'day': jDate.getDate(), 'currentMonth': !!currentMonth, 'clickable': (currentMonth || o.selectOtherMonth) && clickable, 'visible': !!(currentMonth || o.selectOtherMonth || o.showOtherMonth), 'selected': jTime===selectedTime, 'now': jTime===nowTime }); } data.push(add); } return data; }, _setElemVal: function( noBubbling ){ var val = new ep.Date( this._dateSelected.getFullYear(), this._dateSelected.getMonth(), this._dateSelected.getDate(), isNaN(this._timeSelected.getHours()) ? 0 : this._timeSelected.getHours(), isNaN(this._timeSelected.getMinutes()) ? 0 : this._timeSelected.getMinutes(), isNaN(this._timeSelected.getSeconds()) ? 0 : this._timeSelected.getSeconds() ) .getFormat( this.options.format, {region:this.options.region} ); if( noBubbling ){ this.elem.val( val ); } else{ this.elem.attr( 'value', val ); } // callback called after value was set if(this.options.onAfterSetValue !== undefined){ this.options.onAfterSetValue(this.elem); } }, _getElemVal: function(){ var date = $.i18n.parseDate( this.elem.val(), this.options.format, {region:this.options.region} ); return date ? new ep.Date( date.getTime() ) : null; }, _changeElemVal: function(){ var o = this.options, date = this._getElemVal(); if( date && inRange( o.min, date.clone().setLastMonthDay().getTime() ) && inRange( undefined, date.clone().setFirstMonthDay().getTime(), o.max ) ){ if( this.elem.is(':focus') ){ this._date = date.clone().setHours(0,0,0,0); } this._dateSelected = date.clone().setHours(0,0,0,0); this._timeSelected = date.clone().setFullYear(0,0,0); if( this.timeSelect ){ this._setTimeVal(); } } if( this.container && this.container.filter(':visible').length ){ this._createCalendars(); } }, _setTimeVal: function(){ if( !this.timeSelect.is(':focus') ){ this.timeSelect.val( this._timeSelected.getFormat( 't', {region:this.options.region} ) ); } }, _getTimeVal: function(){ var date = $.i18n.parseDate( this.timeSelect.val(), 't', {region:this.options.region} ); return date ? new ep.Date( date.getTime() ) : null; }, _changeTimeVal: function(event){ var date = this._getTimeVal(); if( date ){ this._timeSelected = date.setFullYear(0,0,0); this._setElemVal(); } }, _changeMonth: function( inc ){ this._date.addMonth(inc); this._createCalendars(); }, _changeDay: function( tstamp ){ this._dateSelected = new ep.Date(tstamp); this._setElemVal(); }, _selectDay: function( event ){ this._changeDay( parseInt( $(event.target).attr('data-tstamp'), 10 ) ); }, _show: function(){ var o = this.options; if( o.showOn !== 'inline' ){ this.container.contextOrientation( this.elem, 'bottom', [-1,1] ).focus(); this.container .stop(true,true) [ $.fn[o.show] ? o.show : 'show' ](o.duration); $('html').on( 'keyup', $.proxy(this,'_hide') ); } else{ this.buttonBar .hide(); this.container .insertAfter(this.elem) .css('position','relative') .show(); } this.elem.blur(); }, show: function(){ var self = this; if( this.elem.is(":disabled") ){ return; } if( !this.container ){ this._createBase(); } this._show(); this._changeElemVal(); this._setValidStatus(true); }, _hide: function( event ){ if( event.keyCode===27 ){ this.hide(); this._validate(event); } }, hide: function(){ var o = this.options; if( o.showOn !== 'inline' ){ this.container .stop(true,true) [ $.fn[o.hide] ? o.hide : 'hide' ](o.duration); $('html').off( 'keyup', $.proxy(this,'_hide') ); } }, /** * Returns the current date for the datepicker as `ep.Date()` object. * * @method getDate * @member jQuery.ui.uiDatepicker * * @since 6.11.0 */ getDate: function(){ return new ep.Date( this._dateSelected.getFullYear(), this._dateSelected.getMonth(), this._dateSelected.getDate(), this._timeSelected.getHours(), this._timeSelected.getMinutes(), this._timeSelected.getSeconds() ); } }); var arrayRotate = function( array, size ){ var tmp = array.splice( 0, size ); return array.concat( tmp ); }, inRange = function( min, val, max ){ min = new ep.Date(typeof min === 'object' && min.getTime ? min.getTime() : min).setHours(0,0,0,0).getTime(); max = new ep.Date(typeof max === 'object' && max.getTime ? max.getTime() : max).setHours(0,0,0,0).getTime(); return !min || val >= min ? !max || val <= max ? true : false : false; }; return ep; });