1 sc_require("views/view");
  2 
  3 SC.CoreView.mixin(
  4   /** @scope SC.CoreView */ {
  5 
  6   /**
  7     The view is enabled.
  8 
  9     @static
 10     @constant
 11   */
 12   ENABLED: 0x08, // 8
 13 
 14   /**
 15     The view has been disabled.
 16 
 17     @static
 18     @constant
 19   */
 20   IS_DISABLED: 0x10, // 16
 21 
 22   /**
 23     The view is disabled.
 24 
 25     @static
 26     @constant
 27   */
 28   DISABLED: 0x11, // 17
 29 
 30   /**
 31     The view is enabled itself, but is effectively disabled in the pane due to
 32     a disabled ancestor.
 33 
 34     @static
 35     @constant
 36   */
 37   DISABLED_BY_PARENT: 0x12, // 18
 38 
 39   /**
 40     The view is disabled itself and is also disabled in the pane due to
 41     a disabled ancestor.
 42 
 43     @static
 44     @constant
 45   */
 46   DISABLED_AND_BY_PARENT: 0x13 // 19
 47 
 48 });
 49 
 50 
 51 SC.View.reopen(
 52   /** @scope SC.View.prototype */ {
 53 
 54   // ------------------------------------------------------------------------
 55   // Properties
 56   //
 57 
 58   /**
 59     The current enabled state of the view.
 60 
 61     Views have a few possible enabled states:
 62 
 63     * SC.CoreView.ENABLED
 64     * SC.CoreView.DISABLED
 65     * SC.CoreView.DISABLED_BY_PARENT
 66 
 67     @type String
 68     @default SC.CoreView.ENABLED
 69     @readonly
 70   */
 71   enabledState: SC.CoreView.ENABLED,
 72 
 73   /**
 74     Set to true when the item is enabled.   Note that changing this value
 75     will alter the `isEnabledInPane` property for this view and any
 76     child views as well as to automatically add or remove a 'disabled' CSS
 77     class name.
 78 
 79     This property is observable and bindable.
 80 
 81     @type Boolean
 82   */
 83   isEnabled: YES,
 84   isEnabledBindingDefault: SC.Binding.oneWay().bool(),
 85 
 86   /**
 87     Computed property returns YES if the view and all of its parent views
 88     are enabled in the pane.  You should use this property when deciding
 89     whether to respond to an incoming event or not.
 90 
 91     @type Boolean
 92   */
 93   // The previous version used a lazy upward search method.  This has better
 94   // performance, but made isEnabledInPane non-bindable.
 95   // isEnabledInPane: function() {
 96   //   var ret = this.get('isEnabled'), pv ;
 97   //   if (ret && (pv = this.get('parentView'))) { ret = pv.get('isEnabledInPane'); }
 98   //   return ret ;
 99   // }.property('parentView', 'isEnabled'),
100   isEnabledInPane: function () {
101     return this.get('enabledState') === SC.CoreView.ENABLED;
102   }.property('enabledState').cacheable(),
103 
104   /**
105     By default, setting isEnabled to false on a view will place all of its
106     child views in a disabled state.  To block this from happening to a
107     specific child view and its children, you can set `shouldInheritEnabled`
108     to false.
109 
110     In this way you can set `isEnabled` to false on a main pane to disable all
111     buttons, collections and other controls within it, but can still keep a
112     section of it editable using `shouldInheritEnabled: false`.
113 
114     @type Boolean
115   */
116   shouldInheritEnabled: true,
117 
118   // ------------------------------------------------------------------------
119   // Actions & Events
120   //
121 
122   /** @private */
123   _doEnable: function () {
124     var handled = true,
125       enabledState = this.get('enabledState');
126 
127     // If the view itself is disabled, then we can enable it.
128     if (enabledState === SC.CoreView.DISABLED) {
129       // Update the enabled state of all children. Top-down, because if a child is disabled on its own it
130       // won't affect its childrens' state and we can bail out early.
131       this._callOnChildViews('_parentDidEnableInPane');
132       this._gotoEnabledState();
133 
134     // If the view is disabled and has a disabled parent, we can enable it, but it will be disabled by the parent.
135     } else if (enabledState === SC.CoreView.DISABLED_AND_BY_PARENT) {
136       // The view is no longer disabled itself, but still disabled by an ancestor.
137       this._gotoDisabledByParentState();
138 
139     // If the view is not disabled, we can't enable it.
140     } else {
141       handled = false;
142     }
143 
144     return handled;
145   },
146 
147   /** @private */
148   _doDisable: function () {
149     var handled = true,
150       enabledState = this.get('enabledState');
151 
152     // If the view is not itself disabled, then we can disable it.
153     if (enabledState === SC.CoreView.ENABLED) {
154       // Update the disabled state of all children. Top-down, because if a child is disabled on its own it
155       // won't affect its childrens' state and we can bail out early.
156       this._callOnChildViews('_parentDidDisableInPane');
157       this._gotoDisabledState();
158 
159       // Ensure that first responder status is given up.
160       if (this.get('isFirstResponder')) {
161         this.resignFirstResponder();
162       }
163 
164     // If the view is disabled because of a disabled parent, we can disable the view itself too.
165     } else if (enabledState === SC.CoreView.DISABLED_BY_PARENT) {
166       // The view is now disabled itself and disabled by an ancestor.
167       this._gotoDisabledAndByParentState();
168 
169     // If the view is not enabled, we can't disable it.
170     } else {
171       handled = false;
172     }
173 
174     return handled;
175   },
176 
177   // ------------------------------------------------------------------------
178   // Methods
179   //
180 
181   /** @private
182     Observes the isEnabled property and resigns first responder if set to NO.
183     This will avoid cases where, for example, a disabled text field retains
184     its focus rings.
185 
186     @observes isEnabled
187   */
188   _sc_view_isEnabledDidChange: function () {
189     // Filter the input channel.
190     this.invokeOnce(this._doUpdateEnabled);
191   }.observes('isEnabled'),
192 
193   /** @private */
194   _doUpdateEnabled: function () {
195     var state = this.get('viewState');
196 
197     // Call the proper action.
198     if (this.get('isEnabled')) {
199       this._doEnable();
200     } else {
201       this._doDisable();
202     }
203 
204     // Update the display if in a visible state.
205     switch (state) {
206     case SC.CoreView.ATTACHED_SHOWN:
207     case SC.CoreView.ATTACHED_SHOWING:
208     case SC.CoreView.ATTACHED_BUILDING_IN:
209       // Update the display.
210       this._doUpdateEnabledStyle();
211       break;
212     default:
213       // Indicate that a display update is required the next time we are visible.
214       this._enabledStyleNeedsUpdate = true;
215     }
216   },
217 
218   /** @private */
219   _doUpdateEnabledStyle: function () {
220     var isEnabled = this.get('isEnabled');
221 
222     this.$().toggleClass('disabled', !isEnabled);
223     this.$().attr('aria-disabled', !isEnabled ? true : null);
224 
225     // Reset that an update is required.
226     this._enabledStyleNeedsUpdate = false;
227   },
228 
229   /** @private */
230   _parentDidEnableInPane: function () {
231     var enabledState = this.get('enabledState');
232 
233     if (this.get('shouldInheritEnabled')) {
234 
235       if (enabledState === SC.CoreView.DISABLED_BY_PARENT) { // Was enabled before.
236         this._gotoEnabledState();
237       } else if (enabledState === SC.CoreView.DISABLED_AND_BY_PARENT) { // Was disabled before.
238         this._gotoDisabledState();
239 
240         // There's no need to continue to further child views.
241         return false;
242       }
243     } else {
244       // There's no need to continue to further child views.
245       return false;
246     }
247   },
248 
249   /** @private */
250   _parentDidDisableInPane: function () {
251     var enabledState = this.get('enabledState');
252 
253     if (this.get('shouldInheritEnabled')) {
254 
255       if (enabledState === SC.CoreView.ENABLED) { // Was enabled.
256         this._gotoDisabledByParentState();
257       } else if (enabledState === SC.CoreView.DISABLED) { // Was disabled.
258         this._gotoDisabledAndByParentState();
259       } else { // Was already disabled by ancestor.
260 
261         // There's no need to continue to further child views.
262         return false;
263       }
264     } else {
265       // There's no need to continue to further child views.
266       return false;
267     }
268   },
269 
270   /** @private */
271   _gotoEnabledState: function () {
272     this.set('enabledState', SC.CoreView.ENABLED);
273   },
274 
275   /** @private */
276   _gotoDisabledState: function () {
277     this.set('enabledState', SC.CoreView.DISABLED);
278   },
279 
280   /** @private */
281   _gotoDisabledAndByParentState: function () {
282     this.set('enabledState', SC.CoreView.DISABLED_AND_BY_PARENT);
283   },
284 
285   /** @private */
286   _gotoDisabledByParentState: function () {
287     this.set('enabledState', SC.CoreView.DISABLED_BY_PARENT);
288   }
289 
290 });
291