1 // ========================================================================== 2 // Project: The M-Project - Mobile HTML5 Application Framework 3 // Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved. 4 // (c) 2011 panacoda GmbH. All rights reserved. 5 // Creator: Dominik 6 // Date: 04.02.2011 7 // License: Dual licensed under the MIT or GPL Version 2 licenses. 8 // http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE 9 // http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE 10 // ========================================================================== 11 12 /** 13 * @class 14 * 15 * This defines the prototype for a date picker view. A date picker is a special view, that can 16 * be called out of a controller. It is shown as a date picker popup, based on the mobiscroll 17 * library. You can either connect a date picker with an existing view and automatically pass 18 * the selected date to the source's value property, or you can simply use the date picker to 19 * select a date, return it to the controller (respectively the callback) and handle the date 20 * by yourself. 21 * 22 * @extends M.View 23 */ 24 M.DatePickerView = M.View.extend( 25 /** @scope M.DatePickerView.prototype */ { 26 27 /** 28 * The type of this object. 29 * 30 * @type String 31 */ 32 type: 'M.DatePickerView', 33 34 /** 35 * This property is used to link the date picker to a source. You can either pass the DOM id of 36 * the corresponding source or the javascript object itself. Linking the date picker directly 37 * to a source results in automatic value updates of this source. 38 * 39 * Note: Valid sources need to provide a setValue() method. 40 * 41 * If you do not pass a source, the date picker isn't linked to any view. It simply returns the 42 * selected value/date to given callbacks. So you can call the date picker out of a controller 43 * and handle the selected date all by yourself. 44 * 45 * @type String|Object 46 */ 47 source: null, 48 49 /** 50 * This property can be used to specify several callbacks for the date picker view. There are 51 * three types of callbacks available: 52 * 53 * - before 54 * This callback gets called, right before the date picker is shown. It passes along two 55 * parameters: 56 * - value -> The initial date of the date picker, formatted as a string 57 * - date -> The initial date of the date picker as d8 object 58 * - confirm 59 * This callback gets called, when a selected date was confirmed. It passes along two 60 * parameters: 61 * - value -> The selected date of the date picker, formatted as a string 62 * - date -> The selected date of the date picker as d8 object 63 * - cancel 64 * This callback gets called, when the cancel button is hit. It doesn't pass any 65 * parameters. 66 * 67 * Setting up one of those callbacks works the same as with other controls of The-M-Project. You 68 * simply have to specify an object containing a target function, e.g.: 69 * 70 * callbacks: { 71 * confirm: { 72 * target: this, 73 * action: 'dateSelected' 74 * }, 75 * cancel: { 76 * action: function() { 77 * // do something 78 * } 79 * } 80 * } 81 * 82 * @type Object 83 */ 84 callbacks: null, 85 86 /** 87 * This property can be used to specify the initial date for the date picker. If you use the 88 * date picker without a source, this date is always picked as the initial date. If nothing is 89 * specified, the current date will be displayed. 90 * 91 * If you use the date picker with a valid source, the initial date is picked as long as there 92 * is no valid date available by the source. Once a date was selected and assigned to the source, 93 * this is taken as initial date the next time the date picker is opened. 94 * 95 * @type Object|String 96 */ 97 initialDate: null, 98 99 /** 100 * This property can be used to determine whether to use the data source's value as initial date 101 * or not. If there is no source specified, this property is irrelevant. 102 * 103 * Note: If there is a source specified and this property is set to NO, the 'initialDate' property 104 * will be used anyway if there is no date value available for the source! 105 * 106 * @type Boolean 107 */ 108 useSourceDateAsInitialDate: YES, 109 110 /** 111 * This property can be used to specify whether to show scrollers for picking a date or not. 112 * 113 * Note: If both this and the 'showTimePicker' property are set to NO, no date picker will 114 * be shown! 115 * 116 * @type Boolean 117 */ 118 showDatePicker: YES, 119 120 /** 121 * This property can be used to specify whether to show scrollers for picking a time or not. 122 * 123 * Note: If both this and the 'showDatePicker' property are set to NO, no date picker will 124 * be shown! 125 * 126 * @type Boolean 127 */ 128 showTimePicker: YES, 129 130 /** 131 * This property can be used to specify whether or not to show labels above of the scrollers. 132 * If set to YES, the labels specified with the '...Label' properties are displayed above of 133 * the corresponding scroller. 134 * 135 * @type Boolean 136 */ 137 showLabels: YES, 138 139 /** 140 * This property specified the label shown above of the 'year' scroller. 141 * 142 * Note: This label is only shown if the 'showLabels' property is set to YES. 143 * 144 * @type String 145 */ 146 yearLabel: 'Year', 147 148 /** 149 * This property specified the label shown above of the 'month' scroller. 150 * 151 * Note: This label is only shown if the 'showLabels' property is set to YES. 152 * 153 * @type String 154 */ 155 monthLabel: 'Month', 156 157 /** 158 * This property specified the label shown above of the 'day' scroller. 159 * 160 * Note: This label is only shown if the 'showLabels' property is set to YES. 161 * 162 * @type String 163 */ 164 dayLabel: 'Day', 165 166 /** 167 * This property specified the label shown above of the 'hours' scroller. 168 * 169 * Note: This label is only shown if the 'showLabels' property is set to YES. 170 * 171 * @type String 172 */ 173 hoursLabel: 'Hours', 174 175 /** 176 * This property specified the label shown above of the 'minutes' scroller. 177 * 178 * Note: This label is only shown if the 'showLabels' property is set to YES. 179 * 180 * @type String 181 */ 182 minutesLabel: 'Minutes', 183 184 /** 185 * You can use this property to enable or disable the AM/PM scroller. If set to NO, the 186 * date picker will use the 24h format. 187 * 188 * @type Boolean 189 */ 190 showAmPm: YES, 191 192 /** 193 * This property can be used to specify the first year of the 'year' scroller. By default, 194 * this will be set to 20 years before the current year. 195 * 196 * @type Number 197 */ 198 startYear: null, 199 200 /** 201 * This property can be used to specify the last year of the 'year' scroller. By default, 202 * this will be set to 20 years after the current year. 203 * 204 * @type Number 205 */ 206 endYear: null, 207 208 /** 209 * This property can be used to customize the date format of the date picker. This is important 210 * if you use the date picker on a valid source since the date picker will then automatically 211 * push the selected date/datetime to the 'value' property of the source - based on this format. 212 * 213 * The possible keys: 214 * 215 * - m -> month (without leading zero) 216 * - mm -> month (two-digit) 217 * - M -> month name (short) 218 * - MM -> month name (long) 219 * - d -> day (without leading zero) 220 * - d -> day (two digit) 221 * - D -> day name (short) 222 * - DD -> day name (long) 223 * - y -> year (two digit) 224 * - yy -> year (four digit) 225 * 226 * @type String 227 */ 228 dateFormat: 'M dd, yy', 229 230 /** 231 * This property can be used to customize the date format of the date picker if it is associated 232 * with a text input with the type 'month'. It works the same as the dateFormat property. 233 * 234 * @type String 235 */ 236 dateFormatMonthOnly: 'MM yy', 237 238 /** 239 * This property can be used to customize the time format of the date picker. This is important 240 * if you use the date picker on a valid source since the date picker will then automatically 241 * push the selected time/datetime to the 'value' property of the source - based on this format. 242 * 243 * The possible keys: 244 * 245 * - h -> hours (without leading zero, 12h format) 246 * - hh -> hours (two-digit, 12h format) 247 * - H -> hours (without leading zero, 24h format) 248 * - HH -> hours (two-digit, 24h format) 249 * - i -> minutes (without leading zero) 250 * - ii -> minutes (two-digit) 251 * - A -> AM/PM 252 * 253 * @type String 254 */ 255 timeFormat: 'h:ii A', 256 257 /** 258 * This property determines the order and formating of the date scrollers. The following keys 259 * are possible: 260 * 261 * - m -> month (without leading zero) 262 * - mm -> month (two-digit) 263 * - M -> month name (short) 264 * - MM -> month name (long) 265 * - d -> day (without leading zero) 266 * - d -> day (two digit) 267 * - y -> year (two digit) 268 * - yy -> year (four digit) 269 * 270 * By default, we use this format: Mddyy 271 * 272 * @type String 273 */ 274 dateOrder: 'Mddyy', 275 276 /** 277 * This property determines the order and formating of the date scrollers if it is associated 278 * with an input field of type 'month'. It works the same as the dateOrder property. 279 * 280 * By default, we use this format: MMyy 281 * 282 * @type String 283 */ 284 dateOrderMonthOnly: 'MMyy', 285 286 287 288 /** 289 * This property specifies a list of full month names. 290 * 291 * @type Array 292 */ 293 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 294 295 /** 296 * This property specifies a list of short month names. 297 * 298 * @type Array 299 */ 300 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 301 302 /** 303 * This property specifies a list of full day names. 304 * 305 * @type Array 306 */ 307 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 308 309 /** 310 * This property specifies a list of short day names. 311 * 312 * @type Array 313 */ 314 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 315 316 /** 317 * This property can be used to specify the label of the date picker's cancel button. By default 318 * it shows 'Cancel'. 319 * 320 * @type String 321 */ 322 cancelButtonValue: 'Cancel', 323 324 /** 325 * This property can be used to specify the label of the date picker's cancel button. By default 326 * it shows 'Ok'. 327 * 328 * @type String 329 */ 330 confirmButtonValue: 'Ok', 331 332 /** 333 * This property can be used to specify the steps between hours in the time / date-time picker. 334 * 335 * @type Number 336 */ 337 stepHour: 1, 338 339 /** 340 * This property can be used to specify the steps between minutes in the time / date-time picker. 341 * 342 * @type Number 343 */ 344 stepMinute: 1, 345 346 /** 347 * This property can be used to specify the steps between seconds in the time / date-time picker. 348 * 349 * @type Number 350 */ 351 stepSecond: 1, 352 353 /** 354 * This property can be used to activate the seconds wheel on a time/date-time picker. 355 * 356 * @type Boolean 357 */ 358 seconds: NO, 359 360 /** 361 * This property is used internally to indicate whether the current date picker works on a valid 362 * source or was called without one. This is important for stuff like auto-updating the source's 363 * DOM representation. 364 * 365 * @private 366 */ 367 hasSource: YES, 368 369 /** 370 * This property is used internally to state whether a value, respectively a date, was selected 371 * or not. 372 * 373 * @private 374 * @type Boolean 375 */ 376 isValueSelected: NO, 377 378 /** 379 * This property is used internally to state whether a the date picker is currently activated 380 * or not. 381 * 382 * @private 383 * @type Boolean 384 */ 385 isActive: NO, 386 387 /** 388 * This method is the only important method of a date picker view for 'the outside world'. From within 389 * an application, simply call this method and pass along an object, containing all the properties 390 * you want to set, different from default. 391 * 392 * A sample call: 393 * 394 * M.DatePickerView.show({ 395 * source: M.ViewManager.getView('mainPage', 'myTextField') 396 * initialDate: D8.create('30.04.1985 10:30'), 397 * callbacks: { 398 * confirm: { 399 * target: this, 400 * action: function(value, date) { 401 * // do something... 402 * } 403 * } 404 * } 405 * }); 406 * 407 * @param obj 408 */ 409 show: function(obj) { 410 var datepicker = M.DatePickerView.design(obj); 411 412 /* if a datepicker is active already, return */ 413 if(Object.getPrototypeOf(datepicker).isActive) { 414 return; 415 /* otherwise go on and set the flag to active */ 416 } else { 417 Object.getPrototypeOf(datepicker).isActive = YES; 418 } 419 420 /* check if it's worth the work at all */ 421 if(!(datepicker.showDatePicker || datepicker.showTimePicker)) { 422 M.Logger.log('In order to use the M.DatepickerView, you have to set the \'showDatePicker\' or \'showTimePicker\' property to YES.', M.ERR); 423 return; 424 } 425 426 /* calculate the default start and end years */ 427 this.startYear = this.startYear ? this.startYear : D8.now().format('yyyy') - 20; 428 this.endYear = this.endYear ? this.endYear : D8.now().format('yyyy') + 20; 429 430 /* check if we got a valid source */ 431 if(datepicker.source) { 432 /* if we got a view, get its id */ 433 datepicker.source = typeof(datepicker.source) === 'object' && datepicker.source.type ? datepicker.source.id : datepicker.source; 434 435 var view = M.ViewManager.getViewById(datepicker.source); 436 if(view && typeof(view.setValue) === 'function' && $('#' + datepicker.source) && $('#' + datepicker.source).length > 0) { 437 datepicker.init(); 438 } else { 439 M.Logger.log('The specified source for the M.DatepickerView is invalid!', M.ERR); 440 } 441 } else { 442 /* use default source (the current page) */ 443 datepicker.hasSource = NO; 444 var page = M.ViewManager.getCurrentPage(); 445 if(page) { 446 datepicker.source = page.id; 447 datepicker.init(); 448 } 449 } 450 }, 451 452 /** 453 * This method is used internally to communicate with the mobiscroll library. It actually initializes 454 * the creation of the date picker and is responsible for reacting on events. If the cancel or confirm 455 * button is hit, this method dispatches the events to the corresponding callbacks. 456 * 457 * @private 458 */ 459 init: function() { 460 var that = this; 461 $('#' + this.source).scroller({ 462 preset: (this.showDatePicker && this.showTimePicker ? 'datetime' : (this.showDatePicker ? 'date' : (this.showTimePicker ? 'time' : null))), 463 ampm: this.showAmPm, 464 startYear: this.startYear, 465 endYear: this.endYear, 466 dateFormat: this.dateFormat, 467 timeFormat: this.timeFormat, 468 dateOrder: this.dateOrder, 469 dayText: this.dayLabel, 470 hourText: this.hoursLabel, 471 minuteText: this.minutesLabel, 472 monthText: this.monthLabel, 473 yearText: this.yearLabel, 474 monthNames: this.monthNames, 475 monthNamesShort: this.monthNamesShort, 476 dayNames: this.dayNames, 477 dayNamesShort: this.dayNamesShort, 478 cancelText: this.cancelButtonValue, 479 setText: this.confirmButtonValue, 480 stepHour: this.stepHour, 481 stepMinute: this.stepMinute, 482 stepSecond: this.stepSecond, 483 seconds: this.seconds, 484 485 /* now set the width of the scrollers */ 486 width: (M.Environment.getWidth() - 20) / 3 - 20 > 90 ? 90 : (M.Environment.getWidth() - 20) / 3 - 20, 487 488 beforeShow: function(input, scroller) { 489 that.bindToCaller(that, that.beforeShow, [input, scroller])(); 490 }, 491 onClose: function(value, scroller) { 492 that.bindToCaller(that, that.onClose, [value, scroller])(); 493 }, 494 onSelect: function(value, scroller) { 495 that.bindToCaller(that, that.onSelect, [value, scroller])(); 496 } 497 }); 498 $('#' + this.source).scroller('show'); 499 }, 500 501 /** 502 * This method is used internally to handle the 'beforeShow' event. It does some adjustments to the 503 * rendered scroller by mobiscroll and finally calls the application's 'before' callback, if it is 504 * defined. 505 * 506 * @param source 507 * @param scroller 508 */ 509 beforeShow: function(source, scroller) { 510 var source = null; 511 var date = null; 512 513 /* try to set the date picker's initial date based on its source */ 514 if(this.hasSource && this.useSourceDateAsInitialDate) { 515 source = M.ViewManager.getViewById(this.source); 516 if(source.value) { 517 try { 518 date = D8.create(source.value); 519 } catch(e) { 520 521 } 522 if(date) { 523 if(date.format('yyyy') < this.startYear) { 524 if(this.hasOwnProperty('startYear')) { 525 M.Logger.log('The given date of the source (' + date.format('yyyy') + ') conflicts with the \'startYear\' property (' + this.startYear + ') and therefore will be ignored!', M.WARN); 526 } else { 527 M.Logger.log('The date picker\'s default \'startYear\' property (' + this.startYear + ') conflicts with the given date of the source (' + date.format('yyyy') + ') and therefore will be ignored!', M.WARN); 528 $('#' + this.source).scroller('option', 'startYear', date.format('yyyy')); 529 $('#' + this.source).scroller('setDate', date.date); 530 } 531 } else { 532 $('#' + this.source).scroller('setDate', date.date); 533 } 534 } 535 } 536 } 537 538 /* if there is no source or the retrieval of the date went wrong, try to set it based on the initial date property */ 539 if(this.initialDate && !date) { 540 if(this.initialDate.date) { 541 date = this.initialDate; 542 } else { 543 try { 544 date = D8.create(this.initialDate); 545 } catch(e) { 546 547 } 548 } 549 if(date) { 550 if(date.format('yyyy') < this.startYear) { 551 if(this.hasOwnProperty('startYear')) { 552 M.Logger.log('The specified initial date (' + date.format('yyyy') + ') conflicts with the \'startYear\' property (' + this.startYear + ') and therefore will be ignored!', M.WARN); 553 } else { 554 M.Logger.log('The date picker\'s default \'startYear\' property (' + this.startYear + ') conflicts with the specified initial date (' + date.format('yyyy') + ') and therefore will be ignored!', M.WARN); 555 $('#' + this.source).scroller('option', 'startYear', date.format('yyyy')); 556 $('#' + this.source).scroller('setDate', date.date); 557 } 558 } else { 559 $('#' + this.source).scroller('setDate', date.date); 560 } 561 } 562 } 563 564 /* now we got the date (or use the current date as default), lets compute this as a formatted text for the callback */ 565 value = scroller.formatDate( 566 this.showDatePicker ? this.dateFormat + (this.showTimePicker ? ' ' + this.timeFormat : '') : this.timeFormat, 567 scroller.getDate() 568 ); 569 570 /* kill parts of the scoller */ 571 $('.dwv').remove(); 572 573 /* inject TMP buttons*/ 574 var confirmButton = M.ButtonView.design({ 575 value: this.confirmButtonValue, 576 cssClass: 'b tmp-dialog-smallerbtn-confirm', 577 events: { 578 tap: { 579 action: function() { 580 $('#dw_set').trigger('click'); 581 } 582 } 583 } 584 }); 585 var cancelButton = M.ButtonView.design({ 586 value: this.cancelButtonValue, 587 cssClass: 'd tmp-dialog-smallerbtn-confirm', 588 events: { 589 tap: { 590 action: function() { 591 $('#dw_cancel').trigger('click'); 592 } 593 } 594 } 595 }); 596 597 if(this.showDatePicker) { 598 var grid = M.GridView.design({ 599 childViews: 'confirm cancel', 600 layout: M.TWO_COLUMNS, 601 cssClass: 'tmp-datepicker-buttongrid', 602 confirm: confirmButton, 603 cancel: cancelButton 604 }); 605 606 var html = grid.render(); 607 $('.dw').append(html); 608 grid.theme(); 609 grid.registerEvents(); 610 } else { 611 var html = confirmButton.render(); 612 html += cancelButton.render(); 613 $('.dw').append(html); 614 confirmButton.theme(); 615 confirmButton.registerEvents(); 616 cancelButton.theme(); 617 cancelButton.registerEvents(); 618 } 619 620 /* hide default buttons */ 621 $('#dw_cancel').hide(); 622 $('#dw_set').hide(); 623 624 /* add class to body as selector for showing/hiding labels */ 625 if(!this.showLabels) { 626 $('body').addClass('tmp-datepicker-no-label'); 627 } 628 629 /* call callback */ 630 if(this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['before'])) { 631 M.EventDispatcher.callHandler(this.callbacks['before'], null, NO, [value, date]); 632 } 633 }, 634 635 onClose: function(value, scroller) { 636 /* set value if one was selected */ 637 var source = null; 638 var date = null; 639 if(this.isValueSelected) { 640 /* first compute the date */ 641 try { 642 date = D8.create(scroller.getDate()); 643 } catch(e) { 644 645 } 646 647 /* now, if there is a source, auto-update its value */ 648 if(this.hasSource) { 649 source = M.ViewManager.getViewById(this.source); 650 if(source) { 651 source.setValue(value, NO, YES); 652 } 653 } 654 } 655 656 /* remove class from body as selector for showing/hiding labels */ 657 if(!this.showLabels) { 658 $('body').removeClass('tmp-datepicker-no-label'); 659 } 660 661 /* call cancel callback */ 662 if(!this.isValueSelected && this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['cancel'])) { 663 M.EventDispatcher.callHandler(this.callbacks['cancel'], null, NO, []); 664 } else if(this.isValueSelected && this.callbacks && M.EventDispatcher.checkHandler(this.callbacks['confirm'])) { 665 M.EventDispatcher.callHandler(this.callbacks['confirm'], null, NO, [value, date]); 666 } 667 668 /* kill the datepicker */ 669 Object.getPrototypeOf(this).isActive = NO; 670 $('#' + this.source).scroller('destroy'); 671 $('.dwo').remove(); 672 $('.dw').remove(); 673 this.destroy(); 674 }, 675 676 onSelect: function(value) { 677 /* mark the datepicker as 'valueSelected' */ 678 this.isValueSelected = YES; 679 } 680 681 });