/** * @class Ext.Function * * 関数コールバックを処理するのに便利な静的メソッドのコレクション。 * @singleton */ Ext.Function = (function() { // @define Ext.lang.Function // @define Ext.Function // @require Ext // @require Ext.lang.Array var lastTime = 0, animFrameId, animFrameHandlers = [], animFrameNoArgs = [], idSource = 0, animFrameMap = {}, win = window, requestAnimFrame = win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.oRequestAnimationFrame || function(callback) { var currTime = Ext.now(), timeToCall = Math.max(0, 16 - (currTime - lastTime)), id = win.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }, fireHandlers = function() { var len = animFrameHandlers.length, id, i, handler; animFrameId = null; // Fire all animation frame handlers in one go for (i = 0; i < len; i++) { handler = animFrameHandlers[i]; id = handler[3]; // Check if this timer has been canceled; its map entry is going to be removed if (animFrameMap[id]) { handler[0].apply(handler[1] || Ext.global, handler[2] || animFrameNoArgs); delete animFrameMap[id]; } } // Clear all fired animation frame handlers, don't forget that new handlers // could have been created in user handler functions called in the loop above animFrameHandlers = animFrameHandlers.slice(len); }, fireElevatedHandlers = function() { Ext.elevateFunction(fireHandlers); }, ExtFunction = { /** * フレームワーク全体で非常に一般的に使用されているメソッドです。元々の`name`と`value`の2つの引数を受け入れる、もう一つのメソッドのラッパーとして機能します。ラップされた関数は、以下のような「柔軟な」値の設定を許可します: * * - `name`および`value`の2つの引数。 * - 単一のオブジェクト引数と複数のキーと値のペア。 * * 例えば、 * * var setValue = Ext.Function.flexSetter(function(name, value) { * this[name] = value; * }); * * // Afterwards * // Setting a single name - value * setValue('name1', 'value1'); * * // Settings multiple name - value pairs * setValue({ * name1: 'value1', * name2: 'value2', * name3: 'value3' * }); * * @param {Function} setter 単一値のセッターメソッド。 * @param {String} setter.name 設定する値の名前。 * @param {Object} setter.value 設定する値。 * @return {Function} */ flexSetter: function(setter) { return function(name, value) { var k, i; if (name !== null) { if (typeof name !== 'string') { for (k in name) { if (name.hasOwnProperty(k)) { setter.call(this, k, name[k]); } } if (Ext.enumerables) { for (i = Ext.enumerables.length; i--;) { k = Ext.enumerables[i]; if (name.hasOwnProperty(k)) { setter.call(this, k, name[k]); } } } } else { setter.call(this, name, value); } } return this; }; }, /** * `fn`に設定する新しい関数を作成し、`this`を設定したスコープに変更します。デフォルトは呼び出しにより渡された引数。 * * {@link Ext#bind Ext.bind}は{@link Ext.Function#bind Ext.Function.bind}のエイリアスです。 * * **注意:**このメソッドは推奨されていません。代わりにJavaScriptの`Function`の標準的な`bind`メソッドを使用します。 * * function foo () { * ... * } * * var fn = foo.bind(this); * * IE8およびIE/Quirksではネイティブによるこのメソッドの提供はしていませんが、Ext JSでは「polyfill」を提供して標準的な`bind`メソッドの重要な機能をエミュレートしています。具体的には、polyfillでは「this」とオプションの引数のバインドのみを提供します。 * * @param {Function} fn デリゲートする関数。 * @param {Object} scope (optional) 関数実行時のスコープ(`this`参照)。**指定されない場合、デフォルト値はグローバルでデフォルトとされたオブジェクトです(通常はブラウザのウィンドウ)。** * @param {Array} args (optional) 呼び出し時にオーバーライドする引数。(デフォルトは呼び出しにより渡された引数) * @param {Boolean/Number} appendArgs (optional) trueの場合、argsは、オーバーライドする代わりに、呼び出し時の引数に追加されます。 数値の場合、指定された位置に挿入されます。 * @return {Function} 新しい関数。 */ bind: function(fn, scope, args, appendArgs) { if (arguments.length === 2) { return function() { return fn.apply(scope, arguments); }; } var method = fn, slice = Array.prototype.slice; return function() { var callArgs = args || arguments; if (appendArgs === true) { callArgs = slice.call(arguments, 0); callArgs = callArgs.concat(args); } else if (typeof appendArgs == 'number') { callArgs = slice.call(arguments, 0); // copy arguments first Ext.Array.insert(callArgs, appendArgs, args); } return method.apply(scope || Ext.global, callArgs); }; }, /** * 後で`Ext.callback`を呼び出すために指定されたパラメータをキャプチャします。このバインド処理は、スコープ(たとえば`Ext.app.ViewController`)の解決に最も役に立ちます。 * * `Ext.callback`に一致する引数。ただし、返された関数の最終的な呼び出し元によって提供される引数の先頭に追加される`args`(このメソッドに提供されている場合)を除きます。 * * @return {Function} 呼び出されたときにキャプチャした`callback`を`Ext.callback`を使用して呼び出す関数。 * @since 5.0.0 */ bindCallback: function (callback, scope, args, delay, caller) { return function () { var a = Ext.Array.slice(arguments); return Ext.callback(callback, scope, args ? args.concat(a) : a, delay, caller); }; }, /** * 渡された`fn`から新しい関数を生成します。引数は`args`に予め設定されたものになります。新しくコールバックへ渡される新しい引数は予め設定されていた引数と一緒に読み込まれます。これは特にコールバックを作成するときに有効です。 * * 例えば、 * * var originalFunction = function(){ * alert(Ext.Array.from(arguments).join(' ')); * }; * * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']); * * callback(); // alerts 'Hello World' * callback('by Me'); // alerts 'Hello World by Me' * * {@link Ext#pass Ext.pass}は{@link Ext.Function#pass Ext.Function.pass}のエイリアス。 * * @param {Function} fn 元の関数。 * @param {Array} args 新しいコールバックへ渡される引数 * @param {Object} scope (optional) 関数実行時のスコープ(`this`参照)。 * @return {Function} 新しいコールバック関数 */ pass: function(fn, args, scope) { if (!Ext.isArray(args)) { if (Ext.isIterable(args)) { args = Ext.Array.clone(args); } else { args = args !== undefined ? [args] : []; } } return function() { var fnArgs = args.slice(); fnArgs.push.apply(fnArgs, arguments); return fn.apply(scope || this, fnArgs); }; }, /** * 渡された`object`の`methodName`で提供されているメソッドプロパティのエイリアスを作成します。実行時のスコープは、渡された`object`になることに注意してください。 * * @param {Object/Function} object * @param {String} methodName * @return {Function} aliasFn */ alias: function(object, methodName) { return function() { return object[methodName].apply(object, arguments); }; }, /** * 渡されたメソッドの「クローン」を生成します。返されるメソッドは、指定されたメソッドに全引数と"this"ポインタを渡して呼び出し、その結果を返すメソッドです。 * * @param {Function} method * @return {Function} cloneFn */ clone: function(method) { return function() { return method.apply(this, arguments); }; }, /** * インターセプター関数を生成します。元の関数が実行される前に渡された関数が実行されます。falseを返した場合、元の関数は呼び出されません。関数の結果は元の関数の結果が返されます。渡された関数には元の関数と同じ引数で呼び出されます。使用例: * * var sayHi = function(name){ * alert('Hi, ' + name); * }; * * sayHi('Fred'); // alerts "Hi, Fred" * * // create a new function that validates input without * // directly modifying the original function: * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){ * return name === 'Brian'; * }); * * sayHiToFriend('Fred'); // no alert * sayHiToFriend('Brian'); // alerts "Hi, Brian" * * @param {Function} origFn 元の関数。 * @param {Function} newFn 元の関数の前に呼び出される関数。 * @param {Object} [scope] 渡された関数が実行されるスコープ(`this`参照)。**省略した場合のデフォルトは、元の関数が呼び出されたスコープ、または、ブラウザウィンドウになります。** * @param {Object} [returnValue=null] 渡された関数が`false`を返した場合に返す値。 * @return {Function} 新しい関数。 */ createInterceptor: function(origFn, newFn, scope, returnValue) { if (!Ext.isFunction(newFn)) { return origFn; } else { returnValue = Ext.isDefined(returnValue) ? returnValue : null; return function() { var me = this, args = arguments; newFn.target = me; newFn.method = origFn; return (newFn.apply(scope || me || Ext.global, args) !== false) ? origFn.apply(me || Ext.global, args) : returnValue; }; } }, /** * 呼び出されたときに指定時間後に遅れて実行されるデリゲート(コールバック)を作成します。 * * @param {Function} fn 返された関数が呼び出されたときに、遅延の後で呼び出される関数。オプションで、交換(または追加)する引数リストを指定することもできます。 * @param {Number} delay 呼び出し時に関数実行を遅延するミリ秒。 * @param {Object} scope (optional) 関数実行時に使用されるスコープ(`this`参照)。 * @param {Array} args (optional) 呼び出し時にオーバーライドする引数。(デフォルトは呼び出しにより渡された引数) * @param {Boolean/Number} appendArgs (optional) trueの場合、argsは、オーバーライドする代わりに、呼び出し時の引数に追加されます。 数値の場合、指定された位置に挿入されます。 * @return {Function} 呼び出されると、指定された遅延時間後に元の関数を実行する関数。 */ createDelayed: function(fn, delay, scope, args, appendArgs) { if (scope || args) { fn = Ext.Function.bind(fn, scope, args, appendArgs); } return function() { var me = this, args = Array.prototype.slice.call(arguments); setTimeout(function() { if (Ext.elevateFunction) { Ext.elevateFunction(fn, me, args); } else { fn.apply(me, args); } }, delay); }; }, /** * 指定されたミリ秒後に関数を呼び出します。オプションでスコープを指定することもできます。使用例: * * var sayHi = function(name){ * alert('Hi, ' + name); * } * * // executes immediately: * sayHi('Fred'); * * // executes after 2 seconds: * Ext.Function.defer(sayHi, 2000, this, ['Fred']); * * // this syntax is sometimes useful for deferring * // execution of an anonymous function: * Ext.Function.defer(function(){ * alert('Anonymous'); * }, 100); * * {@link Ext#defer Ext.defer}は{@link Ext.Function#defer Ext.Function.defer}のエイリアスです。 * * @param {Function} fn 遅延する関数。 * @param {Number} millis `setTimeout`呼び出しのためのミリ秒数(0以下の場合は、関数は直ちに実行されます)。 * @param {Object} scope (optional) 関数実行時のスコープ(`this`参照)。**省略時のデフォルトは、ブラウザのウィンドウです。** * @param {Array} [args] 呼び出し時にオーバーライドする引数。デフォルトは呼び出し元に渡された引数。 * @param {Boolean/Number} [appendArgs=false] `true`の場合、argsはオーバーライドされず、呼び出し引数に追加されます。数値であれば、argsは指定された位置に挿入されます。 * @return {Number} `clearTimeout`に利用できるタイムアウトのid。 */ defer: function(fn, millis, scope, args, appendArgs) { fn = Ext.Function.bind(fn, scope, args, appendArgs); if (millis > 0) { return setTimeout(function () { if (Ext.elevateFunction) { Ext.elevateFunction(fn); } else { fn(); } }, millis); } fn(); return 0; }, /** * 指定された間隔で繰り返しこの関数を呼び出します。オプションでスコープを指定することもできます。 * * {@link Ext#defer Ext.defer}は{@link Ext.Function#defer Ext.Function.defer}のエイリアスです。 * * @param {Function} fn 遅延する関数。 * @param {Number} millis `setInterval`呼び出し用のミリ秒数。 * @param {Object} scope (optional) 関数実行時のスコープ(`this`参照)。**省略時のデフォルトは、ブラウザのウィンドウです。** * @param {Array} [args] 呼び出し時にオーバーライドする引数。デフォルトは呼び出し元に渡された引数。 * @param {Boolean/Number} [appendArgs=false] `true`の場合、argsはオーバーライドされず、呼び出し引数に追加されます。数値であれば、argsは指定された位置に挿入されます。 * @return {Number} `clearInterval`に利用できる間隔のid。 */ interval: function(fn, millis, scope, args, appendArgs) { fn = Ext.Function.bind(fn, scope, args, appendArgs); return setInterval(function () { if (Ext.elevateFunction) { Ext.elevateFunction(fn); } else { fn(); } }, millis); }, /** * 元の関数と渡された関数を組み合わせた関数呼び出しのシーケンスを生成します。関数の結果は元の関数の結果が返されます。渡された関数には元の関数と同じ引数で呼び出されます。使用例: * * var sayHi = function(name){ * alert('Hi, ' + name); * }; * * sayHi('Fred'); // alerts "Hi, Fred" * * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){ * alert('Bye, ' + name); * }); * * sayGoodbye('Fred'); // both alerts show * * @param {Function} originalFn 元の関数。 * @param {Function} newFn 連続で呼び出される関数。 * @param {Object} [scope] 渡された関数が実行されるスコープ(`this`参照)。省略した場合のデフォルトは、元の関数が呼び出されたスコープ、または、デフォルトのグローバル環境オブジェクト(通常はブラウザウィンドウ)になります。 * @return {Function} 新しい関数。 */ createSequence: function(originalFn, newFn, scope) { if (!newFn) { return originalFn; } else { return function() { var result = originalFn.apply(this, arguments); newFn.apply(scope || this, arguments); return result; }; } }, /** * 呼び出されると、渡された関数を指定されたミリ数だけ遅延して実行するようなデリゲート関数を生成します。オプションで束縛されるスコープを指定することもできます。指定された期間内に再度呼び出された場合には、呼び出しはキャンセルされ、タイムアウト期間がゼロから再開されます。 * * @param {Function} fn バッファリングされたタイマーによって呼び出される関数。 * @param {Number} buffer 関数の呼び出し遅らせるミリ秒。 * @param {Object} [scope] 渡された関数が実行されるスコープ(`this`参照)。省略した場合には、呼び出し元の指定したスコープになります。 * @param {Array} [args] 呼び出し時にオーバーライドする引数。デフォルトは呼び出し元に渡された引数。 * @return {Function} 渡された関数を指定時間後に呼び出す関数。 */ createBuffered: function(fn, buffer, scope, args) { var timerId; return function() { var callArgs = args || Array.prototype.slice.call(arguments, 0), me = scope || this; if (timerId) { clearTimeout(timerId); } timerId = setTimeout(function(){ if (Ext.elevateFunction) { Ext.elevateFunction(fn, me, callArgs); } else { fn.apply(me, callArgs); } }, buffer); }; }, /** * 呼び出されたときに実行を次のアニメーションフレームまで引き伸ばすラッピングされた関数を作成します。 * @private * @param {Function} fn 呼び出す関数。 * @param {Object} [scope] 関数実行時のスコープ(`this`参照)。デフォルト値はウィンドウオブジェクトです。 * @param {Array} [args] 関数へ渡される引数のリスト。 * @param {Number} [queueStrategy=3] 同一アニメーションフレームにおいて、返された関数に対する複数の呼び出しをどのように処理するかを示すビットフラグ。 * * - 1:すべての呼び出しがFIFOで待ち行列に入れられます * - 2:最初の呼び出しのみが待ち行列に入れられます * - 3:最後の呼び出しでそれまでの呼び出しが置換されます * * @return {Function} */ createAnimationFrame: function(fn, scope, args, queueStrategy) { var timerId; queueStrategy = queueStrategy || 3; return function() { var callArgs = args || Array.prototype.slice.call(arguments, 0); scope = scope || this; if (queueStrategy === 3 && timerId) { ExtFunction.cancelAnimationFrame(timerId); } if ((queueStrategy & 1) || !timerId) { timerId = ExtFunction.requestAnimationFrame(function() { timerId = null; fn.apply(scope, callArgs); }); } }; }, /** * @private * 渡された関数を次のアニメーションフレームで呼び出すようにスケジュールします。 * @param {Function} fn 呼び出す関数。 * @param {Object} [scope] 関数実行時のスコープ(`this`参照)。デフォルト値はウィンドウオブジェクトです。 * @param {Mixed[]} [args] 関数へ渡される引数のリスト。 * * @return {Number} キャンセル時に新しいアニメーションフレームに対して使用するタイマーID。 */ requestAnimationFrame: function(fn, scope, args) { var id = ++idSource, // Ids start at 1 handler = Array.prototype.slice.call(arguments, 0); handler[3] = id; animFrameMap[id] = 1; // A flag to indicate that the timer exists // We might be in fireHandlers at this moment but this new entry will not // be executed until the next frame animFrameHandlers.push(handler); if (!animFrameId) { animFrameId = requestAnimFrame(Ext.elevateFunction ? fireElevatedHandlers : fireHandlers); } return id; }, cancelAnimationFrame: function(id) { // Don't remove any handlers from animFrameHandlers array, because // the might be in use at the moment (when cancelAnimationFrame is called). // Just remove the handler id from the map so it will not be executed delete animFrameMap[id]; }, /** * 前の実行から特定間隔経過後に、急速に何度も呼ばれるときに渡された関数を1度だけ実行する、渡された関数の抑えられたバージョンを作成します。 * * これは処理コストが高いときにマウスのmoveイベントのハンドラのような繰り返し呼ばれることが想定される関数をラップする場合に役立ちます。 * * @param {Function} fn この関数は一定の時間間隔で実行されます。 * @param {Number} interval 渡された関数が実行される間隔(ミリ秒単位)。 * @param {Object} [scope] 渡された関数が実行されるスコープ(`this`参照)。省略した場合には、呼び出し元の指定したスコープになります。 * @returns {Function} 指定された間隔で渡された関数を呼び出す関数。 */ createThrottled: function(fn, interval, scope) { var lastCallTime = 0, elapsed, lastArgs, timer, execute = function() { if (Ext.elevateFunction) { Ext.elevateFunction(fn, scope, lastArgs); } else { fn.apply(scope, lastArgs); } lastCallTime = Ext.now(); timer = null; }; return function() { // Use scope of last call unless the creator specified a scope if (!scope) { scope = this; } elapsed = Ext.now() - lastCallTime; lastArgs = arguments; // If this is the first invocation, or the throttle interval has been reached, clear any // pending invocation, and call the target function now. if (elapsed >= interval) { clearTimeout(timer); execute(); } // Throttle interval has not yet been reached. Only set the timer to fire if not already set. else if (!timer) { timer = Ext.defer(execute, interval - elapsed); } }; }, /** * 呼び出し回数が渡された数値を超過した後に、渡された関数を呼び出すバリア関数に渡された関数をラッピングします。 * @param {Number} count 渡された関数の呼び出しを引き起こす、呼び出し回数。 * @param {Function} fn 必要な数の起動が完了した後に呼び出す関数。 * @param {Object} scope 関数が呼び出されるスコープ(`this`参照)。 */ createBarrier: function(count, fn, scope) { return function() { if (!--count) { fn.apply(scope, arguments); } }; }, /** * 既存のメソッドに、関数の元の動作の前に実行するビヘイビアを追加します。例えば、 * * var soup = { * contents: [], * add: function(ingredient) { * this.contents.push(ingredient); * } * }; * Ext.Function.interceptBefore(soup, "add", function(ingredient){ * if (!this.contents.length && ingredient !== "water") { * // Always add water to start with * this.contents.push("water"); * } * }); * soup.add("onions"); * soup.add("salt"); * soup.contents; // will contain: water, onions, salt * * @param {Object} object ターゲットオブジェクト。 * @param {String} methodName オーバライドするメソッド名 * @param {Function} fn 新しいビヘイビアを持つ関数。これは元のメソッドと同じ引数で呼び出されます。この関数の戻り値は、新しいメソッドの戻り値となります。 * @param {Object} [scope] インターセプター関数を実行するスコープ。デフォルトはobjectです。 * @return {Function} 作成したばかりの新しい関数。 */ interceptBefore: function(object, methodName, fn, scope) { var method = object[methodName] || Ext.emptyFn; return (object[methodName] = function() { var ret = fn.apply(scope || this, arguments); method.apply(this, arguments); return ret; }); }, /** * 既存のメソッドに、関数の元の動作の後に実行するビヘイビアを追加します。例えば、 * * var soup = { * contents: [], * add: function(ingredient) { * this.contents.push(ingredient); * } * }; * Ext.Function.interceptAfter(soup, "add", function(ingredient){ * // Always add a bit of extra salt * this.contents.push("salt"); * }); * soup.add("water"); * soup.add("onions"); * soup.contents; // will contain: water, salt, onions, salt * * @param {Object} object ターゲットオブジェクト。 * @param {String} methodName オーバライドするメソッド名 * @param {Function} fn 新しいビヘイビアを持つ関数。これは元のメソッドと同じ引数で呼び出されます。この関数の戻り値は、新しいメソッドの戻り値となります。 * @param {Object} [scope] インターセプター関数を実行するスコープ。デフォルトはobjectです。 * @return {Function} 作成したばかりの新しい関数。 */ interceptAfter: function(object, methodName, fn, scope) { var method = object[methodName] || Ext.emptyFn; return (object[methodName] = function() { method.apply(this, arguments); return fn.apply(scope || this, arguments); }); }, makeCallback: function (callback, scope) { //<debug> if (!scope[callback]) { if (scope.$className) { Ext.Error.raise('No method "' + callback + '" on ' + scope.$className); } Ext.Error.raise('No method "' + callback + '"'); } //</debug> return function () { return scope[callback].apply(scope, arguments); }; } }; /** * @method * @member Ext * @inheritdoc Ext.Function#defer */ Ext.defer = ExtFunction.defer; /** * @method * @member Ext * @inheritdoc Ext.Function#interval */ Ext.interval = ExtFunction.interval; /** * @method * @member Ext * @inheritdoc Ext.Function#pass */ Ext.pass = ExtFunction.pass; /** * @method * @member Ext * @inheritdoc Ext.Function#bind */ Ext.bind = ExtFunction.bind; Ext.deferCallback = ExtFunction.requestAnimationFrame; return ExtFunction; })();