1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Shows date and time, and make them able to be changed by user
3 //>>label: Datetime picker
4 //>>group: Tizen:Widgets
6 define( [ 'jquery.mobile.tizen.widgetex', 'jquery.mobile.tizen.popupwindow', 'jquery.mobile.tizen.popupwindow.ctxpopup' ], function ( ) {
7 //>>excludeEnd("jqmBuildExclude");
9 /*global Globalize:false, range:false, regexp:false*/
11 * jQuery Mobile Widget @VERSION
13 * This software is licensed under the MIT licence (as defined by the OSI at
14 * http://www.opensource.org/licenses/mit-license.php)
16 * ***************************************************************************
17 * Copyright (C) 2011 by Intel Corporation Ltd.
19 * Permission is hereby granted, free of charge, to any person obtaining a
20 * copy of this software and associated documentation files (the "Software"),
21 * to deal in the Software without restriction, including without limitation
22 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
23 * and/or sell copies of the Software, and to permit persons to whom the
24 * Software is furnished to do so, subject to the following conditions:
26 * The above copyright notice and this permission notice shall be included in
27 * all copies or substantial portions of the Software.
29 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
34 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
35 * DEALINGS IN THE SOFTWARE.
36 * ***************************************************************************
38 * Authors: Salvatore Iovene <salvatore.iovene@intel.com>
39 * Daehyon Jung <darrenh.jung@samsung.com>
43 * datetimepicker is a widget that lets the user select a date and/or a
44 * time. If you'd prefer use as auto-initialization of form elements,
45 * use input elements with type=date/time/datetime within form tag
46 * as same as other form elements.
50 * data-role: 'datetimepicker'
51 * data-format: date format string. e.g) "MMM dd yyyy, HH:mm"
52 * type: 'date', 'datetime', 'time'
53 * value: pre-set value. only accepts ISO date string. e.g) "2012-05-04", "2012-05-04T01:02:03+09:00"
54 * data-date: any date/time string "new Date()" accepts.
57 * type: 'date', 'datetime', 'time'
58 * format: see data-format in HTML Attributes.
59 * value: see value in HTML Attributes.
60 * date: preset value as JavaScript Date Object representation.
64 * : Set date/time to 'datestring'.
66 * : Get current selected date/time as W3C DTF style string.
67 * getValue() - replaced with 'value()'
69 * setValue( datestring ) - replaced with 'value(datestring)'
70 * : same as value( datestring )
71 * changeTypeFormat( type, format ) - deprecated
72 * : Change Type and Format options. use datetimepicker( "option", "format" ) instead
75 * date-changed: Raised when date/time was changed. Date-changed event will be deprecated
78 * <ul data-role="listview">
79 * <li class="ui-li-3-2-2">
80 * <span class="ui-li-text-main">
81 * <input type="datetime" name="demo-date" id="demo-date"
82 * data-format="MMM dd yyyy hh:mm tt"/>
84 * <span class="ui-li-text-sub">
85 * Date/Time Picker - <span id="selected-date1"><em>(select a date first)</em></span>
88 * <li class="ui-li-3-2-2">
89 * <span class="ui-li-text-main">
90 * <input type="date" name="demo-date2" id="demo-date2"/>
92 * <span class="ui-li-text-sub">
93 * Date Picker - <span id="selected-date2"><em>(select a date first)</em></span>
96 * <li class="ui-li-3-2-2">
97 * <span class="ui-li-text-main">
98 * <input type="time" name="demo-date3" id="demo-date3"/>
100 * <span class="ui-li-text-sub">
101 * Time Picker - <span id="selected-date3"><em>(select a date first)</em></span>
105 * How to get a return value:
106 * ==========================
107 * Bind to the 'date-changed' event, e.g.:
108 * $("#myDatetimepicker").bind("change", function() {
114 @class DateTimePicker
115 The picker widgets show a control that you can use to enter date and time values. <br/> To add a date time picker widget to the application, use the following code:
117 <li class="ui-li-dialogue ui-datetime">
118 <div class="ui-datetime-text-main">
119 <input type="datetime" data-format="MMM dd yyyy hh:mm:ss" name="demo-date" id="demo-date" />
121 <div class="ui-li-text-sub">Date/Time Picker
122 <span id="selected-date1"><em>(select a date first)</em></span>
128 ( function ( $, window, undefined ) {
129 $.widget( "tizen.datetimepicker", $.tizen.widgetex, {
132 type: null, // date, time, datetime applicable
135 initSelector: "input[type='date'], input[type='datetime'], input[type='time'], :jqmData(role='datetimepicker')"
140 _calendar: function () {
141 return window.Globalize.culture().calendars.standard;
145 attr: "data-" + ( $.mobile.ns || "" ) + "date",
146 signal: "date-changed"
149 _daysInMonth: [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ],
151 _isLeapYear: function ( year ) {
152 return year % 4 ? 0 : ( year % 100 ? 1 : ( year % 400 ? 0 : 1 ) );
155 _makeTwoDigits: function ( val ) {
156 var ret = val.toString(10);
163 _setType: function ( type ) {
164 //datetime, date, time
169 this.options.type = type;
172 this.options.type = 'datetime';
176 this.element.attr( "data-" + ( $.mobile.ns ? $.mobile.ns + "-" : "" ) + "type", this.options.type );
177 return this.options.type;
180 _setFormat: function ( format ) {
181 if ( this.options.format != format ) {
182 this.options.format = format;
187 this.ui.children().remove();
189 var token = this._parsePattern( format ),
190 div = document.createElement('div'),
197 while ( token.length > 0 ) {
199 tpl = '<span class="ui-datefield-%1" data-pat="' + pat + '">%2</span>';
201 case 'H': //0 1 2 3 ... 21 22 23
202 case 'HH': //00 01 02 ... 21 22 23
203 case 'h': //0 1 2 3 ... 11 12
204 case 'hh': //00 01 02 ... 11 12
205 $(div).append( tpl.replace('%1', 'hour') );
207 case 'mm': //00 01 ... 59
208 case 'm': //0 1 2 ... 59
209 if ( this.options.type == 'date' ) {
210 $(div).append( tpl.replace('%1', 'month') );
212 $(div).append( tpl.replace('%1', 'min') );
217 $(div).append( tpl.replace('%1', 'sec') );
219 case 'd': // day of month 5
220 case 'dd': // day of month(leading zero) 05
221 $(div).append( tpl.replace('%1', 'day') );
223 case 'M': // Month of year 9
224 case 'MM': // Month of year(leading zero) 09
227 $(div).append( tpl.replace('%1', 'month') );
229 case 'yy': // year two digit
230 case 'yyyy': // year four digit
231 $(div).append( tpl.replace('%1', 'year') );
233 case 't': //AM / PM indicator(first letter) A, P
235 case 'tt': //AM / PM indicator AM/PM
237 btn = '<a href="#" class="ui-datefield-period"' +
238 ' data-role="button" data-inline="true">period</a>';
239 $(div).append( btn );
243 $(div).append( tpl.replace('%1', 'era').replace('%2', this._calendar().eras.name) );
246 $(div).append( tpl.replace('%1', 'tab').replace('%2', pat) );
248 default : // string or any non-clickable object
249 $(div).append( tpl.replace('%1', 'seperator').replace('%2', pat) );
254 this.ui.append( div );
255 if ( this.options.date ) {
256 this._setDate( this.options.date );
259 this.ui.find('.ui-datefield-period').buttonMarkup().bind( 'vclick', function ( e ) {
260 obj._switchAmPm( obj );
263 this.element.attr( "data-" + ( $.mobile.ns ? $.mobile.ns + "-" : "" ) + "format", this.options.format );
264 return this.options.format;
267 _setDate: function ( newdate ) {
268 if ( typeof ( newdate ) == "string" ) {
269 newdate = new Date( newdate );
272 var fields = $('span,a', this.ui),
279 function getMonth() {
280 return newdate.getMonth() + 1;
283 for ( i = 0; i < fields.length; i++ ) {
284 $field = $(fields[i]);
285 type = $field.attr("class").match(/ui-datefield-([\w]*)/);
291 fn = newdate.getHours;
294 fn = newdate.getMinutes;
297 fn = newdate.getSeconds;
300 fn = newdate.getFullYear;
306 fn = newdate.getDate;
309 fn = newdate.getHours() < 12 ? this._calendar().AM[0] : this._calendar().PM[0];
310 btn = $field.find( '.ui-btn-text' );
311 if ( btn.length == 0 ) {
313 } else if ( btn.text() != fn ) {
323 this._updateField( $field, fn.call( newdate ) );
327 this.options.date = newdate;
329 this._setValue( newdate );
331 this.element.attr( "data-" + ( $.mobile.ns ? $.mobile.ns + "-" : "" ) + "date", this.options.date );
332 return this.options.date;
335 destroy: function () {
340 if ( this.element ) {
345 value: function ( val ) {
346 function timeStr( t, obj ) {
347 return obj._makeTwoDigits( t.getHours() ) + ':' +
348 obj._makeTwoDigits( t.getMinutes() ) + ':' +
349 obj._makeTwoDigits( t.getSeconds() );
352 function dateStr( d, obj ) {
353 return ( ( d.getFullYear() % 10000 ) + 10000 ).toString().substr(1) + '-' +
354 obj._makeTwoDigits( d.getMonth() + 1 ) + '-' +
355 obj._makeTwoDigits( d.getDate() );
360 rvalue = this._setDate( val );
362 switch ( this.options.type ) {
364 rvalue = timeStr( this.options.date, this );
367 rvalue = dateStr( this.options.date, this );
370 rvalue = dateStr( this.options.date, this ) + 'T' + timeStr( this.options.date, this );
377 setValue: function ( newdate ) {
378 console.warn( "setValue was deprecated. use datetimepicker('option', 'date', value) instead." );
379 return this.value( newdate );
383 * return W3C DTF string
385 getValue: function () {
386 console.warn("getValue() was deprecated. use datetimepicker('value') instead.");
390 _updateField: function ( target, value ) {
391 if ( !target || target.length == 0 ) {
399 var pat = target.jqmData( 'pat' ),
410 if ( pat.charAt(0) == 'h' ) {
413 } else if ( hour == 0 ) {
417 hour = this._makeTwoDigits( hour );
430 text = this._makeTwoDigits( value );
433 text = this._calendar().months.namesAbbr[ value - 1];
436 text = this._calendar().months.names[ value - 1 ];
439 text = this._makeTwoDigits( value % 100 );
443 value = '000' + value;
444 } else if ( value < 100 ) {
445 value = '00' + value;
446 } else if ( value < 1000 ) {
453 // to avoid reflow where its value isn't out-dated
454 if ( target.text() != text ) {
455 if ( target.hasClass("ui-datefield-selected") ) {
456 target.addClass("out");
457 this._new_value = text;
459 target.animationComplete( function () {
460 target.text( self._new_value);
461 target.addClass("in")
464 target.animationComplete( function () {
465 target.removeClass("in").
466 removeClass("ui-datefield-selected");
475 _switchAmPm: function ( obj ) {
476 if ( this._calendar().AM != null ) {
477 var date = new Date( this.options.date ),
479 change = 1000 * 60 * 60 * 12;
480 if ( date.getHours() > 11 ) {
483 date.setTime( date.getTime() + change );
484 this._setDate( date );
488 _parsePattern: function ( pattern ) {
489 var regex = /\/|\s|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|f|gg|g|\'[\w\W]*\'$|[\w\W]/g,
493 matches = pattern.match( regex );
495 for ( i = 0; i < matches.length; i++ ) {
496 if ( matches[i].charAt(0) == "'" ) {
497 matches[i] = matches[i].substr( 1, matches[i].length - 2 );
504 changeTypeFormat: function ( type, format ) {
505 console.warn('changeTypeFormat() was deprecated. use datetimepicker("option", "type"|"format", value) instead');
507 this._setType( type );
511 this._setFormat( format );
515 _create: function () {
518 if ( this.element.is( "input" ) ) {
520 var type, value, format;
522 type = obj.element.get(0).getAttribute( "type" );
523 obj.options.type = type;
525 value = obj.element.get(0).getAttribute( "value" );
527 obj.options.date = new Date( value );
532 if ( !this.options.format ) {
533 switch ( this.options.type ) {
535 this.options.format = this._calendar().patterns.d + "\t" + this._calendar().patterns.t;
538 this.options.format = this._calendar().patterns.d;
541 this.options.format = this._calendar().patterns.t;
546 if ( !this.options.date ) {
547 this.options.date = new Date();
551 this.ui = $('<div class="ui-datefield"></div>');
552 $(this.element).after( this.ui );
554 this._popup_open = false;
555 this.ui.bind('vclick', function ( e ) {
556 obj._showDataSelector( obj, this, e.target );
564 orientationchange: $.proxy( this, "_orientationHandler" )
570 $.each( this._globalHandlers, function( idx, value ) {
571 value.src.bind( value.handler );
574 _orientationHandler: function() {
576 if( self._popup_open ) {
577 self._popup_open = false;
578 self.container.popupwindow( 'close' );
582 _populateDataSelector: function ( field, pat ) {
587 range = window.range,
595 if ( pat == 'H' || pat == 'HH' ) {
597 values = range( 0, 23 );
598 data = range( 0, 23 );
599 current = this.options.date.getHours();
601 values = range( 1, 12 );
602 current = this.options.date.getHours() - 1;//11
603 if ( current >= 11 ) {
604 current = current - 12;
605 data = range( 13, 23 );
606 data.push( 12 ); // consider 12:00 am as 00:00
608 data = range( 1, 11 );
612 current = 11; // 12:00 or 00:00
615 if ( pat.length == 2 ) {
617 values = values.map( this._makeTwoDigits );
619 numItems = values.length;
623 values = range( 0, 59 );
624 if ( pat.length == 2 ) {
625 values = values.map( this._makeTwoDigits );
627 data = range( 0, 59 );
628 current = ( field == 'min' ? this.options.date.getMinutes() : this.options.date.getSeconds() );
629 numItems = values.length;
634 data = range( yearlb, yearhb );
635 current = this.options.date.getFullYear() - yearlb;
636 values = range( yearlb, yearhb );
637 numItems = values.length;
640 switch ( pat.length ) {
642 values = range( 1, 12 );
645 values = range( 1, 12 ).map( this._makeTwoDigits );
648 values = this._calendar().months.namesAbbr.slice();
651 values = this._calendar().months.names.slice();
654 if ( values.length == 13 ) { // @TODO Lunar calendar support
655 if ( values[12] == "" ) { // to remove lunar calendar reserved space
659 data = range( 1, values.length );
660 current = this.options.date.getMonth();
661 numItems = values.length;
664 day = this._daysInMonth[ this.options.date.getMonth() ];
666 day += this._isLeapYear( this.options.date.getFullYear() );
668 values = range( 1, day );
669 if ( pat.length == 2 ) {
670 values = values.map( this._makeTwoDigits );
672 data = range( 1, day );
673 current = this.options.date.getDate() - 1;
687 _showDataSelector: function ( obj, ui, target ) {
690 var attr = target.attr("class"),
691 field = attr ? attr.match(/ui-datefield-([\w]*)/) : undefined,
714 if ( this._popup_open ) {
718 target.not('.ui-datefield-seperator').addClass('ui-datefield-selected');
720 pat = target.jqmData('pat');
721 data = obj._populateDataSelector.call( obj, field[1], pat );
723 values = data.values;
724 numItems = data.numItems;
725 current = data.current;
726 valuesData = data.data;
729 datans = "data-" + ($.mobile.ns ? ($.mobile.ns + '-') : "") + 'val="';
730 for ( i = 0; i < values.length; i++ ) {
731 html += '<li><a class="ui-link" ' + datans + valuesData[i] + '">' + values[i] + '</a></li>';
734 $ul = $("<ul></ul>");
735 $div = $('<div class="ui-datetimepicker-selector" data-transition="fade" data-fade="false"></div>');
736 $div.append( $ul ).appendTo( ui );
737 $ctx = $div.ctxpopup();
738 $ctx.parents('.ui-popupwindow').addClass('ui-datetimepicker');
740 $( $li[current] ).addClass("current");
741 $div.jqmData( "list", $li );
744 obj._reflow = function() {
745 $div.circularview( "reflow" );
746 $div.circularview( 'centerTo', '.current', 0 );
748 $(window).bind( "resize" , obj._reflow );
750 // cause ctxpopup forced to subtract 10
751 if ( $( window ).width() / 2 < target.offset().left ) {
754 $ctx.popupwindow( 'open',
755 target.offset().left + ( target.width() / 2 ) + newLeft - window.pageXOffset ,
756 target.offset().top + target.height() - window.pageYOffset );
758 this.container = $ctx;
759 this._popup_open = true;
761 $div.bind('popupafterclose', function ( e ) {
763 $(window).unbind("resize", obj._reflow);
767 if ( !( target.hasClass("in") || target.hasClass("out") ) ) {
768 target.removeClass("ui-datefield-selected");
771 $div.unbind( 'popupafterclose' );
772 $ul.unbind( 'vclick' );
773 $(obj).unbind( 'update' );
774 $ctx.popupwindow( 'destroy' );
777 self._popup_open = false;
780 $(obj).bind( 'update', function ( e, val ) {
781 var date = new Date( this.options.date ),
783 date_calibration = function () {
785 date.setDate( date.getDate() - 1 );
788 switch ( field[1] ) {
790 date.setMinutes( val );
793 date.setHours( val );
796 date.setSeconds( val );
799 month = date.getMonth();
800 date.setFullYear( val );
802 if ( date.getMonth() != month ) {
807 date.setMonth( val - 1 );
809 if ( date.getMonth() == val ) {
818 obj._setDate( date );
820 $ctx.popupwindow( 'close' );
823 $ul.bind( 'click', function ( e ) {
824 if ( $(e.target).is('a') ) {
825 $ul.find(".current").removeClass("current");
826 $(e.target).parent().addClass('current');
827 var val = $(e.target).jqmData("val");
828 $(obj).trigger( 'update', val ); // close popup, unselect field
832 $div.circularview( 'centerTo', '.current', 500 );
833 $div.bind( 'scrollend' , function ( e ) {
834 if ( !obj._reflow ) {
835 obj._reflow = function () {
836 $div.circularview("reflow");
838 $(window).bind("resize", obj._reflow);
847 $(document).bind("pagecreate create", function ( e ) {
848 $($.tizen.datetimepicker.prototype.options.initSelector, e.target)
849 .not(":jqmData(role='none'), :jqmData(role='nojs')")
853 } ( jQuery, this ) );
855 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
857 //>>excludeEnd("jqmBuildExclude");