/** * Class that can manage the execution of route handlers. All {@link #befores} handlers * will be executed prior to the {@link #actions} handlers. If at any point this `Action` * class is stopped, no other handler (before or action) will be executed. */Ext.define('Ext.route.Action', { config: { /** * @cfg {Function[]} actions * The action handlers to execute in response to the route executing. * The individual functions will be executed with the scope of the class * that connected the route and the arguments will be the configured URL * parameters in order they appear in the token. * * See {@link #befores} also. */ actions: null, /** * @cfg {Function[]} befores * The before handlers to execute prior to the {@link #actions} handlers. * The individual functions will be executed with the scope of the class * that connected the route and the arguments will be the configured URL * parameters in the order they appear in the token plus this `Action` instance * as the last argument. * * **IMPORTANT** A before function must have a resolution. You can do this * by executing the {@link #resume} or {@link #stop} function or you can * return a promise and resolve/reject it. * * var action = new Ext.route.Action({ * before: { * fn: function (action) { * action.resume(); //or action.stop(); * } * } * }); * action.run(); * * var action = new Ext.route.Action({ * before: { * fn: function () { * return new Ext.Promise(function (resolve, reject) { * resolve(); //or reject(); * }); * } * } * }); * action.run(); * * See {@link #actions} also. */ befores: null, /** * @cfg {Array} urlParams * The URL parameters that were matched by the {@link Ext.route.Route}. */ urlParams: [] }, /** * @property {Ext.Deferred} deferred * The deferral object that will resolve after all functions have executed * ({@link #befores} and {@link #actions}) or reject if any {@link #befores} * function stops this action. * @private */ /** * @property {Boolean} [started=false] * Whether or not this class has started executing any {@link #befores} or {@link #actions}. * @readonly * @protected */ started: false, /** * @property {Boolean} [stopped=false] * Whether or not this class was stopped by a {@link #befores} function. * @readonly * @protected */ stopped: false, constructor: function(config) { var me = this; me.deferred = new Ext.Deferred(); me.resume = me.resume.bind(me); me.stop = me.stop.bind(me); me.initConfig(config); me.callParent([config]); }, applyActions: function(actions) { if (actions) { actions = Ext.Array.from(actions); } return actions; }, applyBefores: function(befores) { if (befores) { befores = Ext.Array.from(befores); } return befores; }, destroy: function() { this.deferred = null; this .setBefores(null) .setActions(null) .setUrlParams(null); this.callParent(); }, /** * Allow further function execution of other functions if any. * * @return {Ext.route.Action} this */ resume: function() { return this.next(); }, /** * Prevent other functions from executing and resolve the {@link #deferred}. * * @return {Ext.route.Action} this */ stop: function() { this.stopped = true; return this.done(); }, /** * Executes the next {@link #befores} or {@link #actions} function. If {@link #stopped} * is `true` or no functions are left to execute, the {@link #done} function will be called. * * @private * @return {Ext.route.Action} this */ next: function() { var me = this, actions = me.getActions(), befores = me.getBefores(), urlParams = me.getUrlParams(), config, ret, args; if (Ext.isArray(urlParams)) { args = urlParams.slice(); } else { args = [urlParams]; } if ( me.stopped || (befores ? !befores.length : true) && (actions ? !actions.length : true) ) { me.done(); } else { if (befores && befores.length) { config = befores.shift(); args.push(me); ret = Ext.callback(config.fn, config.scope, args); if (ret && ret.then) { ret.then(function(arg) { me.resume(arg); }, function(arg) { me.stop(arg); }); } } else if (actions && actions.length) { config = actions.shift(); Ext.callback(config.fn, config.scope, args); me.next(); } else { // needed? me.next(); } } return me; }, /** * Starts the execution of {@link #befores} and/or {@link #actions} functions. * * @return {Ext.promise.Promise} */ run: function() { var deferred = this.deferred; if (!this.started) { this.next(); this.started = true; } return deferred.promise; }, /** * When no {@link #befores} or {@link #actions} functions are left to execute * or {@link #stopped} is `true`, this function will be executed to resolve * or reject the {@link #deferred} object. * * @private * @return {Ext.route.Action} this */ done: function() { var deferred = this.deferred; if (this.stopped) { deferred.reject(); } else { deferred.resolve(); } this.destroy(); return this; }, /** * Add a function to the {@link #befores} stack. * * action.before(function() {}, this); * * By default, the function will be added to the end of the {@link #befores} stack. If * instead the function should be placed at the beginning of the stack, you can pass * `true` as the first argument: * * action.before(true, function() {}, this); * * @param {Boolean} [first=false] Pass `true` to add the function to the beginning of the * {@link #befores} stack instead of the end. * @param {Function/String} fn The function to add to the {@link #befores}. * @param {Object} [scope] The scope of the function to execute with. This is normally * the class that is adding the function to the before stack. * @return {Ext.route.Action} this */ before: function(first, fn, scope) { if (!Ext.isBoolean(first)) { scope = fn; fn = first; first = false; } // eslint-disable-next-line vars-on-top var befores = this.getBefores(), config = { fn: fn, scope: scope }; //<debug> if (this.destroyed) { Ext.raise('This action has has already resolved and therefore will never ' + 'execute this function.'); return; } //</debug> if (befores) { if (first) { befores.unshift(config); } else { befores.push(config); } } else { this.setBefores(config); } return this; }, /** * Add a function to the {@link #actions} stack. * * action.action(function() {}, this); * * By default, the function will be added to the end of the {@link #actions} stack. If * instead the function should be placed at the beginning of the stack, you can pass * `true` as the first argument: * * action.action(true, function() {}, this); * * @param {Boolean} [first=false] Pass `true` to add the function to the beginning of the * {@link #befores} stack. * @param {Function/String} fn The function to add to the {@link #actions}. * @param {Object} [scope] The scope of the function to execute with. This is normally * the class that is adding the function to the action stack. * @return {Ext.route.Action} this */ action: function(first, fn, scope) { if (!Ext.isBoolean(first)) { scope = fn; fn = first; first = false; } // eslint-disable-next-line vars-on-top var actions = this.getActions(), config = { fn: fn, scope: scope }; //<debug> if (this.destroyed) { Ext.raise('This action has has already resolved and therefore will never ' + 'execute this function.'); return; } //</debug> if (actions) { if (first) { actions.unshift(config); } else { actions.push(config); } } else { this.setActions(config); } return this; }, /** * Execute functions when this action has been resolved or rejected. * * @param {Function} resolve The function to execute when this action has been resolved. * @param {Function} reject The function to execute when a before function stopped this action. * @return {Ext.Promise} */ then: function(resolve, reject) { //<debug> if (this.destroyed) { Ext.raise('This action has has already resolved and therefore will never ' + 'execute either function.'); return; } //</debug> return this.deferred.then(resolve, reject); }});