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 /*global jQuery*/
  8 
  9 sc_require('system/browser');
 10 sc_require('system/event');
 11 sc_require('system/cursor');
 12 sc_require('system/responder');
 13 sc_require('system/theme');
 14 
 15 sc_require('system/string');
 16 sc_require('views/view/statechart');
 17 
 18 
 19 /**
 20   Default property to disable or enable by default the contextMenu
 21 */
 22 SC.CONTEXT_MENU_ENABLED = YES;
 23 
 24 /**
 25   Default property to disable or enable if the focus can jump to the address
 26   bar or not.
 27 */
 28 SC.TABBING_ONLY_INSIDE_DOCUMENT = NO;
 29 
 30 /**
 31   Tells the property (when fetched with themed()) to get its value from the renderer (if any).
 32 */
 33 SC.FROM_THEME = "__FROM_THEME__"; // doesn't really matter what it is, so long as it is unique. Readability is a plus.
 34 
 35 /** @private - custom array used for child views */
 36 SC.EMPTY_CHILD_VIEWS_ARRAY = [];
 37 SC.EMPTY_CHILD_VIEWS_ARRAY.needsClone = YES;
 38 
 39 /**
 40   @class
 41 
 42 */
 43 SC.CoreView.reopen(
 44 /** @scope SC.View.prototype */ {
 45 
 46   /**
 47     An array of the properties of this class that will be concatenated when
 48     also present on subclasses.
 49 
 50     @type Array
 51     @default ['outlets', 'displayProperties', 'classNames', 'renderMixin', 'didCreateLayerMixin', 'willDestroyLayerMixin', 'classNameBindings', 'attributeBindings']
 52   */
 53   concatenatedProperties: ['outlets', 'displayProperties', 'classNames', 'renderMixin', 'didCreateLayerMixin', 'willDestroyLayerMixin', 'classNameBindings', 'attributeBindings'],
 54 
 55   /**
 56     The WAI-ARIA role of the control represented by this view. For example, a
 57     button may have a role of type 'button', or a pane may have a role of
 58     type 'alertdialog'. This property is used by assistive software to help
 59     visually challenged users navigate rich web applications.
 60 
 61     The full list of valid WAI-ARIA roles is available at:
 62     http://www.w3.org/TR/wai-aria/roles#roles_categorization
 63 
 64     @type String
 65     @default null
 66   */
 67   ariaRole: null,
 68 
 69   /**
 70     The aria-hidden role is managed appropriately by the internal view's
 71     statechart.  When the view is not currently displayed the aria-hidden
 72     attribute will be set to true.
 73 
 74     @type String
 75     @default null
 76     @deprecated Version 1.10
 77   */
 78   ariaHidden: null,
 79 
 80   /**
 81     Whether this view was created by its parent view or not.
 82 
 83     Several views are given child view classes or instances to automatically
 84     append and remove.  In the case that the view was provided an instance,
 85     when it removes the instance and no longer needs it, it should not destroy
 86     the instance because it was created by someone else.
 87 
 88     On the other hand if the view was given a class that it creates internal
 89     instances from, then it should destroy those instances properly to avoid
 90     memory leaks.
 91 
 92     This property should be set by any view that is creating internal child
 93     views so that it can properly remove them later.  Note that if you use
 94     `createChildView`, this property is set automatically for you.
 95 
 96     @type Boolean
 97     @see SC.View#createChildView
 98     @default false
 99   */
100   createdByParent: false,
101 
102   /** @deprecated Version 1.11.0 Please use parentView instead. */
103   owner: function () {
104     //@if(debug)
105     SC.warn("Developer Warning: The `owner` property of SC.View has been deprecated in favor of the `parentView`, which is the same value. Please use `parentView`.");
106     //@endif
107     return this.get('parentView');
108   }.property('parentView').cacheable(),
109 
110   /**
111     The current pane.
112 
113     @field
114     @type SC.Pane
115     @default null
116   */
117   pane: function () {
118     var view = this;
119 
120     while (view && !view.isPane) { view = view.get('parentView'); }
121 
122     return view;
123   }.property('parentView').cacheable(),
124 
125   /**
126     The page this view was instantiated from.  This is set by the page object
127     during instantiation.
128 
129     @type SC.Page
130     @default null
131   */
132   page: null,
133 
134   /**
135     If the view is currently inserted into the DOM of a parent view, this
136     property will point to the parent of the view.
137 
138     @type SC.View
139     @default null
140   */
141   parentView: null,
142 
143   /**
144     The isVisible property determines if the view should be displayed or not.
145 
146     If you also set a transitionShow or transitionHide plugin, then when
147     isVisible changes, the appropriate transition will execute as the view's
148     visibility changes.
149 
150     Note that isVisible can be set to true and the view may still not be
151     "visible" in the window.  This can occur if:
152 
153       1. the view is not attached to the document.
154       2. the view has a view ancestor with isVisible set to false.
155 
156     @type Boolean
157     @see SC.View#viewState
158     @default true
159   */
160   isVisible: true,
161   isVisibleBindingDefault: SC.Binding.bool(),
162 
163   // ..........................................................
164   // CHILD VIEW SUPPORT
165   //
166 
167   /**
168     Array of child views.  You should never edit this array directly unless
169     you are implementing createChildViews().  Most of the time, you should
170     use the accessor methods such as appendChild(), insertBefore() and
171     removeChild().
172 
173     @type Array
174     @default []
175   */
176   childViews: SC.EMPTY_CHILD_VIEWS_ARRAY,
177 
178   /**
179     Use this property to automatically mix in a collection of mixins into all
180     child views created by the view. This collection is applied during createChildView
181     @property
182 
183     @type Array
184     @default null
185   */
186   autoMixins: null,
187 
188   // ..........................................................
189   // LAYER SUPPORT
190   //
191 
192   /**
193     Returns the current layer for the view.  The layer for a view is only
194     generated when the view first becomes visible in the window and even
195     then it will not be computed until you request this layer property.
196 
197     If the layer is not actually set on the view itself, then the layer will
198     be found by calling this.findLayerInParentLayer().
199 
200     You can also set the layer by calling set on this property.
201 
202     @type DOMElement the layer
203   */
204   layer: function (key, value) {
205     if (value !== undefined) {
206       this._view_layer = value;
207 
208     // no layer...attempt to discover it...
209     } else {
210       value = this._view_layer;
211       if (!value) {
212         var parent = this.get('parentView');
213         if (parent) { parent = parent.get('layer'); }
214         this._view_layer = value = this.findLayerInParentLayer(parent);
215       }
216     }
217     return value;
218   }.property('isVisibleInWindow').cacheable(),
219 
220   /**
221     Get a CoreQuery object for this view's layer, or pass in a selector string
222     to get a CoreQuery object for a DOM node nested within this layer.
223 
224     @param {String} sel a CoreQuery-compatible selector string
225     @returns {SC.CoreQuery} the CoreQuery object for the DOM node
226   */
227   $: function (sel) {
228     var layer = this.get('layer');
229 
230     if (!layer) { return SC.$(); }
231     else if (sel === undefined) { return SC.$(layer); }
232     else { return SC.$(sel, layer); }
233   },
234 
235   /**
236     Returns the DOM element that should be used to hold child views when they
237     are added/remove via DOM manipulation.  The default implementation simply
238     returns the layer itself.  You can override this to return a DOM element
239     within the layer.
240 
241     @type DOMElement the container layer
242   */
243   containerLayer: function () {
244     return this.get('layer');
245   }.property('layer').cacheable(),
246 
247   /**
248     The ID to use when trying to locate the layer in the DOM.  If you do not
249     set the layerId explicitly, then the view's GUID will be used instead.
250     This ID must be set at the time the view is created.
251 
252     @type String
253     @readOnly
254   */
255   layerId: function (key, value) {
256     if (value) { this._layerId = value; }
257     if (this._layerId) { return this._layerId; }
258     return SC.guidFor(this);
259   }.property().cacheable(),
260 
261   /**
262     Attempts to discover the layer in the parent layer.  The default
263     implementation looks for an element with an ID of layerId (or the view's
264     guid if layerId is null).  You can override this method to provide your
265     own form of lookup.  For example, if you want to discover your layer using
266     a CSS class name instead of an ID.
267 
268     @param {DOMElement} parentLayer the parent's DOM layer
269     @returns {DOMElement} the discovered layer
270   */
271   findLayerInParentLayer: function (parentLayer) {
272     var id = "#" + this.get('layerId').escapeCssIdForSelector();
273     return jQuery(id, parentLayer)[0] || jQuery(id)[0];
274   },
275 
276   /**
277     Returns YES if the receiver is a subview of a given view or if it's
278     identical to that view. Otherwise, it returns NO.
279 
280     @property {SC.View} view
281   */
282   isDescendantOf: function (view) {
283     var parentView = this.get('parentView');
284 
285     if (this === view) { return YES; }
286     else if (parentView) { return parentView.isDescendantOf(view); }
287     else { return NO; }
288   },
289 
290   /**
291     This method is invoked whenever a display property changes and updates
292     the view's content once at the end of the run loop before any invokeLast
293     functions run.
294 
295     To cause the view to be updated you can call this method directly and
296     if you need to perform additional setup whenever the display changes, you
297     can override this method as well.
298 
299     @returns {SC.View} receiver
300   */
301   displayDidChange: function () {
302     //@if (debug)
303     if (SC.LOG_VIEW_STATES) {
304       SC.Logger.log('%c%@:%@ — displayDidChange()'.fmt(this, this.get('viewState')), SC.LOG_VIEW_STATES_STYLE[this.get('viewState')]);
305     }
306     //@endif
307 
308     // Don't run _doUpdateContent needlessly, because the view may render
309     // before it is invoked, which would result in a needless update.
310     if (this.get('_isRendered')) {
311       // Legacy.
312       this.set('layerNeedsUpdate', true);
313 
314       this.invokeOnce(this._doUpdateContent);
315     }
316 
317     return this;
318   },
319 
320   /**
321     This property has no effect and is deprecated.
322 
323     To cause a view to update immediately, you should just call updateLayer or
324     updateLayerIfNeeded.  To cause a view to update at the end of the run loop
325     before any invokeLast functions run, you should call displayDidChange.
326 
327     @deprecated Version 1.10
328     @type Boolean
329     @test in updateLayer
330   */
331   layerNeedsUpdate: NO,
332 
333   /**
334     Updates the view's layer if the view is in a shown state.  Otherwise, the
335     view will be updated the next time it enters a shown state.
336 
337     This is the same behavior as `displayDidChange` except that calling
338     `updateLayerIfNeeded` will attempt to update each time it is called,
339     while `displayDidChange` will only attempt to update the layer once per run
340     loop.
341 
342     @returns {SC.View} receiver
343     @test in updateLayer
344   */
345   updateLayerIfNeeded: function (skipIsVisibleInWindowCheck) {
346     //@if(debug)
347     if (skipIsVisibleInWindowCheck) {
348       SC.warn("Developer Warning: The `skipIsVisibleInWindowCheck` argument of updateLayerIfNeeded is not supported and will be ignored.");
349     }
350     //@endif
351     this._doUpdateContent(false);
352 
353     return this;
354   },
355 
356   /**
357     This is the core method invoked to update a view layer whenever it has
358     changed.  This method simply creates a render context focused on the
359     layer element and then calls your render() method.
360 
361     You will not usually call or override this method directly.  Instead you
362     should set the layerNeedsUpdate property to YES to cause this method to
363     run at the end of the run loop, or you can call updateLayerIfNeeded()
364     to force the layer to update immediately.
365 
366     Instead of overriding this method, consider overriding the render() method
367     instead, which is called both when creating and updating a layer.  If you
368     do not want your render() method called when updating a layer, then you
369     should override this method instead.
370 
371     @returns {SC.View} receiver
372   */
373   updateLayer: function () {
374     this._doUpdateContent(true);
375 
376     return this;
377   },
378 
379   /** @private */
380   parentViewDidResize: function () {
381     if (!this.get('hasLayout')) { this.notifyPropertyChange('frame'); }
382     this.viewDidResize();
383   },
384 
385   /**
386     Override this in a child class to define behavior that should be invoked
387     when a parent's view was resized.
388    */
389   viewDidResize: function () {},
390 
391   /**
392     Creates a new renderContext with the passed tagName or element.  You
393     can override this method to provide further customization to the context
394     if needed.  Normally you will not need to call or override this method.
395 
396     @returns {SC.RenderContext}
397   */
398   renderContext: function (tagNameOrElement) {
399     return SC.RenderContext(tagNameOrElement);
400   },
401 
402   /**
403     Creates the layer by creating a renderContext and invoking the view's
404     render() method.  This will only create the layer if the layer does not
405     already exist.
406 
407     When you create a layer, it is expected that your render() method will
408     also render the HTML for all child views as well.  This method will
409     notify the view along with any of its childViews that its layer has been
410     created.
411 
412     @returns {SC.View} receiver
413   */
414   createLayer: function () {
415     if (!this.get('_isRendered')) {
416       this._doRender();
417     }
418 
419     return this;
420   },
421 
422   /**
423     Destroys any existing layer along with the layer for any child views as
424     well.  If the view does not currently have a layer, then this method will
425     do nothing.
426 
427     If you implement willDestroyLayer() on your view or if any mixins
428     implement willDestroLayerMixin(), then this method will be invoked on your
429     view before your layer is destroyed to give you a chance to clean up any
430     event handlers, etc.
431 
432     If you write a willDestroyLayer() handler, you can assume that your
433     didCreateLayer() handler was called earlier for the same layer.
434 
435     Normally you will not call or override this method yourself, but you may
436     want to implement the above callbacks when it is run.
437 
438     @returns {SC.View} receiver
439   */
440   destroyLayer: function () {
441     // We allow you to call destroy layer, but you should really detach first.
442     if (this.get('isAttached')) {
443       this._doDetach();
444     }
445 
446     if (this.get('_isRendered')) {
447       this._doDestroyLayer();
448     }
449 
450     return this;
451   },
452 
453   /**
454     Destroys and recreates the current layer.  Doing this on a parent view can
455     be more efficient than modifying individual child views independently.
456 
457     @returns {SC.View} receiver
458   */
459   replaceLayer: function () {
460     var layer, parentNode;
461 
462     // If attached, detach and track our parent node so we can re-attach.
463     if (this.get('isAttached')) {
464       layer = this.get('layer');
465       parentNode = layer.parentNode;
466 
467       this._doDetach();
468     }
469 
470     this.destroyLayer().createLayer();
471 
472     // Reattach our layer (if we have a parentView this is done automatically).
473     if (parentNode && !this.get('isAttached')) { this._doAttach(parentNode); }
474 
475     return this;
476   },
477 
478   /**
479     If the parent view has changed, we need to insert this
480     view's layer into the layer of the new parent view.
481   */
482   parentViewDidChange: function () {
483     //@if(debug)
484     SC.warn("Developer Warning: parentViewDidChange has been deprecated.  Please use the notification methods willAddChild, didAddChild, willRemoveChild or didRemoveChild on the parent or willAddToParent, didAddToParent, willRemoveFromParent or didRemoveFromParent on the child to perform updates when the parent/child status changes.");
485     //@endif
486   },
487 
488   /**
489     Set to YES when the view's layer location is dirty.  You can call
490     updateLayerLocationIfNeeded() to clear this flag if it is set.
491 
492     @deprecated Version 1.10
493     @type Boolean
494   */
495   layerLocationNeedsUpdate: NO,
496 
497   /**
498     Calls updateLayerLocation(), but only if the view's layer location
499     currently needs to be updated.
500 
501     @deprecated Version 1.10
502     @returns {SC.View} receiver
503     @test in updateLayerLocation
504   */
505   updateLayerLocationIfNeeded: function () {
506     //@if(debug)
507     SC.warn("SC.View.prototype.updateLayerLocationIfNeeded is no longer used and has been deprecated.  See the SC.View statechart code for more details on attaching and detaching layers.");
508     //@endif
509 
510     return this;
511   },
512 
513   /**
514     This method is called when a view changes its location in the view
515     hierarchy.  This method will update the underlying DOM-location of the
516     layer so that it reflects the new location.
517 
518     @deprecated Version 1.10
519     @returns {SC.View} receiver
520   */
521   updateLayerLocation: function () {
522     //@if(debug)
523     SC.warn("SC.View.prototype.updateLayerLocation is no longer used and has been deprecated.  See the SC.View statechart code for more details on attaching and detaching layers.");
524     //@endif
525 
526     return this;
527   },
528 
529   /**
530     @private
531 
532     Renders to a context.
533     Rendering only happens for the initial rendering. Further updates happen in updateLayer,
534     and are not done to contexts, but to layers.
535     Note: You should not generally override nor directly call this method. This method is only
536     called by createLayer to set up the layer initially, and by renderChildViews, to write to
537     a context.
538 
539     @param {SC.RenderContext} context the render context.
540   */
541   renderToContext: function (context) {
542     var mixins, idx, len;
543 
544     this.beginPropertyChanges();
545 
546     context.id(this.get('layerId'));
547     context.setAttr('role', this.get('ariaRole'));
548 
549     // Set up the classNameBindings and attributeBindings observers.
550     // TODO: CLEAN UP!!
551     this._applyClassNameBindings();
552     this._applyAttributeBindings(context);
553 
554     context.addClass(this.get('classNames'));
555 
556     if (this.get('isTextSelectable')) { context.addClass('allow-select'); }
557 
558     if (!this.get('isVisible')) {
559       context.addClass('sc-hidden');
560       context.setAttr('aria-hidden', 'true');
561     }
562 
563     // Call applyAttributesToContext so that subclasses that override it can
564     // insert further attributes.
565     this.applyAttributesToContext(context);
566 
567     // We pass true for the second argument to support the old style of render.
568     this.render(context, true);
569 
570     // If we've made it this far and renderChildViews() was never called,
571     // render any child views now.
572     if (!this._didRenderChildViews) { this.renderChildViews(context); }
573     // Reset the flag so that if the layer is recreated we re-render the child views.
574     this._didRenderChildViews = false;
575 
576     if (mixins = this.renderMixin) {
577       len = mixins.length;
578       for (idx = 0; idx < len; ++idx) { mixins[idx].call(this, context, true); }
579     }
580 
581     this.endPropertyChanges();
582   },
583 
584   /** Apply the attributes to the context. */
585   applyAttributesToContext: function (context) {
586 
587   },
588 
589   /**
590     @private
591 
592     Iterates over the view's `classNameBindings` array, inserts the value
593     of the specified property into the `classNames` array, then creates an
594     observer to update the view's element if the bound property ever changes
595     in the future.
596   */
597   _applyClassNameBindings: function () {
598     var classBindings = this.get('classNameBindings'),
599         classNames = this.get('classNames'),
600         dasherizedClass;
601 
602     if (!classBindings) { return; }
603 
604     // Loop through all of the configured bindings. These will be either
605     // property names ('isUrgent') or property paths relative to the view
606     // ('content.isUrgent')
607     classBindings.forEach(function (property) {
608 
609       // Variable in which the old class value is saved. The observer function
610       // closes over this variable, so it knows which string to remove when
611       // the property changes.
612       var oldClass;
613 
614       // Set up an observer on the context. If the property changes, toggle the
615       // class name.
616       var observer = function () {
617         // Get the current value of the property
618         var newClass = this._classStringForProperty(property);
619         var elem = this.$();
620 
621         // If we had previously added a class to the element, remove it.
622         if (oldClass) {
623           elem.removeClass(oldClass);
624           classNames.removeObject(oldClass);
625         }
626 
627         // If necessary, add a new class. Make sure we keep track of it so
628         // it can be removed in the future.
629         if (newClass) {
630           elem.addClass(newClass);
631           classNames.push(newClass);
632           oldClass = newClass;
633         } else {
634           oldClass = null;
635         }
636       };
637 
638       this.addObserver(property.split(':')[0], this, observer);
639 
640       // Get the class name for the property at its current value
641       dasherizedClass = this._classStringForProperty(property);
642 
643       if (dasherizedClass) {
644         // Ensure that it gets into the classNames array
645         // so it is displayed when we render.
646         classNames.push(dasherizedClass);
647 
648         // Save a reference to the class name so we can remove it
649         // if the observer fires. Remember that this variable has
650         // been closed over by the observer.
651         oldClass = dasherizedClass;
652       }
653 
654     }, this);
655   },
656 
657   /**
658     Iterates through the view's attribute bindings, sets up observers for each,
659     then applies the current value of the attributes to the passed render buffer.
660 
661     @param {SC.RenderBuffer} buffer
662   */
663   _applyAttributeBindings: function (context) {
664     var attributeBindings = this.get('attributeBindings'),
665         attributeValue, elem, type;
666 
667     if (!attributeBindings) { return; }
668 
669     attributeBindings.forEach(function (attribute) {
670       // Create an observer to add/remove/change the attribute if the
671       // JavaScript property changes.
672       var observer = function () {
673         elem = this.$();
674         var currentValue = elem.attr(attribute);
675         attributeValue = this.get(attribute);
676 
677         type = typeof attributeValue;
678 
679         if ((type === 'string' || type === 'number') && attributeValue !== currentValue) {
680           elem.attr(attribute, attributeValue);
681         } else if (attributeValue && type === 'boolean') {
682           elem.attr(attribute, attribute);
683         } else if (attributeValue === NO) {
684           elem.removeAttr(attribute);
685         }
686       };
687 
688       this.addObserver(attribute, this, observer);
689 
690       // Determine the current value and add it to the render buffer
691       // if necessary.
692       attributeValue = this.get(attribute);
693       type = typeof attributeValue;
694 
695       if (type === 'string' || type === 'number') {
696         context.setAttr(attribute, attributeValue);
697       } else if (attributeValue && type === 'boolean') {
698         // Apply boolean attributes in the form attribute="attribute"
699         context.setAttr(attribute, attribute);
700       }
701     }, this);
702   },
703 
704   /**
705     @private
706 
707     Given a property name, returns a dasherized version of that
708     property name if the property evaluates to a non-falsy value.
709 
710     For example, if the view has property `isUrgent` that evaluates to true,
711     passing `isUrgent` to this method will return `"is-urgent"`.
712   */
713   _classStringForProperty: function (property) {
714     var split = property.split(':'), className = split[1];
715     property = split[0];
716 
717     var val = SC.getPath(this, property);
718 
719     // If value is a Boolean and true, return the dasherized property
720     // name.
721     if (val === YES) {
722       if (className) { return className; }
723 
724       // Normalize property path to be suitable for use
725       // as a class name. For exaple, content.foo.barBaz
726       // becomes bar-baz.
727       return SC.String.dasherize(property.split('.').get('lastObject'));
728 
729     // If the value is not NO, undefined, or null, return the current
730     // value of the property.
731     } else if (val !== NO && val !== undefined && val !== null) {
732       return val;
733 
734     // Nothing to display. Return null so that the old class is removed
735     // but no new class is added.
736     } else {
737       return null;
738     }
739   },
740 
741   /**
742     Your render method should invoke this method to render any child views,
743     especially if this is the first time the view will be rendered.  This will
744     walk down the childView chain, rendering all of the children in a nested
745     way.
746 
747     @param {SC.RenderContext} context the context
748     @returns {SC.RenderContext} the render context
749     @test in render
750   */
751   renderChildViews: function (context) {
752     var cv = this.get('childViews'), len = cv.length, idx, view;
753     for (idx = 0; idx < len; ++idx) {
754       view = cv[idx];
755       if (!view) { continue; }
756       context = context.begin(view.get('tagName'));
757       view.renderToContext(context);
758       context = context.end();
759     }
760 
761     // Track that renderChildViews was called in case it was called directly
762     // in a render method.
763     this._didRenderChildViews = true;
764 
765     return context;
766   },
767 
768   /** @private -
769     override to add support for theming or in your view
770   */
771   render: function () { },
772 
773   // ..........................................................
774   // STANDARD RENDER PROPERTIES
775   //
776 
777   /**
778     A list of properties on the view to translate dynamically into attributes on
779     the view's layer (element).
780 
781     When the view is rendered, the value of each property listed in
782     attributeBindings will be inserted in the element.  If the value is a
783     Boolean, the attribute name itself will be inserted.  As well, as the
784     value of any of these properties changes, the layer will update itself
785     automatically.
786 
787     This is an easy way to set custom attributes on the View without
788     implementing it through a render or update function.
789 
790     For example,
791 
792         // ...  MyApp.MyView
793 
794         attributeBindings: ['aria-valuenow', 'disabled'],
795 
796         'aria-valuenow': function () {
797           return this.get('value');
798         }.property('value').cacheable(), // adds 'aria-valuenow="{value}"' attribute
799 
800         disabled: YES, // adds 'disabled="disabled"' attribute
801 
802         // ...
803 
804     @type Array
805     @default null
806   */
807   attributeBindings: null,
808 
809 
810   /**
811     Tag name for the view's outer element.  The tag name is only used when
812     a layer is first created.  If you change the tagName for an element, you
813     must destroy and recreate the view layer.
814 
815     @type String
816     @default 'div'
817   */
818   tagName: 'div',
819 
820   /**
821     Standard CSS class names to apply to the view's outer element.  These class
822     names are used in addition to any defined on the view's superclass.
823 
824     @type Array
825     @default []
826   */
827   classNames: [],
828 
829   /**
830     A list of local property names to translate dynamically into standard
831     CSS class names on your view's layer (element).
832 
833     Each entry in the array should take the form "propertyName:css-class".
834     For example, "isRed:my-red-view" will cause the class "my-red-view" to
835     be appended if the property "isRed" is (or becomes) true, and removed
836     if it later becomes false (or null/undefined).
837 
838     Optionally, you may provide just the property name, in which case it will
839     be dasherized and used as the class name.  For example, including
840     "isUpsideDown" will cause the view's isUpsideDown property to mediate the
841     class "is-upside-down".
842 
843     Instead of a boolean value, your property may return a string, which will
844     be used as the class name for that entry.  Use caution when returning other
845     values; numbers will be appended verbatim and objects will be stringified,
846     leading to unintended results such as class="4" or class="Object object".
847 
848     Class names mediated by these bindings are used in addition to any that
849     you've listed in the classNames property.
850 
851     @type Array
852   */
853   classNameBindings: null,
854 
855   /**
856     Tool tip property that will be set to the title attribute on the HTML
857     rendered element.
858 
859     @type String
860   */
861   toolTip: null,
862 
863   /**
864     The computed tooltip.  This is generated by localizing the toolTip
865     property if necessary.
866 
867     @type String
868   */
869   displayToolTip: function () {
870     var ret = this.get('toolTip');
871     return (ret && this.get('localize')) ? SC.String.loc(ret) : (ret || '');
872   }.property('toolTip', 'localize').cacheable(),
873 
874   /**
875     Determines if the user can select text within the view.  Normally this is
876     set to NO to disable text selection.  You should set this to YES if you
877     are creating a view that includes editable text.  Otherwise, settings this
878     to YES will probably make your controls harder to use and it is not
879     recommended.
880 
881     @type Boolean
882     @readOnly
883   */
884   isTextSelectable: NO,
885 
886   /**
887     You can set this array to include any properties that should immediately
888     invalidate the display.  The display will be automatically invalidated
889     when one of these properties change.
890 
891     These are the properties that will be visible to any Render Delegate.
892     When the RenderDelegate asks for a property it needs, the view checks the
893     displayProperties array. It first looks for the property name prefixed
894     by 'display'; for instance, if the render delegate needs a 'title',
895     the view will attempt to find 'displayTitle'. If there is no 'displayTitle'
896     in displayProperties, the view will then try 'title'. If 'title' is not
897     in displayProperties either, an error will be thrown.
898 
899     This allows you to avoid collisions between your view's API and the Render
900     Delegate's API.
901 
902     Implementation note:  'isVisible' is also effectively a display property,
903     but it is not declared as such because it is observed separately in
904     order to manage the view's internal state.
905 
906     @type Array
907     @readOnly
908   */
909   displayProperties: [],
910 
911   // .......................................................
912   // SC.RESPONDER SUPPORT
913   //
914 
915   /** @property
916     The nextResponder is usually the parentView.
917   */
918   nextResponder: function () {
919     return this.get('parentView');
920   }.property('parentView').cacheable(),
921 
922 
923   /** @property
924     Set to YES if your view is willing to accept first responder status.  This
925     is used when calculating key responder loop.
926   */
927   acceptsFirstResponder: NO,
928 
929   // .......................................................
930   // CORE DISPLAY METHODS
931   //
932 
933   /** @private
934     Caches the layerId to detect when it changes.
935     */
936   _lastLayerId: null,
937 
938   /** @private
939     Setup a view, but do not finish waking it up.
940 
941      - configure childViews
942      - Determine the view's theme
943      - Fetch a render delegate from the theme, if necessary
944      - register the view with the global views hash, which is used for event
945        dispatch
946   */
947   init: function () {
948     var childViews, layerId;
949 
950     sc_super();
951 
952     layerId = this._lastLayerId = this.get('layerId');
953 
954     // Register the view for event handling. This hash is used by
955     // SC.RootResponder to dispatch incoming events.
956     //@if (debug)
957     if (SC.View.views[layerId]) {
958       throw new Error("Developer Error: A view with layerId, '%@', already exists.  Each view must have a unique layerId.".fmt(this.get('layerId')));
959     }
960     //@endif
961     SC.View.views[layerId] = this;
962 
963     // setup classNames
964     this.classNames = this.get('classNames').slice();
965 
966     // setup child views.  be sure to clone the child views array first
967     childViews = this.childViews = this.get('childViews').slice();
968     this.createChildViews(); // setup child Views
969   },
970 
971   /**
972     Frame describes this view's current bounding rect, relative to its parent view. You
973     can use this, for example, to reliably access a width for a view whose layout is
974     defined with left and right. (Note that width and height values are calculated in
975     the parent view's frame of reference as well, which has consequences for scaled
976     views.)
977 
978     @type Rect
979     @test in layoutStyle
980   */
981   frame: function () {
982     return this.computeFrameWithParentFrame(null);
983   }.property('useStaticLayout').cacheable(),    // We depend on the layout, but layoutDidChange will call viewDidResize to check the frame for us
984 
985   /**
986     Computes the frame of the view by examining the view's DOM representation.
987     If no representation exists, returns null.
988 
989     If the view has a parent view, the parent's bounds will be taken into account when
990     calculating the frame.
991 
992     @returns {Rect} the computed frame
993   */
994   computeFrameWithParentFrame: function () {
995     var layer,                            // The view's layer
996         pv = this.get('parentView'),      // The view's parent view (if it exists)
997         f;                                // The layer's coordinates in the document
998 
999     // need layer to be able to compute rect
1000     if (layer = this.get('layer')) {
1001       f = SC.offset(layer); // x,y
1002       if (pv) { f = pv.convertFrameFromView(f, null); }
1003 
1004       /*
1005         TODO Can probably have some better width/height values - CC
1006         FIXME This will probably not work right with borders - PW
1007       */
1008       f.width = layer.offsetWidth;
1009       f.height = layer.offsetHeight;
1010 
1011       return f;
1012     }
1013 
1014     // Unable to compute yet
1015     if (this.get('hasLayout')) {
1016       return null;
1017     } else {
1018       return { x: 0, y: 0, width: 0, height: 0 };
1019     }
1020   },
1021 
1022   /** @private Call the method recursively on all child views. */
1023   _callOnChildViews: function (methodName, isTopDown, context) {
1024     var childView,
1025       childViews = this.get('childViews'),
1026       method,
1027       shouldContinue;
1028 
1029     for (var i = childViews.length - 1; i >= 0; i--) {
1030       childView = childViews[i];
1031 
1032       // We allow missing childViews in the array so ignore them.
1033       if (!childView) { continue; }
1034 
1035       // Look up the method on the child.
1036       method = childView[methodName];
1037 
1038       // Call the method on this view *before* its children.
1039       if (isTopDown === undefined || isTopDown) {
1040         shouldContinue = method.call(childView, context);
1041       }
1042 
1043       // Recurse.
1044       if (shouldContinue === undefined || shouldContinue) {
1045         childView._callOnChildViews(methodName, isTopDown, context);
1046       }
1047 
1048       // Call the method on this view *after* its children.
1049       if (isTopDown === false) {
1050         method.call(childView, context);
1051       }
1052     }
1053   },
1054 
1055   /**
1056     The clipping frame returns the visible portion of the view, taking into
1057     account the clippingFrame of the parent view.  (Note that, in contrast
1058     to `frame`, `clippingFrame` is in the context of the view itself, not
1059     its parent view.)
1060 
1061     Normally this will be calculated based on the intersection of your own
1062     clippingFrame and your parentView's clippingFrame.
1063 
1064     @type Rect
1065   */
1066   clippingFrame: function () {
1067     var f = this.get('frame');
1068 
1069     // FAST PATH: No frame, no clipping frame.
1070     if (!f) return null;
1071 
1072     /*jshint eqnull:true */
1073     var scale = (f.scale == null) ? 1 : f.scale,
1074         pv = this.get('parentView'),
1075         pcf = pv ? pv.get('clippingFrame') : null,
1076         ret;
1077 
1078     // FAST PATH: No parent clipping frame, no change. (The origin and scale are reset from parent view's
1079     // context to our own.)
1080     if (!pcf) return { x: 0, y: 0, width: f.width / scale, height: f.height / scale};
1081 
1082     // Get the intersection.
1083     ret = SC.intersectRects(pcf, f);
1084 
1085     // Reorient the top-left from the parent's origin to ours.
1086     ret.x -= f.x;
1087     ret.y -= f.y;
1088 
1089     // If we're scaled, we have to scale the intersected rectangle from our parent's frame of reference
1090     // to our own.
1091     if (scale !== 1) {
1092       var scaleX, scaleY;
1093       // We're scaling from parent space into our space, so the scale is reversed. (Layout scale may be an array.)
1094       if (SC.typeOf(scale) === SC.T_ARRAY) {
1095         scaleX = 1 / scale[0];
1096         scaleY = 1 / scale[1];
1097       } else {
1098         scaleX = scaleY = 1 / scale;
1099       }
1100 
1101       // Convert the entire rectangle into our scale.
1102       ret.x *= scaleX;
1103       ret.width *= scaleX;
1104       ret.y *= scaleY;
1105       ret.height *= scaleY;
1106     }
1107 
1108     return ret;
1109   }.property('parentView', 'frame').cacheable(),
1110 
1111   /** @private
1112     This method is invoked whenever the clippingFrame changes, notifying
1113     each child view that its clippingFrame has also changed.
1114   */
1115   _sc_clippingFrameDidChange: function () {
1116     this.notifyPropertyChange('clippingFrame');
1117   },
1118 
1119   /**
1120     Removes the child view from the parent view *and* detaches it from the
1121     document.
1122 
1123     This does *not* remove the child view's layer (i.e. the node still exists,
1124     but is no longer in the document) and does *not* destroy the child view
1125     (i.e. it can still be re-attached to the document).
1126 
1127     Note that if the child view uses a transitionOut plugin, it will not be
1128     fully detached until the transition completes.  To force the view to detach
1129     immediately you can pass true for the optional `immediately` argument.
1130 
1131     If you wish to remove the child and discard it, use `removeChildAndDestroy`.
1132 
1133     @param {SC.View} view The view to remove as a child view.
1134     @param {Boolean} [immediately=false] Forces the child view to be removed immediately regardless if it uses a transitionOut plugin.
1135     @see SC.View#removeChildAndDestroy
1136     @returns {SC.View} receiver
1137   */
1138   removeChild: function (view, immediately) {
1139     if (view.get('isAttached')) {
1140       view._doDetach(immediately);
1141     }
1142 
1143     // If the view will transition out, wait for the transition to complete
1144     // before orphaning the view entirely.
1145     if (!immediately && view.get('viewState') === SC.CoreView.ATTACHED_BUILDING_OUT) {
1146       view.addObserver('isAttached', this, this._orphanChildView);
1147     } else {
1148       view._doOrphan();
1149     }
1150 
1151     return this;
1152   },
1153 
1154   /**
1155     Removes the child view from the parent view, detaches it from the document
1156     *and* destroys the view and its layer.
1157 
1158     Note that if the child view uses a transitionOut plugin, it will not be
1159     fully detached and destroyed until the transition completes.  To force the
1160     view to detach immediately you can pass true for the optional `immediately`
1161     argument.
1162 
1163     If you wish to remove the child and keep it for further re-use, use
1164     `removeChild`.
1165 
1166     @param {SC.View} view The view to remove as a child view and destroy.
1167     @param {Boolean} [immediately=false] Forces the child view to be removed and destroyed immediately regardless if it uses a transitionOut plugin.
1168     @see SC.View#removeChild
1169     @returns {SC.View} receiver
1170   */
1171   removeChildAndDestroy: function (view, immediately) {
1172     view._doDetach(immediately);
1173 
1174     // If the view will transition out, wait for the transition to complete
1175     // before destroying the view entirely.
1176     if (view.get('transitionOut') && !immediately) {
1177       view.addObserver('isAttached', this, this._destroyChildView);
1178     } else {
1179       view.destroy(); // Destroys the layer and the view.
1180     }
1181 
1182     return this;
1183   },
1184 
1185   /**
1186     Removes all children from the parentView *and* destroys them and their
1187     layers.
1188 
1189     Note that if any child view uses a transitionOut plugin, it will not be
1190     fully removed until the transition completes.  To force all child views to
1191     remove immediately you can pass true as the optional `immediately` argument.
1192 
1193     Tip: If you know that there are no transitions for the child views,
1194     you should pass true to optimize the document removal.
1195 
1196     @param {Boolean} [immediately=false] Forces all child views to be removed immediately regardless if any uses a transitionOut plugin.
1197     @returns {SC.View} receiver
1198   */
1199   removeAllChildren: function (immediately) {
1200     var childViews = this.get('childViews'),
1201       len = childViews.get('length'),
1202       i;
1203 
1204     // OPTIMIZATION!
1205     // If we know that we're removing all children and we are rendered, lets do the document cleanup in one sweep.
1206     // if (immediately && this.get('_isRendered')) {
1207     //   var layer,
1208     //     parentNode;
1209 
1210     //   // If attached, detach and track our parent node so we can re-attach.
1211     //   if (this.get('isAttached')) {
1212     //     layer = this.get('layer');
1213     //     parentNode = layer.parentNode;
1214 
1215     //     this._doDetach();
1216     //   }
1217 
1218     //   // Destroy our layer and thus all the children's layers in one move.
1219     //   this.destroyLayer();
1220 
1221     //   // Remove all the children.
1222     //   for (i = len - 1; i >= 0; i--) {
1223     //     this.removeChildAndDestroy(childViews.objectAt(i), immediately);
1224     //   }
1225 
1226     //   // Recreate our layer (now empty).
1227     //   this.createLayer();
1228 
1229     //   // Reattach our layer.
1230     //   if (parentNode && !this.get('isAttached')) { this._doAttach(parentNode); }
1231     // } else {
1232       for (i = len - 1; i >= 0; i--) {
1233         this.removeChildAndDestroy(childViews.objectAt(i), immediately);
1234       }
1235     // }
1236 
1237     return this;
1238   },
1239 
1240   /**
1241     Removes the view from its parentView, if one is found.  Otherwise
1242     does nothing.
1243 
1244     @returns {SC.View} receiver
1245   */
1246   removeFromParent: function () {
1247     var parent = this.get('parentView');
1248     if (parent) { parent.removeChild(this); }
1249 
1250     return this;
1251   },
1252 
1253   /** @private Observer for child views that are being discarded after transitioning out. */
1254   _destroyChildView: function (view) {
1255     // Commence destroying of the view once it is detached.
1256     if (!view.get('isAttached')) {
1257       view.removeObserver('isAttached', this, this._destroyChildView);
1258       view.destroy();
1259     }
1260   },
1261 
1262   /** @private Observer for child views that are being orphaned after transitioning out. */
1263   _orphanChildView: function (view) {
1264     // Commence orphaning of the view once it is detached.
1265     if (!view.get('isAttached')) {
1266       view.removeObserver('isAttached', this, this._orphanChildView);
1267       view._doOrphan();
1268     }
1269   },
1270 
1271   /**
1272     Completely destroys a view instance so that it may be garbage collected.
1273 
1274     You must call this method on a view to destroy the view (and all of its
1275     child views). This will remove the view from any parent, detach the
1276     view's layer from the DOM if it is attached and clear the view's layer
1277     if it is rendered.
1278 
1279     Once a view is destroyed it can *not* be reused.
1280 
1281     @returns {SC.View} receiver
1282   */
1283   destroy: function () {
1284     // Fast path!
1285     if (this.get('isDestroyed')) { return this; }
1286 
1287     // Do generic destroy. It takes care of mixins and sets isDestroyed to YES.
1288     // Do this first, since it cleans up bindings that may apply to parentView
1289     // (which we will soon null out).
1290     var ret = sc_super();
1291 
1292     // If our parent is already destroyed, then we can defer destroying ourself
1293     // and our own child views momentarily.
1294     if (this.getPath('parentView.isDestroyed')) {
1295       // Complete the destroy in a bit.
1296       this.invokeNext(this._destroy);
1297     } else {
1298       // Immediately remove the layer if attached (ignores transitionOut). This
1299       // will detach the layer for all child views as well.
1300       if (this.get('isAttached')) {
1301         this._doDetach(true);
1302       }
1303 
1304       // Clear the layer if rendered.  This will clear all child views layer
1305       // references as well.
1306       if (this.get('_isRendered')) {
1307         this._doDestroyLayer();
1308       }
1309 
1310       // Complete the destroy.
1311       this._destroy();
1312     }
1313 
1314     // Remove the view from the global hash.
1315     delete SC.View.views[this.get('layerId')];
1316 
1317     // Destroy any children.  Loop backwards since childViews will shrink.
1318     var childViews = this.get('childViews');
1319     for (var i = childViews.length - 1; i >= 0; i--) {
1320       childViews[i].destroy();
1321     }
1322 
1323     return ret;
1324   },
1325 
1326   /** @private */
1327   _destroy: function () {
1328     // Orphan the view if adopted.
1329     this._doOrphan();
1330 
1331     delete this.page;
1332   },
1333 
1334   /**
1335     This method is called when your view is first created to setup any  child
1336     views that are already defined on your class.  If any are found, it will
1337     instantiate them for you.
1338 
1339     The default implementation of this method simply steps through your
1340     childViews array, which is expects to either be empty or to contain View
1341     designs that can be instantiated
1342 
1343     Alternatively, you can implement this method yourself in your own
1344     subclasses to look for views defined on specific properties and then build
1345      a childViews array yourself.
1346 
1347     Note that when you implement this method yourself, you should never
1348     instantiate views directly.  Instead, you should use
1349     this.createChildView() method instead.  This method can be much faster in
1350     a production environment than creating views yourself.
1351 
1352     @returns {SC.View} receiver
1353   */
1354   createChildViews: function () {
1355     var childViews = this.get('childViews'),
1356         len        = childViews.length,
1357         isNoLongerValid = false,
1358         idx, key, view;
1359 
1360     this.beginPropertyChanges();
1361 
1362     // swap the array
1363     for (idx = 0; idx < len; ++idx) {
1364       key = view = childViews[idx];
1365 
1366       // is this is a key name, lookup view class
1367       if (typeof key === SC.T_STRING) {
1368         view = this[key];
1369       } else {
1370         key = null;
1371       }
1372 
1373       if (!view) {
1374         //@if (debug)
1375         SC.warn("Developer Warning: The child view named '%@' was not found in the view, %@.  This child view will be ignored.".fmt(key, this));
1376         //@endif
1377 
1378         // skip this one.
1379         isNoLongerValid = true;
1380         childViews[idx] = null;
1381         continue;
1382       }
1383 
1384       // createChildView creates the view if necessary, but also sets
1385       // important properties, such as parentView
1386       view = this.createChildView(view);
1387       if (key) { this[key] = view; } // save on key name if passed
1388 
1389       childViews[idx] = view;
1390     }
1391 
1392     // Set childViews to be only the valid array.
1393     if (isNoLongerValid) { this.set('childViews', childViews.compact()); }
1394 
1395     this.endPropertyChanges();
1396     return this;
1397   },
1398 
1399   /**
1400     Instantiates a view to be added to the childViews array during view
1401     initialization. You generally will not call this method directly unless
1402     you are overriding createChildViews(). Note that this method will
1403     automatically configure the correct settings on the new view instance to
1404     act as a child of the parent.
1405 
1406     If the given view is a class, then createdByParent will be set to true on
1407     the returned instance.
1408 
1409     @param {Class} view A view class to create or view instance to prepare.
1410     @param {Object} [attrs={}] attributes to add
1411     @returns {SC.View} new instance
1412     @test in createChildViews
1413   */
1414   createChildView: function (view, attrs) {
1415     // Create the view if it is a class.
1416     if (view.isClass) {
1417       // attrs should always exist...
1418       if (!attrs) { attrs = {}; }
1419 
1420       // clone the hash that was given so we do not pollute it if it's being reused
1421       else { attrs = SC.clone(attrs); }
1422 
1423       // Assign the parentView & page to ourself.
1424       attrs.parentView = this;
1425       if (!attrs.page) { attrs.page = this.page; }
1426 
1427       // Track that we created this view.
1428       attrs.createdByParent = true;
1429 
1430       // Insert the autoMixins if defined
1431       var applyMixins = this.autoMixins;
1432       if (!!applyMixins) {
1433         applyMixins = SC.clone(applyMixins);
1434         applyMixins.push(attrs);
1435         view = view.create.apply(view, applyMixins);
1436       } else {
1437         view = view.create(attrs);
1438       }
1439     // Assign the parentView if the view is an instance.
1440     // TODO: This should not be accepting view instances, for the purpose of lazy code elsewhere in the framework.
1441     //       We should ensure users of `createChildViews` are using appendChild and other manipulation methods.
1442     } else {
1443       view.set('parentView', this);
1444       view._adopted();
1445 
1446       if (!view.get('page')) { view.set('page', this.page); }
1447     }
1448 
1449     return view;
1450   },
1451 
1452   /** walk like a duck */
1453   isView: YES,
1454 
1455   /**
1456     Default method called when a selectstart event is triggered. This event is
1457     only supported by IE. Used in sproutcore to disable text selection and
1458     IE8 accelerators. The accelerators will be enabled only in
1459     text selectable views. In FF and Safari we use the css style 'allow-select'.
1460 
1461     If you want to enable text selection in certain controls is recommended
1462     to override this function to always return YES , instead of setting
1463     isTextSelectable to true.
1464 
1465     For example in textfield you do not want to enable textSelection on the text
1466     hint only on the actual text you are entering. You can achieve that by
1467     only overriding this method.
1468 
1469     @param evt {SC.Event} the selectstart event
1470     @returns YES if selectable
1471   */
1472   selectStart: function (evt) {
1473     return this.get('isTextSelectable');
1474   },
1475 
1476   /**
1477     Used to block the contextMenu per view.
1478 
1479     @param evt {SC.Event} the contextmenu event
1480     @returns YES if the contextmenu will be allowed to show up
1481   */
1482   contextMenu: function (evt) {
1483     if (this.get('isContextMenuEnabled')) {
1484       evt.allowDefault();
1485       return YES;
1486     }
1487   },
1488 
1489   // ------------------------------------------------------------------------
1490   // Transitions
1491   //
1492 
1493   /**
1494     The transition plugin to use when this view is appended to the DOM.
1495 
1496     SC.CoreView uses a pluggable transition architecture where the transition
1497     setup, execution and cleanup can be handled by a specified transition
1498     plugin.
1499 
1500     There are a number of pre-built transition plugins available in the
1501     foundation framework:
1502 
1503       SC.View.BOUNCE_IN
1504       SC.View.FADE_IN
1505       SC.View.SLIDE_IN
1506       SC.View.SCALE_IN
1507       SC.View.SPRING_IN
1508 
1509     You can even provide your own custom transition plugins.  Just create a
1510     transition object that conforms to the SC.ViewTransitionProtocol protocol.
1511 
1512     @type Object (SC.ViewTransitionProtocol)
1513     @default null
1514     @since Version 1.10
1515   */
1516   transitionIn: null,
1517 
1518   /**
1519     The options for the given transition in plugin.
1520 
1521     These options are specific to the current transition plugin used and are
1522     used to modify the transition animation.  To determine what options
1523     may be used for a given plugin and to see what the default options are,
1524     see the documentation for the transition plugin being used.
1525 
1526     Most transitions will accept a duration and timing option, but may
1527     also use other options.  For example, SC.View.SLIDE_IN accepts options
1528     like:
1529 
1530         transitionInOptions: {
1531           direction: 'left',
1532           duration: 0.25,
1533           timing: 'ease-in-out'
1534         }
1535 
1536     @type Object
1537     @default null
1538     @since Version 1.10
1539   */
1540   transitionInOptions: null,
1541 
1542   /**
1543     The transition plugin to use when this view is removed from the DOM.
1544 
1545     SC.View uses a pluggable transition architecture where the transition setup,
1546     execution and cleanup can be handled by a specified transition plugin.
1547 
1548     There are a number of pre-built transition plugins available in the
1549     foundation framework:
1550 
1551       SC.View.BOUNCE_OUT
1552       SC.View.FADE_OUT
1553       SC.View.SLIDE_OUT
1554       SC.View.SCALE_OUT
1555       SC.View.SPRING_OUT
1556 
1557     You can even provide your own custom transition plugins.  Just create a
1558     transition object that conforms to the SC.ViewTransitionProtocol protocol.
1559 
1560     @type Object (SC.ViewTransitionProtocol)
1561     @default null
1562     @since Version 1.10
1563   */
1564   transitionOut: null,
1565 
1566   /**
1567     The options for the given transition out plugin.
1568 
1569     These options are specific to the current transition plugin used and are
1570     used to modify the transition animation.  To determine what options
1571     may be used for a given plugin and to see what the default options are,
1572     see the documentation for the transition plugin being used.
1573 
1574     Most transitions will accept a duration and timing option, but may
1575     also use other options.  For example, SC.View.SLIDE accepts options
1576     like:
1577 
1578         transitionOutOptions: {
1579           direction: 'right',
1580           duration: 0.15,
1581           timing: 'ease-in'
1582         }
1583 
1584     @type Object
1585     @default null
1586     @since Version 1.10
1587   */
1588   transitionOutOptions: null,
1589 
1590   /**
1591     The transition plugin to use when this view is made shown from being
1592     hidden.
1593 
1594     SC.CoreView uses a pluggable transition architecture where the transition setup,
1595     execution and cleanup can be handled by a specified transition plugin.
1596 
1597     There are a number of pre-built transition plugins available in the
1598     foundation framework:
1599 
1600       SC.View.BOUNCE_IN
1601       SC.View.FADE_IN
1602       SC.View.SLIDE_IN
1603       SC.View.SCALE_IN
1604       SC.View.SPRING_IN
1605 
1606     You can even provide your own custom transition plugins.  Just create a
1607     transition object that conforms to the SC.ViewTransitionProtocol protocol.
1608 
1609     @type Object (SC.ViewTransitionProtocol)
1610     @default null
1611     @since Version 1.10
1612   */
1613   transitionShow: null,
1614 
1615   /**
1616     The options for the given transition show plugin.
1617 
1618     These options are specific to the current transition plugin used and are
1619     used to modify the transition animation.  To determine what options
1620     may be used for a given plugin and to see what the default options are,
1621     see the documentation for the transition plugin being used.
1622 
1623     Most transitions will accept a duration and timing option, but may
1624     also use other options.  For example, SC.View.SLIDE accepts options
1625     like:
1626 
1627         transitionShowOptions: {
1628           direction: 'left',
1629           duration: 0.25,
1630           timing: 'ease-in-out'
1631         }
1632 
1633     @type Object
1634     @default null
1635     @since Version 1.10
1636   */
1637   transitionShowOptions: null,
1638 
1639   /**
1640     The transition plugin to use when this view is hidden after being shown.
1641 
1642     SC.View uses a pluggable transition architecture where the transition setup,
1643     execution and cleanup can be handled by a specified transition plugin.
1644 
1645     There are a number of pre-built transition plugins available in the
1646     foundation framework:
1647 
1648       SC.View.BOUNCE_OUT
1649       SC.View.FADE_OUT
1650       SC.View.SLIDE_OUT
1651       SC.View.SCALE_OUT
1652       SC.View.SPRING_OUT
1653 
1654     You can even provide your own custom transition plugins.  Just create a
1655     transition object that conforms to the SC.ViewTransitionProtocol protocol.
1656 
1657     @type Object (SC.ViewTransitionProtocol)
1658     @default null
1659     @since Version 1.10
1660   */
1661   transitionHide: null,
1662 
1663   /**
1664     The options for the given transition hide plugin.
1665 
1666     These options are specific to the current transition plugin used and are
1667     used to modify the transition animation.  To determine what options
1668     may be used for a given plugin and to see what the default options are,
1669     see the documentation for the transition plugin being used.
1670 
1671     Most transitions will accept a duration and timing option, but may
1672     also use other options.  For example, SC.View.SLIDE accepts options
1673     like:
1674 
1675         transitionHideOptions: {
1676           direction: 'right',
1677           duration: 0.15,
1678           timing: 'ease-in'
1679         }
1680 
1681     @type Object
1682     @default null
1683     @since Version 1.10
1684   */
1685   transitionHideOptions: null,
1686 
1687   // ............................................
1688   // Patches
1689   //
1690 
1691   /** @private
1692     Override this method to apply design modes to this view and
1693     its children.
1694     @see SC.View
1695   */
1696   updateDesignMode: function (lastDesignMode, designMode) {}
1697 });
1698 
1699 SC.CoreView.mixin(
1700   /** @scope SC.CoreView */ {
1701 
1702   /** @private walk like a duck -- used by SC.Page */
1703   isViewClass: YES,
1704 
1705   /**
1706     This method works just like extend() except that it will also preserve
1707     the passed attributes in case you want to use a view builder later, if
1708     needed.
1709 
1710     @param {Hash} attrs Attributes to add to view
1711     @returns {Class} SC.View subclass to create
1712     @function
1713   */
1714   design: function () {
1715     if (this.isDesign) {
1716       // @if (debug)
1717       SC.Logger.warn("Developer Warning: .design() was called twice for %@.".fmt(this));
1718       // @endif
1719       return this;
1720     }
1721 
1722     var ret = this.extend.apply(this, arguments);
1723     ret.isDesign = YES;
1724     if (SC.ViewDesigner) {
1725       SC.ViewDesigner.didLoadDesign(ret, this, SC.A(arguments));
1726     }
1727     return ret;
1728   },
1729 
1730   extend: function () {
1731     var last = arguments[arguments.length - 1];
1732 
1733     if (last && !SC.none(last.theme)) {
1734       last.themeName = last.theme;
1735       delete last.theme;
1736     }
1737 
1738     return SC.Object.extend.apply(this, arguments);
1739   },
1740 
1741   /**
1742     Helper applies the layout to the prototype.
1743   */
1744   layout: function (layout) {
1745     this.prototype.layout = layout;
1746     return this;
1747   },
1748 
1749   /**
1750     Helper applies the classNames to the prototype
1751   */
1752   classNames: function (sc) {
1753     sc = (this.prototype.classNames || []).concat(sc);
1754     this.prototype.classNames = sc;
1755     return this;
1756   },
1757 
1758   /**
1759     Help applies the tagName
1760   */
1761   tagName: function (tg) {
1762     this.prototype.tagName = tg;
1763     return this;
1764   },
1765 
1766   /**
1767     Helper adds the childView
1768   */
1769   childView: function (cv) {
1770     var childViews = this.prototype.childViews || [];
1771     if (childViews === this.superclass.prototype.childViews) {
1772       childViews = childViews.slice();
1773     }
1774     childViews.push(cv);
1775     this.prototype.childViews = childViews;
1776     return this;
1777   },
1778 
1779   /**
1780     Helper adds a binding to a design
1781   */
1782   bind: function (keyName, path) {
1783     var p = this.prototype, s = this.superclass.prototype;
1784     var bindings = p._bindings;
1785     if (!bindings || bindings === s._bindings) {
1786       bindings = p._bindings = (bindings || []).slice();
1787     }
1788 
1789     keyName = keyName + "Binding";
1790     p[keyName] = path;
1791     bindings.push(keyName);
1792 
1793     return this;
1794   },
1795 
1796   /**
1797     Helper sets a generic property on a design.
1798   */
1799   prop: function (keyName, value) {
1800     this.prototype[keyName] = value;
1801     return this;
1802   },
1803 
1804   /**
1805     Used to construct a localization for a view.  The default implementation
1806     will simply return the passed attributes.
1807   */
1808   localization: function (attrs, rootElement) {
1809     // add rootElement
1810     if (rootElement) attrs.rootElement = SC.$(rootElement)[0];
1811     return attrs;
1812   },
1813 
1814   /**
1815     Creates a view instance, first finding the DOM element you name and then
1816     using that as the root element.  You should not use this method very
1817     often, but it is sometimes useful if you want to attach to already
1818     existing HTML.
1819 
1820     @param {String|Element} element
1821     @param {Hash} attrs
1822     @returns {SC.View} instance
1823   */
1824   viewFor: function (element, attrs) {
1825     var args = SC.$A(arguments); // prepare to edit
1826     if (SC.none(element)) {
1827       args.shift(); // remove if no element passed
1828     } else args[0] = { rootElement: SC.$(element)[0] };
1829     var ret = this.create.apply(this, arguments);
1830     args = args[0] = null;
1831     return ret;
1832   },
1833 
1834   /**
1835     Create a new view with the passed attributes hash.  If you have the
1836     Designer module loaded, this will also create a peer designer if needed.
1837   */
1838   create: function () {
1839     var last = arguments[arguments.length - 1];
1840 
1841     if (last && last.theme) {
1842       last.themeName = last.theme;
1843       delete last.theme;
1844     }
1845 
1846     var C = this, ret = new C(arguments);
1847     if (SC.ViewDesigner) {
1848       SC.ViewDesigner.didCreateView(ret, SC.$A(arguments));
1849     }
1850     return ret;
1851   },
1852 
1853   /**
1854     Applies the passed localization hash to the component views.  Call this
1855     method before you call create().  Returns the receiver.  Typically you
1856     will do something like this:
1857 
1858     view = SC.View.design({...}).loc(localizationHash).create();
1859 
1860     @param {Hash} loc
1861     @param rootElement {String} optional rootElement with prepped HTML
1862     @returns {SC.View} receiver
1863   */
1864   loc: function (loc) {
1865     var childLocs = loc.childViews;
1866     delete loc.childViews; // clear out child views before applying to attrs
1867 
1868     this.applyLocalizedAttributes(loc);
1869     if (SC.ViewDesigner) {
1870       SC.ViewDesigner.didLoadLocalization(this, SC.$A(arguments));
1871     }
1872 
1873     // apply localization recursively to childViews
1874     var childViews = this.prototype.childViews, idx = childViews.length,
1875       viewClass;
1876     while (--idx >= 0) {
1877       viewClass = childViews[idx];
1878       loc = childLocs[idx];
1879       if (loc && viewClass && typeof viewClass === SC.T_STRING) SC.String.loc(viewClass, loc);
1880     }
1881 
1882     return this; // done!
1883   },
1884 
1885   /**
1886     Internal method actually updates the localized attributes on the view
1887     class.  This is overloaded in design mode to also save the attributes.
1888   */
1889   applyLocalizedAttributes: function (loc) {
1890     SC.mixin(this.prototype, loc);
1891   },
1892 
1893   views: {}
1894 
1895 });
1896 
1897 // .......................................................
1898 // OUTLET BUILDER
1899 //
1900 
1901 /**
1902   Generates a computed property that will look up the passed property path
1903   the first time you try to get the value.  Use this whenever you want to
1904   define an outlet that points to another view or object.  The root object
1905   used for the path will be the receiver.
1906 */
1907 SC.outlet = function (path, root) {
1908   return function (key) {
1909     return (this[key] = SC.objectForPropertyPath(path, (root !== undefined) ? root : this));
1910   }.property();
1911 };
1912 
1913 /** @private on unload clear cached divs. */
1914 SC.CoreView.unload = function () {
1915   // delete view items this way to ensure the views are cleared.  The hash
1916   // itself may be owned by multiple view subclasses.
1917   var views = SC.View.views;
1918   if (views) {
1919     for (var key in views) {
1920       if (!views.hasOwnProperty(key)) continue;
1921       delete views[key];
1922     }
1923   }
1924 };
1925 
1926 /**
1927   @class
1928 
1929   Base class for managing a view.  Views provide two functions:
1930 
1931    1. They display – translating your application's state into drawing
1932      instructions for the web browser, and
1933    2. They react – acting as responders for incoming keyboard, mouse, and touch
1934      events.
1935 
1936   View Basics
1937   ====
1938 
1939   SproutCore's view layer is made up of a tree of SC.View instances, nested
1940   using the `childViews` list – usually an array of local property names. You
1941   position each view by specifying a set of layout keys, like 'left', 'right',
1942   'width', or 'centerX', in a hash on the layout property. (See the 'layout'
1943   documentation for more.)
1944 
1945   Other than positioning, SproutCore relies on CSS for all your styling needs.
1946   Set an array of CSS classes on the `classNames` property, then style them with
1947   standard CSS. (SproutCore's build tools come with Sass support built in, too.)
1948   If you have a class that you want automatically added and removed as another
1949   property changes, take a look at `classNameBindings`.
1950 
1951   Different view classes do different things. The so-called "Big Five" view
1952   classes are SC.LabelView, for displaying (optionally editable, optionally
1953   localizable) text; SC.ButtonView, for the user to poke; SC.CollectionView
1954   (most often as its subclass SC.ListView) for displaying an array of content;
1955   SC.ContainerView, for easily swapping child views in and out; and SC.ScrollView,
1956   for containing larger views and allowing them to be scrolled.
1957 
1958   All views live in panes (subclasses of SC.Pane, like SC.MainPane and SC.PanelPane),
1959   which are parentless views that know how to append themselves directly to the document.
1960   Panes also serve as routers for events, like mouse, touch and keyboard events, that are
1961   bound for their views. (See "View Events" below for more.)
1962 
1963   For best performance, you should define your view and pane instances with `extend()`
1964   inside an SC.Page instance, getting them as needed with `get`. As its name suggests,
1965   SC.Page's only job is to instantiate views once when first requested, deferring the
1966   expensive view creation process until each view is needed. Correctly using SC.Page is
1967   considered an important best practice for high-performance applications.
1968 
1969   View Initialization
1970   ====
1971 
1972   When a view is setup, there are several methods you can override that
1973   will be called at different times depending on how your view is created.
1974   Here is a guide to which method you want to override and when:
1975 
1976    - `init` -- override this method for any general object setup (such as
1977      observers, starting timers and animations, etc) that you need to happen
1978      every time the view is created, regardless of whether or not its layer
1979      exists yet.
1980    - `render` -- override this method to generate or update your HTML to reflect
1981      the current state of your view.  This method is called both when your view
1982      is first created and later anytime it needs to be updated.
1983    - `update` -- Normally, when a view needs to update its content, it will
1984      re-render the view using the render() method.  If you would like to
1985      override this behavior with your own custom updating code, you can
1986      replace update() with your own implementation instead.
1987    - `didCreateLayer` -- the render() method is used to generate new HTML.
1988      Override this method to perform any additional setup on the DOM you might
1989      need to do after creating the view.  For example, if you need to listen
1990      for events.
1991    - `willDestroyLayer` -- if you implement didCreateLayer() to setup event
1992      listeners, you should implement this method as well to remove the same
1993      just before the DOM for your view is destroyed.
1994    - `didAppendToDocument` -- in theory all DOM setup could be done
1995      in didCreateLayer() as you already have a DOM element instantiated.
1996      However there is cases where the element has to be first appended to the
1997      Document because there is either a bug on the browser or you are using
1998      plugins which objects are not instantiated until you actually append the
1999      element to the DOM. This will allow you to do things like registering
2000      DOM events on flash or quicktime objects.
2001    - `willRemoveFromDocument` -- This method is called on the view immediately
2002      before its layer is removed from the DOM. You can use this to reverse any
2003      setup that is performed in `didAppendToDocument`.
2004 
2005   View Events
2006   ====
2007 
2008   One of SproutCore's optimizations is application-wide event delegation: SproutCore
2009   handles and standardizes events for you before sending them through your view layer's
2010   chain of responding views. You should never need to attach event listeners to elements;
2011   instead, just implement methods like `click`, `doubleClick`, `mouseEntered` and
2012   `dataDragHover` on your views.
2013 
2014   Note that events generally bubble up an event's responder chain, which is made up of the
2015   targeted view (i.e. the view whose DOM element received the event), and its chain of
2016   parentViews up to its pane. (In certain rare cases, you may wish to manipulate the responder
2017   chain to bypass certain views; you can do so by overriding a view's `nextResponder` property.)
2018 
2019   Simple mouse click events
2020   ----
2021   In many situations, all you need are clicks - in which case, just implement `click` or
2022   `doubleClick` on your views. Note that these events bubble up the responder chain until
2023   they encounter a view which implements the event method. For example, if a view and its
2024   parent both implement `click`, the parent will not be notified of the click. (If you want a
2025   view to handle the event AND allow the event to keep bubbling to its parent views, no
2026   problem: just be sure to return NO from the event method.)
2027   - `click` -- Called on a view when the user clicks the mouse on a view. (Note that the view
2028     on which the user lifts the mouse button will receive the `click` event, regardless of
2029     whether the user depressed the mouse button elsewhere. If you need finer-grained control
2030     than this, see "Granular mouse click events" below.)
2031   - `doubleClick` -- Called on a view when a user has double-clicked it. Double-clicks are
2032     triggered when two clicks of the same button happen within eight pixels and 250ms of each
2033     other. (If you need finer-grained control than this, see "Granular mouse click events"
2034     below.) The same view may receive both `click` and `doubleClick` events.
2035 
2036   Note that defining application behavior directly in event handlers is usually a bad idea; you
2037   should follow the target/action pattern when possible. See SC.ButtonView and SC.ActionSupport.
2038   Also note that you will not need to implement event handling yourself on most built-in
2039   SproutCore controls.
2040 
2041   Note that `click` and `doubleClick` event handlers on your views will not be notified of touch
2042   events; you must also implement touch handling. See "Touch events" below.
2043 
2044   Mouse movement events
2045   ----
2046   SproutCore normalizes (and brings sanity to) mouse movement events by calculating when
2047   the mouse has entered and exited views, and sending the correct event to each view in
2048   the responder chain. For example, if a mouse moves within a parent view but crosses from
2049   one child view to another, the parent view will receive a mouseMoved event while the child
2050   views will receive mouseEntered and mouseExit events.
2051 
2052   In contrast to mouse click events, mouse movement events are called on the entire responder
2053   chain regardless of how you handle it along the way - a view and its parent, both implementing
2054   event methods, will both be notified of the event.
2055 
2056   - `mouseEntered` -- Called when the cursor first enters a view. Called on every view that has
2057     just entered the responder chain.
2058   - `mouseMoved` -- Called when the cursor moves over a view.
2059   - `mouseExited` -- Called when the cursor leaves a view. Called on every view that has
2060     just exited the responder chain.
2061 
2062   Granular mouse click events
2063   ----
2064   If you need more granular handling of mouse click events than what is provided by `click`
2065   and `doubleClick`, you can handle their atomic components `mouseDown`, `mouseDrag` and
2066   `mouseUp`. Like the compound events, these events bubble up their responder chain towards
2067   the pane until they find an event which implements the event handler method. (Again, to
2068   handle an event but allow it to continue bubbling, just return NO.)
2069 
2070   It bears emphasizing that `mouseDrag` and `mouseUp` events for a given mouse click sequence
2071   are *only ever called* on the view which successfully responded to the `mouseDown` event. This
2072   gives `mouseDown` control over which view responder-chain is allowed to handle the entire
2073   click sequence.
2074 
2075   (Note that because of how events bubble up the responder chain, if a child view implements
2076   `mouseDown` but not `mouseDrag` or `mouseUp`, those events will bubble to its parent. This
2077   may cause unexpected behavior if similar events are handled at different parts of your view
2078   hierarchy, for example if you handle `mouseDown` in a child and a parent, and only handle
2079   `mouseUp` in the parent.)
2080 
2081   - `mouseDown` -- Called on the target view and responder chain when the user depresses a
2082     button. A view must implement `mouseDown` (and not return NO) in order to be notified
2083     of the subsequent drag and up events.
2084   - `mouseDrag` -- Called on the target view if it handled mouseDown. A view must implement
2085     mouseDown (and not return NO) in order to receive mouseDrag; only the view which handled a
2086     given click sequence's mouseDown will receive `mouseDrag` events (and will continue to
2087     receive them even if the user drags the mouse off of it).
2088   - `mouseUp` -- Called on the target view when the user lifts a mouse button. A view must
2089     implement mouseDown (and not return NO) in order to receive mouseUp.
2090 
2091   SproutCore implements a higher-level API for handling in-application dragging and dropping.
2092   See `SC.Drag`, `SC.DragSourceProtocol`, `SC.DragDataSourceProtocol`, and `SC.DropTargetProtocol`
2093   for more.
2094 
2095   Data-drag events
2096   ----
2097   Browsers implement a parallel system of events for drags which bring something with them: for
2098   example, dragging text, an image, a URL or (in modern browsers) a file. They behave differently,
2099   and require different responses from the developer, so SproutCore implements them as a separate
2100   set of "data drag" events. These behave much like mouse events; the data-drag movement events
2101   bubble indiscriminately, and the data-drag drop event bubbles until it finds a view which handles
2102   it (and doesn't return NO).
2103 
2104   By default, SproutCore cancels the default behavior of any data drag event which carries URLs
2105   or files, as by default these would quit the app and open the dragged item in the browser. If
2106   you wish to implement data drag-and-drop support in your application, you should set the event's
2107   dataTransfer.dropEffect property to 'copy' in a `dataDragHovered` event handler.
2108 
2109   - `dataDragEntered` -- Triggered when a data drag enters a view. You can use this handler to
2110     update the view to visually signal that a drop is possible.
2111   - `dataDragHovered` -- Triggered when the browser sends a dragover event to a view. If you want
2112     to support dropping data on your view, you must set the event's `dataTransfer.dropEffect`
2113     property to 'copy' (or related). Note that `dataDragHovered` is given access to dragenter
2114     events as well, so you do not need to worry about this in your `dataDragEntered` methods.
2115   - `dataDragDropped` -- If the last hover event's dropEffect was set correctly, this event will
2116     give the view access to the data that was dropped. This event bubbles up the responder chain
2117     until it finds a view which handles it (and doesn't return NO).
2118   - `dataDragExited` -- Triggered when a data drag leaves a view. You can use this handler to
2119     update the view to remove the visual drop signal. This event is fired regardless of whether
2120     a drop occurred.
2121 
2122 
2123   Touch events
2124   ----
2125   Touch events can be much more complicated than mouse events: multiple touches may be in flight
2126   at once, and views may wish to handle average touches rather than individual touches.
2127 
2128   Basic support for touch events is required to make your application touch-aware. (You will not
2129   need to implement touch support for built-in SproutCore controls, which are touch-aware out of
2130   the box.) The basic touch event handlers are `touchStart` and `touchEnd`; if all you need is
2131   basic support then you can simply proxy these events to their mouse counterparts.
2132 
2133   The counterpart to `mouseDragged` is `touchesDragged`, which is passed two arguments: a special
2134   multitouch event object which includes methods for accessing information about all currently
2135   in-flight touches, and a list of touches active on the current view. If you need to check the
2136   status of touches currently being handled by other views, the special multitouch event object
2137   exposes the `touchesForView` method. It also exposes the convenient `averagedTouchesForView`
2138   method, which gives you easy access to an average touch center and distance. Unlike `mouseDragged`,
2139   `touchesDragged` does not bubble, being only called on views whic handled `touchStart` for touches
2140   which have moved.
2141 
2142   To facilitate intuitive behavior in situations like scroll views with touch handlers inside them,
2143   you may capture a touch from part way up its responder chain before it has a chance to bubble
2144   up from the target. To capture a touch, expose a method on your view called `captureTouch` which
2145   accepts the touch as its only argument, and which returns YES if you would like to capture that
2146   touch. A captured touch will not bubble as normal, instead bubbling up from the capture point. Any
2147   child views will not have the opportunity to handle the captured event unless you implement custom
2148   responder swapping yourself.
2149 
2150   Touch events bubble differently than mouse and keyboard events. The initial reverse `captureTouch`
2151   bubbling is followed by regular `touchStart` bubbling; however, once this process has found a view
2152   that's willing to respond to the touch, further events are applied only to that view. If a view
2153   wishes to assign respondership for a touch to a different view, it can call one of several methods
2154   on the touch object. For a fuller discussion of touch events, touch responder behavior, and the touch
2155   object itself, see the documentation for SC.Touch.
2156 
2157   Keyboard events
2158   ----
2159   The basic key events are `keyDown` and `keyUp`. In order to be notified of keyboard events,
2160   a view must set `acceptsFirstResponder` to `YES`, and be on an active pane with
2161   `acceptsKeyPane` set to YES. (You may also need to call `becomeFirstResponder` on your view
2162   on a `mouseDown`, for example, to focus it. You can verify whether your view has successfully
2163   received first responder status by checking `isFirstResponder`.)
2164 
2165   Note that key events bubble similarly to mouse click events: they will stop bubbling if they
2166   encounter a view which handles the event and does not return NO.
2167 
2168   SproutCore implements a set of very convenient, higher-level keyboard events for action keys
2169   such as *tab*, *enter*, and the arrow keys. These are not triggered automatically, but you
2170   can gain access to them by proxying the keyboard event of your choice to `interpretKeyEvent`.
2171   For example:
2172 
2173         // Proxy the keyboard event to SC's built-in interpreter.
2174         keyDown: function(evt) {
2175           return this.interpretKeyEvents(evt);
2176         },
2177         // The interpreter will trigger the view's `cancel` event if the escape key was pressed.
2178         cancel: function(evt) {
2179           console.log('The escape key was pressed.'');
2180         }
2181 
2182   This will analyze the key press and fire an appropriate event. These events include, but are
2183   not limited to:
2184 
2185   - `moveUp`, `moveDown`, `moveLeft`, `moveRight` -- The arrow keys
2186   - `insertNewline` -- The enter key (note the lower-case 'line')
2187   - `cancel` -- The escape key
2188   - `insertTab` -- The tab key
2189   - `insertBacktab` -- Shift + the tab key
2190   - `moveToBeginningOfDocument` -- The *home* key
2191   - `moveToEndOfDocument` -- The *end* key
2192   - `pageUp` and `pageDown`
2193   - `moveLeftAndModifySelection` -- Shift + the left arrow
2194   - `selectAll` -- Ctrl + A / Cmd + A
2195 
2196   For a full list of available methods, see the key values on SC.BASE_KEY_BINDINGS and
2197   SC.MODIFIED_KEY_BINDINGS.
2198 
2199   @extends SC.Responder
2200   @extends SC.DelegateSupport
2201   @since SproutCore 1.0
2202 
2203 */
2204 SC.View = SC.CoreView.extend(/** @scope SC.View.prototype */{
2205   classNames: ['sc-view'],
2206 
2207   displayProperties: [],
2208 
2209   /** @private Enhance. */
2210   _executeQueuedUpdates: function () {
2211     sc_super();
2212 
2213     // Enabled
2214     // Update the layout style of the layer if necessary.
2215     if (this._enabledStyleNeedsUpdate) {
2216       this._doUpdateEnabledStyle();
2217     }
2218 
2219     // Layout
2220     // Update the layout style of the layer if necessary.
2221     if (this._layoutStyleNeedsUpdate) {
2222       this._doUpdateLayoutStyle();
2223     }
2224   },
2225 
2226   /** Apply the attributes to the context. */
2227   applyAttributesToContext: function (context) {
2228     // Cursor
2229     var cursor = this.get('cursor');
2230     if (cursor) { context.addClass(cursor.get('className')); }
2231 
2232     // Enabled
2233     if (!this.get('isEnabled')) {
2234       context.addClass('disabled');
2235       context.setAttr('aria-disabled', 'true');
2236     }
2237 
2238     // Layout
2239     // Have to pass 'true' for second argument for legacy.
2240     this.renderLayout(context, true);
2241 
2242     if (this.get('useStaticLayout')) { context.addClass('sc-static-layout'); }
2243 
2244     // Background color defaults to null; for performance reasons we should ignore it
2245     // unless it's ever been non-null.
2246     var backgroundColor = this.get('backgroundColor');
2247     if (!SC.none(backgroundColor) || this._scv_hasBackgroundColor) {
2248       this._scv_hasBackgroundColor = YES;
2249       if (backgroundColor) context.setStyle('backgroundColor', backgroundColor);
2250       else context.removeStyle('backgroundColor');
2251     }
2252 
2253     // Theming
2254     var theme = this.get('theme');
2255     var themeClassNames = theme.classNames, idx, len = themeClassNames.length;
2256 
2257     for (idx = 0; idx < len; idx++) {
2258       context.addClass(themeClassNames[idx]);
2259     }
2260 
2261     sc_super();
2262 
2263     var renderDelegate = this.get('renderDelegate');
2264     if (renderDelegate && renderDelegate.className) {
2265       context.addClass(renderDelegate.className);
2266     }
2267 
2268     // @if(debug)
2269     if (renderDelegate && renderDelegate.name) {
2270       SC.Logger.error("Render delegates now use 'className' instead of 'name'.");
2271       SC.Logger.error("Name '%@' will be ignored.", renderDelegate.name);
2272     }
2273     // @endif
2274   },
2275 
2276   /**
2277     Computes what the frame of this view would be if the parent were resized
2278     to the passed dimensions.  You can use this method to project the size of
2279     a frame based on the resize behavior of the parent.
2280 
2281     This method is used especially by the scroll view to automatically
2282     calculate when scrollviews should be visible.
2283 
2284     Passing null for the parent dimensions will use the actual current
2285     parent dimensions.  This is the same method used to calculate the current
2286     frame when it changes.
2287 
2288     @param {Rect} pdim the projected parent dimensions (optional)
2289     @returns {Rect} the computed frame
2290   */
2291   computeFrameWithParentFrame: function (pdim) {
2292     // Layout.
2293     var layout = this.get('layout'),
2294         f;
2295 
2296     // We can't predict the frame for static layout, so just return the view's
2297     // current frame (see original computeFrameWithParentFrame in views/view.js)
2298     if (this.get('useStaticLayout')) {
2299       f = sc_super();
2300       f = f ? this._sc_adjustForBorder(f, layout) : null;
2301       f = f ? this._sc_adjustForScale(f, layout) : null;
2302       return f;
2303     }
2304 
2305     f = {};
2306 
2307     var error, layer, AUTO = SC.LAYOUT_AUTO,
2308         dH, dW, //shortHand for parentDimensions
2309         lR = layout.right,
2310         lL = layout.left,
2311         lT = layout.top,
2312         lB = layout.bottom,
2313         lW = layout.width,
2314         lH = layout.height,
2315         lcX = layout.centerX,
2316         lcY = layout.centerY;
2317 
2318     if (lW === AUTO) {
2319       SC.throw(("%@.layout() cannot use width:auto if staticLayout is disabled").fmt(this), "%@".fmt(this), -1);
2320     }
2321 
2322     if (lH === AUTO) {
2323       SC.throw(("%@.layout() cannot use height:auto if staticLayout is disabled").fmt(this), "%@".fmt(this), -1);
2324     }
2325 
2326     if (!pdim) { pdim = this.computeParentDimensions(layout); }
2327     dH = pdim.height;
2328     dW = pdim.width;
2329 
2330     // handle left aligned and left/right
2331     if (!SC.none(lL)) {
2332       if (SC.isPercentage(lL)) {
2333         f.x = dW * lL;
2334       } else {
2335         f.x = lL;
2336       }
2337 
2338       if (lW !== undefined) {
2339         if (lW === AUTO) { f.width = AUTO; }
2340         else if (SC.isPercentage(lW)) { f.width = dW * lW; }
2341         else { f.width = lW; }
2342       } else { // better have lR!
2343         f.width = dW - f.x;
2344         if (lR && SC.isPercentage(lR)) { f.width = f.width - (lR * dW); }
2345         else { f.width = f.width - (lR || 0); }
2346       }
2347 
2348     // handle right aligned
2349     } else if (!SC.none(lR)) {
2350       if (SC.none(lW)) {
2351         if (SC.isPercentage(lR)) {
2352           f.width = dW - (dW * lR);
2353         }
2354         else f.width = dW - lR;
2355         f.x = 0;
2356       } else {
2357         if (lW === AUTO) f.width = AUTO;
2358         else if (SC.isPercentage(lW)) f.width = dW * lW;
2359         else f.width = (lW || 0);
2360         if (SC.isPercentage(lW)) f.x = dW - (lR * dW) - f.width;
2361         else f.x = dW - lR - f.width;
2362       }
2363 
2364     // handle centered
2365     } else if (!SC.none(lcX)) {
2366       if (lW === AUTO) f.width = AUTO;
2367       else if (SC.isPercentage(lW)) f.width = lW * dW;
2368       else f.width = (lW || 0);
2369       if (SC.isPercentage(lcX)) f.x = (dW - f.width) / 2 + (lcX * dW);
2370       else f.x = (dW - f.width) / 2 + lcX;
2371     } else {
2372       f.x = 0; // fallback
2373       if (SC.none(lW)) {
2374         f.width = dW;
2375       } else {
2376         if (lW === AUTO) f.width = AUTO;
2377         if (SC.isPercentage(lW)) f.width = lW * dW;
2378         else f.width = (lW || 0);
2379       }
2380     }
2381 
2382     // handle top aligned and top/bottom
2383     if (!SC.none(lT)) {
2384       if (SC.isPercentage(lT)) f.y = lT * dH;
2385       else f.y = lT;
2386       if (lH !== undefined) {
2387         if (lH === AUTO) f.height = AUTO;
2388         else if (SC.isPercentage(lH)) f.height = lH * dH;
2389         else f.height = lH;
2390       } else { // better have lB!
2391         if (lB && SC.isPercentage(lB)) f.height = dH - f.y - (lB * dH);
2392         else f.height = dH - f.y - (lB || 0);
2393       }
2394 
2395     // handle bottom aligned
2396     } else if (!SC.none(lB)) {
2397       if (SC.none(lH)) {
2398         if (SC.isPercentage(lB)) f.height = dH - (lB * dH);
2399         else f.height = dH - lB;
2400         f.y = 0;
2401       } else {
2402         if (lH === AUTO) f.height = AUTO;
2403         if (lH && SC.isPercentage(lH)) f.height = lH * dH;
2404         else f.height = (lH || 0);
2405         if (SC.isPercentage(lB)) f.y = dH - (lB * dH) - f.height;
2406         else f.y = dH - lB - f.height;
2407       }
2408 
2409     // handle centered
2410     } else if (!SC.none(lcY)) {
2411       if (lH === AUTO) f.height = AUTO;
2412       if (lH && SC.isPercentage(lH)) f.height = lH * dH;
2413       else f.height = (lH || 0);
2414       if (SC.isPercentage(lcY)) f.y = (dH - f.height) / 2 + (lcY * dH);
2415       else f.y = (dH - f.height) / 2 + lcY;
2416 
2417     // fallback
2418     } else {
2419       f.y = 0; // fallback
2420       if (SC.none(lH)) {
2421         f.height = dH;
2422       } else {
2423         if (lH === AUTO) f.height = AUTO;
2424         if (SC.isPercentage(lH)) f.height = lH * dH;
2425         else f.height = lH || 0;
2426       }
2427     }
2428 
2429     f.x = Math.floor(f.x);
2430     f.y = Math.floor(f.y);
2431     if (f.height !== AUTO) f.height = Math.floor(f.height);
2432     if (f.width !== AUTO) f.width = Math.floor(f.width);
2433 
2434     // if width or height were set to auto and we have a layer, try lookup
2435     if (f.height === AUTO || f.width === AUTO) {
2436       layer = this.get('layer');
2437       if (f.height === AUTO) f.height = layer ? layer.clientHeight : 0;
2438       if (f.width === AUTO) f.width = layer ? layer.clientWidth : 0;
2439     }
2440 
2441     // Okay we have all our numbers. Let's adjust them for things.
2442 
2443     // First, adjust for border.
2444     f = this._sc_adjustForBorder(f, layout);
2445 
2446     // Make sure the width/height fix their min/max (note the inlining of SC.none for performance)...
2447     /*jshint eqnull:true */
2448     if ((layout.maxHeight != null) && (f.height > layout.maxHeight)) f.height = layout.maxHeight;
2449     if ((layout.minHeight != null) && (f.height < layout.minHeight)) f.height = layout.minHeight;
2450     if ((layout.maxWidth != null) && (f.width > layout.maxWidth)) f.width = layout.maxWidth;
2451     if ((layout.minWidth != null) && (f.width < layout.minWidth)) f.width = layout.minWidth;
2452 
2453     // Finally, adjust for scale.
2454     f = this._sc_adjustForScale(f, layout);
2455 
2456     return f;
2457   },
2458 
2459   init: function () {
2460     sc_super();
2461 
2462     // Enabled.
2463     // If the view is pre-configured as disabled, then go to the proper initial state.
2464     if (!this.get('isEnabled')) { this._doDisable(); }
2465 
2466     // Layout
2467     this._previousLayout = this.get('layout');
2468 
2469     // Apply the automatic child view layout if it is defined.
2470     var childViewLayout = this.childViewLayout;
2471     if (childViewLayout) {
2472       // Layout the child views once.
2473       this.set('childViewsNeedLayout', true);
2474       this.layoutChildViewsIfNeeded();
2475 
2476       // If the child view layout is live, start observing affecting properties.
2477       if (this.get('isChildViewLayoutLive')) {
2478         this.addObserver('childViews.[]', this, this._cvl_childViewsDidChange);
2479         // DISABLED. this.addObserver('childViewLayout', this, this._cvl_childViewLayoutDidChange);
2480         this.addObserver('childViewLayoutOptions', this, this._cvl_childViewLayoutDidChange);
2481 
2482         // Initialize the child views.
2483         this._cvl_setupChildViewsLiveLayout();
2484 
2485         // Initialize our own frame observer.
2486         if (!this.get('isFixedSize') && childViewLayout.layoutDependsOnSize && childViewLayout.layoutDependsOnSize(this)) {
2487           this.addObserver('frame', this, this._cvl_childViewLayoutDidChange);
2488         }
2489       }
2490     }
2491 
2492     // Theming
2493     this._lastTheme = this.get('theme');
2494 
2495   },
2496 
2497   /** @private */
2498   destroy: function () {
2499     // Clean up.
2500     this._previousLayout = null;
2501 
2502     return sc_super();
2503   },
2504 
2505   /** SC.CoreView.prototype. */
2506   removeChild: function(view) {
2507     // Manipulation
2508     if (!view) { return this; } // nothing to do
2509     if (view.parentView !== this) {
2510       throw new Error("%@.removeChild(%@) must belong to parent".fmt(this, view));
2511     }
2512 
2513     // notify views
2514     // TODO: Deprecate these notifications.
2515     if (view.willRemoveFromParent) { view.willRemoveFromParent() ; }
2516     if (this.willRemoveChild) { this.willRemoveChild(view) ; }
2517 
2518     sc_super();
2519 
2520     return this;
2521   }
2522 
2523 });
2524 
2525 //unload views for IE, trying to collect memory.
2526 if (SC.browser.isIE) SC.Event.add(window, 'unload', SC.View, SC.View.unload);
2527 
2528 
2529