1 /*global Globalize:false, range:false, regexp:false*/
3 * jQuery Mobile Widget @VERSION
5 * This software is licensed under the MIT licence (as defined by the OSI at
6 * http://www.opensource.org/licenses/mit-license.php)
8 * ***************************************************************************
9 * Copyright (C) 2011 by Intel Corporation Ltd.
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 * ***************************************************************************
30 * Authors: Salvatore Iovene <salvatore.iovene@intel.com>
31 * Daehyon Jung <darrenh.jung@samsung.com>
35 * datetimepicker is a widget that lets the user select a date and/or a
36 * time. If you'd prefer use as auto-initialization of form elements,
37 * use input elements with type=date/time/datetime within form tag
38 * as same as other form elements.
42 * data-role: 'datetimepicker'
43 * data-format: date format string. e.g) "MMM dd yyyy, HH:mm"
44 * type: 'date', 'datetime', 'time'
45 * data-val: pre-set value. any date/time string Date.parse() accepts.
48 * type: 'date', 'datetime', 'time'
49 * format: see data-format in HTML Attributes.
50 * val: see data-val in HTML Attributes.
54 * : Get current selected date/time as W3C DTF style string.
56 * : Force to update fields.
59 * data-changed: Raised when date/time was changed.
62 * <ul data-role="listview">
63 * <li class="ui-li-3-2-2">
64 * <span class="ui-li-text-main">
65 * <input type="datetime" name="demo-date" id="demo-date"
66 * data-format="MMM dd yyyy hh:mm tt"/>
68 * <span class="ui-li-text-sub">
69 * Date/Time Picker - <span id="selected-date1"><em>(select a date first)</em></span>
72 * <li class="ui-li-3-2-2">
73 * <span class="ui-li-text-main">
74 * <input type="date" name="demo-date2" id="demo-date2"/>
76 * <span class="ui-li-text-sub">
77 * Date Picker - <span id="selected-date2"><em>(select a date first)</em></span>
80 * <li class="ui-li-3-2-2">
81 * <span class="ui-li-text-main">
82 * <input type="time" name="demo-date3" id="demo-date3"/>
84 * <span class="ui-li-text-sub">
85 * Time Picker - <span id="selected-date3"><em>(select a date first)</em></span>
89 * How to get a return value:
90 * ==========================
91 * Bind to the 'date-changed' event, e.g.:
92 * $("#myDatetimepicker").bind("date-changed", function(e, date) {
93 * alert("New date: " + date.toString());
98 ( function ( $, window, undefined ) {
99 $.widget( "tizen.datetimepicker", $.tizen.widgetex, {
101 type: 'datetime', // date, time, datetime applicable
104 initSelector: "input[type='date'], input[type='datetime'], input[type='time'], :jqmData(role='datetimepicker')"
107 _makeTwoDigits: function ( val ) {
108 var ret = val.toString(10);
117 * return W3C DTF string
119 getValue: function () {
127 for ( item in this.data ) {
128 data[item] = this.data[item];
131 if ( this.calendar.convert ) {
132 greg = this.calendar.convert.toGregorian( data.year, data.month, data.day );
133 data.year = greg.getFullYear();
134 data.month = greg.getMonth();
135 data.day = greg.getDate();
138 toTimeString = function timeStr( t ) {
139 return obj._makeTwoDigits( t.hour ) + ':' +
140 obj._makeTwoDigits( t.min ) + ':' +
141 obj._makeTwoDigits( t.sec );
144 toDateString = function dateStr( d ) {
145 return ( ( d.year % 10000 ) + 10000 ).toString().substr(1) + '-' +
146 obj._makeTwoDigits( d.month ) + '-' +
147 obj._makeTwoDigits( d.day );
150 switch ( this.options.type ) {
152 return toTimeString( data );
154 return toDateString( data );
156 return toDateString( data ) + 'T' + toTimeString( data );
160 _updateField: function ( target, value ) {
161 if ( !target || target.length == 0 ) {
169 var pat = target.jqmData( 'pat' ),
177 if ( pat.charAt(0) == 'h' ) {
180 } else if ( hour == 0 ) {
184 if ( pat.length == 2 ) {
185 hour = this._makeTwoDigits( hour );
193 target.text( value );
199 target.text( this._makeTwoDigits( value ) );
202 target.text( this.calendar.months.namesAbbr[ value - 1] );
205 target.text( this.calendar.months.names[ value - 1 ] );
208 target.text( this._makeTwoDigits( value % 100 ) );
212 value = '000' + value;
213 } else if ( value < 100 ) {
214 value = '00' + value;
215 } else if ( value < 1000 ) {
218 target.text( value );
224 _format: function ( pattern ) {
225 var token = this._parsePattern( pattern ),
226 div = document.createElement('div'),
233 while ( token.length > 0 ) {
235 tpl = '<span class="ui-datefield-%1" data-pat="' + pat + '">%2</span>';
237 case 'H': //0 1 2 3 ... 21 22 23
238 case 'HH': //00 01 02 ... 21 22 23
239 case 'h': //0 1 2 3 ... 11 12
240 case 'hh': //00 01 02 ... 11 12
241 $(div).append( tpl.replace('%1', 'hour') );
244 case 'mm': //00 01 ... 59
245 case 'm': //0 1 2 ... 59
246 $(div).append( tpl.replace('%1', 'min') );
251 $(div).append( tpl.replace('%1', 'sec') );
254 case 'd': // day of month 5
255 case 'dd': // day of month(leading zero) 05
256 $(div).append( tpl.replace('%1', 'day') );
259 case 'M': // Month of year 9
260 case 'MM': // Month of year(leading zero) 09
263 $(div).append( tpl.replace('%1', 'month') );
266 case 'yy': // year two digit
267 case 'yyyy': // year four digit
268 $(div).append( tpl.replace('%1', 'year') );
271 case 't': //AM / PM indicator(first letter) A, P
273 case 'tt': //AM / PM indicator AM/PM
275 ampm = this.data.hour > 11 ?
276 this.calendar.PM[0] : this.calendar.AM[0];
277 btn = '<a href="#" class="ui-datefield-ampm"' +
278 ' data-role="button" data-inline="true">' +
280 $(div).append( btn );
285 $(div).append( tpl.replace('%1', 'era').replace('%2', this.calendar.eras.name) );
287 default : // string or any non-clickable object
288 $(div).append( tpl.replace('%1', 'seperator').replace('%2', pat) );
299 _switchAmPm: function ( obj, owner ) {
300 if ( this.calendar.AM != null ) {
301 if ( this.calendar.AM[0] == $(owner).find('.ui-btn-text').text() ) { // AM to PM
302 this.data.hour += 12;
303 $(owner).find('.ui-btn-text').text( this.calendar.PM[0] );
305 this.data.hour -= 12;
306 $(owner).find('.ui-btn-text').text( this.calendar.AM[0] );
312 update: function () {
313 if ( $(this.elem).is('input') ) {
314 this.options.val = this.getValue();
315 this.elem.value = this.options.val;
317 $(this.elem).trigger('date-changed', this.getValue() );
320 _parsePattern: function ( pattern ) {
321 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\s]*?')/,
325 while ( pattern.length > 0 ) {
326 s = regex.exec( pattern );
328 pattern = pattern.substr( s[0].length );
329 if ( s[0].charAt(0) == "'" ) {
330 s[0] = s[0].substr( 1, s[0].length - 2 );
334 token.push( pattern.charAt(0) );
335 pattern = pattern.substr(1);
342 _create: function () {
343 var input = this.element.get(0),
344 type = $(input).attr("type"),
355 obj.options.type = type;
358 isTime = type.indexOf("time") > -1;
359 isDate = type.indexOf("date") > -1;
364 calendar: window.Globalize.culture().calendars.standard,
377 val = this.options.val;
379 now = new Date( Date.parse( val ) );
386 if ( obj.calendar.convert ) {
387 local = obj.calendar.convert.fromGregorian( now );
388 data.year = local.year;
389 data.month = local.month + 1;
390 data.day = local.day;
392 data.year = now.getFullYear();
393 data.month = now.getMonth() + 1;
394 data.day = now.getDate();
399 data.hour = now.getHours();
400 data.min = now.getMinutes();
401 data.sec = now.getSeconds();
404 $(input).css('display', 'none');
405 $div = $(document.createElement('div'));
406 $div.addClass('ui-datefield');
407 $(input).after( $div );
408 this._initField( this.options.type, $div );
409 $div.trigger('create');
411 $div.bind('vclick', function ( e ) {
412 obj._showDataSelector( obj, this, e.target );
415 $div.find('.ui-datefield-ampm').bind( 'vclick', function ( e ) {
416 obj._switchAmPm( obj, this );
420 _populateDataSelector: function ( field, pat, obj ) {
425 range = window.range,
436 values = range( 0, 23 );
437 data = range( 0, 23 );
438 current = obj.data.hour;
440 values = range( 1, 12 );
441 current = obj.data.hour - 1;//11
442 if ( current >= 11 ) {
443 current = current - 12;
444 data = range( 13, 23 );
445 data.push( 12 ); // consider 12:00 am as 00:00
447 data = range( 1, 11 );
451 current = 11; // 12:00 or 00:00
454 if ( pat.length == 2 ) {
456 values = values.map( obj._makeTwoDigits );
458 numItems = values.length;
462 values = range( 0, 59 );
463 if ( pat.length == 2 ) {
464 values = values.map( obj._makeTwoDigits );
466 data = range( 0, 59 );
467 current = ( field == 'min' ? obj.data.min : obj.data.sec );
468 numItems = values.length;
471 local = new Date( 1900, 0, 1 );
472 if ( obj.calendar.convert ) {
473 local = obj.calendar.convert.fromGregorian( local );
475 yearhb = yearlb + 200;
477 yearlb = local.getFullYear();
478 yearhb = yearlb + 200;
480 data = range( yearlb, yearhb );
481 current = obj.data.year - yearlb;
482 values = range( yearlb, yearhb );
483 numItems = values.length;
486 switch ( pat.length ) {
488 values = range( 1, 12 );
491 values = range( 1, 12 ).map( obj._makeTwoDigits );
494 values = obj.calendar.months.namesAbbr.slice();
497 values = obj.calendar.months.names.slice();
500 if ( values.length == 13 ) { // @TODO Lunar calendar support
501 if ( values[12] == "" ) { // to remove lunar calendar reserved space
505 data = range( 1, values.length );
506 current = obj.data.month - 1;
507 numItems = values.length;
510 //@TODO max number 31 -> depends on month
512 values = range( 1, day );
513 if ( pat.length == 2 ) {
514 values = values.map( obj._makeTwoDigits );
516 data = range( 1, day );
517 current = obj.data.day - 1;
531 _showDataSelector: function ( obj, ui, target ) {
534 var attr = target.attr("class"),
535 field = attr.match(/ui-datefield-([\w]*)/),
556 target.not('.ui-datefield-seperator').addClass('ui-datefield-selected');
558 pat = target.jqmData('pat');
559 data = obj._populateDataSelector( field[1], pat, obj );
561 values = data.values;
562 numItems = data.numItems;
563 current = data.current;
564 valuesData = data.data;
567 $ul = $(document.createElement('ul'));
568 for ( item in values ) {
569 $li = $(document.createElement('li'));
570 $item = $(document.createElement('a'));
571 $item.addClass('ui-link');
572 $item.text( values[item] );
573 $item.jqmData( "val", valuesData[item] );
578 if ( current == item ) {
579 $li.addClass('current');
583 /* TODO NEED TO REFACTORING HERE */
584 $div = $(document.createElement('div'));
585 $div.append( $ul ).appendTo( ui );
586 $div.addClass('ui-datetimepicker-selector');
587 $div.attr( 'data-transition', 'none' );
588 $ctx = $div.ctxpopup();
589 $ctx.parents('.ui-popupwindow').addClass('ui-datetimepicker');
591 $div.circularview( 'centerTo', '.current' );
592 $ctx.popupwindow( 'open',
593 target.offset().left + target.width() / 2 - window.pageXOffset,
594 target.offset().top + target.height() - window.pageYOffset );
595 $div.bind('closed', function ( e ) {
596 $div.unbind( 'closed' );
597 $ul.unbind( 'vclick' );
598 $(obj).unbind( 'update' );
599 $(ui).find('.ui-datefield-selected').removeClass('ui-datefield-selected');
600 $ctx.popupwindow( 'destroy' );
604 $(obj).bind( 'update', function ( e, val ) {
605 $ctx.popupwindow( 'close' );
606 var data = $(ui).find( '.' + field[0] );
607 obj._updateField( $(data), val );
608 obj.data[ field[1] ] = val;
612 $ul.bind( 'vclick', function ( e ) {
613 if ( $(e.target).is('a') ) {
614 $ul.find(".current").removeClass("current");
615 $(e.target).parent().addClass('current');
616 var val = $(e.target).jqmData("val");
617 $(obj).trigger( 'update', val ); // close popup, unselect field
623 _initField: function ( type, div ) {
627 updateFields = function ( obj, html, attr ) {
629 for ( item in attr ) {
631 obj._updateField( $(html).find( '.ui-datefield-' + item ),
637 if ( this.options.format ) {
638 datetime = this._format( this.options.format );
639 updateFields( this, datetime.html, datetime.attr );
640 div.append( datetime.html );
642 if ( type.match( 'date' ) ) {
643 date = this._format( this.calendar.patterns.d );
644 $(date.html).addClass('date');
645 updateFields( this, date.html, date.attr );
646 div.append( date.html );
649 if ( type.match( 'datetime' ) ) {
650 div.append( '<span class="ui-datefield-tab"></span>' );
653 if ( type.match( 'time' ) ) {
654 time = this._format( this.calendar.patterns.t );
655 $(time.html).addClass('time');
656 updateFields( this, time.html, time.attr );
657 div.append( time.html );
664 $(document).bind("pagecreate create", function ( e ) {
665 $($.tizen.datetimepicker.prototype.options.initSelector, e.target)
666 .not(":jqmData(role='none'), :jqmData(role='nojs')")
670 } ( jQuery, this ) );