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