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 // Extensions to the core SC.Object class
  9 SC.mixin(SC.Object.prototype, /** @scope SC.Object.prototype */ {
 10 
 11   /**
 12     Invokes the named method after the specified period of time.  This
 13     uses SC.Timer, which works properly with the Run Loop.
 14 
 15     Any additional arguments given to invokeOnce will be passed to the
 16     method.
 17 
 18     For example,
 19 
 20         var obj = SC.Object.create({
 21           myMethod: function(a, b, c) {
 22             alert('a: %@, b: %@, c: %@'.fmt(a, b, c));
 23           }
 24         });
 25 
 26         obj.invokeLater('myMethod', 200, 'x', 'y', 'z');
 27 
 28         // After 200 ms, alerts "a: x, b: y, c: z"
 29 
 30     @param method {String} method name to perform.
 31     @param interval {Number} period from current time to schedule.
 32     @returns {SC.Timer} scheduled timer.
 33   */
 34   invokeLater: function(method, interval) {
 35     var f, args;
 36 
 37     // Normalize the method and interval.
 38     if (SC.typeOf(method) === SC.T_STRING) { method = this[method]; }
 39     if (interval === undefined) { interval = 1 ; }
 40 
 41     // If extra arguments were passed - build a function binding.
 42     if (arguments.length > 2) {
 43       args = SC.$A(arguments).slice(2);
 44       f = function() { return method.apply(this, args); } ;
 45     } else {
 46       f = method;
 47     }
 48 
 49     // schedule the timer
 50     return SC.Timer.schedule({ target: this, action: f, interval: interval });
 51   },
 52 
 53   /**
 54     A convenience method which makes it easy to coalesce invocations to ensure
 55     that the method is only called once after the given period of time. This is
 56     useful if you need to schedule a call from multiple places, but only want
 57     it to run at most once.
 58 
 59     Any additional arguments given to invokeOnceLater will be passed to the
 60     method.
 61 
 62     For example,
 63 
 64         var obj = SC.Object.create({
 65           myMethod: function(a, b, c) {
 66             alert('a: %@, b: %@, c: %@'.fmt(a, b, c));
 67           }
 68         });
 69 
 70         obj.invokeOnceLater('myMethod', 200, 'x', 'y', 'z');
 71 
 72         // After 200 ms, alerts "a: x, b: y, c: z"
 73 
 74     @param {Function|String} method reference or method name
 75     @param {Number} interval
 76   */
 77   invokeOnceLater: function(method, interval) {
 78     var args, f,
 79         methodGuid,
 80         timers = this._sc_invokeOnceLaterTimers,
 81         existingTimer, newTimer;
 82 
 83     // Normalize the method, interval and timers cache.
 84     if (SC.typeOf(method) === SC.T_STRING) { method = this[method]; }
 85     if (interval === undefined) { interval = 1 ; }
 86     if (!timers) { timers = this._sc_invokeOnceLaterTimers = {}; }
 87 
 88     // If there's a timer outstanding for this method, invalidate it in favor of
 89     // the new timer.
 90     methodGuid = SC.guidFor(method);
 91     existingTimer = timers[methodGuid];
 92     if (existingTimer) existingTimer.invalidate();
 93 
 94     // If extra arguments were passed - apply them to the method.
 95     if (arguments.length > 2) {
 96       args = SC.$A(arguments).slice(2);
 97     } else {
 98       args = arguments;
 99     }
100 
101     // Create a function binding every time, so that the timers cache is properly cleaned out.
102     f = function() {
103       // GC assistance for IE
104       delete timers[methodGuid];
105       return method.apply(this, args);
106     };
107 
108     // Schedule the new timer and track it.
109     newTimer = SC.Timer.schedule({ target: this, action: f, interval: interval });
110     timers[methodGuid] = newTimer;
111 
112     return newTimer;
113   },
114 
115   /**
116     Lookup the named property path and then invoke the passed function,
117     passing the resulting value to the function.
118 
119     This method is a useful way to handle deferred loading of properties.
120     If you want to defer loading a property, you can override this method.
121     When the method is called, passing a deferred property, you can load the
122     property before invoking the callback method.
123 
124     You can even swap out the receiver object.
125 
126     The callback method should have the signature:
127 
128     function callback(objectAtPath, sourceObject) { ... }
129 
130     You may pass either a function itself or a target/method pair.
131 
132     @param {String} pathName
133     @param {Object} target target or method
134     @param {Function|String} method
135     @returns {SC.Object} receiver
136   */
137   invokeWith: function(pathName, target, method) {
138     // normalize target/method
139     if (method === undefined) {
140       method = target; target = this;
141     }
142     if (!target) { target = this ; }
143     if (SC.typeOf(method) === SC.T_STRING) { method = target[method]; }
144 
145     // get value
146     var v = this.getPath(pathName);
147 
148     // invoke method
149     method.call(target, v, this);
150     return this ;
151   }
152 
153 });
154