1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 /** 9 @class 10 11 Implements basic target + action support for views. To use, simply set the `action` property on 12 the view to the name of a method handled by an object in the current responder chain and call 13 `fireAction`. If the `target` property is also set, the `action` will only be attempted on that 14 target object. If not set, then the responder chain will be searched for an object that implements 15 the named action. 16 17 *Note* Because searching the responder chain is slower, it is recommended to specify an actual 18 target whenever possible. 19 20 ### Implementing Actions in a Target 21 22 The method signature for target + action implementors is, 23 24 methodName: function (sender, context) { 25 return; // Optional: return a value to the sender. 26 } 27 28 For views implementing `SC.ActionSupport`, the `sender` will always be `this`, which can be useful 29 when an action may be called by multiple views and the target needs to know from which view it was 30 triggered. For example, here is an action that will hide the sender's (any sender's) pane, 31 32 // Hides the pane of the current sender. 33 hidePane: function (sender) { 34 var pane = sender.get('pane'); 35 pane.set('isVisible', false); 36 } 37 38 In order to pass additional information to the target, the target's action method may accept a 39 second argument, `context`. This argument will be the value of the same-named `context` argument 40 passed to `fireAction()` of the view. Here is a simple example to help illustrate this, 41 42 // Target 43 var theTargetObject = SC.Object.create({ 44 45 theActionMethod: function (sender, context) { 46 console.log("theActionMethod was called at: %@".fmt(context.calledAt)) 47 } 48 49 }); 50 51 // View with SC.ActionSupport 52 var view = SC.View.create(SC.ActionSupport, { 53 action: 'theActionMethod', 54 target: theTargetObject, 55 56 someEvent: function () { 57 var addedContext = { 58 calledAt: new Date() // Calling specific information to pass to the target. 59 }; 60 61 this.fireAction(addedContext); 62 } 63 64 }); 65 66 @since SproutCore 1.7 67 */ 68 SC.ActionSupport = 69 /** @scope SC.ActionSupport.prototype */ { 70 71 //@if(debug) 72 // Provide some debug-only developer warning support. 73 initMixin: function () { 74 if (this.actionContext !== null) { 75 SC.warn("Developer Warning: The `actionContext` property of `SC.ActionSupport` has been deprecated. Please pass the `context` argument to `fireAction()` directly."); 76 } 77 }, 78 //@endif 79 80 /** 81 The name of the method to call when `fireAction` is called. 82 83 This property is used in conjunction with the `target` property to execute a method when 84 `fireAction` is called. If you do not specify a target, then the responder chain will be 85 searched for a view that implements the named action. If you do set a target, then the button 86 will only try to call the method on that target. 87 88 The action method of the target should implement the following signature: 89 90 methodName: function (sender, context) { 91 return; // Optional: return a value to the sender. 92 } 93 94 ### Supporting multiple actions 95 96 The most correct way to handle variable properties in SproutCore is to use a computed property. 97 For example, imagine if the action depended on a property, `isReady`. While we could set 98 `action` accordingly each time prior to calling `fireAction()` like so, 99 100 mouseUp: function () { 101 var isReady = this.get('isReady'); 102 103 if (isReady) { 104 this.set('action', 'doReadyAction'); 105 } else { 106 this.set('action', 'doNotReadyAction'); 107 } 108 109 this.fireAction(); 110 } 111 112 This is a bit wasteful (imagine `isReady` doesn't change very often) and leaves `action` in 113 an improper state (i.e. what if `isReady` changes without a call to mouseUp, then `action` is 114 incorrect for any code that may reference it). 115 116 The better approach is to make `action` a computed property dependent on `isReady`. 117 118 For example, the previous example would be better as, 119 120 action: function () { 121 return this.get('isReady') ? 'doReadyAction' : 'doNotReadyAction'; 122 }.property('isReady'), // .cacheable() - optional to cache the result (consider memory used to cache result vs. computation time to compute result) 123 124 mouseUp: function () { 125 this.fireAction(); // Fires with current value of `action`. 126 } 127 128 @type String 129 @default null 130 */ 131 action: null, 132 133 /** @deprecated Version 1.11.0. Please specify `context` argument when calling fireAction method. 134 @type Object 135 @default null 136 */ 137 actionContext: null, 138 139 /** 140 The target to invoke the action on when `fireAction` is called. 141 142 If you set this target, the action will be called on the target object directly when the button 143 is clicked. If you leave this property set to `null`, then the responder chain will be 144 searched for a view that implements the action when the button is pressed. 145 146 The action method of the target should implement the following signature: 147 148 methodName: function (sender, context) { 149 return; // Optional: return a value to the sender. 150 } 151 152 ### Supporting multiple targets 153 154 The most correct way to handle variable properties in SproutCore is to use a computed property. 155 For example, imagine if the target depended on a property, `isReady`. While we could set 156 `target` accordingly each time prior to calling `fireAction()` like so, 157 158 mouseUp: function () { 159 var isReady = this.get('isReady'); 160 161 if (isReady) { 162 this.set('target', MyApp.readyTarget); 163 } else { 164 this.set('target', MyApp.notReadyTarget); 165 } 166 167 this.fireAction(); 168 } 169 170 This is a bit wasteful (imagine `isReady` doesn't change very often) and leaves `target` in 171 an improper state (i.e. what if `isReady` changes without a call to mouseUp, then `target` is 172 incorrect for any code that may reference it). 173 174 The better approach is to make `target` a computed property dependent on `isReady`. 175 176 For example, the previous example would be better as, 177 178 target: function () { 179 return this.get('isReady') ? MyApp.readyTarget : MyApp.notReadyTarget; 180 }.property('isReady'), // .cacheable() - optional to cache the result (consider memory used to cache result vs. computation time to compute result) 181 182 mouseUp: function () { 183 this.fireAction(); // Fires with current value of `target`. 184 } 185 186 @type Object 187 @default null 188 */ 189 target: null, 190 191 /** 192 Perform the current action on the current target with the given context. If no target is set, 193 then the responder chain will be searched for an object that implements the action. 194 195 You can pass the `context` parameter, which is useful in order to provide additional 196 information to the target, such as the current state of the sender when the action was 197 triggered. 198 199 @param {Object} [context] additional context information to pass to the target 200 @returns {Boolean} true if successful; false otherwise 201 */ 202 // TODO: remove backwards compatibility for `action` argument 203 fireAction: function (actionOrContext) { 204 var pane = this.get('pane'), 205 rootResponder = pane.get('rootResponder'), 206 action = this.get('action'), 207 context; 208 209 // Normalize arguments. 210 // TODO: Fully deprecate action argument and actionContext property. 211 212 // No argument, use action (above) and actionContext properties. 213 if (actionOrContext === undefined) { 214 context = this.get('actionContext'); 215 216 // String argument and no action (above) property, assume action method name. Use argument with actionContext property. 217 } else if (typeof actionOrContext === SC.T_STRING && action == null) { 218 //@if(debug) 219 // Provide some debug-only developer warning support. 220 SC.warn("Developer Warning: The signature of `SC.ActionSupport.prototype.fireAction` has changed. Please set the `action` property on your view and only pass an optional context argument to `fireAction`."); 221 //@endif 222 action = actionOrContext; 223 context = this.get('actionContext'); 224 225 // Something else, use action property (above) and context argument. 226 } else { 227 context = actionOrContext; 228 } 229 230 if (action && rootResponder) { 231 return rootResponder.sendAction(action, this.get('target'), this, pane, context, this); 232 } 233 234 return false; 235 } 236 237 }; 238