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