// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2009 Sprout Systems, Inc. and contributors. // Portions ©2008-2009 Apple, Inc. All rights reserved. // License: Licened under MIT license (see license.js) // ========================================================================== require('system/browser'); require('system/event'); require('system/cursor'); require('mixins/responder') ; require('mixins/string') ; SC.viewKey = SC.guidKey + "_view" ; /** Select a horizontal layout for various views.*/ SC.LAYOUT_HORIZONTAL = 'sc-layout-horizontal'; /** Select a vertical layout for various views.*/ SC.LAYOUT_VERTICAL = 'sc-layout-vertical'; /** @private */ SC._VIEW_DEFAULT_DIMS = 'marginTop marginLeft'.w(); /** Layout properties needed to anchor a view to the top. */ SC.ANCHOR_TOP = { top: 0 }; /** Layout properties needed to anchor a view to the left. */ SC.ANCHOR_LEFT = { left: 0 }; /* Layout properties to anchor a view to the top left */ SC.ANCHOR_TOP_LEFT = { top: 0, left: 0 }; /** Layout properties to anchoe view to the bottom. */ SC.ANCHOR_BOTTOM = { bottom: 0 }; /** Layout properties to anchor a view to the right. */ SC.ANCHOR_RIGHT = { right: 0 } ; /** Layout properties to anchor a view to the bottom right. */ SC.ANCHOR_BOTTOM_RIGHT = { bottom: 0, right: 0 }; /** Layout properties to take up the full width of a parent view. */ SC.FULL_WIDTH = { left: 0, right: 0 }; /** Layout properties to take up the full height of a parent view. */ SC.FULL_HEIGHT = { top: 0, bottom: 0 }; /** Layout properties to center. Note that you must also specify a width and height for this to work. */ SC.ANCHOR_CENTER = { centerX: 0, centerY: 0 }; /** @private - custom array used for child views */ SC.EMPTY_CHILD_VIEWS_ARRAY = []; SC.EMPTY_CHILD_VIEWS_ARRAY.needsClone = YES; /** @class Base class for managing a view. View's provide two functions: 1. They translate state and events into drawing instructions for the web browser and 2. They act as first responders for incoming keyboard, mouse, and touch events. h2. View Initialization When a view is setup, there are several methods you can override that will be called at different times depending on how your view is created. Here is a guide to which method you want to override and when: - *init:* override this method for any general object setup (such as observers, starting timers and animations, etc) that you need to happen everytime the view is created, regardless of whether or not its layer exists yet. - *render:* override this method to generate or update your HTML to reflect the current state of your view. This method is called both when your view is first created and later anytime it needs to be updated. - *didCreateLayer:* the render() method is used to generate new HTML. Override this method to perform any additional setup on the DOM you might need to do after creating the view. For example, if you need to listen for events. - *willDestroyLayer:* if you implement didCreateLayer() to setup event listeners, you should implement this method as well to remove the same just before the DOM for your view is destroyed. - *updateLayer:* Normally, when a view needs to update its content, it will re-render the view using the render() method. If you would like to override this behavior with your own custom updating code, you can replace updateLayer() with your own implementation instead. @extends SC.Object @extends SC.Responder @extends SC.DelegateSupport @since SproutCore 1.0 */ SC.View = SC.Object.extend(SC.Responder, SC.DelegateSupport, /** @scope SC.View.prototype */ { concatenatedProperties: 'outlets displayProperties layoutProperties classNames renderMixin didCreateLayerMixin willDestroyLayerMixin'.w(), /** The current pane. @property {SC.Pane} */ pane: function() { var view = this ; while (view && !view.isPane) view = view.get('parentView') ; return view ; }.property('parentView').cacheable(), /** The page this view was instantiated from. This is set by the page object during instantiation. @property {SC.Page} */ page: null, /** The current split view this view is embedded in (may be null). @property {SC.SplitView} */ splitView: function() { var view = this ; while (view && !view.isSplitView) view = view.get('parentView') ; return view ; }.property('parentView').cacheable(), /** If the view is currently inserted into the DOM of a parent view, this property will point to the parent of the view. */ parentView: null, /** Optional background color. Will be applied to the view's element if set. This property is intended for one-off views that need a background element. If you plan to create many view instances it is probably better to use CSS. @property {String} */ backgroundColor: null, // .......................................................... // IS ENABLED SUPPORT // /** Set to true when the item is enabled. Note that changing this value will also alter the isVisibleInWindow property for this view and any child views. Note that if you apply the SC.Control mixin, changing this property will also automatically add or remove a 'disabled' CSS class name as well. This property is observable and bindable. @property {Boolean} */ isEnabled: YES, isEnabledBindingDefault: SC.Binding.oneWay().bool(), /** Computed property returns YES if the view and all of its parent views are enabled in the pane. You should use this property when deciding whether to respond to an incoming event or not. This property is not observable. @property {Boolean} */ isEnabledInPane: function() { var ret = this.get('isEnabled'), pv ; if (ret && (pv = this.get('parentView'))) ret = pv.get('isEnabledInPane'); return ret ; }.property('parentView', 'isEnabled'), // .......................................................... // IS VISIBLE IN WINDOW SUPPORT // /** The isVisible property determines if the view is shown in the view hierarchy it is a part of. A view can have isVisible == YES and still have isVisibleInWindow == NO. This occurs, for instance, when a parent view has isVisible == NO. Default is YES. The isVisible property is considered part of the layout and so changing it will trigger a layout update. @property {Boolean} */ isVisible: YES, /** YES only if the view and all of its parent views are currently visible in the window. This property is used to optimize certain behaviors in the view. For example, updates to the view layer are not performed if the view until the view becomes visible in the window. */ isVisibleInWindow: NO, /** Recomputes the isVisibleInWindow property based on the visibility of the view and its parent. If the recomputed value differs from the current isVisibleInWindow state, this method will also call recomputIsVisibleInWindow() on its child views as well. As an optional optimization, you can pass the isVisibleInWindow state of the parentView if you already know it. You will not generally need to call or override this method yourself. It is used by the SC.View hierarchy to relay window visibility changes up and down the chain. @property {Boolean} parentViewIsVisible @returns {SC.View} receiver */ recomputeIsVisibleInWindow: function(parentViewIsVisible) { var last = this.get('isVisibleInWindow') ; var cur = this.get('isVisible'), parentView ; // isVisibleInWindow = isVisible && parentView.isVisibleInWindow // this approach only goes up to the parentView if necessary. if (cur) { cur = (parentViewIsVisible === undefined) ? ((parentView=this.get('parentView')) ? parentView.get('isVisibleInWindow') : NO) : parentViewIsVisible ; } // if the state has changed, update it and notify children if (last !== cur) { this.set('isVisibleInWindow', cur) ; var childViews = this.get('childViews'), len = childViews.length, idx; for(idx=0;idx=0) childViews.removeAt(idx); // The DOM will need some fixing up, note this on the view. view.parentViewDidChange() ; // notify views if (this.didRemoveChild) this.didRemoveChild(view); if (view.didRemoveFromParent) view.didRemoveFromParent(this) ; return this ; }, /** Removes all children from the parentView. @returns {SC.View} receiver */ removeAllChildren: function() { var childViews = this.get('childViews'), view ; while (view = childViews.objectAt(childViews.get('length')-1)) { this.removeChild(view) ; } return this ; }, /** Removes the view from its parentView, if one is found. Otherwise does nothing. @returns {SC.View} receiver */ removeFromParent: function() { var parent = this.get('parentView') ; if (parent) parent.removeChild(this) ; return this ; }, /** Replace the oldView with the specified view in the receivers childNodes array. This will also replace the DOM node of the oldView with the DOM node of the new view in the receivers DOM. If the specified view already belongs to another parent, it will be removed from that view first. @param view {SC.View} the view to insert in the DOM @param view {SC.View} the view to remove from the DOM. @returns {SC.View} the receiver */ replaceChild: function(view, oldView) { // suspend notifications view.beginPropertyChanges(); oldView.beginPropertyChanges(); this.beginPropertyChanges(); this.insertBefore(view,oldView).removeChild(oldView) ; // resume notifications this.endPropertyChanges(); oldView.endPropertyChanges(); view.endPropertyChanges(); return this; }, /** Appends the specified view to the end of the receivers childViews array. This is equivalent to calling insertBefore(view, null); @param view {SC.View} the view to insert @returns {SC.View} the receiver */ appendChild: function(view) { return this.insertBefore(view, null); }, /** This method is called whenever the receiver's parentView has changed. The default implementation of this method marks the view's display location as dirty so that it will update at the end of the run loop. You will not usually need to override or call this method yourself, though if you manually patch the parentView hierarchy for some reason, you should call this method to notify the view that it's parentView has changed. @returns {SC.View} receiver */ parentViewDidChange: function() { this.recomputeIsVisibleInWindow() ; this.set('layerLocationNeedsUpdate', YES) ; this.invokeOnce(this.updateLayerLocationIfNeeded) ; return this ; }.observes('isVisible'), // .......................................................... // LAYER SUPPORT // /** Returns the current layer for the view. The layer for a view is only generated when the view first becomes visible in the window and even then it will not be computed until you request this layer property. If the layer is not actually set on the view itself, then the layer will be found by calling this.findLayerInParentLayer(). You can also set the layer by calling set on this property. @property {DOMElement} the layer */ layer: function(key, value) { if (value !== undefined) { this._view_layer = value ; // no layer...attempt to discover it... } else { value = this._view_layer; if (!value) { var parent = this.get('parentView'); if (parent) parent = parent.get('layer'); if (parent) { this._view_layer = value = this.findLayerInParentLayer(parent); } parent = null ; // avoid memory leak } } return value ; }.property('isVisibleInWindow').cacheable(), /** Get a CoreQuery object for this view's layer, or pass in a selector string to get a CoreQuery object for a DOM node nested within this layer. @param {String} sel a CoreQuery-compatible selector string @returns {SC.CoreQuery} the CoreQuery object for the DOM node */ $: function(sel) { var ret, layer = this.get('layer') ; // note: SC.$([]) returns an empty CoreQuery object. SC.$() would // return an object selecting hte document. ret = !layer ? SC.$([]) : (sel === undefined) ? SC.$(layer) : SC.$(sel, layer) ; layer = null ; // avoid memory leak return ret ; }, /** Returns the DOM element that should be used to hold child views when they are added/remove via DOM manipulation. The default implementation simply returns the layer itself. You can override this to return a DOM element within the layer. @property {DOMElement} the container layer */ containerLayer: function() { return this.get('layer') ; }.property('layer').cacheable(), /** The ID to use when trying to locate the layer in the DOM. If you do not set the layerId explicitly, then the view's GUID will be used instead. This ID must be set at the time the view is created. @property {String} @readOnly */ layerId: function() { return SC.guidFor(this) ; }.property().cacheable(), /** Attempts to discover the layer in the parent layer. The default implementation looks for an element with an ID of layerId (or the view's guid if layerId is null). You can override this method to provide your own form of lookup. For example, if you want to discover your layer using a CSS class name instead of an ID. @param {DOMElement} parentLayer the parent's DOM layer @returns {DOMElement} the discovered layer */ findLayerInParentLayer: function(parentLayer) { var layerId = this.get('layerId') ; // first, let's try the fast path... var elem = document.getElementById(layerId) ; // TODO: use code generation to only really do this check on IE if (SC.browser.msie && elem && elem.id !== layerId) elem = null ; // if browser supports querySelector use that. if (!elem && parentLayer.querySelector) { // TODO: make querySelector work on all platforms... // elem = parentLayer.querySelector('#' + layerId)[0]; } // if no element was found the fast way, search down the parentLayer for // the element. This code should not be invoked very often. Usually a // DOM element will be discovered by the first method above. if (!elem) { elem = parentLayer.firstChild ; while (elem && (elem.id !== layerId)) { // try to get first child or next sibling if no children var next = elem.firstChild || elem.nextSibling ; // if no next sibling, then get next sibling of parent. Walk up // until we find parent with next sibling or find ourselves back at // the beginning. while (!next && elem && ((elem = elem.parentNode) !== parentLayer)) { next = elem.nextSibling ; } elem = next ; } } return elem; }, /** This method is invoked whenever a display property changes. It will set the layerNeedsUpdate method to YES. */ displayDidChange: function() { this.set('layerNeedsUpdate', YES) ; }, /** Setting this property to YES will cause the updateLayerIfNeeded method to be invoked at the end of the runloop. You can also force a view to update sooner by calling updateLayerIfNeeded() directly. The method will update the layer only if this property is YES. @property {Boolean} @test in updateLayer */ layerNeedsUpdate: NO, /** @private Schedules the updateLayerIfNeeded method to run at the end of the runloop if layerNeedsUpdate is set to YES. */ _view_layerNeedsUpdateDidChange: function() { if (this.get('layerNeedsUpdate')) { this.invokeOnce(this.updateLayerIfNeeded) ; } }.observes('layerNeedsUpdate'), /** Updates the layer only if the view is visible onscreen and if layerNeedsUpdate is set to YES. Normally you will not invoke this method directly. Instead you set the layerNeedsUpdate property to YES and this method will be called once at the end of the runloop. If you need to update view's layer sooner than the end of the runloop, you can call this method directly. If your view is not visible in the window but you want it to update anyway, then call this method, passing YES for the 'force' parameter. You should not override this method. Instead override updateLayer() or render(). @param {Boolean} isVisible if true assume view is visible even if it is not. @returns {SC.View} receiver @test in updateLayer */ updateLayerIfNeeded: function(isVisible) { if (!isVisible) isVisible = this.get('isVisibleInWindow') ; if (isVisible && this.get('layerNeedsUpdate')) { this.beginPropertyChanges() ; this.set('layerNeedsUpdate', NO) ; this.updateLayer() ; this.endPropertyChanges() ; } return this ; }, /** This is the core method invoked to update a view layer whenever it has changed. This method simply creates a render context focused on the layer element and then calls your render() method. You will not usually call or override this method directly. Instead you should set the layerNeedsUpdate property to YES to cause this method to run at the end of the run loop, or you can call updateLayerIfNeeded() to force the layer to update immediately. Instead of overriding this method, consider overidding the render() method instead, which is called both when creating and updating a layer. If you do not want your render() method called when updating a layer, then you should override this method instead. @returns {SC.View} receiver */ updateLayer: function() { var context = this.renderContext(this.get('layer')) ; this.prepareContext(context, NO) ; context.update() ; return this ; }, /** Creates a new renderContext with the passed tagName or element. You can override this method to provide further customization to the context if needed. Normally you will not need to call or override this method. @returns {SC.RenderContext} */ renderContext: function(tagNameOrElement) { return SC.RenderContext(tagNameOrElement) ; }, /** Creates the layer by creating a renderContext and invoking the view's render() method. This will only create the layer if the layer does not already exist. When you create a layer, it is expected that your render() method will also render the HTML for all child views as well. This method will notify the view along with any of its childViews that its layer has been created. @returns {SC.View} receiver */ createLayer: function() { if (this.get('layer')) return this ; // nothing to do var context = this.renderContext(this.get('tagName')) ; // now prepare the contet like normal. this.prepareContext(context, YES) ; this.set('layer', context.element()) ; // now notify the view and its child views.. this._notifyDidCreateLayer() ; return this ; }, /** @private - Invokes the receivers didCreateLayer() method if it exists and then invokes the same on all child views. */ _notifyDidCreateLayer: function() { if (this.didCreateLayer) this.didCreateLayer() ; var mixins = this.didCreateLayerMixin, len, idx ; if (mixins) { len = mixins.length ; for (idx=0; idx= 0) { this.addObserver(dp[idx], this, this.displayDidChange) ; } // register for drags if (this.get('isDropTarget')) SC.Drag.addDropTarget(this) ; // register scroll views for autoscroll during drags if (this.get('isScrollable')) SC.Drag.addScrollableView(this) ; }, /** Wakes up the view. The default implementation immediately syncs any bindings, which may cause the view to need its display updated. You can override this method to perform any additional setup. Be sure to call sc_super to setup bindings and to call awake on childViews. It is best to awake a view before you add it to the DOM. This way when the DOM is generated, it will have the correct initial values and will not require any additional setup. @returns {void} */ awake: function() { sc_super(); var childViews = this.get('childViews'), len = childViews.length, idx ; for (idx=0; idx layout.maxHeight)) { f.height = layout.maxHeight ; } if (!SC.none(layout.minHeight) && (f.height < layout.minHeight)) { f.height = layout.minHeight ; } if (!SC.none(layout.maxWidth) && (f.width > layout.maxWidth)) { f.width = layout.maxWidth ; } if (!SC.none(layout.minWidth) && (f.width < layout.minWidth)) { f.width = layout.minWidth ; } // make sure width/height are never < 0 if (f.height < 0) f.height = 0 ; if (f.width < 0) f.width = 0 ; return f; }, computeParentDimensions: function(frame) { var ret, pv = this.get('parentView'), pf = (pv) ? pv.get('frame') : null ; if (pf) { ret = { width: pf.width, height: pf.height }; } else { var f = frame ; ret = { width: (f.left || 0) + (f.width || 0) + (f.right || 0), height: (f.top || 0) + (f.height || 0) + (f.bottom || 0) }; } return ret ; }, /** The clipping frame returns the visible portion of the view, taking into account the clippingFrame of the parent view. Keep in mind that the clippingFrame is in the context of the view itself, not it's parent view. Normally this will be calculate based on the intersection of your own clippingFrame and your parentView's clippingFrame. @property {Rect} */ clippingFrame: function() { var pv= this.get('parentView'), f = this.get('frame'), ret = f ; if (pv) { pv = pv.get('clippingFrame') ; ret = SC.intersectRects(pv, f) ; } ret.x -= f.x ; ret.y -= f.y ; return ret ; }.property('parentView', 'frame').cacheable(), /** @private Whenever the clippingFrame changes, this observer will fire, notifying child views that their frames have also changed. */ _sc_view_clippingFrameDidChange: function() { var cvs = this.get('childViews'), len = cvs.length, idx, cv ; for (idx=0; idx View A.beginXXX() // -> View B.beginXXX() // -> View C.begitXXX() // -> View D.beginXXX() // // ...later on, endXXX methods are called in reverse order of beginXXX... // // <- View D.endXXX() // <- View C.endXXX() // <- View B.endXXX() // <- View A.endXXX() // // See the two methods below for an example implementation. /** Call this method when you plan to begin a live resize. This will notify the receiver view and any of its children that are interested that the resize is about to begin. @returns {SC.View} receiver @test in viewDidResize */ beginLiveResize: function() { // call before children have been notified... if (this.willBeginLiveResize) this.willBeginLiveResize() ; // notify children in order var ary = this.get('childViews'), len = ary.length, idx, view ; for (idx=0; idx=0; --idx) { // loop backwards view = ary[idx] ; if (view.endLiveResize) view.endLiveResize() ; } // call *after* all children have been notified... if (this.didEndLiveResize) this.didEndLiveResize() ; return this ; }, /** layoutStyle describes the current styles to be written to your element based on the layout you defined. Both layoutStyle and frame reset when you edit the layout property. Both are read only. Computes the layout style settings needed for the current anchor. @property {Hash} @readOnly */ layoutStyle: function() { var layout = this.get('layout'), ret = {}, pdim = null; // X DIRECTION // handle left aligned and left/right if (!SC.none(layout.left)) { ret.left = Math.floor(layout.left); if (layout.width !== undefined) { ret.width = Math.floor(layout.width) ; ret.right = null ; } else { ret.width = null ; ret.right = Math.floor(layout.right || 0) ; } ret.marginLeft = 0 ; // handle right aligned } else if (!SC.none(layout.right)) { ret.right = Math.floor(layout.right) ; ret.marginLeft = 0 ; if (SC.none(layout.width)) { ret.left = 0; ret.width = null; } else { ret.left = null ; ret.width = Math.floor(layout.width || 0) ; } // handle centered } else if (!SC.none(layout.centerX)) { ret.left = "50%"; ret.width = Math.floor(layout.width || 0) ; ret.marginLeft = Math.floor(layout.centerX - ret.width/2) ; ret.right = null ; // if width defined, assume top/left of zero } else if (!SC.none(layout.width)) { ret.left = 0; ret.right = null; ret.width = Math.floor(layout.width); ret.marginLeft = 0; // fallback, full width. } else { ret.left = 0; ret.right = 0; ret.width = null ; ret.marginLeft= 0; } // handle min/max ret.minWidth = (layout.minWidth === undefined) ? null : layout.minWidth; ret.maxWidth = (layout.maxWidth === undefined) ? null : layout.maxWidth; // Y DIRECTION // handle left aligned and left/right if (!SC.none(layout.top)) { ret.top = Math.floor(layout.top); if (layout.height !== undefined) { ret.height = Math.floor(layout.height) ; ret.bottom = null ; } else { ret.height = null ; ret.bottom = Math.floor(layout.bottom || 0) ; } ret.marginTop = 0 ; // handle right aligned } else if (!SC.none(layout.bottom)) { ret.marginTop = 0 ; ret.bottom = Math.floor(layout.bottom) ; if (SC.none(layout.height)) { ret.top = 0; ret.height = null ; } else { ret.top = null ; ret.height = Math.floor(layout.height || 0) ; } // handle centered } else if (!SC.none(layout.centerY)) { ret.top = "50%"; ret.height = Math.floor(layout.height || 0) ; ret.marginTop = Math.floor(layout.centerY - ret.height/2) ; ret.bottom = null ; } else if (!SC.none(layout.height)) { ret.top = 0; ret.bottom = null; ret.height = Math.floor(layout.height || 0); ret.marginTop = 0; // fallback, full width. } else { ret.top = 0; ret.bottom = 0; ret.height = null ; ret.marginTop= 0; } // handle min/max ret.minHeight = (layout.minHeight === undefined) ? null : layout.minHeight; ret.maxHeight = (layout.maxHeight === undefined) ? null : layout.maxHeight; // if zIndex is set, use it. otherwise let default shine through ret.zIndex = SC.none(layout.zIndex) ? null : layout.zIndex.toString(); // if backgroundPosition is set, use it. otherwise let default shine through ret.backgroundPosition = SC.none(layout.backgroundPosition) ? null : layout.backgroundPosition.toString(); // set default values to null to allow built-in CSS to shine through // currently applies only to marginLeft & marginTop var dims = SC._VIEW_DEFAULT_DIMS, loc = dims.length, x; while(--loc >=0) { x = dims[loc]; if (ret[x]===0) ret[x]=null; } // convert any numbers into a number + "px". for(var key in ret) { var value = ret[key]; if (typeof value === SC.T_NUMBER) ret[key] = (value + "px"); } return ret ; }.property().cacheable(), /** The view responsible for laying out this view. The default version returns the current parent view. */ layoutView: function() { return this.get('parentView') ; }.property('parentView').cacheable(), /** This method is called whenever a property changes that invalidates the layout of the view. Changing the layout will do this automatically, but you can add others if you want. @returns {SC.View} receiver */ layoutDidChange: function() { // console.log('%@.layoutDidChange()'.fmt(this)); this.beginPropertyChanges() ; if (this.frame) this.notifyPropertyChange('frame') ; this.notifyPropertyChange('layoutStyle') ; this.endPropertyChanges() ; // notify layoutView... var layoutView = this.get('layoutView'); if (layoutView) { layoutView.set('childViewsNeedLayout', YES); layoutView.layoutDidChangeFor(this) ; if (layoutView.get('childViewsNeedLayout')) { layoutView.invokeOnce(layoutView.layoutChildViewsIfNeeded); } } return this ; }.observes('layout'), /** This this property to YES whenever the view needs to layout its child views. Normally this property is set automatically whenever the layout property for a child view changes. @property {Boolean} */ childViewsNeedLayout: NO, /** One of two methods that are invoked whenever one of your childViews layout changes. This method is invoked everytime a child view's layout changes to give you a chance to record the information about the view. Since this method may be called many times during a single run loop, you should keep this method pretty short. The other method called when layout changes, layoutChildViews(), is invoked only once at the end of the run loop. You should do any expensive operations (including changing a childView's actual layer) in this other method. Note that if as a result of running this method you decide that you do not need your layoutChildViews() method run later, you can set the childViewsNeedsLayout property to NO from this method and the layout method will not be called layer. @param {SC.View} childView the view whose layout has changed. @returns {void} */ layoutDidChangeFor: function(childView) { var set = this._needLayoutViews ; if (!set) set = this._needLayoutViews = SC.Set.create(); set.add(childView); }, /** Called your layout method if the view currently needs to layout some child views. @param {Boolean} isVisible if true assume view is visible even if it is not. @returns {SC.View} receiver @test in layoutChildViews */ layoutChildViewsIfNeeded: function(isVisible) { if (!isVisible) isVisible = this.get('isVisibleInWindow'); if (isVisible && this.get('childViewsNeedLayout')) { this.set('childViewsNeedLayout', NO); this.layoutChildViews(); } return this ; }, /** Applies the current layout to the layer. This method is usually only called once per runloop. You can override this method to provide your own layout updating method if you want, though usually the better option is to override the layout method from the parent view. The default implementation of this method simply calls the renderLayout() method on the views that need layout. @returns {void} */ layoutChildViews: function() { var set = this._needLayoutViews, len = set ? set.length : 0, idx; var view, context, layer; for(idx=0;idx=0) { var viewClass = childViews[idx]; loc = childLocs[idx]; if (loc && viewClass && viewClass.loc) viewClass.loc(loc) ; } return this; // done! }, /** Internal method actually updates the localizated attributes on the view class. This is overloaded in design mode to also save the attributes. */ applyLocalizedAttributes: function(loc) { SC.mixin(this.prototype, loc) ; }, views: {} }) ; // ....................................................... // OUTLET BUILDER // /** Generates a computed property that will look up the passed property path the first time you try to get the value. Use this whenever you want to define an outlet that points to another view or object. The root object used for the path will be the receiver. */ SC.outlet = function(path) { return function(key) { return (this[key] = SC.objectForPropertyPath(path, this)) ; }.property(); }; /** @private on unload clear cached divs. */ SC.View.unload = function() { // delete view items this way to ensure the views are cleared. The hash // itself may be owned by multiple view subclasses. var views = SC.View.views; if (views) { for(var key in views) { if (!views.hasOwnProperty(key)) continue ; delete views[key]; } } } ; SC.Event.add(window, 'unload', SC.View, SC.View.unload) ;