a45cee9bc541966178d682b22674be1181a68af9
[framework/web/web-ui-fw.git] / src / widgets / datetimepicker / js / jquery.mobile.tizen.datetimepicker.js
1 /*
2  * jQuery Mobile Widget @VERSION
3  *
4  * This software is licensed under the MIT licence (as defined by the OSI at
5  * http://www.opensource.org/licenses/mit-license.php)
6  *
7  * ***************************************************************************
8  * Copyright (C) 2011 by Intel Corporation Ltd.
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  * ***************************************************************************
28  *
29  * Authors: Salvatore Iovene <salvatore.iovene@intel.com>
30  *                      Daehyon Jung <darrenh.jung@samsung.com>
31  */
32
33 /**
34  * datetimepicker is a widget that lets the user select a date and/or a 
35  * time. If you'd prefer use as auto-initialization of form elements, 
36  * use input elements with type=date/time/datetime within form tag
37  * as same as other form elements.
38  * 
39  * HTML Attributes:
40  * 
41  *      data-role: 'datetimepicker'
42  *      data-format: date format string. e.g) "MMM dd yyyy, HH:mm"
43  *      type: 'date', 'datetime', 'time'
44  *
45  * Options:
46  *      type: 'date', 'datetime', 'time'
47  *      format: see data-format in HTML Attributes.
48  *
49  * APIs:
50  *      getValue()
51  *              : Get current selected date/time as W3C DTF style string.
52  *      update()
53  *              : Force to update fields.
54  *
55  * Events:
56  *      data-changed: Raised when date/time was changed.
57  *
58  * Examples:
59  *      <ul data-role="listview">
60  *              <li class="ui-li-3-2-2">
61  *                      <span class="ui-li-text-main">
62  *                              <input type="datetime" name="demo-date" id="demo-date" 
63  *                                      data-format="MMM dd yyyy hh:mm tt"/>
64  *                      </span>
65  *                      <span class="ui-li-text-sub">
66  *                              Date/Time Picker - <span id="selected-date1"><em>(select a date first)</em></span>
67  *                      </span>
68  *              </li>
69  *              <li class="ui-li-3-2-2">
70  *                      <span class="ui-li-text-main">
71  *                              <input type="date" name="demo-date2" id="demo-date2"/>
72  *                      </span>
73  *                      <span class="ui-li-text-sub">
74  *                              Date Picker  - <span id="selected-date2"><em>(select a date first)</em></span>
75  *                      </span>
76  *              </li>
77  *              <li class="ui-li-3-2-2">
78  *                      <span class="ui-li-text-main">
79  *                              <input type="time" name="demo-date3" id="demo-date3"/>
80  *                      </span>
81  *                      <span class="ui-li-text-sub">
82  *                              Time Picker - <span id="selected-date3"><em>(select a date first)</em></span>
83  *                      </span>
84  *              </li>
85  *      </ul>
86  * How to get a return value:
87  * ==========================
88  * Bind to the 'date-changed' event, e.g.:
89  *    $("#myDatetimepicker").bind("date-changed", function(e, date) {
90  *        alert("New date: " + date.toString());
91  *    });
92  */
93
94
95 (function($, window, undefined) {
96         $.widget("tizen.datetimepicker", $.tizen.widgetex, {
97                 options: {
98                         type: 'datetime', // date, time, datetime applicable
99                         format: null,
100                         initSelector: "input[type='date'], input[type='datetime'], input[type='time'], :jqmData(role='datetimepicker')"
101                 },
102
103                 _makeTwoDigits: function(val) {
104                         var ret = val.toString(10);
105
106                         if ( val < 10 ) {
107                                 ret = "0" + ret;
108                         }
109                         return ret;
110                 },
111
112                 /**
113                  * return W3C DTF string
114                  */
115                 getValue: function() {
116                         var data = [];
117                         for ( item in this.data ) {
118                                 data[item] = this.data[item];
119                         }
120
121                         if ( this.calendar.convert ) {
122                                 var greg = this.calendar.convert.toGregorian(
123                                                 data.year, data.month, data.day );
124                                 data["year"] = greg.getFullYear();
125                                 data["month"] = greg.getMonth();
126                                 data["day"] = greg.getDate();
127                         }
128                         var obj = this;
129                         var toTimeString = function timeStr( t ) {
130                                 return  obj._makeTwoDigits( t["hour"] ) + ':' +
131                                         obj._makeTwoDigits( t["min"] ) + ':' +
132                                         obj._makeTwoDigits( t["sec"] );
133                         };
134
135                         var toDateString = function dateStr( d ) {
136                                 return ( "" + ( ( d["year"] % 10000 ) + 10000 ) ).substr(1) + '-' +
137                                         obj._makeTwoDigits( d["month"] ) + '-' +
138                                         obj._makeTwoDigits( d["day"] );
139                         };
140
141                         switch ( this.options.type ) {
142                         case 'time':
143                                 return toTimeString( data );
144                         case 'date':
145                                 return toDateString( data );
146                         default:
147                                 return toDateString( data ) + 'T' + toTimeString( data );
148                         }
149                 },
150
151                 _updateField: function( target, value ) {
152                         if ( !target || target.length == 0 ) {
153                                 return;
154                         }
155
156                         if ( value == 0 ) {
157                                 value = "0";
158                         }
159
160                         var pat = target.jqmData( 'pat' );
161                         switch ( pat ) {
162                         case 'H':
163                         case 'HH':
164                         case 'h':
165                         case 'hh':
166                                 var hour = value;
167                                 if ( pat.charAt(0) == 'h' ) {
168                                         if ( hour > 12 ) {
169                                                 hour -= 12;
170                                         }
171                                         else if ( hour == 0 ) {
172                                                 hour = 12;
173                                         }
174                                 }
175                                 if ( pat.length == 2 ) {
176                                         hour = this._makeTwoDigits( hour );
177                                 }
178                                 target.text( hour );
179                                 break;
180                         case 'm':
181                         case 'M':
182                         case 'd':
183                         case 's':
184                                 target.text( value );
185                                 break;
186                         case 'mm':
187                         case 'dd':
188                         case 'MM':
189                         case 'ss':
190                                 target.text( this._makeTwoDigits( value ) );
191                                 break;
192                         case 'MMM':
193                                 target.text( this.calendar.months.namesAbbr[ value - 1] );
194                                 break;
195                         case 'MMMM':
196                                 target.text( this.calendar.months.names[ value - 1 ] );
197                                 break;
198                         case 'yy':
199                                 target.text( this._makeTwoDigits( value % 100 ) );
200                                 break;
201                         case 'yyyy':
202                                 if ( value < 10 ) {
203                                         value = '000' + value;
204                                 } else if ( value < 100 ) {
205                                         value = '00' + value;
206                                 } else if ( value < 1000 ) {
207                                         value = '0' + value;
208                                 }
209                                 target.text( value );
210                                 break;
211                         }
212
213                 },
214
215                 _format: function( pattern ) {
216                         var token = this._parsePattern( pattern );
217                         var div = document.createElement('div');
218                         var attr = [];
219                         while ( token.length > 0 ) {
220                                 var pat = token.shift();
221                                 var tpl = '<span class="ui-datefield-%1" data-pat="' + pat + '">%2</span>';
222                                 switch ( pat ) {
223                                 case 'H': //0 1 2 3 ... 21 22 23
224                                 case 'HH': //00 01 02 ... 21 22 23
225                                 case 'h': //0 1 2 3 ... 11 12
226                                 case 'hh': //00 01 02 ... 11 12                                         
227                                         $(div).append( tpl.replace('%1', 'hour') );
228                                         attr['hour'] = true;
229                                         break;
230                                 case 'mm': //00 01 ... 59
231                                 case 'm': //0 1 2 ... 59
232                                         $(div).append( tpl.replace('%1', 'min') );
233                                         attr['min'] = true;
234                                         break;
235                                 case 'ss':
236                                 case 's':
237                                         $(div).append( tpl.replace('%1', 'sec') );
238                                         attr['sec'] = true;
239                                         break;
240                                 case 'd': // day of month 5                                     
241                                 case 'dd': // day of month(leading zero) 05
242                                         $(div).append( tpl.replace('%1', 'day') );
243                                         attr['day'] = true;
244                                         break;
245                                 case 'M': // Month of year 9
246                                 case 'MM': // Month of year(leading zero) 09
247                                 case 'MMM':
248                                 case 'MMMM':
249                                         $(div).append( tpl.replace('%1', 'month') );
250                                         attr['month'] = true;
251                                         break;
252                                 case 'yy':      // year two digit
253                                 case 'yyyy': // year four digit
254                                         $(div).append( tpl.replace('%1', 'year') );
255                                         attr['year'] = true;
256                                         break;
257                                 case 't': //AM / PM indicator(first letter) A, P
258                                         // add button
259                                 case 'tt': //AM / PM indicator AM/PM
260                                         // add button
261                                         var ampm = this.data["hour"] > 11 ?
262                                                         this.calendar.PM[0] : this.calendar.AM[0];
263                                         var btn = '<a href="#" class="ui-datefield-ampm"' +
264                                                         ' data-role="button" data-inline="true">' +
265                                                         ampm + '</a>';
266                                         $(div).append( btn );
267                                         attr['ampm'] = true;
268                                         break;
269                                 case 'g':
270                                 case 'gg':
271                                         $(div).append( tpl.replace('%1', 'era').replace('%2',
272                                                                                                                                 this.calendar.eras.name) );
273                                         break;
274                                 default : // string or any non-clickable object
275                                         $(div).append( tpl.replace('%1', 'seperator').replace('%2', pat) );
276                                         break;
277                                 }
278                         }
279
280                         return {
281                                 attr: attr,
282                                 html: div,
283                         };
284                 },
285
286                 _switchAmPm: function( obj, owner ) {
287                         if ( this.calendar.AM != null ) {
288                                 if ( this.calendar.AM[0] == $(owner).find('.ui-btn-text').text() ) { // AM to PM
289                                         this.data["hour"] += 12;
290                                         $(owner).find('.ui-btn-text').text( this.calendar.PM[0] );
291                                 } else {        // PM to AM
292                                         this.data["hour"] -= 12;
293                                         $(owner).find('.ui-btn-text').text( this.calendar.AM[0] );
294                                 }
295                                 obj.update();
296                         }
297                 },
298
299                 update: function() {
300                         if ( $(this.elem).is('input') ) {
301                                 this.elem.value = this.getValue();
302                         }
303                         $(this.elem).trigger('date-changed', this.getValue() );
304                 },
305
306                 _parsePattern: function( pattern ) {
307                         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)|('.*?')/;
308                         var token = [];
309
310                         while ( pattern.length > 0 ) {
311                                 var s = regex.exec( pattern );
312                                 if ( s ) {
313                                         pattern = pattern.substr( s[0].length );
314                                         if ( s[0].charAt(0) == "'" ) {
315                                                 s[0] = s[0].substr( 1, s[0].length - 2 );
316                                         }
317                                         token.push( s[0] );
318                                 } else {
319                                         token.push( pattern.charAt(0) );
320                                         pattern = pattern.substr(1);
321                                 }
322                         }
323
324                         return token;
325                 },
326
327                 _create: function() {
328                         var input = this.element;
329                         var type = $(input).attr("type");
330                         if ( type ) {
331                                 this.options.type = type;
332                         }
333
334                         var isTime = type.indexOf("time") > -1;
335                         var isDate = type.indexOf("date") > -1;
336
337                         $.extend( this, {
338                                 elem: input,
339                                 time: isTime,
340                                 date: isDate,
341                                 calendar: Globalize.culture().calendars.standard,
342                                 data: {
343                                         now             : new Date(),
344                                         "hour"  : 0,
345                                         "min"   : 0,
346                                         "sec"   : 0,
347                                         "year"  : 0,
348                                         "month" : 0,
349                                         "day"   : 0
350                                 },
351
352                         });
353
354                         // init date&time
355                         var now = this.data.now;
356                         var data = this.data;
357                         if ( isDate ) {
358                                 if ( this.calendar.convert ) {
359                                         var local = this.calendar.convert.fromGregorian(
360                                                         this.data.now );
361                                         data["year"] = local.year;
362                                         data["month"] = local.month + 1;
363                                         data["day"] = local.day;
364                                 } else {
365                                         data["year"] = now.getFullYear();
366                                         data["month"] = now.getMonth() + 1;
367                                         data["day"] = now.getDate();
368                                 }
369                         }
370
371                         if ( isTime ) {
372                                 data["hour"] = now.getHours();
373                                 data["min"] = now.getMinutes();
374                                 data["sec"] = now.getSeconds();
375                         }
376
377                         $(input).css('display', 'none');
378                         $div = $(document.createElement('div'));
379                         $div.addClass('ui-datefield');
380                         $(input).after( $div );
381                         this._initField( this.options.type, $div );
382                         $div.trigger('create');
383                         var obj = this;
384                         $div.bind('vclick', function(e) {
385                                 obj._showDataSelector( obj, this, e.target );
386                         });
387                         $div.find('.ui-datefield-ampm').bind( 'vclick', function(e) {
388                                 obj._switchAmPm( obj, this );
389                         });
390                 },
391
392                 _populateDataSelector: function( field, pat, obj ) {
393                         var values, numItems, current, data;
394                         switch ( field ) {
395                         case 'hour':
396                                 if ( pat == 'H' ) {
397                                         // twentyfour
398                                         values = range( 0, 23 );
399                                         data = range( 0, 23 );
400                                         current = obj.data["hour"];
401                                 } else {
402                                         values = range( 1, 12 );
403                                         current = obj.data["hour"] - 1;//11
404                                         if ( current >= 11 ) {
405                                                 current = current - 12;
406                                                 data = range( 13, 23 );
407                                                 data.push( 12 ); // consider 12:00 am as 00:00
408                                         } else {
409                                                 data = range( 1, 11 );
410                                                 data.push( 0 );
411                                         }
412                                         if ( current < 0 ) {
413                                                 current = 11; // 12:00 or 00:00
414                                         }
415                                 }
416                                 if ( pat.length == 2 ) {
417                                         // two digit
418                                         values = values.map( obj._makeTwoDigits );
419                                 }
420                                 numItems = values.length;
421                                 break;
422                         case 'min':
423                         case 'sec':
424                                 values = range( 0, 59 );
425                                 if ( pat.length == 2 ) {
426                                         values = values.map( obj._makeTwoDigits );
427                                 }
428                                 data = range( 0, 59 );
429                                 current = ( field == 'min' ? obj.data["min"] : obj.data["sec"] );
430                                 numItems = values.length;
431                                 break;
432                         case 'year':
433                                 var local = new Date( 1900, 0, 1 );
434                                 var yearlb;
435                                 var yearhb;
436                                 if ( obj.calendar.convert ) {
437                                         local = obj.calendar.convert.fromGregorian( local );
438                                         yearlb = local.year;
439                                         yearhb = yearlb + 200;
440                                 } else {
441                                         yearlb = local.getFullYear();
442                                         yearhb = yearlb + 200;
443                                 }
444                                 data = range( yearlb, yearhb );
445                                 current = obj.data["year"] - yearlb;
446                                 values = range( yearlb, yearhb );
447                                 numItems = values.length;
448                                 break;
449                         case 'month':
450                                 switch ( pat.length ) {
451                                 case 1:
452                                         values = range( 1, 12 );
453                                         break;
454                                 case 2:
455                                         values = range( 1, 12 ).map( obj._makeTwoDigits );
456                                         break;
457                                 case 3:
458                                         values = obj.calendar.months.namesAbbr.slice();
459                                         break;
460                                 case 4:
461                                         values = obj.calendar.months.names.slice();
462                                         break;
463                                 }
464                                 if ( values.length == 13 ) { // @TODO Lunar calendar support
465                                         if ( values[12] == "" ) { // to remove lunar calendar reserved space
466                                                 values.pop();
467                                         }
468                                 }
469                                 data = range( 1, values.length );
470                                 current = obj.data["month"] - 1;
471                                 numItems = values.length;
472                                 break;
473                         case 'day':
474                                 //@TODO max number 31 -> depends on month
475                                 var day = 31;
476                                 values = range( 1, day );
477                                 if ( pat.length == 2 ) {
478                                         values = values.map( obj._makeTwoDigits );
479                                 }
480                                 data = range( 1, day );
481                                 current = obj.data["day"] - 1;
482                                 numItems = day;
483                                 break;
484                         }
485
486                         return {
487                                 values: values,
488                                 data: data,
489                                 numItems: numItems,
490                                 current: current
491                         };
492
493                 },
494
495                 _showDataSelector: function( obj, ui, target ) {
496                         target = $(target);
497
498                         var attr = target.attr("class");
499                         if ( !attr ) {
500                                 return;
501                         }
502                         var field = attr.match(/ui-datefield-([^ ]*)/);
503                         if ( !field ) {
504                                 return;
505                         }
506
507                         target.not('.ui-datefield-seperator').addClass('ui-datefield-selected');
508
509                         var pat = target.jqmData('pat');
510                         var data = obj._populateDataSelector( field[1], pat, obj );
511
512                         var values = data.values,
513                                 numItems = data.numItems;
514                                 current = data.current;
515                                 valuesData = data.data;
516
517                         if ( values ) {
518                                 $ul = $(document.createElement('ul'));
519                                 for ( item in values ) {
520                                         $li = $(document.createElement('li'));
521                                         $item = $(document.createElement('a'));
522                                         $item.addClass('ui-link');
523                                         $item.text( values[item] );
524                                         $item.jqmData( "val", valuesData[item] );
525
526                                         $li.append( $item );
527                                         $ul.append( $li );
528
529                                         if ( current == item ) {
530                                                 $li.addClass('current');
531                                         }
532                                 }
533
534                                 /* TODO NEED TO REFACTORING HERE */
535                                 var $div = $(document.createElement('div'));
536                                 $div.append( $ul ).appendTo( ui );
537                                 $div.addClass('ui-datetimepicker-selector');
538                                 $div.attr( 'data-transition', 'none' );
539                                 var $ctx = $div.ctxpopup();
540                                 $ctx.parents('.ui-popupwindow').addClass('ui-datetimepicker');
541                                 $div.circularview();
542                                 $div.circularview( 'centerTo', '.current' );
543                                 $ctx.popupwindow( 'open',
544                                                 target.offset().left + target.width() / 2 - window.pageXOffset,
545                                                 target.offset().top + target.height() - window.pageYOffset );
546                                 $div.bind('closed', function(e) {
547                                         $div.unbind( 'closed' );
548                                         $ul.unbind( 'vclick' );
549                                         $(obj).unbind( 'update' );
550                                         $(ui).find('.ui-datefield-selected').removeClass('ui-datefield-selected');
551                                         $ctx.popupwindow( 'destroy' );
552                                         $div.remove();
553                                 });
554
555                                 $(obj).bind( 'update', function( e, val ) {
556                                         $ctx.popupwindow( 'close' );
557                                         var data = $(ui).find( '.' + field[0] );
558                                         obj._updateField( $(data), val );
559                                         obj.data[ field[1] ] = val;
560                                         obj.update();
561                                 });
562
563                                 $ul.bind( 'vclick', function( e ) {
564                                         if ( $(e.target).is('a') ) {
565                                                 $ul.find(".current").removeClass("current");
566                                                 $(e.target).parent().addClass('current');
567                                                 var val = $(e.target).jqmData("val");
568                                                 $(obj).trigger( 'update', val ); // close popup, unselect field
569                                         }
570                                 });
571                         }
572                 },
573
574                 _initField: function( type, div ){
575                         var updateFields = function( obj, html, attr ) {
576                                 for( item in attr ) {
577                                         if ( attr[item] ) {
578                                                 obj._updateField( $(html).find( '.ui-datefield-' + item ),
579                                                         obj.data[item] );
580                                         }
581                                 }
582                         };
583
584                         if ( this.options.format ) {
585                                 var datetime = this._format( this.options.format );
586                                 updateFields( this, datetime.html, datetime.attr );
587                                 div.append( datetime.html );
588                         } else {
589                                 if ( type.match( 'date' ) ) {
590                                         var date = this._format( this.calendar.patterns.d );
591                                         $(date.html).addClass('date');
592                                         updateFields( this, date.html, date.attr );
593                                         div.append( date.html );
594                                 }
595
596                                 if ( type.match( 'datetime' ) ) {
597                                         div.append( '<span class="ui-datefield-tab"></span>' );
598                                 }
599
600                                 if ( type.match( 'time' ) ) {
601                                         var time = this._format( this.calendar.patterns.t );
602                                         $(time.html).addClass('time');
603                                         updateFields( this, time.html, time.attr );
604                                         div.append( time.html );
605                                 }
606                         }
607                 },
608
609         });
610
611         $(document).bind("pagecreate create", function(e) {
612                 $($.tizen.datetimepicker.prototype.options.initSelector, e.target)
613                         .not(":jqmData(role='none'), :jqmData(role='nojs')")
614                         .datetimepicker();
615         });
616
617 })(jQuery, this);