/* * Mobi Pick - An Android-style datepicker widget for jQuery Mobile. * * Created by Christoph Baudson. * * Please report issues at https://github.com/sustainablepace/mobipick/issues * * Version 0.9 * * Licensed under MIT license, see MIT-license.txt */ (function( factory ) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define( ['jquery', 'jquery.mobile'], factory ); } else { // Browser globals factory( jQuery, jQuery.mobile ); } }( function( $, $mobile, undefined ){ $.widget( "sustainablepace.mobipick", $mobile.widget, { options: { date : null, dateFormat : "yyyy-MM-dd", dateFormatMonth : "yyyy-MM", dateFormatYear : "yyyy", inputOrder : "d m y", locale : "en", intlStdDate : true, buttonTheme : "b", popup : { dismissible: false, history: false, overlayTheme: "a", positionTo: "window", theme: "a", transition: "none" } }, // Controller _picker: $( [] ), widgetEventPrefix: "mobipick", destroy: function() { this._close(); this.element.off( "tap click" ); this._picker.popup( "destroy" ); $.Widget.prototype.destroy.call( this ); }, _create: function() { this._initOptions(); // parses options this._createView(); // inserts markup into DOM this.element.on( "tap click", $.proxy( this.open, this ) ); }, _initOptions: function() { var date = this.element.val() || this.options.date, minDate = this.element.attr( "min" ) || this.options.minDate, maxDate = this.element.attr( "max" ) || this.options.maxDate; if( date ) { this._setOption( "date", date ); } // Min and max dates can be set in the markup. // See http://dev.w3.org/html5/markup/input.date.html if( maxDate ) { this._setOption( "maxDate", maxDate ); } if( minDate ) { this._setOption( "minDate", minDate ); } this._setOption( "locale", this.options.locale ); }, _init: function() { // fill input field with default value if( this._getDate() !== null ) { this.updateDateInput(); } }, open: function( evt ) { if( evt ) { evt.stopPropagation(); evt.preventDefault(); } var date = this._getDate(); if( !this._isXDate( date ) ) { date = new XDate(); } this._setOption( "date", this._fitDate( date ) ); this._setOption( "originalDate", this._getDate() ); this._showView(); this._updateView(); this._bindEvents(); }, _bindEvents: function() { var self = this, p = this._picker, confirm = $.proxy( this._confirmDate, this ), cancel = $.proxy( this._cancelDate, this ), esc = $.proxy( this._cancelDateOnEsc, this ); // Set and Cancel buttons p.find( ".mobipick-set" ).off().on( "tap", confirm ); p.find( ".mobipick-cancel" ).off().on( "tap", cancel ); $( document ).off( "keyup", esc ).on( "keyup", esc ); // +/- Buttons var selectorMap = { ".mobipick-prev-day" : "_prevDay", ".mobipick-prev-month" : "_prevMonth", ".mobipick-prev-year" : "_prevYear", ".mobipick-next-day" : "_nextDay", ".mobipick-next-month" : "_nextMonth", ".mobipick-next-year" : "_nextYear" }; for( var selector in selectorMap ) { (function() { var handler = self[ selectorMap[ selector ] ]; p.find( selector ).off().on( "tap", $.proxy( function() { return self._handleDate( handler ); }, self )); })(); } }, _close: function() { this._hideView(); }, _handleDate: function( dateHandler ) { this._setOption( "date", this._fitDate( dateHandler.apply( this ) ) ); return false; }, _confirmDate: function() { var proceed = true, dateDiff = this._getDate().diffDays( this.options.originalDate ), hasChanged = dateDiff !== 0 || this.element.val() === ""; if( this.options.close && typeof this.options.close === "function" ) { proceed = this.options.close.call() !== false; } if( proceed && hasChanged ) { this._setOption( "originalDate", this._getDate() ); this.updateDateInput(); this.element.trigger("change"); } else { this._setOption( "date", this.options.originalDate ); } this._close(); return false; }, _cancelDate: function() { this._setOption( "date", this.options.originalDate ); this._close(); return false; }, _cancelDateOnEsc: function( evt ) { if( evt.keyCode === 27 ) { this._cancelDate(); } }, _setOption: function( key, val ) { switch( key ) { case "date": var sane = this._sanitizeDate( val ); this.options[ key ] = sane ? sane.toDate() : sane; break; case "originalDate": this.options[ key ] = this._sanitizeDate( val ).toDate(); break; case "maxDate": this.options[ key ] = this._sanitizeMaxDate( val ).toDate(); break; case "minDate": this.options[ key ] = this._sanitizeMinDate( val ).toDate(); break; case "intlStdDate": this.options[ key ] = !!val; break; case "locale": this.options[ key ] = this._isValidLocale( val ) ? val : "en"; break; default: // Do not update view! return $.Widget.prototype._setOption.apply( this, arguments ); } this._updateView(); }, // // Model // _sanitizeDate: function( date ) { if( date === null ) { return null; } var d = date; if( typeof d === "string" ) { d = new XDate( d ); } if( this._isXDate( d ) ) { d = d.toDate(); } if( !this._isDate( d ) ) { throw "Parameter 'date' must be a Date."; } return new XDate( d.getFullYear(), d.getMonth(), d.getDate() ); }, _sanitizeMinDate: function( date ) { var minDate = this._sanitizeDate( date ); if( this._isAfterMaxDate( minDate ) ) { throw "Min date must be before max date."; } return minDate; }, _sanitizeMaxDate: function( date ) { var maxDate = this._sanitizeDate( date ); if( this._isBeforeMinDate( maxDate ) ) { throw "Max date must be after min date."; } return maxDate; }, _getDateFormat: function() { switch( this.options.accuracy ) { case "month": return "dateFormatMonth"; case "year": return "dateFormatYear"; default: return "dateFormat"; } }, dateString: function() { var format = this._getDateFormat(), date = this._getDate(); return !date ? '' : date.toString( this.options[ format ] ); }, localeString: function() { var l = this.options.locale, a = this._getDateFormat(), date = this._getDate(); return !date ? '' : date.toString( this._isValidLocale( l ) ? XDate.locales[ l ][ a ] : this.options[ a ] ); }, _fitDate: function( d ) { return this._isAfterMaxDate( d ) ? this._getMaxDate() : ( this._isBeforeMinDate( d ) ? this._getMinDate() : d ); }, _isAfterMaxDate: function( d ) { var maxDate = this._getMaxDate(); return this._isXDate( maxDate ) && d.diffDays( maxDate ) < 0; }, _isBeforeMinDate: function( d ) { var m = this._getMinDate(); return this._isXDate( m ) && m.diffDays( d ) < 0; }, _isValidLocale: function( l ) { return typeof l === "string" && XDate.locales && XDate.locales[ l ]; }, _isDate: function( d ) { return typeof d === "object" && d !== null && d.constructor === Date; }, _isXDate: function( x ) { return typeof x === "object" && x !== null && x.constructor === XDate; }, _getMaxDate: function() { var maxDate = this.options.maxDate; return this._isDate( maxDate ) ? new XDate( maxDate ) : null; }, _getMinDate: function() { var minDate = this.options.minDate; return this._isDate( minDate ) ? new XDate( minDate ) : null; }, _getDate: function() { var date = this.options.date; return this._isDate( date ) ? new XDate( date ) : null; }, _prevDay: function() { return this._addDay( -1 ); }, _nextDay: function() { return this._addDay( 1 ); }, _prevMonth: function() { return this._addMonth( -1 ); }, _nextMonth: function() { return this._addMonth( 1 ); }, _prevYear: function() { return this._addYear( -1 ); }, _nextYear: function() { return this._addYear( 1 ); }, _addYear: function( y ) { return this._getDate().addYears( y, true ); }, _addMonth: function( m ) { var date = this._getDate(), month = date.getMonth(), newMonth = ( 12 + month + m ) % 12; return date.setMonth( newMonth, true ); }, _addDay: function( d ) { var dt = this._getDate(), n = XDate.getDaysInMonth( dt.getFullYear(), dt.getMonth() ); return dt.setDate( ( dt.getDate() - 1 + n + d ) % n + 1 ); }, // // View // _markup_header: "
Date
", _applyTheme: function() { var p = this._picker, buttons = { "bottom": "ul.mobipick-groups ul > li:first-child > a", "top": "ul.mobipick-groups ul > li:last-child > a" }; for( var key in buttons ) { p.find( buttons[ key ] ).addClass( "ui-corner-all" ) .css( "border-" + key + "-left-radius", "0") .css( "border-" + key + "-right-radius", "0"); } p.addClass( "ui-body-" + this.options.popup.theme + " ui-corner-all" ); p.find( "a" ).attr( "href", "#" ) .addClass( "ui-body-" + this.options.buttonTheme ); p.find( "ul.mobipick-buttons a" ).addClass( "ui-corner-all" ); p.find( "input" ) .attr( "readonly", "readonly" ).addClass( "ui-shadow-inset" ); }, _createView: function() { this.element.attr( "readonly", "readonly" ); var markup = this._markup_header + this.options.inputOrder.split(" ").map(function(v, i, a) { return this["_markup_" + v]; }, this).join("") + this._markup_trailer; this._picker = $( markup ).popup( this.options.popup ); $.data( this.element, "mobipick", this ); this._applyTheme(); }, _updateView: function() { var date = this._getDate(), p = this._picker; if( this._isXDate( date ) ) { p.find( ".mobipick-year" ).val( date.toString( "yyyy" ) ); p.find( ".mobipick-month" ).val( date.toString( "MMM" ) ); p.find( ".mobipick-day" ).val( date.toString( "dd" ) ); p.find( ".mobipick-date-formatted" ).text( this.localeString() ); } var locale = {}; if( this._isValidLocale( this.options.locale ) ) { XDate.defaultLocale = this.options.locale; locale = XDate.locales[ this.options.locale ]; } p.find( ".mobipick-set" ).text( locale.ok || "Set" ); p.find( ".mobipick-cancel" ).text( locale.cancel || "Cancel" ); // Display items based on accuracy setting var columns = p.find( ".mobipick-groups > li" ) .removeClass( "mobipick-hide" ) .addClass( "mobipick-inline-block" ); if( this.options.accuracy === "month" ) { p.css( "max-width", "280px" ) .find( ".mobipick-groups > li:first-child" ) .addClass( "mobipick-hide" ) .removeClass( "mobipick-inline-block" ); } else if( this.options.accuracy === "year" ) { p.css( "max-width", "200px" ) .find( ".mobipick-groups > li:last-child" ) .siblings().addClass( "mobipick-hide" ) .removeClass( "mobipick-inline-block" ); } else { p.css( "max-width", "300px" ); } // minus 1% margin (left and right) per column var columnCount = columns.filter( ":visible" ).size(), width = ( ( 100 - columnCount * 2 ) / columnCount ) + "%"; columns.css( "width", width ); }, _showView: function() { this._picker.show().popup("open").focus(); }, _hideView: function() { this._picker.popup("close"); }, updateDateInput: function() { this.element.val( this.options.intlStdDate ? this.dateString() : this.localeString() ); } }); }));