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