1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 sc_require('mixins/validatable') ; 9 10 /** @class 11 12 Base view for managing a view backed by an input element. Since the web 13 browser provides native support for editing input elements, this view 14 provides basic support for listening to changes on these input elements and 15 responding to them. 16 17 Generally you will not work with a FieldView directly. Instead, you should 18 use one of the subclasses implemented by your target platform such as 19 SC.CheckboxView, SC.RadioView, SC.TextFieldView, and so on. 20 21 @extends SC.View 22 @extends SC.Control 23 @extends SC.Validatable 24 @since SproutCore 1.0 25 */ 26 SC.FieldView = SC.View.extend(SC.Control, SC.Validatable, 27 /** @scope SC.FieldView.prototype */ { 28 29 /** 30 _field_isMouseDown: NO, 31 32 /** 33 The raw value of the field itself. This is computed from the 'value' 34 property by passing it through any validator you might have set. This is 35 the value that will be set on the field itself when the view is updated. 36 37 @type String 38 */ 39 fieldValue: function() { 40 var value = this.get('value'); 41 if (SC.typeOf(value) === SC.T_ERROR) value = value.get('errorValue'); 42 return this.fieldValueForObject(value); 43 }.property('value', 'validator').cacheable(), 44 45 // .......................................................... 46 // PRIMITIVES 47 // 48 49 /** 50 Override to return an CoreQuery object that selects the input elements 51 for the view. If this method is defined, the field view will 52 automatically edit the attrbutes of the input element to reflect the 53 current isEnabled state among other things. 54 */ 55 $input: function() { 56 var elementTagName = this._inputElementTagName(); // usually "input" 57 return this.$(elementTagName).andSelf().filter(elementTagName); 58 }, 59 60 /** @private 61 Override to specify the HTML element type to use as the field. For 62 example, "input" or "textarea". 63 */ 64 _inputElementTagName: function() { 65 return 'input'; 66 }, 67 68 /** 69 Override to set the actual value of the field. 70 71 The default implementation will simple copy the newValue to the value 72 attribute of any input tags in the receiver view. You can override this 73 method to provide specific functionality needed by your view. 74 75 @param {Object} newValue the value to display. 76 @returns {SC.FieldView} receiver 77 */ 78 setFieldValue: function(newValue) { 79 if (SC.none(newValue)) newValue = '' ; 80 var input = this.$input(); 81 82 // Don't needlessly set the element if it already has the value, because 83 // doing so moves the cursor to the end in some browsers. 84 if (input.val() !== newValue) { 85 input.val(newValue); 86 } 87 return this ; 88 }, 89 90 /** 91 Override to retrieve the actual value of the field. 92 93 The default implementation will simply retrieve the value attribute from 94 the first input tag in the receiver view. 95 96 @returns {String} value 97 */ 98 getFieldValue: function() { 99 return this.$input().val(); 100 }, 101 102 _field_fieldValueDidChange: function(evt) { 103 SC.run(function() { 104 this.fieldValueDidChange(NO); 105 }, this); 106 }, 107 108 /** 109 Your class should call this method anytime you think the value of the 110 input element may have changed. This will retrieve the value and update 111 the value property of the view accordingly. 112 113 If this is a partial change (i.e. the user is still editing the field and 114 you expect the value to change further), then be sure to pass YES for the 115 partialChange parameter. This will change the kind of validation done on 116 the value. Otherwise, the validator may mark the field as having an error 117 when the user is still in mid-edit. 118 119 @param partialChange (optional) YES if this is a partial change. 120 @returns {Boolean|SC.Error} result of validation. 121 */ 122 fieldValueDidChange: function(partialChange) { 123 // collect the field value and convert it back to a value 124 var fieldValue = this.getFieldValue(); 125 var value = this.objectForFieldValue(fieldValue, partialChange); 126 this.setIfChanged('value', value); 127 128 129 // ======= [Old code -- left here for concept reminders. Basic validation 130 // API works without it] ======= 131 132 // validate value if needed... 133 134 // this.notifyPropertyChange('fieldValue'); 135 // 136 // // get the field value and set it. 137 // // if ret is an error, use that instead of the field value. 138 // var ret = this.performValidate ? this.performValidate(partialChange) : YES; 139 // if (ret === SC.VALIDATE_NO_CHANGE) return ret ; 140 // 141 // this.propertyWillChange('fieldValue'); 142 // 143 // // if the validator says everything is OK, then in addition to posting 144 // // out the value, go ahead and pass the value back through itself. 145 // // This way if you have a formatter applied, it will reformat. 146 // // 147 // // Do this BEFORE we set the value so that the valueObserver will not 148 // // overreact. 149 // // 150 // var ok = SC.$ok(ret); 151 // var value = ok ? this._field_getFieldValue() : ret ; 152 // if (!partialChange && ok) this._field_setFieldValue(value) ; 153 // this.set('value',value) ; 154 // 155 // this.propertyDidChange('fieldValue'); 156 // 157 // return ret ; 158 }, 159 160 // .......................................................... 161 // INTERNAL SUPPORT 162 // 163 164 /** @private 165 invoked when the value property changes. Sets the field value... 166 */ 167 _field_valueDidChange: function() { 168 this.setFieldValue(this.get('fieldValue')); 169 }.observes('fieldValue'), 170 171 /** 172 SC.View view state callback. 173 174 After the layer is created, set the field value and begin observing 175 change events on the input field. 176 */ 177 didCreateLayer: function() { 178 this.setFieldValue(this.get('fieldValue')); 179 this._addChangeEvent(); 180 }, 181 182 /** 183 SC.View state callback. 184 185 Removes the change event from the input field. 186 */ 187 willDestroyLayer: function() { 188 SC.Event.remove(this.$input(), 'change', this, this._field_fieldValueDidChange); 189 }, 190 191 // ACTIONS 192 // You generally do not need to override these but they may be used. 193 194 /** 195 Called to perform validation on the field just before the form 196 is submitted. If you have a validator attached, this will get the 197 validators. 198 */ 199 // validateSubmit: function() { 200 // var ret = this.performValidateSubmit ? this.performValidateSubmit() : YES; 201 // // save the value if needed 202 // var value = SC.$ok(ret) ? this._field_getFieldValue() : ret ; 203 // if (value != this.get('value')) this.set('value', value) ; 204 // return ret ; 205 // }, 206 207 // OVERRIDE IN YOUR SUBCLASS 208 // Override these primitives in your subclass as required. 209 210 /** 211 SC.RootResponder event handler. 212 213 Allow the browser to do its normal event handling for the mouse down 214 event. But first, set isActive to YES. 215 */ 216 mouseDown: function(evt) { 217 this._field_isMouseDown = YES; 218 evt.allowDefault(); 219 return YES; 220 }, 221 222 /** 223 SC.RootResponder event handler. 224 225 Remove the active class on mouseExited if mouse is down. 226 */ 227 mouseExited: function(evt) { 228 if (this._field_isMouseDown) this.set('isActive', NO); 229 evt.allowDefault(); 230 return YES; 231 }, 232 233 /** 234 SC.RootResponder event handler. 235 236 If mouse was down and we renter the button area, set the active state again. 237 */ 238 mouseEntered: function(evt) { 239 this.set('isActive', this._field_isMouseDown); 240 evt.allowDefault(); 241 return YES; 242 }, 243 244 /** 245 SC.RootResponder event handler. 246 247 On mouse up, remove the isActive class and then allow the browser to do 248 its normal thing. 249 */ 250 mouseUp: function(evt) { 251 // track independently in case isEnabled has changed 252 if (this._field_isMouseDown) this.set('isActive', NO); 253 this._field_isMouseDown = NO; 254 evt.allowDefault(); 255 return YES ; 256 }, 257 258 /** 259 SC.RootResponder event handler. 260 261 Simply allow keyDown & keyUp to pass through to the default web browser 262 implementation. 263 */ 264 keyDown: function(evt) { 265 266 // handle tab key 267 if (evt.which === 9 || evt.keyCode===9) { 268 var view = evt.shiftKey ? this.get('previousValidKeyView') : this.get('nextValidKeyView'); 269 if (view) view.becomeFirstResponder(); 270 else evt.allowDefault(); 271 return YES ; // handled 272 } 273 274 // validate keyDown... 275 if (this.performValidateKeyDown(evt)) { 276 this._isKeyDown = YES ; 277 evt.allowDefault(); 278 } else { 279 evt.stop(); 280 } 281 282 return YES; 283 }, 284 285 /** 286 Override of SC.Responder.prototype.acceptsFirstResponder. 287 288 Tied to the `isEnabledInPane` state. 289 */ 290 acceptsFirstResponder: function() { 291 if (SC.FOCUS_ALL_CONTROLS) { return this.get('isEnabledInPane'); } 292 return NO; 293 }.property('isEnabledInPane'), 294 295 /** @private */ 296 _addChangeEvent: function() { 297 SC.Event.add(this.$input(), 'change', this, this._field_fieldValueDidChange); 298 }, 299 300 /** @private */ 301 // these methods use the validator to convert the raw field value returned 302 // by your subclass into an object and visa versa. 303 _field_setFieldValue: function(newValue) { 304 this.propertyWillChange('fieldValue'); 305 if (this.fieldValueForObject) { 306 newValue = this.fieldValueForObject(newValue) ; 307 } 308 var ret = this.setFieldValue(newValue) ; 309 this.propertyDidChange('fieldValue'); 310 return ret ; 311 }, 312 313 /** @private */ 314 _field_getFieldValue: function() { 315 var ret = this.getFieldValue() ; 316 if (this.objectForFieldValue) ret = this.objectForFieldValue(ret); 317 return ret ; 318 } 319 }); 320 321