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: Sebastian 6 // Date: 04.11.2010 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 * A constant value for input type: text 14 * 15 * @type String 16 */ 17 M.INPUT_TEXT = 'text'; 18 19 /** 20 * A constant value for input type: password 21 * 22 * @type String 23 */ 24 M.INPUT_PASSWORD = 'password'; 25 26 /** 27 * A constant value for input type: number 28 * 29 * @type String 30 */ 31 M.INPUT_NUMBER = 'number'; 32 33 /** 34 * A constant value for input type: tel 35 * 36 * @type String 37 */ 38 M.INPUT_TELEPHONE = 'tel'; 39 40 /** 41 * A constant value for input type: url 42 * 43 * @type String 44 */ 45 M.INPUT_URL = 'url'; 46 47 /** 48 * A constant value for input type: email 49 * 50 * @type String 51 */ 52 M.INPUT_EMAIL = 'email'; 53 54 /** 55 * A constant value for input type: time 56 * 57 * @type String 58 */ 59 M.INPUT_TIME = 'time'; 60 61 /** 62 * A constant value for input type: date 63 * 64 * @type String 65 */ 66 M.INPUT_DATE = 'date'; 67 68 /** 69 * A constant value for input type: month 70 * 71 * @type String 72 */ 73 M.INPUT_MONTH = 'month'; 74 75 /** 76 * A constant value for input type: week 77 * 78 * @type String 79 */ 80 M.INPUT_WEEK = 'week'; 81 82 /** 83 * A constant value for input type: datetime 84 * 85 * @type String 86 */ 87 M.INPUT_DATETIME = 'datetime'; 88 89 /** 90 * A constant value for input type: datetime-local 91 * 92 * @type String 93 */ 94 M.INPUT_DATETIME_LOCAL = 'datetime-local'; 95 96 /** 97 * @class 98 * 99 * M.TextFieldView is the prototype of any text field input view. It can be rendered as both 100 * a single line text field and a multiple line text field. If it is styled as a multiple 101 * line text field, is has a built-in autogrow mechanism so the textfield is getting larger 102 * depending on the number of lines of text a user enters. 103 * 104 * @extends M.View 105 */ 106 M.TextFieldView = M.View.extend( 107 /** @scope M.TextFieldView.prototype */ { 108 109 /** 110 * The type of this object. 111 * 112 * @type String 113 */ 114 type: 'M.TextFieldView', 115 116 /** 117 * The name of the text field. During the rendering, this property gets assigned to the name 118 * property of the text field's html representation. This can be used to manually access the 119 * text field's DOM representation later on. 120 * 121 * @type String 122 */ 123 name: null, 124 125 /** 126 * The label proeprty defines a text that is shown above or next to the textfield as a 'title' 127 * for the textfield. e.g. "Name:". If no label is specified, no label will be displayed. 128 * 129 * @type String 130 */ 131 label: null, 132 133 /** 134 * The initial text shown inside the text field describing the input or making a suggestion for input 135 * e.g. "Please enter your Name." 136 * 137 * @type String 138 */ 139 initialText: '', 140 141 /** 142 * Determines whether to display the textfield grouped with the label specified with the label property. 143 * If set to YES, the textfield and its label are wrapped in a container and styled as a unit 'out of 144 * the box'. If set to NO, custom styling could be necessary. 145 * 146 * If there is no label specified, this property is ignored by default. 147 * 148 * @type Boolean 149 */ 150 isGrouped: NO, 151 152 /** 153 * Defines whether the text field has multiple lines respectively is a text area. 154 * 155 * @type Boolean 156 */ 157 hasMultipleLines: NO, 158 159 /** 160 * This property specifies the input type of this input field. Possible values are: 161 * 162 * - M.INPUT_TEXT --> text input (default) 163 * - M.INPUT_PASSWORD --> password 164 * - M.INPUT_NUMBER --> number 165 * - M.INPUT_TELEPHONE --> tel 166 * - M.INPUT_URL --> url 167 * - M.INPUT_EMAIL --> email 168 * 169 * Note, that these types are not yet supported by all browsers! 170 * 171 * @type String 172 */ 173 inputType: M.INPUT_TEXT, 174 175 /** 176 * This property is used internally to determine all the possible input types for a 177 * date textfield. 178 * 179 * @private 180 * @type Array 181 */ 182 dateInputTypes: [M.INPUT_DATETIME, M.INPUT_DATE, M.INPUT_MONTH, M.INPUT_WEEK, M.INPUT_TIME, M.INPUT_DATETIME_LOCAL], 183 184 /** 185 * This property can be used to specify the allowed number if chars for this text field 186 * view. If nothing is specified, the corresponding 'maxlength' HTML property will not 187 * be set. 188 * 189 * @type Number 190 */ 191 numberOfChars: null, 192 193 /** 194 * This property can be used to specify whether to use the native implementation 195 * of one of the HTML5 input types if it is available. If set to YES, e.g. iOS5 196 * will render its own date/time picker controls to the corresponding input 197 * type. If set to no, the native implementation will be disabled. 198 * 199 * @type Boolean 200 */ 201 useNativeImplementationIfAvailable: YES, 202 203 /** 204 * This property specifies the recommended events for this type of view. 205 * 206 * @type Array 207 */ 208 recommendedEvents: ['focus', 'blur', 'enter', 'keyup', 'tap'], 209 210 /** 211 * Renders a TextFieldView 212 * 213 * @private 214 * @returns {String} The text field view's html representation. 215 */ 216 render: function() { 217 this.computeValue(); 218 this.html += '<div'; 219 220 if(this.label && this.isGrouped) { 221 this.html += ' data-role="fieldcontain"'; 222 } 223 224 if(this.cssClass) { 225 this.html += ' class="' + this.cssClass + '_container"'; 226 } 227 228 this.html += '>'; 229 230 if(this.label) { 231 this.html += '<label for="' + (this.name ? this.name : this.id) + '">' + this.label + '</label>'; 232 } 233 234 if(this.hasMultipleLines) { 235 this.html += '<textarea cols="40" rows="8" name="' + (this.name ? this.name : this.id) + '" id="' + this.id + '"' + this.style() + '>' + (this.value ? this.value : this.initialText) + '</textarea>'; 236 237 } else { 238 var type = this.inputType; 239 if(_.include(this.dateInputTypes, this.inputType) && !this.useNativeImplementationIfAvailable || (this.initialText && this.inputType == M.INPUT_PASSWORD)) { 240 type = 'text'; 241 } 242 243 this.html += '<input ' + (this.numberOfChars ? 'maxlength="' + this.numberOfChars + '"' : '') + 'type="' + type + '" name="' + (this.name ? this.name : this.id) + '" id="' + this.id + '"' + this.style() + ' value="' + (this.value ? this.value : this.initialText) + '" />'; 244 } 245 246 this.html += '</div>'; 247 248 return this.html; 249 }, 250 251 /** 252 * This method is responsible for registering events for view elements and its child views. It 253 * basically passes the view's event-property to M.EventDispatcher to bind the appropriate 254 * events. 255 * 256 * It extend M.View's registerEvents method with some special stuff for text field views and 257 * their internal events. 258 */ 259 registerEvents: function() { 260 this.internalEvents = { 261 focus: { 262 target: this, 263 action: 'gotFocus' 264 }, 265 blur: { 266 target: this, 267 action: 'lostFocus' 268 }, 269 keyup: { 270 target: this, 271 action: 'setValueFromDOM' 272 }, 273 tap: { 274 target: this, 275 action: 'handleTap' 276 } 277 } 278 this.bindToCaller(this, M.View.registerEvents)(); 279 }, 280 281 /** 282 * The contentDidChange method is automatically called by the observable when the 283 * observable's state did change. It then updates the view's value property based 284 * on the specified content binding. 285 * 286 * This is a special implementation for M.TextFieldView. 287 */ 288 contentDidChange: function(){ 289 /* if the text field has the focus, we do not apply the content binding */ 290 if(this.hasFocus) { 291 return; 292 } 293 294 /* let M.View do the real job */ 295 this.bindToCaller(this, M.View.contentDidChange)(); 296 297 this.renderUpdate(); 298 this.delegateValueUpdate(); 299 }, 300 301 /** 302 * Updates a TextFieldView with DOM access by jQuery. 303 * 304 * @param {Boolean} preventValueComputing Determines whether to execute computeValue() or not. 305 * @private 306 */ 307 renderUpdate: function(preventValueComputing) { 308 if(!preventValueComputing) { 309 this.computeValue(); 310 } 311 $('#' + this.id).val(this.value); 312 this.styleUpdate(); 313 }, 314 315 /** 316 * This method is called whenever the view is taped/clicked. Typically a text 317 * field view would not use a tap event. But since a tap is called before the 318 * focus event, we use this to do some input type depending stuff, e.g. show 319 * a date picker. 320 * 321 * @param {String} id The DOM id of the event target. 322 * @param {Object} event The DOM event. 323 * @param {Object} nextEvent The next event (external event), if specified. 324 */ 325 handleTap: function(id, event, nextEvent) { 326 if(_.include(this.dateInputTypes, this.inputType) && (!M.Environment.supportsInputType(this.inputType) || !this.useNativeImplementationIfAvailable)) { 327 M.DatePickerView.show({ 328 source: this, 329 useSourceDateAsInitialDate: YES, 330 showDatePicker: (this.inputType !== M.INPUT_TIME), 331 showTimePicker: (this.inputType === M.INPUT_TIME || this.inputType === M.INPUT_DATETIME || this.inputType === M.INPUT_DATETIME_LOCAL), 332 dateOrder: (this.inputType === M.INPUT_MONTH ? M.DatePickerView.dateOrderMonthOnly : M.DatePickerView.dateOrder), 333 dateFormat: (this.inputType === M.INPUT_MONTH ? M.DatePickerView.dateFormatMonthOnly : M.DatePickerView.dateFormat) 334 }); 335 } 336 337 if(nextEvent) { 338 M.EventDispatcher.callHandler(nextEvent, event, YES); 339 } 340 }, 341 342 /** 343 * This method is called whenever the view gets the focus. 344 * If there is a initial text specified and the value of this text field 345 * still equals this initial text, the value is emptied. 346 * 347 * @param {String} id The DOM id of the event target. 348 * @param {Object} event The DOM event. 349 * @param {Object} nextEvent The next event (external event), if specified. 350 */ 351 gotFocus: function(id, event, nextEvent) { 352 if(this.initialText && (!this.value || this.initialText === this.value)) { 353 this.setValue(''); 354 } 355 this.hasFocus = YES; 356 357 if(nextEvent) { 358 M.EventDispatcher.callHandler(nextEvent, event, YES); 359 } 360 }, 361 362 /** 363 * This method is called whenever the view lost the focus. 364 * If there is a initial text specified and the value of this text field 365 * is empty, the value is set to the initial text. 366 * 367 * @param {String} id The DOM id of the event target. 368 * @param {Object} event The DOM event. 369 * @param {Object} nextEvent The next event (external event), if specified. 370 */ 371 lostFocus: function(id, event, nextEvent) { 372 /* if this is a native date field, get the value from dom */ 373 if(_.include(this.dateInputTypes, this.inputType) && M.Environment.supportsInputType(this.inputType) && this.useNativeImplementationIfAvailable) { 374 this.setValueFromDOM(); 375 } 376 377 if(this.initialText && !this.value) { 378 this.setValue(this.initialText, NO); 379 this.value = ''; 380 } 381 this.hasFocus = NO; 382 383 if(nextEvent) { 384 M.EventDispatcher.callHandler(nextEvent, event, YES); 385 } 386 }, 387 388 /** 389 * Method to append css styles inline to the rendered text field. 390 * 391 * @private 392 * @returns {String} The text field's styling as html representation. 393 */ 394 style: function() { 395 var html = ' style="'; 396 if(this.isInline) { 397 html += 'display:inline;'; 398 } 399 html += '"'; 400 401 if(!this.isEnabled) { 402 html += ' disabled="disabled"'; 403 } 404 405 if(this.cssClass) { 406 html += ' class="' + this.cssClass + '"'; 407 } 408 409 return html; 410 }, 411 412 /** 413 * Triggers the rendering engine, jQuery mobile, to style the text field. 414 * 415 * @private 416 */ 417 theme: function() { 418 if(this.initialText && !this.value && this.cssClassOnInit) { 419 this.addCssClass(this.cssClassOnInit); 420 } 421 422 /* trigger keyup event to make the text field autogrow */ 423 if(this.value) { 424 $('#' + this.id).trigger('keyup'); 425 } 426 }, 427 428 /** 429 * Method to append css styles inline to the rendered view on the fly. 430 * 431 * @private 432 */ 433 styleUpdate: function() { 434 /* trigger keyup event to make the text field autogrow (enable fist, if necessary) */ 435 if(this.value) { 436 $('#' + this.id).removeAttr('disabled'); 437 $('#' + this.id).trigger('keyup'); 438 } 439 440 if(this.isInline) { 441 $('#' + this.id).attr('display', 'inline'); 442 } else { 443 $('#' + this.id).removeAttr('display'); 444 } 445 446 if(!this.isEnabled) { 447 $('#' + this.id).attr('disabled', 'disabled'); 448 } else { 449 $('#' + this.id).removeAttr('disabled'); 450 } 451 }, 452 453 /** 454 * This method sets its value to the value it has in its DOM representation 455 * and then delegates these changes to a controller property if the 456 * contentBindingReverse property is set. 457 * 458 * Additionally call target / action if set. 459 * 460 * @param {String} id The DOM id of the event target. 461 * @param {Object} event The DOM event. 462 * @param {Object} nextEvent The next event (external event), if specified. 463 */ 464 setValueFromDOM: function(id, event, nextEvent) { 465 this.value = this.secure($('#' + this.id).val()); 466 this.delegateValueUpdate(); 467 468 if(nextEvent) { 469 M.EventDispatcher.callHandler(nextEvent, event, YES); 470 } 471 }, 472 473 /** 474 * This method sets the text field's value, initiates its re-rendering 475 * and call the delegateValueUpdate(). 476 * 477 * @param {String} value The value to be applied to the text field view. 478 * @param {Boolean} delegateUpdate Determines whether to delegate this value update to any observer or not. 479 * @param {Boolean} preventValueComputing Determines whether to execute computeValue() or not. 480 */ 481 setValue: function(value, delegateUpdate, preventValueComputing) { 482 this.value = value; 483 484 // Handle the classOnInit for initial text 485 if(value != this.initialText) { 486 if(this.cssClassOnInit) { 487 this.removeCssClass(this.cssClassOnInit); 488 } 489 if(this.inputType == M.INPUT_PASSWORD) { 490 // Set the field type to password 491 $('#' + this.id).prop('type','password'); 492 } 493 } 494 else { 495 if(this.cssClassOnInit) { 496 this.addCssClass(this.cssClassOnInit); 497 } 498 499 if(this.inputType == M.INPUT_PASSWORD) { 500 // Set the field type to text 501 $('#' + this.id).prop('type','text'); 502 } 503 } 504 505 this.renderUpdate(preventValueComputing); 506 507 if(delegateUpdate) { 508 this.delegateValueUpdate(); 509 } 510 }, 511 512 /** 513 * This method disables the text field by setting the disabled property of its 514 * html representation to true. 515 */ 516 disable: function() { 517 this.isEnabled = NO; 518 this.renderUpdate(); 519 }, 520 521 /** 522 * This method enables the text field by setting the disabled property of its 523 * html representation to false. 524 */ 525 enable: function() { 526 this.isEnabled = YES; 527 this.renderUpdate(); 528 }, 529 530 /** 531 * This method clears the text field's value, both in the DOM and within the JS object. 532 */ 533 clearValue: function() { 534 this.setValue(''); 535 536 /* call lostFocus() to get the initial text displayed */ 537 this.lostFocus(); 538 }, 539 540 /** 541 * This method returns the text field view's value. 542 * 543 * @returns {String} The text field view's value. 544 */ 545 getValue: function() { 546 return this.value; 547 } 548 549 }); 550