e9e28c9120915a0390a85672ab24a8589ba4e35c
[framework/web/web-ui-fw.git] / src / widgets / datetimepicker / js / jquery.mobile.tizen.datetimepicker.js
1 /*global Globalize:false, range:false, regexp:false*/
2 /*
3  * jQuery Mobile Widget @VERSION
4  *
5  * This software is licensed under the MIT licence (as defined by the OSI at
6  * http://www.opensource.org/licenses/mit-license.php)
7  *
8  * ***************************************************************************
9  * Copyright (C) 2011 by Intel Corporation Ltd.
10  *
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:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies or substantial portions of the Software.
20  *
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  * ***************************************************************************
29  *
30  * Authors: Salvatore Iovene <salvatore.iovene@intel.com>
31  *                      Daehyon Jung <darrenh.jung@samsung.com>
32  */
33
34 /**
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.
39  * 
40  * HTML Attributes:
41  * 
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.
46  *
47  * Options:
48  *      type: 'date', 'datetime', 'time'
49  *      format: see data-format in HTML Attributes.
50  *      val: see data-val in HTML Attributes.
51  *
52  * APIs:
53  *      getValue()
54  *              : Get current selected date/time as W3C DTF style string.
55  *      update()
56  *              : Force to update fields.
57  *
58  * Events:
59  *      data-changed: Raised when date/time was changed.
60  *
61  * Examples:
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"/>
67  *                      </span>
68  *                      <span class="ui-li-text-sub">
69  *                              Date/Time Picker - <span id="selected-date1"><em>(select a date first)</em></span>
70  *                      </span>
71  *              </li>
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"/>
75  *                      </span>
76  *                      <span class="ui-li-text-sub">
77  *                              Date Picker  - <span id="selected-date2"><em>(select a date first)</em></span>
78  *                      </span>
79  *              </li>
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"/>
83  *                      </span>
84  *                      <span class="ui-li-text-sub">
85  *                              Time Picker - <span id="selected-date3"><em>(select a date first)</em></span>
86  *                      </span>
87  *              </li>
88  *      </ul>
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());
94  *    });
95  */
96
97
98 ( function ( $, window, undefined ) {
99         $.widget( "tizen.datetimepicker", $.tizen.widgetex, {
100                 options: {
101                         type: 'datetime', // date, time, datetime applicable
102                         format: null,
103                         val: null,
104                         initSelector: "input[type='date'], input[type='datetime'], input[type='time'], :jqmData(role='datetimepicker')"
105                 },
106
107                 _makeTwoDigits: function ( val ) {
108                         var ret = val.toString(10);
109
110                         if ( val < 10 ) {
111                                 ret = "0" + ret;
112                         }
113                         return ret;
114                 },
115
116                 /**
117                  * return W3C DTF string
118                  */
119                 getValue: function () {
120                         var data = [],
121                                 item,
122                                 greg,
123                                 obj = this,
124                                 toTimeString,
125                                 toDateString;
126
127                         for ( item in this.data ) {
128                                 data[item] = this.data[item];
129                         }
130
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();
136                         }
137                         obj = this;
138                         toTimeString = function timeStr( t ) {
139                                 return obj._makeTwoDigits( t.hour ) + ':' +
140                                         obj._makeTwoDigits( t.min ) + ':' +
141                                         obj._makeTwoDigits( t.sec );
142                         };
143
144                         toDateString = function dateStr( d ) {
145                                 return ( ( d.year % 10000 ) + 10000 ).toString().substr(1) + '-' +
146                                         obj._makeTwoDigits( d.month ) + '-' +
147                                         obj._makeTwoDigits( d.day );
148                         };
149
150                         switch ( this.options.type ) {
151                         case 'time':
152                                 return toTimeString( data );
153                         case 'date':
154                                 return toDateString( data );
155                         default:
156                                 return toDateString( data ) + 'T' + toTimeString( data );
157                         }
158                 },
159
160                 _updateField: function ( target, value ) {
161                         if ( !target || target.length == 0 ) {
162                                 return;
163                         }
164
165                         if ( value == 0 ) {
166                                 value = "0";
167                         }
168
169                         var pat = target.jqmData( 'pat' ),
170                                 hour;
171                         switch ( pat ) {
172                         case 'H':
173                         case 'HH':
174                         case 'h':
175                         case 'hh':
176                                 hour = value;
177                                 if ( pat.charAt(0) == 'h' ) {
178                                         if ( hour > 12 ) {
179                                                 hour -= 12;
180                                         } else if ( hour == 0 ) {
181                                                 hour = 12;
182                                         }
183                                 }
184                                 if ( pat.length == 2 ) {
185                                         hour = this._makeTwoDigits( hour );
186                                 }
187                                 target.text( hour );
188                                 break;
189                         case 'm':
190                         case 'M':
191                         case 'd':
192                         case 's':
193                                 target.text( value );
194                                 break;
195                         case 'mm':
196                         case 'dd':
197                         case 'MM':
198                         case 'ss':
199                                 target.text( this._makeTwoDigits( value ) );
200                                 break;
201                         case 'MMM':
202                                 target.text( this.calendar.months.namesAbbr[ value - 1] );
203                                 break;
204                         case 'MMMM':
205                                 target.text( this.calendar.months.names[ value - 1 ] );
206                                 break;
207                         case 'yy':
208                                 target.text( this._makeTwoDigits( value % 100 ) );
209                                 break;
210                         case 'yyyy':
211                                 if ( value < 10 ) {
212                                         value = '000' + value;
213                                 } else if ( value < 100 ) {
214                                         value = '00' + value;
215                                 } else if ( value < 1000 ) {
216                                         value = '0' + value;
217                                 }
218                                 target.text( value );
219                                 break;
220                         }
221
222                 },
223
224                 _format: function ( pattern ) {
225                         var token = this._parsePattern( pattern ),
226                                 div = document.createElement('div'),
227                                 attr = [],
228                                 pat,
229                                 tpl,
230                                 ampm,
231                                 btn;
232
233                         while ( token.length > 0 ) {
234                                 pat = token.shift();
235                                 tpl = '<span class="ui-datefield-%1" data-pat="' + pat + '">%2</span>';
236                                 switch ( pat ) {
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') );
242                                         attr.hour = true;
243                                         break;
244                                 case 'mm': //00 01 ... 59
245                                 case 'm': //0 1 2 ... 59
246                                         $(div).append( tpl.replace('%1', 'min') );
247                                         attr.min = true;
248                                         break;
249                                 case 'ss':
250                                 case 's':
251                                         $(div).append( tpl.replace('%1', 'sec') );
252                                         attr.sec = true;
253                                         break;
254                                 case 'd': // day of month 5                                     
255                                 case 'dd': // day of month(leading zero) 05
256                                         $(div).append( tpl.replace('%1', 'day') );
257                                         attr.day = true;
258                                         break;
259                                 case 'M': // Month of year 9
260                                 case 'MM': // Month of year(leading zero) 09
261                                 case 'MMM':
262                                 case 'MMMM':
263                                         $(div).append( tpl.replace('%1', 'month') );
264                                         attr.month = true;
265                                         break;
266                                 case 'yy':      // year two digit
267                                 case 'yyyy': // year four digit
268                                         $(div).append( tpl.replace('%1', 'year') );
269                                         attr.year = true;
270                                         break;
271                                 case 't': //AM / PM indicator(first letter) A, P
272                                         // add button
273                                 case 'tt': //AM / PM indicator AM/PM
274                                         // add button
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">' +
279                                                 ampm + '</a>';
280                                         $(div).append( btn );
281                                         attr.ampm = true;
282                                         break;
283                                 case 'g':
284                                 case 'gg':
285                                         $(div).append( tpl.replace('%1', 'era').replace('%2', this.calendar.eras.name) );
286                                         break;
287                                 default : // string or any non-clickable object
288                                         $(div).append( tpl.replace('%1', 'seperator').replace('%2', pat) );
289                                         break;
290                                 }
291                         }
292
293                         return {
294                                 attr: attr,
295                                 html: div
296                         };
297                 },
298
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] );
304                                 } else {        // PM to AM
305                                         this.data.hour -= 12;
306                                         $(owner).find('.ui-btn-text').text( this.calendar.AM[0] );
307                                 }
308                                 obj.update();
309                         }
310                 },
311
312                 update: function () {
313                         if ( $(this.elem).is('input') ) {
314                                 this.options.val = this.getValue();
315                                 this.elem.value = this.options.val;
316                         }
317                         $(this.elem).trigger('date-changed', this.getValue() );
318                 },
319
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]*?')/,
322                                 token = [],
323                                 s;
324
325                         while ( pattern.length > 0 ) {
326                                 s = regex.exec( pattern );
327                                 if ( s ) {
328                                         pattern = pattern.substr( s[0].length );
329                                         if ( s[0].charAt(0) == "'" ) {
330                                                 s[0] = s[0].substr( 1, s[0].length - 2 );
331                                         }
332                                         token.push( s[0] );
333                                 } else {
334                                         token.push( pattern.charAt(0) );
335                                         pattern = pattern.substr(1);
336                                 }
337                         }
338
339                         return token;
340                 },
341
342                 _create: function () {
343                         var input = this.element.get(0),
344                                 type = $(input).attr("type"),
345                                 isTime,
346                                 isDate,
347                                 val,
348                                 now,
349                                 data,
350                                 local,
351                                 obj = this,
352                                 $div;
353
354                         if ( type ) {
355                                 obj.options.type = type;
356                         }
357
358                         isTime = type.indexOf("time") > -1;
359                         isDate = type.indexOf("date") > -1;
360                         $.extend( obj, {
361                                 elem: input,
362                                 time: isTime,
363                                 date: isDate,
364                                 calendar: window.Globalize.culture().calendars.standard,
365                                 data: {
366                                         "hour"  : 0,
367                                         "min"   : 0,
368                                         "sec"   : 0,
369                                         "year"  : 0,
370                                         "month" : 0,
371                                         "day"   : 0
372                                 }
373
374                         });
375
376                         // init date&time
377                         val = this.options.val;
378                         if ( val ) {
379                                 now = new Date( Date.parse( val ) );
380                         } else {
381                                 now = new Date();
382                         }
383
384                         data = obj.data;
385                         if ( isDate ) {
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;
391                                 } else {
392                                         data.year = now.getFullYear();
393                                         data.month = now.getMonth() + 1;
394                                         data.day = now.getDate();
395                                 }
396                         }
397
398                         if ( isTime ) {
399                                 data.hour = now.getHours();
400                                 data.min = now.getMinutes();
401                                 data.sec = now.getSeconds();
402                         }
403
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');
410
411                         $div.bind('vclick', function ( e ) {
412                                 obj._showDataSelector( obj, this, e.target );
413                         });
414
415                         $div.find('.ui-datefield-ampm').bind( 'vclick', function ( e ) {
416                                 obj._switchAmPm( obj, this );
417                         });
418                 },
419
420                 _populateDataSelector: function ( field, pat, obj ) {
421                         var values,
422                                 numItems,
423                                 current,
424                                 data,
425                                 range = window.range,
426                                 local,
427                                 yearlb,
428                                 yearhb,
429                                 day;
430
431
432                         switch ( field ) {
433                         case 'hour':
434                                 if ( pat == 'H' ) {
435                                         // twentyfour
436                                         values = range( 0, 23 );
437                                         data = range( 0, 23 );
438                                         current = obj.data.hour;
439                                 } else {
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
446                                         } else {
447                                                 data = range( 1, 11 );
448                                                 data.push( 0 );
449                                         }
450                                         if ( current < 0 ) {
451                                                 current = 11; // 12:00 or 00:00
452                                         }
453                                 }
454                                 if ( pat.length == 2 ) {
455                                         // two digit
456                                         values = values.map( obj._makeTwoDigits );
457                                 }
458                                 numItems = values.length;
459                                 break;
460                         case 'min':
461                         case 'sec':
462                                 values = range( 0, 59 );
463                                 if ( pat.length == 2 ) {
464                                         values = values.map( obj._makeTwoDigits );
465                                 }
466                                 data = range( 0, 59 );
467                                 current = ( field == 'min' ? obj.data.min : obj.data.sec );
468                                 numItems = values.length;
469                                 break;
470                         case 'year':
471                                 local = new Date( 1900, 0, 1 );
472                                 if ( obj.calendar.convert ) {
473                                         local = obj.calendar.convert.fromGregorian( local );
474                                         yearlb = local.year;
475                                         yearhb = yearlb + 200;
476                                 } else {
477                                         yearlb = local.getFullYear();
478                                         yearhb = yearlb + 200;
479                                 }
480                                 data = range( yearlb, yearhb );
481                                 current = obj.data.year - yearlb;
482                                 values = range( yearlb, yearhb );
483                                 numItems = values.length;
484                                 break;
485                         case 'month':
486                                 switch ( pat.length ) {
487                                 case 1:
488                                         values = range( 1, 12 );
489                                         break;
490                                 case 2:
491                                         values = range( 1, 12 ).map( obj._makeTwoDigits );
492                                         break;
493                                 case 3:
494                                         values = obj.calendar.months.namesAbbr.slice();
495                                         break;
496                                 case 4:
497                                         values = obj.calendar.months.names.slice();
498                                         break;
499                                 }
500                                 if ( values.length == 13 ) { // @TODO Lunar calendar support
501                                         if ( values[12] == "" ) { // to remove lunar calendar reserved space
502                                                 values.pop();
503                                         }
504                                 }
505                                 data = range( 1, values.length );
506                                 current = obj.data.month - 1;
507                                 numItems = values.length;
508                                 break;
509                         case 'day':
510                                 //@TODO max number 31 -> depends on month
511                                 day = 31;
512                                 values = range( 1, day );
513                                 if ( pat.length == 2 ) {
514                                         values = values.map( obj._makeTwoDigits );
515                                 }
516                                 data = range( 1, day );
517                                 current = obj.data.day - 1;
518                                 numItems = day;
519                                 break;
520                         }
521
522                         return {
523                                 values: values,
524                                 data: data,
525                                 numItems: numItems,
526                                 current: current
527                         };
528
529                 },
530
531                 _showDataSelector: function ( obj, ui, target ) {
532                         target = $(target);
533
534                         var attr = target.attr("class"),
535                                 field = attr.match(/ui-datefield-([\w]*)/),
536                                 pat,
537                                 data,
538                                 values,
539                                 numItems,
540                                 current,
541                                 valuesData,
542                                 item,
543                                 $li,
544                                 $item,
545                                 $ul,
546                                 $div,
547                                 $ctx;
548
549                         if ( !attr ) {
550                                 return;
551                         }
552                         if ( !field ) {
553                                 return;
554                         }
555
556                         target.not('.ui-datefield-seperator').addClass('ui-datefield-selected');
557
558                         pat = target.jqmData('pat');
559                         data = obj._populateDataSelector( field[1], pat, obj );
560
561                         values = data.values;
562                         numItems = data.numItems;
563                         current = data.current;
564                         valuesData = data.data;
565
566                         if ( values ) {
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] );
574
575                                         $li.append( $item );
576                                         $ul.append( $li );
577
578                                         if ( current == item ) {
579                                                 $li.addClass('current');
580                                         }
581                                 }
582
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');
590                                 $div.circularview();
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' );
601                                         $div.remove();
602                                 });
603
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;
609                                         obj.update();
610                                 });
611
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
618                                         }
619                                 });
620                         }
621                 },
622
623                 _initField: function ( type, div ) {
624                         var date,
625                                 time,
626                                 datetime,
627                                 updateFields = function ( obj, html, attr ) {
628                                         var item;
629                                         for ( item in attr ) {
630                                                 if ( attr[item] ) {
631                                                         obj._updateField( $(html).find( '.ui-datefield-' + item ),
632                                                                 obj.data[item] );
633                                                 }
634                                         }
635                                 };
636
637                         if ( this.options.format ) {
638                                 datetime = this._format( this.options.format );
639                                 updateFields( this, datetime.html, datetime.attr );
640                                 div.append( datetime.html );
641                         } else {
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 );
647                                 }
648
649                                 if ( type.match( 'datetime' ) ) {
650                                         div.append( '<span class="ui-datefield-tab"></span>' );
651                                 }
652
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 );
658                                 }
659                         }
660                 }
661
662         });
663
664         $(document).bind("pagecreate create", function ( e ) {
665                 $($.tizen.datetimepicker.prototype.options.initSelector, e.target)
666                         .not(":jqmData(role='none'), :jqmData(role='nojs')")
667                         .datetimepicker();
668         });
669
670 } ( jQuery, this ) );