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/content_value_support'); 9 sc_require('system/string'); 10 11 /** 12 Option for controls to automatically calculate their size (should be default 13 on controls that use renderers). 14 15 @type String 16 @constant 17 */ 18 SC.AUTO_CONTROL_SIZE = '__AUTO__'; 19 20 /** 21 Option for HUGE control size. 22 23 @type String 24 @constant 25 */ 26 SC.JUMBO_CONTROL_SIZE = 'sc-jumbo-size'; 27 28 /** 29 Option for HUGE control size. 30 31 @type String 32 @constant 33 */ 34 SC.HUGE_CONTROL_SIZE = 'sc-huge-size'; 35 36 /** 37 Option for large control size. 38 39 @type String 40 @constant 41 */ 42 SC.LARGE_CONTROL_SIZE = 'sc-large-size'; 43 44 /** 45 Option for standard control size. 46 47 @type String 48 @constant 49 */ 50 SC.REGULAR_CONTROL_SIZE = 'sc-regular-size'; 51 52 /** 53 Option for small control size. 54 55 @type String 56 @constant 57 */ 58 SC.SMALL_CONTROL_SIZE = 'sc-small-size'; 59 60 /** 61 Option for tiny control size 62 63 @type String 64 @constant 65 */ 66 SC.TINY_CONTROL_SIZE = 'sc-tiny-size'; 67 68 /** 69 @namespace 70 71 A Control is a view that also implements some basic state functionality. 72 Apply this mixin to any view that you want to have standard control 73 functionality including showing a selected state, enabled state, focus 74 state, etc. 75 76 ## About Values and Content 77 78 Controls typically are used to represent a single value, such as a number, 79 boolean or string. The value a control is managing is typically stored in 80 a "value" property. You will typically use the value property when working 81 with controls such as buttons and text fields in a form. 82 83 An alternative way of working with a control is to use it to manage some 84 specific aspect of a content object. For example, you might use a label 85 view control to display the "name" property of a Contact record. This 86 approach is often necessary when using the control as part of a collection 87 view. 88 89 You can use the content-approach to work with a control by setting the 90 "content" and "contentValueKey" properties of the control. The 91 "content" property is the content object you want to manage, while the 92 "contentValueKey" is the name of the property on the content object 93 you want the control to display. 94 95 The default implementation of the Control mixin will essentially map the 96 contentValueKey of a content object to the value property of the 97 control. Thus if you are writing a custom control yourself, you can simply 98 work with the value property and the content object support will come for 99 free. Just write an observer for the value property and update your 100 view accordingly. 101 102 If you are working with a control that needs to display multiple aspects 103 of a single content object (for example showing an icon and label), then 104 you can override the contentValueDidChange() method instead of observing 105 the value property. This method will be called anytime _any_ property 106 on the content object changes. You should use this method to check the 107 properties you care about on the content object and update your view if 108 anything you care about has changed. 109 110 ## Delegate Support 111 112 Controls can optionally get the contentDisplayProperty from a 113 displayDelegate, if it is set. The displayDelegate is often used to 114 delegate common display-related configurations such as which content value 115 to show. Anytime your control is shown as part of a collection view, the 116 collection view will be automatically set as its displayDelegate. 117 118 @since SproutCore 1.0 119 @extends SC.ContentValueSupport 120 */ 121 SC.Control = SC.mixin(SC.clone(SC.ContentValueSupport), 122 /** @scope SC.Control.prototype */{ 123 124 /** 125 Walk like a duck 126 127 @type Boolean 128 @default YES 129 @readOnly 130 */ 131 isControl: YES, 132 133 /** 134 The selected state of this control. Possible values: 135 136 - `YES` 137 - `NO` 138 - SC.MIXED_STATE. 139 140 @type Boolean 141 @default NO 142 */ 143 isSelected: NO, 144 145 /** @private */ 146 isSelectedBindingDefault: SC.Binding.oneWay().bool(), 147 148 /** 149 Set to YES when the item is currently active. Usually this means the 150 mouse is current pressed and hovering over the control, however the 151 specific implementation my vary depending on the control. 152 153 Changing this property value by default will cause the Control mixin to 154 add/remove an 'active' class name to the root element. 155 156 @type Boolean 157 @default NO 158 */ 159 isActive: NO, 160 161 /** @private */ 162 isActiveBindingDefault: SC.Binding.oneWay().bool(), 163 164 /** 165 The name of the property this control should display if it is part of an 166 SC.FormView. 167 168 If you add a control as part of an SC.FormView, then the form view will 169 automatically bind the value to the property key you name here on the 170 content object. 171 172 @type String 173 @default null 174 */ 175 fieldKey: null, 176 177 /** 178 The human readable label you want shown for errors. May be a loc string. 179 180 If your field fails validation, then this is the name that will be shown 181 in the error explanation. If you do not set this property, then the 182 fieldKey or the class name will be used to generate a human readable name. 183 184 @type String 185 @default null 186 */ 187 fieldLabel: null, 188 189 /** 190 The human readable label for this control for use in error strings. This 191 property is computed dynamically using the following rules: 192 193 If the fieldLabel is defined, that property is localized and returned. 194 Otherwise, if the keyField is defined, try to localize using the string 195 "ErrorLabel.{fieldKeyName}". If a localized name cannot be found, use a 196 humanized form of the fieldKey. 197 198 Try to localize using the string "ErrorLabel.{ClassName}". Return a 199 humanized form of the class name. 200 201 @field 202 @type String 203 @observes 'fieldLabel' 204 @observes 'fieldKey' 205 */ 206 errorLabel: function () { 207 var ret = this.get('fieldLabel'), fk, def, locFK; 208 209 // Fast path! 210 if (ret) return ret; 211 212 // if field label is not provided, compute something... 213 fk = this.get('fieldKey') || this.constructor.toString(); 214 def = fk ? SC.String.capitalize(SC.String.humanize(fk)) : ''; 215 locFK = SC.String.locWithDefault("FieldKey." + fk, def); 216 return SC.String.locWithDefault("ErrorLabel." + fk, locFK); 217 }.property('fieldLabel', 'fieldKey').cacheable(), 218 219 /** 220 The control size. This will set a CSS style on the element that can be 221 used by the current theme to vary the appearance of the control. 222 223 Some controls will default to SC.AUTO_CONTROL_SIZE, which will allow you 224 to simply size the control, and the most appropriate control size will 225 automatically be picked; be warned, though, that if you don't specify 226 a height, performance will be impacted as it must be calculated; if you do 227 this, a warning will be issued. If you don't care, use SC.CALCULATED_CONTROL_SIZE. 228 229 @type String 230 @default SC.REGULAR_CONTROL_SIZE 231 */ 232 controlSize: SC.REGULAR_CONTROL_SIZE, 233 234 /** @private */ 235 displayProperties: ['isSelected', 'isActive', 'controlSize'], 236 237 /** @private */ 238 _CONTROL_TMP_CLASSNAMES: {}, 239 240 /** @private 241 Invoke this method in your updateDisplay() method to update any basic 242 control CSS classes. 243 */ 244 renderMixin: function (context, firstTime) { 245 var sel = this.get('isSelected'), 246 disabled = !this.get('isEnabledInPane'), 247 // update the CSS classes for the control. note we reuse the same hash 248 // to avoid consuming more memory 249 names = this._CONTROL_TMP_CLASSNAMES; // temporary object 250 251 names.mixed = sel === SC.MIXED_STATE; 252 names.sel = sel && (sel !== SC.MIXED_STATE); 253 names.active = this.get('isActive'); 254 255 var controlSize = this.get("controlSize"); 256 if (!controlSize) { 257 controlSize = SC.REGULAR_CONTROL_SIZE; 258 } 259 260 if (firstTime) { 261 context.setClass(names); 262 263 // delegates handle adding 'controlSize' on their own. We only support it 264 // here for backwards-compatibility. 265 if (!this.get('renderDelegate')) { 266 context.addClass(controlSize); 267 } 268 } else { 269 context.$().setClass(names); 270 if (!this.get('renderDelegate')) { 271 context.$().addClass(controlSize); 272 } 273 } 274 275 // if the control implements the $input() helper, then fixup the input 276 // tags 277 if (!firstTime && this.$input) { 278 var inps = this.$input(); 279 280 if (inps.attr('type') !== "radio") { 281 this.$input().attr('disabled', disabled); 282 } 283 } 284 } 285 286 }); 287 288