2 * jQuery Mobile Widget @VERSION
4 * This software is licensed under the MIT licence (as defined by the OSI at
5 * http://www.opensource.org/licenses/mit-license.php)
7 * ***************************************************************************
8 * Copyright (C) 2011 by Intel Corporation Ltd.
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:
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
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 * ***************************************************************************
29 * Authors: Salvatore Iovene <salvatore.iovene@intel.com>
30 * Daehyon Jung <darrenh.jung@samsung.com>
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.
41 * data-role: 'datetimepicker'
42 * data-format: date format string. e.g) "MMM dd yyyy, HH:mm"
43 * type: 'date', 'datetime', 'time'
46 * type: 'date', 'datetime', 'time'
47 * format: see data-format in HTML Attributes.
51 * : Get current selected date/time as W3C DTF style string.
53 * : Force to update fields.
56 * data-changed: Raised when date/time was changed.
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"/>
65 * <span class="ui-li-text-sub">
66 * Date/Time Picker - <span id="selected-date1"><em>(select a date first)</em></span>
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"/>
73 * <span class="ui-li-text-sub">
74 * Date Picker - <span id="selected-date2"><em>(select a date first)</em></span>
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"/>
81 * <span class="ui-li-text-sub">
82 * Time Picker - <span id="selected-date3"><em>(select a date first)</em></span>
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());
95 (function($, window, undefined) {
96 $.widget("tizen.datetimepicker", $.tizen.widgetex, {
98 type: 'datetime', // date, time, datetime applicable
100 initSelector: "input[type='date'], input[type='datetime'], input[type='time'], :jqmData(role='datetimepicker')"
103 _makeTwoDigits: function(val) {
104 var ret = val.toString(10);
113 * return W3C DTF string
115 getValue: function() {
117 for ( item in this.data ) {
118 data[item] = this.data[item];
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();
129 var toTimeString = function timeStr( t ) {
130 return obj._makeTwoDigits( t["hour"] ) + ':' +
131 obj._makeTwoDigits( t["min"] ) + ':' +
132 obj._makeTwoDigits( t["sec"] );
135 var toDateString = function dateStr( d ) {
136 return ( "" + ( ( d["year"] % 10000 ) + 10000 ) ).substr(1) + '-' +
137 obj._makeTwoDigits( d["month"] ) + '-' +
138 obj._makeTwoDigits( d["day"] );
141 switch ( this.options.type ) {
143 return toTimeString( data );
145 return toDateString( data );
147 return toDateString( data ) + 'T' + toTimeString( data );
151 _updateField: function( target, value ) {
152 if ( !target || target.length == 0 ) {
160 var pat = target.jqmData( 'pat' );
167 if ( pat.charAt(0) == 'h' ) {
171 else if ( hour == 0 ) {
175 if ( pat.length == 2 ) {
176 hour = this._makeTwoDigits( hour );
184 target.text( value );
190 target.text( this._makeTwoDigits( value ) );
193 target.text( this.calendar.months.namesAbbr[ value - 1] );
196 target.text( this.calendar.months.names[ value - 1 ] );
199 target.text( this._makeTwoDigits( value % 100 ) );
203 value = '000' + value;
204 } else if ( value < 100 ) {
205 value = '00' + value;
206 } else if ( value < 1000 ) {
209 target.text( value );
215 _format: function( pattern ) {
216 var token = this._parsePattern( pattern );
217 var div = document.createElement('div');
219 while ( token.length > 0 ) {
220 var pat = token.shift();
221 var tpl = '<span class="ui-datefield-%1" data-pat="' + pat + '">%2</span>';
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') );
230 case 'mm': //00 01 ... 59
231 case 'm': //0 1 2 ... 59
232 $(div).append( tpl.replace('%1', 'min') );
237 $(div).append( tpl.replace('%1', 'sec') );
240 case 'd': // day of month 5
241 case 'dd': // day of month(leading zero) 05
242 $(div).append( tpl.replace('%1', 'day') );
245 case 'M': // Month of year 9
246 case 'MM': // Month of year(leading zero) 09
249 $(div).append( tpl.replace('%1', 'month') );
250 attr['month'] = true;
252 case 'yy': // year two digit
253 case 'yyyy': // year four digit
254 $(div).append( tpl.replace('%1', 'year') );
257 case 't': //AM / PM indicator(first letter) A, P
259 case 'tt': //AM / PM indicator AM/PM
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">' +
266 $(div).append( btn );
271 $(div).append( tpl.replace('%1', 'era').replace('%2',
272 this.calendar.eras.name) );
274 default : // string or any non-clickable object
275 $(div).append( tpl.replace('%1', 'seperator').replace('%2', pat) );
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] );
292 this.data["hour"] -= 12;
293 $(owner).find('.ui-btn-text').text( this.calendar.AM[0] );
300 if ( $(this.elem).is('input') ) {
301 this.elem.value = this.getValue();
303 $(this.elem).trigger('date-changed', this.getValue() );
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)|('.*?')/;
310 while ( pattern.length > 0 ) {
311 var s = regex.exec( pattern );
313 pattern = pattern.substr( s[0].length );
314 if ( s[0].charAt(0) == "'" ) {
315 s[0] = s[0].substr( 1, s[0].length - 2 );
319 token.push( pattern.charAt(0) );
320 pattern = pattern.substr(1);
327 _create: function() {
328 var input = this.element;
329 var type = $(input).attr("type");
331 this.options.type = type;
334 var isTime = type.indexOf("time") > -1;
335 var isDate = type.indexOf("date") > -1;
341 calendar: Globalize.culture().calendars.standard,
355 var now = this.data.now;
356 var data = this.data;
358 if ( this.calendar.convert ) {
359 var local = this.calendar.convert.fromGregorian(
361 data["year"] = local.year;
362 data["month"] = local.month + 1;
363 data["day"] = local.day;
365 data["year"] = now.getFullYear();
366 data["month"] = now.getMonth() + 1;
367 data["day"] = now.getDate();
372 data["hour"] = now.getHours();
373 data["min"] = now.getMinutes();
374 data["sec"] = now.getSeconds();
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');
384 $div.bind('vclick', function(e) {
385 obj._showDataSelector( obj, this, e.target );
387 $div.find('.ui-datefield-ampm').bind( 'vclick', function(e) {
388 obj._switchAmPm( obj, this );
392 _populateDataSelector: function( field, pat, obj ) {
393 var values, numItems, current, data;
398 values = range( 0, 23 );
399 data = range( 0, 23 );
400 current = obj.data["hour"];
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
409 data = range( 1, 11 );
413 current = 11; // 12:00 or 00:00
416 if ( pat.length == 2 ) {
418 values = values.map( obj._makeTwoDigits );
420 numItems = values.length;
424 values = range( 0, 59 );
425 if ( pat.length == 2 ) {
426 values = values.map( obj._makeTwoDigits );
428 data = range( 0, 59 );
429 current = ( field == 'min' ? obj.data["min"] : obj.data["sec"] );
430 numItems = values.length;
433 var local = new Date( 1900, 0, 1 );
436 if ( obj.calendar.convert ) {
437 local = obj.calendar.convert.fromGregorian( local );
439 yearhb = yearlb + 200;
441 yearlb = local.getFullYear();
442 yearhb = yearlb + 200;
444 data = range( yearlb, yearhb );
445 current = obj.data["year"] - yearlb;
446 values = range( yearlb, yearhb );
447 numItems = values.length;
450 switch ( pat.length ) {
452 values = range( 1, 12 );
455 values = range( 1, 12 ).map( obj._makeTwoDigits );
458 values = obj.calendar.months.namesAbbr.slice();
461 values = obj.calendar.months.names.slice();
464 if ( values.length == 13 ) { // @TODO Lunar calendar support
465 if ( values[12] == "" ) { // to remove lunar calendar reserved space
469 data = range( 1, values.length );
470 current = obj.data["month"] - 1;
471 numItems = values.length;
474 //@TODO max number 31 -> depends on month
476 values = range( 1, day );
477 if ( pat.length == 2 ) {
478 values = values.map( obj._makeTwoDigits );
480 data = range( 1, day );
481 current = obj.data["day"] - 1;
495 _showDataSelector: function( obj, ui, target ) {
498 var attr = target.attr("class");
502 var field = attr.match(/ui-datefield-([^ ]*)/);
507 target.not('.ui-datefield-seperator').addClass('ui-datefield-selected');
509 var pat = target.jqmData('pat');
510 var data = obj._populateDataSelector( field[1], pat, obj );
512 var values = data.values,
513 numItems = data.numItems;
514 current = data.current;
515 valuesData = data.data;
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] );
529 if ( current == item ) {
530 $li.addClass('current');
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');
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' );
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;
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
574 _initField: function( type, div ){
575 var updateFields = function( obj, html, attr ) {
576 for( item in attr ) {
578 obj._updateField( $(html).find( '.ui-datefield-' + item ),
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 );
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 );
596 if ( type.match( 'datetime' ) ) {
597 div.append( '<span class="ui-datefield-tab"></span>' );
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 );
611 $(document).bind("pagecreate create", function(e) {
612 $($.tizen.datetimepicker.prototype.options.initSelector, e.target)
613 .not(":jqmData(role='none'), :jqmData(role='nojs')")