Comparing version 2.0.0-beta.4 to 3.0.0
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.UnresolvedHandlerInfoByObject = exports.UnresolvedHandlerInfoByParam = exports.ResolvedHandlerInfo = exports.DEFAULT_HANDLER = exports.noopGetHandler = undefined; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _rsvp = require('rsvp'); | ||
var _transition = require('./transition'); | ||
var _utils = require('./utils'); | ||
var _rsvp = require('rsvp'); | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var DEFAULT_HANDLER = Object.freeze({}); | ||
var stubHandler = { | ||
_handlerName: '', | ||
context: undefined, | ||
handler: '', | ||
names: [] | ||
}; | ||
var noopGetHandler = exports.noopGetHandler = function noopGetHandler() { | ||
return _rsvp.Promise.resolve(stubHandler); | ||
}; | ||
var DEFAULT_HANDLER = exports.DEFAULT_HANDLER = Object.freeze({ | ||
_handlerName: '', | ||
context: undefined, | ||
handler: '', | ||
names: [] | ||
}); | ||
var HandlerInfo = function () { | ||
function HandlerInfo() { | ||
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
function HandlerInfo(name, handler) { | ||
_classCallCheck(this, HandlerInfo); | ||
_classCallCheck(this, HandlerInfo); | ||
// initialize local properties to ensure consistent object shape | ||
this._handler = DEFAULT_HANDLER; | ||
this._handlerPromise = null; | ||
this.factory = null; // Injected by the handler info factory | ||
this.name = props.name; | ||
for (var prop in props) { | ||
if (prop === 'handler') { | ||
this._processHandler(props.handler); | ||
} else { | ||
this[prop] = props[prop]; | ||
} | ||
this.params = {}; | ||
this.isResolved = false; | ||
// initialize local properties to ensure consistent object shape | ||
this._handler = DEFAULT_HANDLER; | ||
this._handlerPromise = undefined; | ||
this.name = name; | ||
if (handler) { | ||
this._processHandler(handler); | ||
} | ||
} | ||
} | ||
_createClass(HandlerInfo, [{ | ||
key: 'getHandler', | ||
value: function getHandler() {} | ||
}, { | ||
key: 'fetchHandler', | ||
value: function fetchHandler() { | ||
var handler = this.getHandler(this.name); | ||
return this._processHandler(handler); | ||
} | ||
}, { | ||
key: '_processHandler', | ||
value: function _processHandler(handler) { | ||
var _this = this; | ||
_createClass(HandlerInfo, [{ | ||
key: 'serialize', | ||
value: function serialize(_context) { | ||
return this.params || {}; | ||
} | ||
}, { | ||
key: 'resolve', | ||
value: function resolve(shouldContinue, transition) { | ||
var _this = this; | ||
// Setup a handlerPromise so that we can wait for asynchronously loaded handlers | ||
this.handlerPromise = _rsvp.Promise.resolve(handler); | ||
return _rsvp.Promise.resolve(this.handlerPromise, this.promiseLabel('Start handler')).then(function (handler) { | ||
return _this.checkForAbort(shouldContinue, handler); | ||
}, null, this.promiseLabel('Check for abort')).then(function () { | ||
return _this.runBeforeModelHook(transition); | ||
}, null, this.promiseLabel('Before model')).then(function () { | ||
return _this.checkForAbort(shouldContinue, null); | ||
}, null, this.promiseLabel("Check if aborted during 'beforeModel' hook")).then(function () { | ||
return _this.getModel(transition); | ||
}).then(function (resolvedModel) { | ||
return _this.checkForAbort(shouldContinue, resolvedModel); | ||
}, null, this.promiseLabel("Check if aborted in 'model' hook")).then(function (resolvedModel) { | ||
return _this.runAfterModelHook(transition, resolvedModel); | ||
}).then(function (resolvedModel) { | ||
return _this.becomeResolved(transition, resolvedModel); | ||
}); | ||
} | ||
}, { | ||
key: 'becomeResolved', | ||
value: function becomeResolved(transition, resolvedContext) { | ||
var params = this.serialize(resolvedContext); | ||
if (transition) { | ||
this.stashResolvedModel(transition, resolvedContext); | ||
transition.params = transition.params || {}; | ||
transition.params[this.name] = params; | ||
} | ||
var context = void 0; | ||
var contextsMatch = resolvedContext === this.context; | ||
if ('context' in this || !contextsMatch) { | ||
context = resolvedContext; | ||
} | ||
return new ResolvedHandlerInfo(this.name, this.handler, params, context); | ||
} | ||
}, { | ||
key: 'shouldSupercede', | ||
value: function shouldSupercede(other) { | ||
// Prefer this newer handlerInfo over `other` if: | ||
// 1) The other one doesn't exist | ||
// 2) The names don't match | ||
// 3) This handler has a context that doesn't match | ||
// the other one (or the other one doesn't have one). | ||
// 4) This handler has parameters that don't match the other. | ||
if (!other) { | ||
return true; | ||
} | ||
var contextsMatch = other.context === this.context; | ||
return other.name !== this.name || 'context' in this && !contextsMatch || this.hasOwnProperty('params') && !paramsMatch(this.params, other.params); | ||
} | ||
}, { | ||
key: 'promiseLabel', | ||
value: function promiseLabel(label) { | ||
return (0, _utils.promiseLabel)("'" + this.name + "' " + label); | ||
} | ||
}, { | ||
key: 'log', | ||
value: function log(transition, message) { | ||
if (transition.log) { | ||
transition.log(this.name + ': ' + message); | ||
} | ||
} | ||
}, { | ||
key: 'updateHandler', | ||
value: function updateHandler(handler) { | ||
// Store the name of the handler on the handler for easy checks later | ||
handler._handlerName = this.name; | ||
return this.handler = handler; | ||
} | ||
}, { | ||
key: 'runBeforeModelHook', | ||
value: function runBeforeModelHook(transition) { | ||
if (transition.trigger) { | ||
transition.trigger(true, 'willResolveModel', transition, this.handler); | ||
} | ||
var result = void 0; | ||
if (this.handler) { | ||
if (this.handler._beforeModel !== undefined) { | ||
result = this.handler._beforeModel(transition); | ||
} else if (this.handler.beforeModel !== undefined) { | ||
result = this.handler.beforeModel(transition); | ||
} | ||
} | ||
if ((0, _transition.isTransition)(result)) { | ||
result = null; | ||
} | ||
return _rsvp.Promise.resolve(result); | ||
} | ||
}, { | ||
key: 'runAfterModelHook', | ||
value: function runAfterModelHook(transition, resolvedModel) { | ||
// Stash the resolved model on the payload. | ||
// This makes it possible for users to swap out | ||
// the resolved model in afterModel. | ||
var name = this.name; | ||
this.stashResolvedModel(transition, resolvedModel); | ||
var result = void 0; | ||
if (this.handler !== undefined) { | ||
if (this.handler._afterModel !== undefined) { | ||
result = this.handler._afterModel(resolvedModel, transition); | ||
} else if (this.handler.afterModel !== undefined) { | ||
result = this.handler.afterModel(resolvedModel, transition); | ||
} | ||
} | ||
result = (0, _transition.prepareResult)(result); | ||
return _rsvp.Promise.resolve(result).then(function () { | ||
// Ignore the fulfilled value returned from afterModel. | ||
// Return the value stashed in resolvedModels, which | ||
// might have been swapped out in afterModel. | ||
return transition.resolvedModels[name]; | ||
}); | ||
} | ||
}, { | ||
key: 'checkForAbort', | ||
value: function checkForAbort(shouldContinue, value) { | ||
return _rsvp.Promise.resolve(shouldContinue(), this.promiseLabel('Check for abort')).then(function () { | ||
// We don't care about shouldContinue's resolve value; | ||
// pass along the original value passed to this fn. | ||
return value; | ||
}, null, this.promiseLabel('Ignore fulfillment value and continue')); | ||
} | ||
}, { | ||
key: 'stashResolvedModel', | ||
value: function stashResolvedModel(transition, resolvedModel) { | ||
transition.resolvedModels = transition.resolvedModels || {}; | ||
transition.resolvedModels[this.name] = resolvedModel; | ||
} | ||
}, { | ||
key: 'fetchHandler', | ||
value: function fetchHandler() { | ||
var handler = this.getHandler(this.name); | ||
return this._processHandler(handler); | ||
} | ||
}, { | ||
key: '_processHandler', | ||
value: function _processHandler(handler) { | ||
var _this2 = this; | ||
// Wait until the 'handler' property has been updated when chaining to a handler | ||
// that is a promise | ||
if ((0, _utils.isPromise)(handler)) { | ||
this.handlerPromise = this.handlerPromise.then(function (h) { | ||
return _this.updateHandler(h); | ||
}); | ||
// set to undefined to avoid recursive loop in the handler getter | ||
return this.handler = undefined; | ||
} else if (handler) { | ||
return this.updateHandler(handler); | ||
} | ||
} | ||
}, { | ||
key: 'log', | ||
value: function log(payload, message) { | ||
if (payload.log) { | ||
payload.log(this.name + ': ' + message); | ||
} | ||
} | ||
}, { | ||
key: 'promiseLabel', | ||
value: function promiseLabel(label) { | ||
return (0, _utils.promiseLabel)("'" + this.name + "' " + label); | ||
} | ||
}, { | ||
key: 'getUnresolved', | ||
value: function getUnresolved() { | ||
return this; | ||
} | ||
}, { | ||
key: 'serialize', | ||
value: function serialize() { | ||
return this.params || {}; | ||
} | ||
}, { | ||
key: 'updateHandler', | ||
value: function updateHandler(handler) { | ||
// Store the name of the handler on the handler for easy checks later | ||
handler._handlerName = this.name; | ||
return this.handler = handler; | ||
} | ||
}, { | ||
key: 'resolve', | ||
value: function resolve(shouldContinue, payload) { | ||
var checkForAbort = this.checkForAbort.bind(this, shouldContinue); | ||
var beforeModel = this.runBeforeModelHook.bind(this, payload); | ||
var model = this.getModel.bind(this, payload); | ||
var afterModel = this.runAfterModelHook.bind(this, payload); | ||
var becomeResolved = this.becomeResolved.bind(this, payload); | ||
// Setup a handlerPromise so that we can wait for asynchronously loaded handlers | ||
this.handlerPromise = _rsvp.Promise.resolve(handler); | ||
// Wait until the 'handler' property has been updated when chaining to a handler | ||
// that is a promise | ||
if ((0, _utils.isPromise)(handler)) { | ||
this.handlerPromise = this.handlerPromise.then(function (h) { | ||
return _this2.updateHandler(h); | ||
}); | ||
// set to undefined to avoid recursive loop in the handler getter | ||
return this.handler = undefined; | ||
} else if (handler) { | ||
return this.updateHandler(handler); | ||
} | ||
return undefined; | ||
} | ||
}, { | ||
key: 'handler', | ||
get: function get() { | ||
// _handler could be set to either a handler object or undefined, so we | ||
// compare against a default reference to know when it's been set | ||
if (this._handler !== DEFAULT_HANDLER) { | ||
return this._handler; | ||
} | ||
return this.fetchHandler(); | ||
}, | ||
set: function set(handler) { | ||
this._handler = handler; | ||
} | ||
}, { | ||
key: 'handlerPromise', | ||
get: function get() { | ||
if (this._handlerPromise) { | ||
return this._handlerPromise; | ||
} | ||
this.fetchHandler(); | ||
return this._handlerPromise; | ||
}, | ||
set: function set(handlerPromise) { | ||
this._handlerPromise = handlerPromise; | ||
} | ||
}]); | ||
return _rsvp.Promise.resolve(this.handlerPromise, this.promiseLabel('Start handler')).then(checkForAbort, null, this.promiseLabel('Check for abort')).then(beforeModel, null, this.promiseLabel('Before model')).then(checkForAbort, null, this.promiseLabel("Check if aborted during 'beforeModel' hook")).then(model, null, this.promiseLabel('Model')).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'model' hook")).then(afterModel, null, this.promiseLabel('After model')).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'afterModel' hook")).then(becomeResolved, null, this.promiseLabel('Become resolved')); | ||
} | ||
}, { | ||
key: 'runBeforeModelHook', | ||
value: function runBeforeModelHook(payload) { | ||
if (payload.trigger) { | ||
payload.trigger(true, 'willResolveModel', payload, this.handler); | ||
} | ||
return this.runSharedModelHook(payload, 'beforeModel', []); | ||
} | ||
}, { | ||
key: 'runAfterModelHook', | ||
value: function runAfterModelHook(payload, resolvedModel) { | ||
// Stash the resolved model on the payload. | ||
// This makes it possible for users to swap out | ||
// the resolved model in afterModel. | ||
var name = this.name; | ||
this.stashResolvedModel(payload, resolvedModel); | ||
return HandlerInfo; | ||
}(); | ||
return this.runSharedModelHook(payload, 'afterModel', [resolvedModel]).then(function () { | ||
// Ignore the fulfilled value returned from afterModel. | ||
// Return the value stashed in resolvedModels, which | ||
// might have been swapped out in afterModel. | ||
return payload.resolvedModels[name]; | ||
}, null, this.promiseLabel('Ignore fulfillment value and return model value')); | ||
} | ||
}, { | ||
key: 'runSharedModelHook', | ||
value: function runSharedModelHook(payload, hookName, args) { | ||
this.log(payload, 'calling ' + hookName + ' hook'); | ||
exports.default = HandlerInfo; | ||
if (this.queryParams) { | ||
args.push(this.queryParams); | ||
} | ||
args.push(payload); | ||
var ResolvedHandlerInfo = exports.ResolvedHandlerInfo = function (_HandlerInfo) { | ||
_inherits(ResolvedHandlerInfo, _HandlerInfo); | ||
var result = (0, _utils.applyHook)(this.handler, hookName, args); | ||
function ResolvedHandlerInfo(name, handler, params, context) { | ||
_classCallCheck(this, ResolvedHandlerInfo); | ||
if (result && result.isTransition) { | ||
result = null; | ||
} | ||
var _this3 = _possibleConstructorReturn(this, (ResolvedHandlerInfo.__proto__ || Object.getPrototypeOf(ResolvedHandlerInfo)).call(this, name, handler)); | ||
return _rsvp.Promise.resolve(result, this.promiseLabel('Resolve value returned from one of the model hooks')); | ||
_this3.getHandler = function (_name) { | ||
throw new Error('Method not implemented.'); | ||
}; | ||
_this3.params = params; | ||
_this3.isResolved = true; | ||
_this3.context = context; | ||
return _this3; | ||
} | ||
// overridden by subclasses | ||
_createClass(ResolvedHandlerInfo, [{ | ||
key: 'resolve', | ||
value: function resolve(_shouldContinue, transition) { | ||
// A ResolvedHandlerInfo just resolved with itself. | ||
if (transition && transition.resolvedModels) { | ||
transition.resolvedModels[this.name] = this.context; | ||
} | ||
return _rsvp.Promise.resolve(this, this.promiseLabel('Resolve')); | ||
} | ||
}, { | ||
key: 'getUnresolved', | ||
value: function getUnresolved() { | ||
return new UnresolvedHandlerInfoByParam(this.name, noopGetHandler, this.params, this.handler); | ||
} | ||
}, { | ||
key: 'getModel', | ||
value: function getModel() { | ||
throw new Error('Method not implemented.'); | ||
} | ||
}]); | ||
}, { | ||
key: 'getModel', | ||
value: function getModel() {} | ||
}, { | ||
key: 'checkForAbort', | ||
value: function checkForAbort(shouldContinue, promiseValue) { | ||
return _rsvp.Promise.resolve(shouldContinue(), this.promiseLabel('Check for abort')).then(function () { | ||
// We don't care about shouldContinue's resolve value; | ||
// pass along the original value passed to this fn. | ||
return promiseValue; | ||
}, null, this.promiseLabel('Ignore fulfillment value and continue')); | ||
} | ||
}, { | ||
key: 'stashResolvedModel', | ||
value: function stashResolvedModel(payload, resolvedModel) { | ||
payload.resolvedModels = payload.resolvedModels || {}; | ||
payload.resolvedModels[this.name] = resolvedModel; | ||
} | ||
}, { | ||
key: 'becomeResolved', | ||
value: function becomeResolved(payload, resolvedContext) { | ||
var params = this.serialize(resolvedContext); | ||
return ResolvedHandlerInfo; | ||
}(HandlerInfo); | ||
if (payload) { | ||
this.stashResolvedModel(payload, resolvedContext); | ||
payload.params = payload.params || {}; | ||
payload.params[this.name] = params; | ||
} | ||
var UnresolvedHandlerInfoByParam = exports.UnresolvedHandlerInfoByParam = function (_HandlerInfo2) { | ||
_inherits(UnresolvedHandlerInfoByParam, _HandlerInfo2); | ||
var resolution = { | ||
name: this.name, | ||
handler: this.handler, | ||
params: params | ||
}; | ||
function UnresolvedHandlerInfoByParam(name, getHandler, params, handler) { | ||
_classCallCheck(this, UnresolvedHandlerInfoByParam); | ||
// Don't set a context on the resolution unless we actually have one. | ||
var contextsMatch = resolvedContext === this.context; | ||
if ('context' in this || !contextsMatch) { | ||
resolution.context = resolvedContext; | ||
} | ||
var _this4 = _possibleConstructorReturn(this, (UnresolvedHandlerInfoByParam.__proto__ || Object.getPrototypeOf(UnresolvedHandlerInfoByParam)).call(this, name, handler)); | ||
return this.factory('resolved', resolution); | ||
_this4.params = {}; | ||
_this4.params = params; | ||
_this4.getHandler = getHandler; | ||
return _this4; | ||
} | ||
}, { | ||
key: 'shouldSupercede', | ||
value: function shouldSupercede(other) { | ||
// Prefer this newer handlerInfo over `other` if: | ||
// 1) The other one doesn't exist | ||
// 2) The names don't match | ||
// 3) This handler has a context that doesn't match | ||
// the other one (or the other one doesn't have one). | ||
// 4) This handler has parameters that don't match the other. | ||
if (!other) { | ||
return true; | ||
} | ||
var contextsMatch = other.context === this.context; | ||
return other.name !== this.name || 'context' in this && !contextsMatch || this.hasOwnProperty('params') && !paramsMatch(this.params, other.params); | ||
} | ||
}, { | ||
key: 'handler', | ||
get: function get() { | ||
// _handler could be set to either a handler object or undefined, so we | ||
// compare against a default reference to know when it's been set | ||
if (this._handler !== DEFAULT_HANDLER) { | ||
return this._handler; | ||
} | ||
_createClass(UnresolvedHandlerInfoByParam, [{ | ||
key: 'getUnresolved', | ||
value: function getUnresolved() { | ||
return this; | ||
} | ||
}, { | ||
key: 'getModel', | ||
value: function getModel(transition) { | ||
var fullParams = this.params; | ||
if (transition && transition.queryParams) { | ||
fullParams = {}; | ||
(0, _utils.merge)(fullParams, this.params); | ||
fullParams.queryParams = transition.queryParams; | ||
} | ||
var handler = this.handler; | ||
var result = undefined; | ||
if (handler._deserialize) { | ||
result = handler._deserialize(fullParams, transition); | ||
} else if (handler.deserialize) { | ||
result = handler.deserialize(fullParams, transition); | ||
} else if (handler._model) { | ||
result = handler._model(fullParams, transition); | ||
} else if (handler.model) { | ||
result = handler.model(fullParams, transition); | ||
} | ||
if (result && (0, _transition.isTransition)(result)) { | ||
result = undefined; | ||
} | ||
return _rsvp.Promise.resolve(result, this.promiseLabel('Resolve value returned from one of the model hooks')); | ||
} | ||
}]); | ||
return this.fetchHandler(); | ||
}, | ||
set: function set(handler) { | ||
return this._handler = handler; | ||
} | ||
}, { | ||
key: 'handlerPromise', | ||
get: function get() { | ||
if (this._handlerPromise !== null) { | ||
return this._handlerPromise; | ||
} | ||
return UnresolvedHandlerInfoByParam; | ||
}(HandlerInfo); | ||
this.fetchHandler(); | ||
var UnresolvedHandlerInfoByObject = exports.UnresolvedHandlerInfoByObject = function (_HandlerInfo3) { | ||
_inherits(UnresolvedHandlerInfoByObject, _HandlerInfo3); | ||
return this._handlerPromise; | ||
}, | ||
set: function set(handlerPromise) { | ||
this._handlerPromise = handlerPromise; | ||
function UnresolvedHandlerInfoByObject(name, names, getHandler, serializer, context) { | ||
_classCallCheck(this, UnresolvedHandlerInfoByObject); | ||
return handlerPromise; | ||
var _this5 = _possibleConstructorReturn(this, (UnresolvedHandlerInfoByObject.__proto__ || Object.getPrototypeOf(UnresolvedHandlerInfoByObject)).call(this, name)); | ||
_this5.names = []; | ||
_this5.names = names; | ||
_this5.getHandler = getHandler; | ||
_this5.serializer = serializer; | ||
_this5.context = context; | ||
_this5.names = _this5.names || []; | ||
return _this5; | ||
} | ||
}]); | ||
return HandlerInfo; | ||
}(); | ||
_createClass(UnresolvedHandlerInfoByObject, [{ | ||
key: 'getModel', | ||
value: function getModel(transition) { | ||
this.log(transition, this.name + ': resolving provided model'); | ||
return _rsvp.Promise.resolve(this.context); | ||
} | ||
}, { | ||
key: 'getUnresolved', | ||
value: function getUnresolved() { | ||
return this; | ||
} | ||
/** | ||
@private | ||
Serializes a handler using its custom `serialize` method or | ||
by a default that looks up the expected property name from | ||
the dynamic segment. | ||
@param {Object} model the model to be serialized for this handler | ||
*/ | ||
exports.default = HandlerInfo; | ||
}, { | ||
key: 'serialize', | ||
value: function serialize(model) { | ||
var names = this.names, | ||
context = this.context; | ||
if (!model) { | ||
model = context; | ||
} | ||
var object = {}; | ||
if ((0, _utils.isParam)(model)) { | ||
object[names[0]] = model; | ||
return object; | ||
} | ||
// Use custom serialize if it exists. | ||
if (this.serializer) { | ||
// invoke this.serializer unbound (getSerializer returns a stateless function) | ||
return this.serializer.call(null, model, names); | ||
} else if (this.handler) { | ||
if (this.handler._serialize) { | ||
return this.handler._serialize(model, names); | ||
} | ||
if (this.handler.serialize) { | ||
return this.handler.serialize(model, names); | ||
} | ||
} | ||
if (names.length !== 1) { | ||
return; | ||
} | ||
var name = names[0]; | ||
if (/_id$/.test(name)) { | ||
object[name] = model.id; | ||
} else { | ||
object[name] = model; | ||
} | ||
return object; | ||
} | ||
}]); | ||
return UnresolvedHandlerInfoByObject; | ||
}(HandlerInfo); | ||
function paramsMatch(a, b) { | ||
if (!a ^ !b) { | ||
// Only one is null. | ||
return false; | ||
} | ||
if (!a) { | ||
// Both must be null. | ||
if (!a !== !b) { | ||
// Only one is null. | ||
return false; | ||
} | ||
if (!a) { | ||
// Both must be null. | ||
return true; | ||
} | ||
// Note: this assumes that both params have the same | ||
// number of keys, but since we're comparing the | ||
// same handlers, they should. | ||
for (var k in a) { | ||
if (a.hasOwnProperty(k) && a[k] !== b[k]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
// Note: this assumes that both params have the same | ||
// number of keys, but since we're comparing the | ||
// same handlers, they should. | ||
for (var k in a) { | ||
if (a.hasOwnProperty(k) && a[k] !== b[k]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
} | ||
//# sourceMappingURL=handler-info.js.map |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -15,8 +15,2 @@ | ||
var _utils = require('./utils'); | ||
var _transitionState = require('./transition-state'); | ||
var _transitionState2 = _interopRequireDefault(_transitionState); | ||
var _transition = require('./transition'); | ||
@@ -36,2 +30,8 @@ | ||
var _transitionState = require('./transition-state'); | ||
var _transitionState2 = _interopRequireDefault(_transitionState); | ||
var _utils = require('./utils'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -41,425 +41,359 @@ | ||
var pop = Array.prototype.pop; | ||
// Todo: | ||
// this should just be an abstract class | ||
var Router = function () { | ||
function Router() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
function Router(options) { | ||
var _this = this; | ||
_classCallCheck(this, Router); | ||
_classCallCheck(this, Router); | ||
this.getHandler = options.getHandler || this.getHandler; | ||
this.getSerializer = options.getSerializer || this.getSerializer; | ||
this.updateURL = options.updateURL || this.updateURL; | ||
this.replaceURL = options.replaceURL || this.replaceURL; | ||
this.didTransition = options.didTransition || this.didTransition; | ||
this.willTransition = options.willTransition || this.willTransition; | ||
this.delegate = options.delegate || this.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.dslCallBacks = []; // NOTE: set by Ember | ||
this.state = undefined; | ||
this.activeTransition = undefined; | ||
this._changedQueryParams = undefined; | ||
this.oldState = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this.currentSequence = 0; | ||
this.recognizer = new _routeRecognizer2.default(); | ||
this.reset(); | ||
} | ||
/** | ||
The main entry point into the router. The API is essentially | ||
the same as the `map` method in `route-recognizer`. | ||
This method extracts the String handler at the last `.to()` | ||
call and uses it as the name of the whole route. | ||
@param {Function} callback | ||
*/ | ||
_createClass(Router, [{ | ||
key: 'map', | ||
value: function map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
this.recognizer.map(callback, function (recognizer, routes) { | ||
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { | ||
var route = routes[i]; | ||
recognizer.add(routes, { as: route.handler }); | ||
proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index'; | ||
} | ||
}); | ||
this.state = undefined; | ||
this.oldState = undefined; | ||
this.activeTransition = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this._changedQueryParams = undefined; | ||
this.currentSequence = 0; | ||
this.dslCallBacks = []; // TODO: set by ember. Please refactor. | ||
this.getHandler = options.getHandler; | ||
this.getSerializer = options.getSerializer; | ||
this.updateURL = options.updateURL; | ||
this.replaceURL = options.replaceURL || function (url) { | ||
_this.updateURL(url); | ||
}; | ||
this.didTransition = options.didTransition; | ||
this.willTransition = options.willTransition; | ||
this.delegate = options.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.recognizer = new _routeRecognizer2.default(); | ||
this.reset(); | ||
} | ||
}, { | ||
key: 'hasRoute', | ||
value: function hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
} | ||
}, { | ||
key: 'getHandler', | ||
value: function getHandler() {} | ||
}, { | ||
key: 'getSerializer', | ||
value: function getSerializer() {} | ||
}, { | ||
key: 'queryParamsTransition', | ||
value: function queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
var router = this; | ||
fireQueryParamDidChange(this, newState, changelist); | ||
if (!wasTransitioning && this.activeTransition) { | ||
// One of the handlers in queryParamsDidChange | ||
// caused a transition. Just return that transition. | ||
return this.activeTransition; | ||
} else { | ||
// Running queryParamsDidChange didn't change anything. | ||
// Just update query params and be on our way. | ||
// We have to return a noop transition that will | ||
// perform a URL update at the end. This gives | ||
// the user the ability to set the url update | ||
// method (default is replaceState). | ||
var newTransition = new _transition.Transition(this); | ||
newTransition.queryParamsOnly = true; | ||
oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition); | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
updateURL(newTransition, oldState, true); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
return result; | ||
}, null, (0, _utils.promiseLabel)('Transition complete')); | ||
return newTransition; | ||
} | ||
} | ||
// NOTE: this doesn't really belong here, but here | ||
// it shall remain until our ES6 transpiler can | ||
// handle cyclical deps. | ||
}, { | ||
key: 'transitionByIntent', | ||
value: function transitionByIntent(intent /*, isIntermediate*/) { | ||
try { | ||
return getTransitionByIntent.apply(this, arguments); | ||
} catch (e) { | ||
return new _transition.Transition(this, intent, null, e); | ||
} | ||
} | ||
/** | ||
Clears the current and target route handlers and triggers exit | ||
on each of them starting at the leaf and traversing up through | ||
its ancestors. | ||
The main entry point into the router. The API is essentially | ||
the same as the `map` method in `route-recognizer`. | ||
This method extracts the String handler at the last `.to()` | ||
call and uses it as the name of the whole route. | ||
@param {Function} callback | ||
*/ | ||
}, { | ||
key: 'reset', | ||
value: function reset() { | ||
if (this.state) { | ||
(0, _utils.forEach)(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { | ||
var handler = handlerInfo.handler; | ||
(0, _utils.callHook)(handler, 'exit'); | ||
}); | ||
} | ||
this.oldState = undefined; | ||
this.state = new _transitionState2.default(); | ||
this.currentHandlerInfos = null; | ||
} | ||
_createClass(Router, [{ | ||
key: 'map', | ||
value: function map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
this.recognizer.map(callback, function (recognizer, routes) { | ||
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { | ||
var route = routes[i]; | ||
var handler = route.handler; | ||
recognizer.add(routes, { as: handler }); | ||
proceed = route.path === '/' || route.path === '' || handler.slice(-6) === '.index'; | ||
} | ||
}); | ||
} | ||
}, { | ||
key: 'hasRoute', | ||
value: function hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
} | ||
}, { | ||
key: 'queryParamsTransition', | ||
value: function queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
var _this2 = this; | ||
/** | ||
var handler = handlerInfo.handler; | ||
The entry point for handling a change to the URL (usually | ||
via the back and forward button). | ||
Returns an Array of handlers and the parameters associated | ||
with those parameters. | ||
@param {String} url a URL to process | ||
@return {Array} an Array of `[handler, parameter]` tuples | ||
*/ | ||
fireQueryParamDidChange(this, newState, changelist); | ||
if (!wasTransitioning && this.activeTransition) { | ||
// One of the handlers in queryParamsDidChange | ||
// caused a transition. Just return that transition. | ||
return this.activeTransition; | ||
} else { | ||
// Running queryParamsDidChange didn't change anything. | ||
// Just update query params and be on our way. | ||
// We have to return a noop transition that will | ||
// perform a URL update at the end. This gives | ||
// the user the ability to set the url update | ||
// method (default is replaceState). | ||
var newTransition = new _transition.Transition(this, undefined, undefined); | ||
newTransition.queryParamsOnly = true; | ||
oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition); | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
updateURL(newTransition, oldState); | ||
if (_this2.didTransition) { | ||
_this2.didTransition(_this2.currentHandlerInfos); | ||
} | ||
return result; | ||
}, null, (0, _utils.promiseLabel)('Transition complete')); | ||
return newTransition; | ||
} | ||
} | ||
// NOTE: this doesn't really belong here, but here | ||
// it shall remain until our ES6 transpiler can | ||
// handle cyclical deps. | ||
}, { | ||
key: 'handleURL', | ||
value: function handleURL() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
}, { | ||
key: 'transitionByIntent', | ||
value: function transitionByIntent(intent, isIntermediate) { | ||
try { | ||
return getTransitionByIntent.apply(this, [intent, isIntermediate]); | ||
} catch (e) { | ||
return new _transition.Transition(this, intent, undefined, e, undefined); | ||
} | ||
} | ||
/** | ||
Clears the current and target route handlers and triggers exit | ||
on each of them starting at the leaf and traversing up through | ||
its ancestors. | ||
*/ | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
var url = args[0]; | ||
if (url.charAt(0) !== '/') { | ||
args[0] = '/' + url; | ||
} | ||
}, { | ||
key: 'reset', | ||
value: function reset() { | ||
if (this.state) { | ||
(0, _utils.forEach)(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { | ||
var handler = handlerInfo.handler; | ||
if (handler !== undefined) { | ||
if (handler._exit !== undefined) { | ||
handler._exit(); | ||
} else if (handler.exit !== undefined) { | ||
handler.exit(); | ||
} | ||
} | ||
return true; | ||
}); | ||
} | ||
this.oldState = undefined; | ||
this.state = new _transitionState2.default(); | ||
this.currentHandlerInfos = undefined; | ||
} | ||
/** | ||
let handler = handlerInfo.handler; | ||
The entry point for handling a change to the URL (usually | ||
via the back and forward button). | ||
Returns an Array of handlers and the parameters associated | ||
with those parameters. | ||
@param {String} url a URL to process | ||
@return {Array} an Array of `[handler, parameter]` tuples | ||
*/ | ||
return doTransition(this, args).method(null); | ||
} | ||
}, { | ||
key: 'handleURL', | ||
value: function handleURL(url) { | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
if (url.charAt(0) !== '/') { | ||
url = '/' + url; | ||
} | ||
return doTransition(this, url).method(null); | ||
} | ||
/** | ||
Transition into the specified named route. | ||
If necessary, trigger the exit callback on any handlers | ||
that are no longer represented by the target route. | ||
@param {String} name the name of the route | ||
*/ | ||
/** | ||
Hook point for updating the URL. | ||
@param {String} url a URL to update to | ||
*/ | ||
}, { | ||
key: 'transitionTo', | ||
value: function transitionTo(name) { | ||
for (var _len = arguments.length, contexts = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
contexts[_key - 1] = arguments[_key]; | ||
} | ||
}, { | ||
key: 'updateURL', | ||
value: function updateURL() { | ||
throw new Error('updateURL is not implemented'); | ||
} | ||
if (typeof name === 'object') { | ||
contexts.push(name); | ||
return doTransition(this, undefined, contexts, false); | ||
} | ||
return doTransition(this, name, contexts); | ||
} | ||
}, { | ||
key: 'intermediateTransitionTo', | ||
value: function intermediateTransitionTo(name) { | ||
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
args[_key2 - 1] = arguments[_key2]; | ||
} | ||
/** | ||
Hook point for replacing the current URL, i.e. with replaceState | ||
By default this behaves the same as `updateURL` | ||
@param {String} url a URL to update to | ||
*/ | ||
return doTransition(this, name, args, true); | ||
} | ||
}, { | ||
key: 'refresh', | ||
value: function refresh(pivotHandler) { | ||
var previousTransition = this.activeTransition; | ||
var state = previousTransition ? previousTransition.state : this.state; | ||
var handlerInfos = state.handlerInfos; | ||
if (pivotHandler === undefined) { | ||
pivotHandler = handlerInfos[0].handler; | ||
} | ||
(0, _utils.log)(this, 'Starting a refresh transition'); | ||
var name = handlerInfos[handlerInfos.length - 1].name; | ||
var intent = new _namedTransitionIntent2.default(name, pivotHandler, [], this._changedQueryParams || state.queryParams); | ||
var newTransition = this.transitionByIntent(intent, false); | ||
// if the previous transition is a replace transition, that needs to be preserved | ||
if (previousTransition && previousTransition.urlMethod === 'replace') { | ||
newTransition.method(previousTransition.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
/** | ||
Identical to `transitionTo` except that the current URL will be replaced | ||
if possible. | ||
This method is intended primarily for use with `replaceState`. | ||
@param {String} name the name of the route | ||
*/ | ||
}, { | ||
key: 'replaceURL', | ||
value: function replaceURL(url) { | ||
this.updateURL(url); | ||
} | ||
}, { | ||
key: 'replaceWith', | ||
value: function replaceWith(name) { | ||
return doTransition(this, name).method('replace'); | ||
} | ||
/** | ||
Take a named route and context objects and generate a | ||
URL. | ||
@param {String} name the name of the route to generate | ||
a URL for | ||
@param {...Object} objects a list of objects to serialize | ||
@return {String} a URL | ||
*/ | ||
/** | ||
Transition into the specified named route. | ||
If necessary, trigger the exit callback on any handlers | ||
that are no longer represented by the target route. | ||
@param {String} name the name of the route | ||
*/ | ||
}, { | ||
key: 'generate', | ||
value: function generate(handlerName) { | ||
for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { | ||
args[_key3 - 1] = arguments[_key3]; | ||
} | ||
}, { | ||
key: 'transitionTo', | ||
value: function transitionTo() /*name*/{ | ||
return doTransition(this, arguments); | ||
} | ||
}, { | ||
key: 'intermediateTransitionTo', | ||
value: function intermediateTransitionTo() /*name*/{ | ||
return doTransition(this, arguments, true); | ||
} | ||
}, { | ||
key: 'refresh', | ||
value: function refresh(pivotHandler) { | ||
var previousTransition = this.activeTransition; | ||
var state = previousTransition ? previousTransition.state : this.state; | ||
var handlerInfos = state.handlerInfos; | ||
var partitionedArgs = (0, _utils.extractQueryParams)(args), | ||
suppliedParams = partitionedArgs[0], | ||
queryParams = partitionedArgs[1]; | ||
// Construct a TransitionIntent with the provided params | ||
// and apply it to the present state of the router. | ||
var intent = new _namedTransitionIntent2.default(handlerName, undefined, suppliedParams); | ||
var state = intent.applyToState(this.state, this.recognizer, this.getHandler, false, this.getSerializer); | ||
var params = {}; | ||
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
var handlerParams = handlerInfo.serialize(); | ||
(0, _utils.merge)(params, handlerParams); | ||
} | ||
params.queryParams = queryParams; | ||
return this.recognizer.generate(handlerName, params); | ||
} | ||
}, { | ||
key: 'applyIntent', | ||
value: function applyIntent(handlerName, contexts) { | ||
var intent = new _namedTransitionIntent2.default(handlerName, undefined, contexts); | ||
var state = this.activeTransition && this.activeTransition.state || this.state; | ||
return intent.applyToState(state, this.recognizer, this.getHandler, false, this.getSerializer); | ||
} | ||
}, { | ||
key: 'isActiveIntent', | ||
value: function isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
var state = _state || this.state, | ||
targetHandlerInfos = state.handlerInfos, | ||
handlerInfo = void 0, | ||
len = void 0; | ||
if (!targetHandlerInfos.length) { | ||
return false; | ||
} | ||
var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
var recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
var index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
handlerInfo = targetHandlerInfos[index]; | ||
if (handlerInfo.name === handlerName) { | ||
break; | ||
} | ||
} | ||
if (index === recogHandlers.length) { | ||
// The provided route name isn't even in the route hierarchy. | ||
return false; | ||
} | ||
var testState = new _transitionState2.default(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
var intent = new _namedTransitionIntent2.default(targetHandler, undefined, contexts); | ||
var newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true, this.getSerializer); | ||
var handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos); | ||
if (!queryParams || !handlersEqual) { | ||
return handlersEqual; | ||
} | ||
// Get a hash of QPs that will still be active on new route | ||
var activeQPsOnNewHandler = {}; | ||
(0, _utils.merge)(activeQPsOnNewHandler, queryParams); | ||
var activeQueryParams = state.queryParams; | ||
for (var key in activeQueryParams) { | ||
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) { | ||
activeQPsOnNewHandler[key] = activeQueryParams[key]; | ||
} | ||
} | ||
return handlersEqual && !(0, _utils.getChangelist)(activeQPsOnNewHandler, queryParams); | ||
} | ||
}, { | ||
key: 'isActive', | ||
value: function isActive(handlerName) { | ||
for (var _len4 = arguments.length, args = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { | ||
args[_key4 - 1] = arguments[_key4]; | ||
} | ||
(0, _utils.log)(this, 'Starting a refresh transition'); | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
pivotHandler: pivotHandler || handlerInfos[0].handler, | ||
contexts: [], // TODO collect contexts...? | ||
queryParams: this._changedQueryParams || state.queryParams || {} | ||
}); | ||
var partitionedArgs = (0, _utils.extractQueryParams)(args); | ||
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); | ||
} | ||
}, { | ||
key: 'trigger', | ||
value: function trigger(name) { | ||
for (var _len5 = arguments.length, args = Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) { | ||
args[_key5 - 1] = arguments[_key5]; | ||
} | ||
var newTransition = this.transitionByIntent(intent, false); | ||
// if the previous transition is a replace transition, that needs to be preserved | ||
if (previousTransition && previousTransition.urlMethod === 'replace') { | ||
newTransition.method(previousTransition.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
/** | ||
Identical to `transitionTo` except that the current URL will be replaced | ||
if possible. | ||
This method is intended primarily for use with `replaceState`. | ||
@param {String} name the name of the route | ||
*/ | ||
}, { | ||
key: 'replaceWith', | ||
value: function replaceWith() /*name*/{ | ||
return doTransition(this, arguments).method('replace'); | ||
} | ||
/** | ||
Take a named route and context objects and generate a | ||
URL. | ||
@param {String} name the name of the route to generate | ||
a URL for | ||
@param {...Object} objects a list of objects to serialize | ||
@return {String} a URL | ||
*/ | ||
}, { | ||
key: 'generate', | ||
value: function generate(handlerName) { | ||
var partitionedArgs = (0, _utils.extractQueryParams)(_utils.slice.call(arguments, 1)), | ||
suppliedParams = partitionedArgs[0], | ||
queryParams = partitionedArgs[1]; | ||
// Construct a TransitionIntent with the provided params | ||
// and apply it to the present state of the router. | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerName, | ||
contexts: suppliedParams | ||
}); | ||
var state = intent.applyToState(this.state, this.recognizer, this.getHandler, null, this.getSerializer); | ||
var params = {}; | ||
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
var handlerParams = handlerInfo.serialize(); | ||
(0, _utils.merge)(params, handlerParams); | ||
} | ||
params.queryParams = queryParams; | ||
return this.recognizer.generate(handlerName, params); | ||
} | ||
}, { | ||
key: 'applyIntent', | ||
value: function applyIntent(handlerName, contexts) { | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerName, | ||
contexts: contexts | ||
}); | ||
var state = this.activeTransition && this.activeTransition.state || this.state; | ||
return intent.applyToState(state, this.recognizer, this.getHandler, null, this.getSerializer); | ||
} | ||
}, { | ||
key: 'isActiveIntent', | ||
value: function isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
var state = _state || this.state, | ||
targetHandlerInfos = state.handlerInfos, | ||
handlerInfo = void 0, | ||
len = void 0; | ||
if (!targetHandlerInfos.length) { | ||
return false; | ||
} | ||
var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
var recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
var index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
handlerInfo = targetHandlerInfos[index]; | ||
if (handlerInfo.name === handlerName) { | ||
break; | ||
_utils.trigger.apply(undefined, [this, this.currentHandlerInfos, false, name].concat(args)); | ||
} | ||
} | ||
}]); | ||
if (index === recogHandlers.length) { | ||
// The provided route name isn't even in the route hierarchy. | ||
return false; | ||
} | ||
return Router; | ||
}(); | ||
var testState = new _transitionState2.default(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: targetHandler, | ||
contexts: contexts | ||
}); | ||
var newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true, this.getSerializer); | ||
var handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos); | ||
if (!queryParams || !handlersEqual) { | ||
return handlersEqual; | ||
} | ||
// Get a hash of QPs that will still be active on new route | ||
var activeQPsOnNewHandler = {}; | ||
(0, _utils.merge)(activeQPsOnNewHandler, queryParams); | ||
var activeQueryParams = state.queryParams; | ||
for (var key in activeQueryParams) { | ||
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) { | ||
activeQPsOnNewHandler[key] = activeQueryParams[key]; | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition = void 0; | ||
var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate, this.getSerializer); | ||
var queryParamChangelist = (0, _utils.getChangelist)(oldState.queryParams, newState.queryParams); | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState); | ||
if (newTransition) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
} | ||
return handlersEqual && !(0, _utils.getChangelist)(activeQPsOnNewHandler, queryParams); | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new _transition.Transition(this, undefined, undefined); | ||
} | ||
}, { | ||
key: 'isActive', | ||
value: function isActive(handlerName) { | ||
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
args[_key2 - 1] = arguments[_key2]; | ||
} | ||
var partitionedArgs = (0, _utils.extractQueryParams)(args); | ||
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
}, { | ||
key: 'trigger', | ||
value: function trigger() { | ||
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { | ||
args[_key3] = arguments[_key3]; | ||
} | ||
(0, _utils.trigger)(this, this.currentHandlerInfos, false, args); | ||
} | ||
}]); | ||
return Router; | ||
}(); | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition; | ||
var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate, this.getSerializer); | ||
var queryParamChangelist = (0, _utils.getChangelist)(oldState.queryParams, newState.queryParams); | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState); | ||
if (newTransition) { | ||
// Create a new transition to the destination route. | ||
newTransition = new _transition.Transition(this, intent, newState, undefined, this.activeTransition); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if (handlerInfosSameExceptQueryParams(newState.handlerInfos, oldState.handlerInfos)) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new _transition.Transition(this); | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
// Create a new transition to the destination route. | ||
newTransition = new _transition.Transition(this, intent, newState, undefined, this.activeTransition); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if (handlerInfosSameExceptQueryParams(newState.handlerInfos, oldState.handlerInfos)) { | ||
newTransition.queryParamsOnly = true; | ||
} | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
return finalizeTransition(newTransition, result.state); | ||
}, null, (0, _utils.promiseLabel)('Settle transition promise when transition is finalized')); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
return finalizeTransition(newTransition, result); | ||
}, null, (0, _utils.promiseLabel)('Settle transition promise when transition is finalized')); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
} | ||
/** | ||
@@ -471,13 +405,12 @@ @private | ||
function fireQueryParamDidChange(router, newState, queryParamChangelist) { | ||
// If queryParams changed trigger event | ||
if (queryParamChangelist) { | ||
// This is a little hacky but we need some way of storing | ||
// changed query params given that no activeTransition | ||
// is guaranteed to have occurred. | ||
router._changedQueryParams = queryParamChangelist.all; | ||
(0, _utils.trigger)(router, newState.handlerInfos, true, ['queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed]); | ||
router._changedQueryParams = null; | ||
} | ||
// If queryParams changed trigger event | ||
if (queryParamChangelist) { | ||
// This is a little hacky but we need some way of storing | ||
// changed query params given that no activeTransition | ||
// is guaranteed to have occurred. | ||
router._changedQueryParams = queryParamChangelist.all; | ||
(0, _utils.trigger)(router, newState.handlerInfos, true, 'queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed); | ||
router._changedQueryParams = undefined; | ||
} | ||
} | ||
/** | ||
@@ -525,39 +458,49 @@ @private | ||
function setupContexts(router, newState, transition) { | ||
var partition = partitionHandlers(router.state, newState); | ||
var i, l, handler; | ||
for (i = 0, l = partition.exited.length; i < l; i++) { | ||
handler = partition.exited[i].handler; | ||
delete handler.context; | ||
(0, _utils.callHook)(handler, 'reset', true, transition); | ||
(0, _utils.callHook)(handler, 'exit', transition); | ||
} | ||
var oldState = router.oldState = router.state; | ||
router.state = newState; | ||
var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice(); | ||
try { | ||
for (i = 0, l = partition.reset.length; i < l; i++) { | ||
handler = partition.reset[i].handler; | ||
(0, _utils.callHook)(handler, 'reset', false, transition); | ||
var partition = partitionHandlers(router.state, newState); | ||
var i = void 0, | ||
l = void 0, | ||
handler = void 0; | ||
for (i = 0, l = partition.exited.length; i < l; i++) { | ||
handler = partition.exited[i].handler; | ||
delete handler.context; | ||
if (handler !== undefined) { | ||
if (handler._reset !== undefined) { | ||
handler._reset(true, transition); | ||
} else if (handler.reset !== undefined) { | ||
handler.reset(true, transition); | ||
} | ||
if (handler._exit !== undefined) { | ||
handler._exit(transition); | ||
} else if (handler.exit !== undefined) { | ||
handler.exit(transition); | ||
} | ||
} | ||
} | ||
for (i = 0, l = partition.updatedContext.length; i < l; i++) { | ||
handlerEnteredOrUpdated(currentHandlerInfos, partition.updatedContext[i], false, transition); | ||
var oldState = router.oldState = router.state; | ||
router.state = newState; | ||
var currentHandlerInfos = router.currentHandlerInfos = partition.unchanged.slice(); | ||
try { | ||
for (i = 0, l = partition.reset.length; i < l; i++) { | ||
handler = partition.reset[i].handler; | ||
if (handler !== undefined) { | ||
if (handler._reset !== undefined) { | ||
handler._reset(false, transition); | ||
} else if (handler.reset !== undefined) { | ||
handler.reset(false, transition); | ||
} | ||
} | ||
} | ||
for (i = 0, l = partition.updatedContext.length; i < l; i++) { | ||
handlerEnteredOrUpdated(currentHandlerInfos, partition.updatedContext[i], false, transition); | ||
} | ||
for (i = 0, l = partition.entered.length; i < l; i++) { | ||
handlerEnteredOrUpdated(currentHandlerInfos, partition.entered[i], true, transition); | ||
} | ||
} catch (e) { | ||
router.state = oldState; | ||
router.currentHandlerInfos = oldState.handlerInfos; | ||
throw e; | ||
} | ||
for (i = 0, l = partition.entered.length; i < l; i++) { | ||
handlerEnteredOrUpdated(currentHandlerInfos, partition.entered[i], true, transition); | ||
} | ||
} catch (e) { | ||
router.state = oldState; | ||
router.currentHandlerInfos = oldState.handlerInfos; | ||
throw e; | ||
} | ||
router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams, transition); | ||
router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams, transition); | ||
} | ||
/** | ||
@@ -570,35 +513,40 @@ @private | ||
function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) { | ||
var handler = handlerInfo.handler, | ||
context = handlerInfo.context; | ||
function _handlerEnteredOrUpdated(handler) { | ||
if (enter) { | ||
(0, _utils.callHook)(handler, 'enter', transition); | ||
var handler = handlerInfo.handler, | ||
context = handlerInfo.context; | ||
function _handlerEnteredOrUpdated(handler) { | ||
if (enter) { | ||
if (handler._enter !== undefined) { | ||
handler._enter(transition); | ||
} else if (handler.enter !== undefined) { | ||
handler.enter(transition); | ||
} | ||
} | ||
if (transition && transition.isAborted) { | ||
throw new _transitionAbortedError2.default(); | ||
} | ||
handler.context = context; | ||
if (handler._contextDidChange !== undefined) { | ||
handler._contextDidChange(); | ||
} else if (handler.contextDidChange !== undefined) { | ||
handler.contextDidChange(); | ||
} | ||
if (handler._setup !== undefined) { | ||
handler._setup(context, transition); | ||
} else if (handler.setup !== undefined) { | ||
handler.setup(context, transition); | ||
} | ||
if (transition && transition.isAborted) { | ||
throw new _transitionAbortedError2.default(); | ||
} | ||
currentHandlerInfos.push(handlerInfo); | ||
return handler; | ||
} | ||
if (transition && transition.isAborted) { | ||
throw new _transitionAbortedError2.default(); | ||
// If the handler doesn't exist, it means we haven't resolved the handler promise yet | ||
if (!handler) { | ||
handlerInfo.handlerPromise = handlerInfo.handlerPromise.then(_handlerEnteredOrUpdated); | ||
} else { | ||
_handlerEnteredOrUpdated(handler); | ||
} | ||
handler.context = context; | ||
(0, _utils.callHook)(handler, 'contextDidChange'); | ||
(0, _utils.callHook)(handler, 'setup', context, transition); | ||
if (transition && transition.isAborted) { | ||
throw new _transitionAbortedError2.default(); | ||
} | ||
currentHandlerInfos.push(handlerInfo); | ||
} | ||
// If the handler doesn't exist, it means we haven't resolved the handler promise yet | ||
if (!handler) { | ||
handlerInfo.handlerPromise = handlerInfo.handlerPromise.then(_handlerEnteredOrUpdated); | ||
} else { | ||
_handlerEnteredOrUpdated(handler); | ||
} | ||
return true; | ||
return true; | ||
} | ||
/** | ||
@@ -646,111 +594,93 @@ @private | ||
function partitionHandlers(oldState, newState) { | ||
var oldHandlers = oldState.handlerInfos; | ||
var newHandlers = newState.handlerInfos; | ||
var handlers = { | ||
updatedContext: [], | ||
exited: [], | ||
entered: [], | ||
unchanged: [], | ||
reset: undefined | ||
}; | ||
var handlerChanged, | ||
contextChanged = false, | ||
i, | ||
l; | ||
for (i = 0, l = newHandlers.length; i < l; i++) { | ||
var oldHandler = oldHandlers[i], | ||
newHandler = newHandlers[i]; | ||
if (!oldHandler || oldHandler.handler !== newHandler.handler) { | ||
handlerChanged = true; | ||
var oldHandlerInfos = oldState.handlerInfos; | ||
var newHandlerInfos = newState.handlerInfos; | ||
var handlers = { | ||
updatedContext: [], | ||
exited: [], | ||
entered: [], | ||
unchanged: [], | ||
reset: [] | ||
}; | ||
var handlerChanged = void 0, | ||
contextChanged = false, | ||
i = void 0, | ||
l = void 0; | ||
for (i = 0, l = newHandlerInfos.length; i < l; i++) { | ||
var oldHandlerInfo = oldHandlerInfos[i], | ||
newHandlerInfo = newHandlerInfos[i]; | ||
if (!oldHandlerInfo || oldHandlerInfo.handler !== newHandlerInfo.handler) { | ||
handlerChanged = true; | ||
} | ||
if (handlerChanged) { | ||
handlers.entered.push(newHandlerInfo); | ||
if (oldHandlerInfo) { | ||
handlers.exited.unshift(oldHandlerInfo); | ||
} | ||
} else if (contextChanged || oldHandlerInfo.context !== newHandlerInfo.context) { | ||
contextChanged = true; | ||
handlers.updatedContext.push(newHandlerInfo); | ||
} else { | ||
handlers.unchanged.push(oldHandlerInfo); | ||
} | ||
} | ||
if (handlerChanged) { | ||
handlers.entered.push(newHandler); | ||
if (oldHandler) { | ||
handlers.exited.unshift(oldHandler); | ||
} | ||
} else if (contextChanged || oldHandler.context !== newHandler.context) { | ||
contextChanged = true; | ||
handlers.updatedContext.push(newHandler); | ||
} else { | ||
handlers.unchanged.push(oldHandler); | ||
for (i = newHandlerInfos.length, l = oldHandlerInfos.length; i < l; i++) { | ||
handlers.exited.unshift(oldHandlerInfos[i]); | ||
} | ||
} | ||
for (i = newHandlers.length, l = oldHandlers.length; i < l; i++) { | ||
handlers.exited.unshift(oldHandlers[i]); | ||
} | ||
handlers.reset = handlers.updatedContext.slice(); | ||
handlers.reset.reverse(); | ||
return handlers; | ||
handlers.reset = handlers.updatedContext.slice(); | ||
handlers.reset.reverse(); | ||
return handlers; | ||
} | ||
function updateURL(transition, state /*, inputUrl*/) { | ||
var urlMethod = transition.urlMethod; | ||
if (!urlMethod) { | ||
return; | ||
} | ||
var router = transition.router, | ||
handlerInfos = state.handlerInfos, | ||
handlerName = handlerInfos[handlerInfos.length - 1].name, | ||
params = {}; | ||
for (var i = handlerInfos.length - 1; i >= 0; --i) { | ||
var handlerInfo = handlerInfos[i]; | ||
(0, _utils.merge)(params, handlerInfo.params); | ||
if (handlerInfo.handler.inaccessibleByURL) { | ||
urlMethod = null; | ||
function updateURL(transition, state, _inputUrl) { | ||
var urlMethod = transition.urlMethod; | ||
if (!urlMethod) { | ||
return; | ||
} | ||
} | ||
var router = transition.router; | ||
var handlerInfos = state.handlerInfos; | ||
var handlerName = handlerInfos[handlerInfos.length - 1].name; | ||
if (urlMethod) { | ||
params.queryParams = transition._visibleQueryParams || state.queryParams; | ||
var url = router.recognizer.generate(handlerName, params); | ||
// transitions during the initial transition must always use replaceURL. | ||
// When the app boots, you are at a url, e.g. /foo. If some handler | ||
// redirects to bar as part of the initial transition, you don't want to | ||
// add a history entry for /foo. If you do, pressing back will immediately | ||
// hit the redirect again and take you back to /bar, thus killing the back | ||
// button | ||
var initial = transition.isCausedByInitialTransition; | ||
// say you are at / and you click a link to route /foo. In /foo's | ||
// handler, the transition is aborted using replacewith('/bar'). | ||
// Because the current url is still /, the history entry for / is | ||
// removed from the history. Clicking back will take you to the page | ||
// you were on before /, which is often not even the app, thus killing | ||
// the back button. That's why updateURL is always correct for an | ||
// aborting transition that's not the initial transition | ||
var replaceAndNotAborting = urlMethod === 'replace' && !transition.isCausedByAbortingTransition; | ||
// because calling refresh causes an aborted transition, this needs to be | ||
// special cased - if the initial transition is a replace transition, the | ||
// urlMethod should be honored here. | ||
var isQueryParamsRefreshTransition = transition.queryParamsOnly && urlMethod === 'replace'; | ||
// say you are at / and you a `replaceWith(/foo)` is called. Then, that | ||
// transition is aborted with `replaceWith(/bar)`. At the end, we should | ||
// end up with /bar replacing /. We are replacing the replace. We only | ||
// will replace the initial route if all subsequent aborts are also | ||
// replaces. However, there is some ambiguity around the correct behavior | ||
// here. | ||
var replacingReplace = urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition; | ||
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition || replacingReplace) { | ||
router.replaceURL(url); | ||
} else { | ||
router.updateURL(url); | ||
var params = {}; | ||
for (var i = handlerInfos.length - 1; i >= 0; --i) { | ||
var handlerInfo = handlerInfos[i]; | ||
(0, _utils.merge)(params, handlerInfo.params); | ||
if (handlerInfo.handler.inaccessibleByURL) { | ||
urlMethod = null; | ||
} | ||
} | ||
} | ||
if (urlMethod) { | ||
params.queryParams = transition._visibleQueryParams || state.queryParams; | ||
var url = router.recognizer.generate(handlerName, params); | ||
// transitions during the initial transition must always use replaceURL. | ||
// When the app boots, you are at a url, e.g. /foo. If some handler | ||
// redirects to bar as part of the initial transition, you don't want to | ||
// add a history entry for /foo. If you do, pressing back will immediately | ||
// hit the redirect again and take you back to /bar, thus killing the back | ||
// button | ||
var initial = transition.isCausedByInitialTransition; | ||
// say you are at / and you click a link to route /foo. In /foo's | ||
// handler, the transition is aborted using replacewith('/bar'). | ||
// Because the current url is still /, the history entry for / is | ||
// removed from the history. Clicking back will take you to the page | ||
// you were on before /, which is often not even the app, thus killing | ||
// the back button. That's why updateURL is always correct for an | ||
// aborting transition that's not the initial transition | ||
var replaceAndNotAborting = urlMethod === 'replace' && !transition.isCausedByAbortingTransition; | ||
// because calling refresh causes an aborted transition, this needs to be | ||
// special cased - if the initial transition is a replace transition, the | ||
// urlMethod should be honored here. | ||
var isQueryParamsRefreshTransition = transition.queryParamsOnly && urlMethod === 'replace'; | ||
// say you are at / and you a `replaceWith(/foo)` is called. Then, that | ||
// transition is aborted with `replaceWith(/bar)`. At the end, we should | ||
// end up with /bar replacing /. We are replacing the replace. We only | ||
// will replace the initial route if all subsequent aborts are also | ||
// replaces. However, there is some ambiguity around the correct behavior | ||
// here. | ||
var replacingReplace = urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition; | ||
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition || replacingReplace) { | ||
router.replaceURL(url); | ||
} else { | ||
router.updateURL(url); | ||
} | ||
} | ||
} | ||
/** | ||
@@ -763,45 +693,34 @@ @private | ||
function finalizeTransition(transition, newState) { | ||
try { | ||
(0, _utils.log)(transition.router, transition.sequence, 'Resolved all models on destination route; finalizing transition.'); | ||
var router = transition.router, | ||
handlerInfos = newState.handlerInfos; | ||
// Run all the necessary enter/setup/exit hooks | ||
setupContexts(router, newState, transition); | ||
// Check if a redirect occurred in enter/setup | ||
if (transition.isAborted) { | ||
// TODO: cleaner way? distinguish b/w targetHandlerInfos? | ||
router.state.handlerInfos = router.currentHandlerInfos; | ||
return _rsvp.Promise.reject((0, _transition.logAbort)(transition)); | ||
try { | ||
(0, _utils.log)(transition.router, transition.sequence, 'Resolved all models on destination route; finalizing transition.'); | ||
var router = transition.router, | ||
handlerInfos = newState.handlerInfos; | ||
// Run all the necessary enter/setup/exit hooks | ||
setupContexts(router, newState, transition); | ||
// Check if a redirect occurred in enter/setup | ||
if (transition.isAborted) { | ||
// TODO: cleaner way? distinguish b/w targetHandlerInfos? | ||
router.state.handlerInfos = router.currentHandlerInfos; | ||
return _rsvp.Promise.reject((0, _transition.logAbort)(transition)); | ||
} | ||
updateURL(transition, newState, transition.intent.url); | ||
transition.isActive = false; | ||
router.activeTransition = undefined; | ||
(0, _utils.trigger)(router, router.currentHandlerInfos, true, 'didTransition'); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
(0, _utils.log)(router, transition.sequence, 'TRANSITION COMPLETE.'); | ||
// Resolve with the final handler. | ||
return handlerInfos[handlerInfos.length - 1].handler; | ||
} catch (e) { | ||
if (!(e instanceof _transitionAbortedError2.default)) { | ||
//let erroneousHandler = handlerInfos.pop(); | ||
var infos = transition.state.handlerInfos; | ||
transition.trigger(true, 'error', e, transition, infos[infos.length - 1].handler); | ||
transition.abort(); | ||
} | ||
throw e; | ||
} | ||
updateURL(transition, newState, transition.intent.url); | ||
transition.isActive = false; | ||
router.activeTransition = null; | ||
(0, _utils.trigger)(router, router.currentHandlerInfos, true, ['didTransition']); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
(0, _utils.log)(router, transition.sequence, 'TRANSITION COMPLETE.'); | ||
// Resolve with the final handler. | ||
return handlerInfos[handlerInfos.length - 1].handler; | ||
} catch (e) { | ||
if (!(e instanceof _transitionAbortedError2.default)) { | ||
//var erroneousHandler = handlerInfos.pop(); | ||
var infos = transition.state.handlerInfos; | ||
transition.trigger(true, 'error', e, transition, infos[infos.length - 1].handler); | ||
transition.abort(); | ||
} | ||
throw e; | ||
} | ||
} | ||
/** | ||
@@ -818,156 +737,125 @@ @private | ||
*/ | ||
function doTransition(router, args, isIntermediate) { | ||
// Normalize blank transitions to root URL transitions. | ||
var name = args[0] || '/'; | ||
function doTransition(router, name) { | ||
var modelsArray = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; | ||
var isIntermediate = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
var lastArg = args[args.length - 1]; | ||
var queryParams = {}; | ||
if (lastArg && lastArg.hasOwnProperty('queryParams')) { | ||
queryParams = pop.call(args).queryParams; | ||
} | ||
var lastArg = modelsArray[modelsArray.length - 1]; | ||
var queryParams = {}; | ||
if (lastArg !== undefined && lastArg.hasOwnProperty('queryParams')) { | ||
queryParams = modelsArray.pop().queryParams; | ||
} | ||
var intent = void 0; | ||
if (name === undefined) { | ||
(0, _utils.log)(router, 'Updating query params'); | ||
// A query param update is really just a transition | ||
// into the route you're already on. | ||
var handlerInfos = router.state.handlerInfos; | ||
var intent; | ||
if (args.length === 0) { | ||
(0, _utils.log)(router, 'Updating query params'); | ||
// A query param update is really just a transition | ||
// into the route you're already on. | ||
var handlerInfos = router.state.handlerInfos; | ||
intent = new _namedTransitionIntent2.default({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
contexts: [], | ||
queryParams: queryParams | ||
}); | ||
} else if (name.charAt(0) === '/') { | ||
(0, _utils.log)(router, 'Attempting URL transition to ' + name); | ||
intent = new _urlTransitionIntent2.default({ url: name }); | ||
} else { | ||
(0, _utils.log)(router, 'Attempting transition to ' + name); | ||
intent = new _namedTransitionIntent2.default({ | ||
name: args[0], | ||
contexts: _utils.slice.call(args, 1), | ||
queryParams: queryParams | ||
}); | ||
} | ||
return router.transitionByIntent(intent, isIntermediate); | ||
intent = new _namedTransitionIntent2.default(handlerInfos[handlerInfos.length - 1].name, undefined, [], queryParams); | ||
} else if (name.charAt(0) === '/') { | ||
(0, _utils.log)(router, 'Attempting URL transition to ' + name); | ||
intent = new _urlTransitionIntent2.default(name); | ||
} else { | ||
(0, _utils.log)(router, 'Attempting transition to ' + name); | ||
intent = new _namedTransitionIntent2.default(name, undefined, modelsArray, queryParams); | ||
} | ||
return router.transitionByIntent(intent, isIntermediate); | ||
} | ||
function handlerInfosEqual(handlerInfos, otherHandlerInfos) { | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i] !== otherHandlerInfos[i]) { | ||
return false; | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i] !== otherHandlerInfos[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function handlerInfosSameExceptQueryParams(handlerInfos, otherHandlerInfos) { | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i].name !== otherHandlerInfos[i].name) { | ||
return false; | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
if (!paramsEqual(handlerInfos[i].params, otherHandlerInfos[i].params)) { | ||
return false; | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i].name !== otherHandlerInfos[i].name) { | ||
return false; | ||
} | ||
if (!paramsEqual(handlerInfos[i].params, otherHandlerInfos[i].params)) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
return true; | ||
} | ||
function paramsEqual(params, otherParams) { | ||
if (!params && !otherParams) { | ||
if (!params && !otherParams) { | ||
return true; | ||
} else if (!params && !!otherParams || !!params && !otherParams) { | ||
// one is falsy but other is not; | ||
return false; | ||
} | ||
var keys = Object.keys(params); | ||
var otherKeys = Object.keys(otherParams); | ||
if (keys.length !== otherKeys.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = keys.length; i < len; ++i) { | ||
var key = keys[i]; | ||
if (params[key] !== otherParams[key]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} else if (!params && !!otherParams || !!params && !otherParams) { | ||
// one is falsy but other is not; | ||
return false; | ||
} | ||
var keys = Object.keys(params); | ||
var otherKeys = Object.keys(otherParams); | ||
if (keys.length !== otherKeys.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = keys.length; i < len; ++i) { | ||
var key = keys[i]; | ||
if (params[key] !== otherParams[key]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams, transition) { | ||
// We fire a finalizeQueryParamChange event which | ||
// gives the new route hierarchy a chance to tell | ||
// us which query params it's consuming and what | ||
// their final values are. If a query param is | ||
// no longer consumed in the final route hierarchy, | ||
// its serialized segment will be removed | ||
// from the URL. | ||
for (var k in newQueryParams) { | ||
if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) { | ||
delete newQueryParams[k]; | ||
// We fire a finalizeQueryParamChange event which | ||
// gives the new route hierarchy a chance to tell | ||
// us which query params it's consuming and what | ||
// their final values are. If a query param is | ||
// no longer consumed in the final route hierarchy, | ||
// its serialized segment will be removed | ||
// from the URL. | ||
for (var k in newQueryParams) { | ||
if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) { | ||
delete newQueryParams[k]; | ||
} | ||
} | ||
} | ||
var finalQueryParamsArray = []; | ||
(0, _utils.trigger)(router, resolvedHandlers, true, ['finalizeQueryParamChange', newQueryParams, finalQueryParamsArray, transition]); | ||
if (transition) { | ||
transition._visibleQueryParams = {}; | ||
} | ||
var finalQueryParams = {}; | ||
for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) { | ||
var qp = finalQueryParamsArray[i]; | ||
finalQueryParams[qp.key] = qp.value; | ||
if (transition && qp.visible !== false) { | ||
transition._visibleQueryParams[qp.key] = qp.value; | ||
var finalQueryParamsArray = []; | ||
(0, _utils.trigger)(router, resolvedHandlers, true, 'finalizeQueryParamChange', newQueryParams, finalQueryParamsArray, transition); | ||
if (transition) { | ||
transition._visibleQueryParams = {}; | ||
} | ||
} | ||
return finalQueryParams; | ||
var finalQueryParams = {}; | ||
for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) { | ||
var qp = finalQueryParamsArray[i]; | ||
finalQueryParams[qp.key] = qp.value; | ||
if (transition && qp.visible !== false) { | ||
transition._visibleQueryParams[qp.key] = qp.value; | ||
} | ||
} | ||
return finalQueryParams; | ||
} | ||
function notifyExistingHandlers(router, newState, newTransition) { | ||
var oldHandlers = router.state.handlerInfos, | ||
changing = [], | ||
i, | ||
oldHandlerLen, | ||
oldHandler, | ||
newHandler; | ||
oldHandlerLen = oldHandlers.length; | ||
for (i = 0; i < oldHandlerLen; i++) { | ||
oldHandler = oldHandlers[i]; | ||
newHandler = newState.handlerInfos[i]; | ||
if (!newHandler || oldHandler.name !== newHandler.name) { | ||
break; | ||
var oldHandlers = router.state.handlerInfos, | ||
changing = [], | ||
i = void 0, | ||
oldHandlerLen = void 0, | ||
oldHandler = void 0, | ||
newHandler = void 0; | ||
oldHandlerLen = oldHandlers.length; | ||
for (i = 0; i < oldHandlerLen; i++) { | ||
oldHandler = oldHandlers[i]; | ||
newHandler = newState.handlerInfos[i]; | ||
if (!newHandler || oldHandler.name !== newHandler.name) { | ||
break; | ||
} | ||
if (!newHandler.isResolved) { | ||
changing.push(oldHandler); | ||
} | ||
} | ||
if (!newHandler.isResolved) { | ||
changing.push(oldHandler); | ||
(0, _utils.trigger)(router, oldHandlers, true, 'willTransition', newTransition); | ||
if (router.willTransition) { | ||
router.willTransition(oldHandlers, newState.handlerInfos, newTransition); | ||
} | ||
} | ||
(0, _utils.trigger)(router, oldHandlers, true, ['willTransition', newTransition]); | ||
if (router.willTransition) { | ||
router.willTransition(oldHandlers, newState.handlerInfos, newTransition); | ||
} | ||
} | ||
exports.default = Router; | ||
exports.default = Router; | ||
//# sourceMappingURL=router.js.map |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
function TransitionAbortedError(message) { | ||
if (!(this instanceof TransitionAbortedError)) { | ||
return new TransitionAbortedError(message); | ||
} | ||
var error = Error.call(this, message); | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, TransitionAbortedError); | ||
} else { | ||
this.stack = error.stack; | ||
} | ||
this.description = error.description; | ||
this.fileName = error.fileName; | ||
this.lineNumber = error.lineNumber; | ||
this.message = error.message || 'TransitionAborted'; | ||
this.name = 'TransitionAborted'; | ||
this.number = error.number; | ||
this.code = error.code; | ||
} | ||
TransitionAbortedError.prototype = Object.create(Error.prototype); | ||
exports.default = TransitionAbortedError; | ||
var TransitionAbortedError = function () { | ||
TransitionAbortedError.prototype = Object.create(Error.prototype); | ||
TransitionAbortedError.prototype.constructor = TransitionAbortedError; | ||
function TransitionAbortedError(message) { | ||
var error = Error.call(this, message); | ||
this.name = 'TransitionAborted'; | ||
this.message = message || 'TransitionAborted'; | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, TransitionAbortedError); | ||
} else { | ||
this.stack = error.stack; | ||
} | ||
} | ||
return TransitionAbortedError; | ||
}(); | ||
exports.default = TransitionAbortedError; | ||
//# sourceMappingURL=transition-aborted-error.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -9,8 +9,7 @@ | ||
var TransitionIntent = function TransitionIntent() { | ||
_classCallCheck(this, TransitionIntent); | ||
var TransitionIntent = exports.TransitionIntent = function TransitionIntent(data) { | ||
_classCallCheck(this, TransitionIntent); | ||
this.data = this.data || {}; | ||
this.data = data || {}; | ||
}; | ||
exports.default = TransitionIntent; | ||
//# sourceMappingURL=transition-intent.js.map |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -9,6 +9,6 @@ | ||
var _handlerInfo = require('../handler-info'); | ||
var _transitionIntent = require('../transition-intent'); | ||
var _transitionIntent2 = _interopRequireDefault(_transitionIntent); | ||
var _transitionState = require('../transition-state'); | ||
@@ -18,6 +18,2 @@ | ||
var _factory = require('../handler-info/factory'); | ||
var _factory2 = _interopRequireDefault(_factory); | ||
var _utils = require('../utils'); | ||
@@ -34,193 +30,172 @@ | ||
var NamedTransitionIntent = function (_TransitionIntent) { | ||
_inherits(NamedTransitionIntent, _TransitionIntent); | ||
_inherits(NamedTransitionIntent, _TransitionIntent); | ||
function NamedTransitionIntent(props) { | ||
_classCallCheck(this, NamedTransitionIntent); | ||
function NamedTransitionIntent(name, pivotHandler) { | ||
var contexts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; | ||
var queryParams = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
var _this = _possibleConstructorReturn(this, (NamedTransitionIntent.__proto__ || Object.getPrototypeOf(NamedTransitionIntent)).call(this, props)); | ||
_classCallCheck(this, NamedTransitionIntent); | ||
_this.name = props.name; | ||
_this.pivotHandler = props.pivotHandler; | ||
_this.contexts = props.contexts || []; | ||
_this.queryParams = props.queryParams; | ||
return _this; | ||
} | ||
var _this = _possibleConstructorReturn(this, (NamedTransitionIntent.__proto__ || Object.getPrototypeOf(NamedTransitionIntent)).call(this)); | ||
_createClass(NamedTransitionIntent, [{ | ||
key: 'applyToState', | ||
value: function applyToState(oldState, recognizer, getHandler, isIntermediate, getSerializer) { | ||
var partitionedArgs = (0, _utils.extractQueryParams)([this.name].concat(this.contexts)), | ||
pureArgs = partitionedArgs[0], | ||
handlers = recognizer.handlersFor(pureArgs[0]); | ||
var targetRouteName = handlers[handlers.length - 1].handler; | ||
return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate, null, getSerializer); | ||
_this.preTransitionState = undefined; | ||
_this.name = name; | ||
_this.pivotHandler = pivotHandler; | ||
_this.contexts = contexts; | ||
_this.queryParams = queryParams; | ||
return _this; | ||
} | ||
}, { | ||
key: 'applyToHandlers', | ||
value: function applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive, getSerializer) { | ||
var i, len; | ||
var newState = new _transitionState2.default(); | ||
var objects = this.contexts.slice(0); | ||
var invalidateIndex = handlers.length; | ||
// Pivot handlers are provided for refresh transitions | ||
if (this.pivotHandler) { | ||
for (i = 0, len = handlers.length; i < len; ++i) { | ||
if (handlers[i].handler === this.pivotHandler._handlerName) { | ||
invalidateIndex = i; | ||
break; | ||
} | ||
_createClass(NamedTransitionIntent, [{ | ||
key: 'applyToState', | ||
value: function applyToState(oldState, recognizer, getHandler, isIntermediate, getSerializer) { | ||
// TODO: WTF fix me | ||
var partitionedArgs = (0, _utils.extractQueryParams)([this.name].concat(this.contexts)), | ||
pureArgs = partitionedArgs[0], | ||
handlers = recognizer.handlersFor(pureArgs[0]); | ||
var targetRouteName = handlers[handlers.length - 1].handler; | ||
return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate, false, getSerializer); | ||
} | ||
} | ||
for (i = handlers.length - 1; i >= 0; --i) { | ||
var result = handlers[i]; | ||
var name = result.handler; | ||
var oldHandlerInfo = oldState.handlerInfos[i]; | ||
var newHandlerInfo = null; | ||
if (result.names.length > 0) { | ||
if (i >= invalidateIndex) { | ||
newHandlerInfo = this.createParamHandlerInfo(name, getHandler, result.names, objects, oldHandlerInfo); | ||
} else { | ||
var serializer = getSerializer(name); | ||
newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, getHandler, result.names, objects, oldHandlerInfo, targetRouteName, i, serializer); | ||
} | ||
} else { | ||
// This route has no dynamic segment. | ||
// Therefore treat as a param-based handlerInfo | ||
// with empty params. This will cause the `model` | ||
// hook to be called with empty params, which is desirable. | ||
newHandlerInfo = this.createParamHandlerInfo(name, getHandler, result.names, objects, oldHandlerInfo); | ||
}, { | ||
key: 'applyToHandlers', | ||
value: function applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive, getSerializer) { | ||
var i = void 0, | ||
len = void 0; | ||
var newState = new _transitionState2.default(); | ||
var objects = this.contexts.slice(0); | ||
var invalidateIndex = handlers.length; | ||
// Pivot handlers are provided for refresh transitions | ||
if (this.pivotHandler) { | ||
for (i = 0, len = handlers.length; i < len; ++i) { | ||
if (handlers[i].handler === this.pivotHandler._handlerName) { | ||
invalidateIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
for (i = handlers.length - 1; i >= 0; --i) { | ||
var result = handlers[i]; | ||
var name = result.handler; | ||
var oldHandlerInfo = oldState.handlerInfos[i]; | ||
var newHandlerInfo = null; | ||
if (result.names.length > 0) { | ||
if (i >= invalidateIndex) { | ||
newHandlerInfo = this.createParamHandlerInfo(name, getHandler, result.names, objects, oldHandlerInfo); | ||
} else { | ||
var serializer = getSerializer(name); | ||
newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, getHandler, result.names, objects, oldHandlerInfo, targetRouteName, i, serializer); | ||
} | ||
} else { | ||
// This route has no dynamic segment. | ||
// Therefore treat as a param-based handlerInfo | ||
// with empty params. This will cause the `model` | ||
// hook to be called with empty params, which is desirable. | ||
newHandlerInfo = this.createParamHandlerInfo(name, getHandler, result.names, objects, oldHandlerInfo); | ||
} | ||
if (checkingIfActive) { | ||
// If we're performing an isActive check, we want to | ||
// serialize URL params with the provided context, but | ||
// ignore mismatches between old and new context. | ||
newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context); | ||
var oldContext = oldHandlerInfo && oldHandlerInfo.context; | ||
if (result.names.length > 0 && oldHandlerInfo.context !== undefined && newHandlerInfo.context === oldContext) { | ||
// If contexts match in isActive test, assume params also match. | ||
// This allows for flexibility in not requiring that every last | ||
// handler provide a `serialize` method | ||
newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params; | ||
} | ||
newHandlerInfo.context = oldContext; | ||
} | ||
var handlerToUse = oldHandlerInfo; | ||
if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
invalidateIndex = Math.min(i, invalidateIndex); | ||
handlerToUse = newHandlerInfo; | ||
} | ||
if (isIntermediate && !checkingIfActive) { | ||
handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context); | ||
} | ||
newState.handlerInfos.unshift(handlerToUse); | ||
} | ||
if (objects.length > 0) { | ||
throw new Error('More context objects were passed than there are dynamic segments for the route: ' + targetRouteName); | ||
} | ||
if (!isIntermediate) { | ||
this.invalidateChildren(newState.handlerInfos, invalidateIndex); | ||
} | ||
(0, _utils.merge)(newState.queryParams, this.queryParams || {}); | ||
return newState; | ||
} | ||
if (checkingIfActive) { | ||
// If we're performing an isActive check, we want to | ||
// serialize URL params with the provided context, but | ||
// ignore mismatches between old and new context. | ||
newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context); | ||
var oldContext = oldHandlerInfo && oldHandlerInfo.context; | ||
if (result.names.length > 0 && 'context' in oldHandlerInfo && newHandlerInfo.context === oldContext) { | ||
// If contexts match in isActive test, assume params also match. | ||
// This allows for flexibility in not requiring that every last | ||
// handler provide a `serialize` method | ||
newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params; | ||
} | ||
newHandlerInfo.context = oldContext; | ||
}, { | ||
key: 'invalidateChildren', | ||
value: function invalidateChildren(handlerInfos, invalidateIndex) { | ||
for (var i = invalidateIndex, l = handlerInfos.length; i < l; ++i) { | ||
var handlerInfo = handlerInfos[i]; | ||
handlerInfos[i] = handlerInfo.getUnresolved(); | ||
} | ||
} | ||
var handlerToUse = oldHandlerInfo; | ||
if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
invalidateIndex = Math.min(i, invalidateIndex); | ||
handlerToUse = newHandlerInfo; | ||
}, { | ||
key: 'getHandlerInfoForDynamicSegment', | ||
value: function getHandlerInfoForDynamicSegment(name, getHandler, names, objects, oldHandlerInfo, _targetRouteName, i, serializer) { | ||
var objectToUse = void 0; | ||
if (objects.length > 0) { | ||
// Use the objects provided for this transition. | ||
objectToUse = objects[objects.length - 1]; | ||
if ((0, _utils.isParam)(objectToUse)) { | ||
return this.createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo); | ||
} else { | ||
objects.pop(); | ||
} | ||
} else if (oldHandlerInfo && oldHandlerInfo.name === name) { | ||
// Reuse the matching oldHandlerInfo | ||
return oldHandlerInfo; | ||
} else { | ||
if (this.preTransitionState) { | ||
var preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i]; | ||
objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context; | ||
} else { | ||
// Ideally we should throw this error to provide maximal | ||
// information to the user that not enough context objects | ||
// were provided, but this proves too cumbersome in Ember | ||
// in cases where inner template helpers are evaluated | ||
// before parent helpers un-render, in which cases this | ||
// error somewhat prematurely fires. | ||
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]"); | ||
return oldHandlerInfo; | ||
} | ||
} | ||
return new _handlerInfo.UnresolvedHandlerInfoByObject(name, names, getHandler, serializer, objectToUse); | ||
} | ||
if (isIntermediate && !checkingIfActive) { | ||
handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context); | ||
}, { | ||
key: 'createParamHandlerInfo', | ||
value: function createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo) { | ||
var params = {}; | ||
// Soak up all the provided string/numbers | ||
var numNames = names.length; | ||
while (numNames--) { | ||
// Only use old params if the names match with the new handler | ||
var oldParams = oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params || {}; | ||
var peek = objects[objects.length - 1]; | ||
var paramName = names[numNames]; | ||
if ((0, _utils.isParam)(peek)) { | ||
params[paramName] = '' + objects.pop(); | ||
} else { | ||
// If we're here, this means only some of the params | ||
// were string/number params, so try and use a param | ||
// value from a previous handler. | ||
if (oldParams.hasOwnProperty(paramName)) { | ||
params[paramName] = oldParams[paramName]; | ||
} else { | ||
throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name); | ||
} | ||
} | ||
} | ||
return new _handlerInfo.UnresolvedHandlerInfoByParam(name, getHandler, params); | ||
} | ||
}]); | ||
newState.handlerInfos.unshift(handlerToUse); | ||
} | ||
return NamedTransitionIntent; | ||
}(_transitionIntent.TransitionIntent); | ||
//# sourceMappingURL=named-transition-intent.js.map | ||
if (objects.length > 0) { | ||
throw new Error('More context objects were passed than there are dynamic segments for the route: ' + targetRouteName); | ||
} | ||
if (!isIntermediate) { | ||
this.invalidateChildren(newState.handlerInfos, invalidateIndex); | ||
} | ||
(0, _utils.merge)(newState.queryParams, this.queryParams || {}); | ||
return newState; | ||
} | ||
}, { | ||
key: 'invalidateChildren', | ||
value: function invalidateChildren(handlerInfos, invalidateIndex) { | ||
for (var i = invalidateIndex, l = handlerInfos.length; i < l; ++i) { | ||
var handlerInfo = handlerInfos[i]; | ||
handlerInfos[i] = handlerInfo.getUnresolved(); | ||
} | ||
} | ||
}, { | ||
key: 'getHandlerInfoForDynamicSegment', | ||
value: function getHandlerInfoForDynamicSegment(name, getHandler, names, objects, oldHandlerInfo, targetRouteName, i, serializer) { | ||
var objectToUse; | ||
if (objects.length > 0) { | ||
// Use the objects provided for this transition. | ||
objectToUse = objects[objects.length - 1]; | ||
if ((0, _utils.isParam)(objectToUse)) { | ||
return this.createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo); | ||
} else { | ||
objects.pop(); | ||
} | ||
} else if (oldHandlerInfo && oldHandlerInfo.name === name) { | ||
// Reuse the matching oldHandlerInfo | ||
return oldHandlerInfo; | ||
} else { | ||
if (this.preTransitionState) { | ||
var preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i]; | ||
objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context; | ||
} else { | ||
// Ideally we should throw this error to provide maximal | ||
// information to the user that not enough context objects | ||
// were provided, but this proves too cumbersome in Ember | ||
// in cases where inner template helpers are evaluated | ||
// before parent helpers un-render, in which cases this | ||
// error somewhat prematurely fires. | ||
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]"); | ||
return oldHandlerInfo; | ||
} | ||
} | ||
return (0, _factory2.default)('object', { | ||
name: name, | ||
getHandler: getHandler, | ||
serializer: serializer, | ||
context: objectToUse, | ||
names: names | ||
}); | ||
} | ||
}, { | ||
key: 'createParamHandlerInfo', | ||
value: function createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo) { | ||
var params = {}; | ||
// Soak up all the provided string/numbers | ||
var numNames = names.length; | ||
while (numNames--) { | ||
// Only use old params if the names match with the new handler | ||
var oldParams = oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params || {}; | ||
var peek = objects[objects.length - 1]; | ||
var paramName = names[numNames]; | ||
if ((0, _utils.isParam)(peek)) { | ||
params[paramName] = '' + objects.pop(); | ||
} else { | ||
// If we're here, this means only some of the params | ||
// were string/number params, so try and use a param | ||
// value from a previous handler. | ||
if (oldParams.hasOwnProperty(paramName)) { | ||
params[paramName] = oldParams[paramName]; | ||
} else { | ||
throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + name); | ||
} | ||
} | ||
} | ||
return (0, _factory2.default)('param', { | ||
name: name, | ||
getHandler: getHandler, | ||
params: params | ||
}); | ||
} | ||
}]); | ||
return NamedTransitionIntent; | ||
}(_transitionIntent2.default); | ||
exports.default = NamedTransitionIntent; |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -9,6 +9,6 @@ | ||
var _handlerInfo = require('../handler-info'); | ||
var _transitionIntent = require('../transition-intent'); | ||
var _transitionIntent2 = _interopRequireDefault(_transitionIntent); | ||
var _transitionState = require('../transition-state'); | ||
@@ -18,12 +18,8 @@ | ||
var _factory = require('../handler-info/factory'); | ||
var _unrecognizedUrlError = require('../unrecognized-url-error'); | ||
var _factory2 = _interopRequireDefault(_factory); | ||
var _unrecognizedUrlError2 = _interopRequireDefault(_unrecognizedUrlError); | ||
var _utils = require('../utils'); | ||
var _unrecognizedUrlError = require('../unrecognized-url-error'); | ||
var _unrecognizedUrlError2 = _interopRequireDefault(_unrecognizedUrlError); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -38,76 +34,65 @@ | ||
var URLTransitionIntent = function (_TransitionIntent) { | ||
_inherits(URLTransitionIntent, _TransitionIntent); | ||
_inherits(URLTransitionIntent, _TransitionIntent); | ||
function URLTransitionIntent(props) { | ||
_classCallCheck(this, URLTransitionIntent); | ||
function URLTransitionIntent(url) { | ||
_classCallCheck(this, URLTransitionIntent); | ||
var _this = _possibleConstructorReturn(this, (URLTransitionIntent.__proto__ || Object.getPrototypeOf(URLTransitionIntent)).call(this, props)); | ||
var _this = _possibleConstructorReturn(this, (URLTransitionIntent.__proto__ || Object.getPrototypeOf(URLTransitionIntent)).call(this)); | ||
_this.url = props.url; | ||
return _this; | ||
} | ||
_this.url = url; | ||
_this.preTransitionState = undefined; | ||
return _this; | ||
} | ||
_createClass(URLTransitionIntent, [{ | ||
key: 'applyToState', | ||
value: function applyToState(oldState, recognizer, getHandler) { | ||
var newState = new _transitionState2.default(); | ||
var results = recognizer.recognize(this.url), | ||
i, | ||
len; | ||
if (!results) { | ||
throw new _unrecognizedUrlError2.default(this.url); | ||
} | ||
var statesDiffer = false; | ||
var url = this.url; | ||
// Checks if a handler is accessible by URL. If it is not, an error is thrown. | ||
// For the case where the handler is loaded asynchronously, the error will be | ||
// thrown once it is loaded. | ||
function checkHandlerAccessibility(handler) { | ||
if (handler && handler.inaccessibleByURL) { | ||
throw new _unrecognizedUrlError2.default(url); | ||
_createClass(URLTransitionIntent, [{ | ||
key: 'applyToState', | ||
value: function applyToState(oldState, recognizer, getHandler) { | ||
var newState = new _transitionState2.default(); | ||
var results = recognizer.recognize(this.url), | ||
i = void 0, | ||
len = void 0; | ||
if (!results) { | ||
throw new _unrecognizedUrlError2.default(this.url); | ||
} | ||
var statesDiffer = false; | ||
var url = this.url; | ||
// Checks if a handler is accessible by URL. If it is not, an error is thrown. | ||
// For the case where the handler is loaded asynchronously, the error will be | ||
// thrown once it is loaded. | ||
function checkHandlerAccessibility(handler) { | ||
if (handler && handler.inaccessibleByURL) { | ||
throw new _unrecognizedUrlError2.default(url); | ||
} | ||
return handler; | ||
} | ||
for (i = 0, len = results.length; i < len; ++i) { | ||
var result = results[i]; | ||
var name = result.handler; | ||
var newHandlerInfo = new _handlerInfo.UnresolvedHandlerInfoByParam(name, getHandler, result.params); | ||
var handler = newHandlerInfo.handler; | ||
if (handler) { | ||
checkHandlerAccessibility(handler); | ||
} else { | ||
// If the hanlder is being loaded asynchronously, check if we can | ||
// access it after it has resolved | ||
newHandlerInfo.handlerPromise = newHandlerInfo.handlerPromise.then(checkHandlerAccessibility); | ||
} | ||
var oldHandlerInfo = oldState.handlerInfos[i]; | ||
if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
statesDiffer = true; | ||
newState.handlerInfos[i] = newHandlerInfo; | ||
} else { | ||
newState.handlerInfos[i] = oldHandlerInfo; | ||
} | ||
} | ||
(0, _utils.merge)(newState.queryParams, results.queryParams); | ||
return newState; | ||
} | ||
}]); | ||
return handler; | ||
} | ||
return URLTransitionIntent; | ||
}(_transitionIntent.TransitionIntent); | ||
//# sourceMappingURL=url-transition-intent.js.map | ||
for (i = 0, len = results.length; i < len; ++i) { | ||
var result = results[i]; | ||
var name = result.handler; | ||
var newHandlerInfo = (0, _factory2.default)('param', { | ||
name: name, | ||
getHandler: getHandler, | ||
params: result.params | ||
}); | ||
var handler = newHandlerInfo.handler; | ||
if (handler) { | ||
checkHandlerAccessibility(handler); | ||
} else { | ||
// If the hanlder is being loaded asynchronously, check if we can | ||
// access it after it has resolved | ||
newHandlerInfo.handlerPromise = newHandlerInfo.handlerPromise.then(checkHandlerAccessibility); | ||
} | ||
var oldHandlerInfo = oldState.handlerInfos[i]; | ||
if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
statesDiffer = true; | ||
newState.handlerInfos[i] = newHandlerInfo; | ||
} else { | ||
newState.handlerInfos[i] = oldHandlerInfo; | ||
} | ||
} | ||
(0, _utils.merge)(newState.queryParams, results.queryParams); | ||
return newState; | ||
} | ||
}]); | ||
return URLTransitionIntent; | ||
}(_transitionIntent2.default); | ||
exports.default = URLTransitionIntent; |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.TransitionError = undefined; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _rsvp = require('rsvp'); | ||
var _utils = require('./utils'); | ||
var _rsvp = require('rsvp'); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var TransitionState = function () { | ||
function TransitionState() { | ||
_classCallCheck(this, TransitionState); | ||
function TransitionState() { | ||
_classCallCheck(this, TransitionState); | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
_createClass(TransitionState, [{ | ||
key: 'promiseLabel', | ||
value: function promiseLabel(label) { | ||
var targetName = ''; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
if (targetName !== '') { | ||
targetName += '.'; | ||
} | ||
targetName += handlerInfo.name; | ||
}); | ||
return (0, _utils.promiseLabel)("'" + targetName + "': " + label); | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
}, { | ||
key: 'resolve', | ||
value: function resolve(shouldContinue) { | ||
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
var params = this.params; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
params[handlerInfo.name] = handlerInfo.params || {}; | ||
}); | ||
payload.resolveIndex = 0; | ||
var currentState = this; | ||
var wasAborted = false; | ||
// The prelude RSVP.resolve() asyncs us into the promise land. | ||
return _rsvp.Promise.resolve(null, this.promiseLabel('Start transition')).then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler')).catch(handleError, this.promiseLabel('Handle error')); | ||
function innerShouldContinue() { | ||
return _rsvp.Promise.resolve(shouldContinue(), currentState.promiseLabel('Check if should continue')).catch(function (reason) { | ||
// We distinguish between errors that occurred | ||
// during resolution (e.g. before"Model/model/afterModel), | ||
// and aborts due to a rejecting promise from shouldContinue(). | ||
wasAborted = true; | ||
return _rsvp.Promise.reject(reason); | ||
}, currentState.promiseLabel('Handle abort')); | ||
} | ||
function handleError(error) { | ||
// This is the only possible | ||
// reject value of TransitionState#resolve | ||
var handlerInfos = currentState.handlerInfos; | ||
var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ? handlerInfos.length - 1 : payload.resolveIndex; | ||
return _rsvp.Promise.reject({ | ||
error: error, | ||
handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler, | ||
wasAborted: wasAborted, | ||
state: currentState | ||
}); | ||
} | ||
function proceed(resolvedHandlerInfo) { | ||
var wasAlreadyResolved = currentState.handlerInfos[payload.resolveIndex].isResolved; | ||
// Swap the previously unresolved handlerInfo with | ||
// the resolved handlerInfo | ||
currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo; | ||
if (!wasAlreadyResolved) { | ||
// Call the redirect hook. The reason we call it here | ||
// vs. afterModel is so that redirects into child | ||
// routes don't re-run the model hooks for this | ||
// already-resolved route. | ||
var handler = resolvedHandlerInfo.handler; | ||
(0, _utils.callHook)(handler, 'redirect', resolvedHandlerInfo.context, payload); | ||
_createClass(TransitionState, [{ | ||
key: 'promiseLabel', | ||
value: function promiseLabel(label) { | ||
var targetName = ''; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
if (targetName !== '') { | ||
targetName += '.'; | ||
} | ||
targetName += handlerInfo.name; | ||
return true; | ||
}); | ||
return (0, _utils.promiseLabel)("'" + targetName + "': " + label); | ||
} | ||
// Proceed after ensuring that the redirect hook | ||
// didn't abort this transition by transitioning elsewhere. | ||
return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler')); | ||
} | ||
function resolveOneHandlerInfo() { | ||
if (payload.resolveIndex === currentState.handlerInfos.length) { | ||
// This is is the only possible | ||
// fulfill value of TransitionState#resolve | ||
return { | ||
error: null, | ||
state: currentState | ||
}; | ||
}, { | ||
key: 'resolve', | ||
value: function resolve(shouldContinue, transition) { | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
var params = this.params; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
params[handlerInfo.name] = handlerInfo.params || {}; | ||
return true; | ||
}); | ||
transition.resolveIndex = 0; | ||
var currentState = this; | ||
var wasAborted = false; | ||
// The prelude RSVP.resolve() asyncs us into the promise land. | ||
return _rsvp.Promise.resolve(null, this.promiseLabel('Start transition')).then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler')).catch(handleError, this.promiseLabel('Handle error')); | ||
function innerShouldContinue() { | ||
return _rsvp.Promise.resolve(shouldContinue(), currentState.promiseLabel('Check if should continue')).catch(function (reason) { | ||
// We distinguish between errors that occurred | ||
// during resolution (e.g. before"Model/model/afterModel), | ||
// and aborts due to a rejecting promise from shouldContinue(). | ||
wasAborted = true; | ||
return _rsvp.Promise.reject(reason); | ||
}, currentState.promiseLabel('Handle abort')); | ||
} | ||
function handleError(error) { | ||
// This is the only possible | ||
// reject value of TransitionState#resolve | ||
var handlerInfos = currentState.handlerInfos; | ||
var errorHandlerIndex = transition.resolveIndex >= handlerInfos.length ? handlerInfos.length - 1 : transition.resolveIndex; | ||
return _rsvp.Promise.reject(new TransitionError(error, currentState.handlerInfos[errorHandlerIndex].handler, wasAborted, currentState)); | ||
} | ||
function proceed(resolvedHandlerInfo) { | ||
var wasAlreadyResolved = currentState.handlerInfos[transition.resolveIndex].isResolved; | ||
// Swap the previously unresolved handlerInfo with | ||
// the resolved handlerInfo | ||
currentState.handlerInfos[transition.resolveIndex++] = resolvedHandlerInfo; | ||
if (!wasAlreadyResolved) { | ||
// Call the redirect hook. The reason we call it here | ||
// vs. afterModel is so that redirects into child | ||
// routes don't re-run the model hooks for this | ||
// already-resolved route. | ||
var handler = resolvedHandlerInfo.handler; | ||
if (handler !== undefined) { | ||
if (handler._redirect) { | ||
handler._redirect(resolvedHandlerInfo.context, transition); | ||
} else if (handler.redirect) { | ||
handler.redirect(resolvedHandlerInfo.context, transition); | ||
} | ||
} | ||
} | ||
// Proceed after ensuring that the redirect hook | ||
// didn't abort this transition by transitioning elsewhere. | ||
return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler')); | ||
} | ||
function resolveOneHandlerInfo() { | ||
if (transition.resolveIndex === currentState.handlerInfos.length) { | ||
// This is is the only possible | ||
// fulfill value of TransitionState#resolve | ||
return currentState; | ||
} | ||
var handlerInfo = currentState.handlerInfos[transition.resolveIndex]; | ||
return handlerInfo.resolve(innerShouldContinue, transition).then(proceed, null, currentState.promiseLabel('Proceed')); | ||
} | ||
} | ||
}]); | ||
var handlerInfo = currentState.handlerInfos[payload.resolveIndex]; | ||
return TransitionState; | ||
}(); | ||
return handlerInfo.resolve(innerShouldContinue, payload).then(proceed, null, currentState.promiseLabel('Proceed')); | ||
} | ||
} | ||
}]); | ||
exports.default = TransitionState; | ||
return TransitionState; | ||
}(); | ||
var TransitionError = exports.TransitionError = function TransitionError(error, handler, wasAborted, state) { | ||
_classCallCheck(this, TransitionError); | ||
exports.default = TransitionState; | ||
this.error = error; | ||
this.handler = handler; | ||
this.wasAborted = wasAborted; | ||
this.state = state; | ||
}; | ||
//# sourceMappingURL=transition-state.js.map |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -13,15 +13,17 @@ exports.Transition = exports.TransitionAborted = undefined; | ||
Object.defineProperty(exports, 'TransitionAborted', { | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_transitionAbortedError).default; | ||
} | ||
enumerable: true, | ||
get: function get() { | ||
return _interopRequireDefault(_transitionAbortedError).default; | ||
} | ||
}); | ||
exports.logAbort = logAbort; | ||
exports.isTransition = isTransition; | ||
exports.prepareResult = prepareResult; | ||
var _rsvp = require('rsvp'); | ||
var _transitionAbortedError2 = _interopRequireDefault(_transitionAbortedError); | ||
var _utils = require('./utils'); | ||
var _transitionAbortedError2 = _interopRequireDefault(_transitionAbortedError); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -47,325 +49,313 @@ | ||
var Transition = function () { | ||
function Transition(router, intent, state, error, previousTransition) { | ||
var _this = this; | ||
function Transition(router, intent, state) { | ||
var _this = this; | ||
_classCallCheck(this, Transition); | ||
var error = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined; | ||
var previousTransition = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : undefined; | ||
this.state = state || router.state; | ||
this.intent = intent; | ||
this.router = router; | ||
this.data = this.intent && this.intent.data || {}; | ||
this.resolvedModels = {}; | ||
this.queryParams = {}; | ||
this.promise = undefined; | ||
this.error = undefined; | ||
this.params = undefined; | ||
this.handlerInfos = undefined; | ||
this.targetName = undefined; | ||
this.pivotHandler = undefined; | ||
this.sequence = undefined; | ||
this.isAborted = false; | ||
this.isActive = true; | ||
this.urlMethod = 'update'; | ||
this.resolveIndex = 0; | ||
this.queryParamsOnly = false; | ||
this.isTransition = true; | ||
_classCallCheck(this, Transition); | ||
if (error) { | ||
this.promise = _rsvp.Promise.reject(error); | ||
this.error = error; | ||
return; | ||
this.isAborted = false; | ||
this.isActive = true; | ||
this.urlMethod = 'update'; | ||
this.resolveIndex = 0; | ||
this.queryParamsOnly = false; | ||
this.isTransition = true; | ||
this.isCausedByAbortingTransition = false; | ||
this.isCausedByInitialTransition = false; | ||
this.isCausedByAbortingReplaceTransition = false; | ||
this._visibleQueryParams = {}; | ||
this.state = state || router.state; | ||
this.intent = intent; | ||
this.router = router; | ||
this.data = intent && intent.data || {}; | ||
this.resolvedModels = {}; | ||
this.queryParams = {}; | ||
this.promise = undefined; | ||
this.error = undefined; | ||
this.params = {}; | ||
this.handlerInfos = []; | ||
this.targetName = undefined; | ||
this.pivotHandler = undefined; | ||
this.sequence = -1; | ||
if (error) { | ||
this.promise = _rsvp.Promise.reject(error); | ||
this.error = error; | ||
return; | ||
} | ||
// if you're doing multiple redirects, need the new transition to know if it | ||
// is actually part of the first transition or not. Any further redirects | ||
// in the initial transition also need to know if they are part of the | ||
// initial transition | ||
this.isCausedByAbortingTransition = !!previousTransition; | ||
this.isCausedByInitialTransition = !!previousTransition && (previousTransition.isCausedByInitialTransition || previousTransition.sequence === 0); | ||
// Every transition in the chain is a replace | ||
this.isCausedByAbortingReplaceTransition = !!previousTransition && previousTransition.urlMethod === 'replace' && (!previousTransition.isCausedByAbortingTransition || previousTransition.isCausedByAbortingReplaceTransition); | ||
if (state) { | ||
this.params = state.params; | ||
this.queryParams = state.queryParams; | ||
this.handlerInfos = state.handlerInfos; | ||
var len = state.handlerInfos.length; | ||
if (len) { | ||
this.targetName = state.handlerInfos[len - 1].name; | ||
} | ||
for (var i = 0; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
// TODO: this all seems hacky | ||
if (!handlerInfo.isResolved) { | ||
break; | ||
} | ||
this.pivotHandler = handlerInfo.handler; | ||
} | ||
this.sequence = router.currentSequence++; | ||
this.promise = state.resolve(function () { | ||
if (_this.isAborted) { | ||
return _rsvp.Promise.reject(false, (0, _utils.promiseLabel)('Transition aborted - reject')); | ||
} | ||
return _rsvp.Promise.resolve(true); | ||
}, this).catch(function (result) { | ||
if (result.wasAborted || _this.isAborted) { | ||
return _rsvp.Promise.reject(logAbort(_this)); | ||
} else { | ||
_this.trigger(false, 'error', result.error, _this, result.handler); | ||
_this.abort(); | ||
return _rsvp.Promise.reject(result.error); | ||
} | ||
}, (0, _utils.promiseLabel)('Handle Abort')); | ||
} else { | ||
this.promise = _rsvp.Promise.resolve(this.state); | ||
this.params = {}; | ||
} | ||
} | ||
// Todo Delete? | ||
// if you're doing multiple redirects, need the new transition to know if it | ||
// is actually part of the first transition or not. Any further redirects | ||
// in the initial transition also need to know if they are part of the | ||
// initial transition | ||
this.isCausedByAbortingTransition = !!previousTransition; | ||
this.isCausedByInitialTransition = previousTransition && (previousTransition.isCausedByInitialTransition || previousTransition.sequence === 0); | ||
// Every transition in the chain is a replace | ||
this.isCausedByAbortingReplaceTransition = previousTransition && previousTransition.urlMethod == 'replace' && (!previousTransition.isCausedByAbortingTransition || previousTransition.isCausedByAbortingReplaceTransition); | ||
if (state) { | ||
this.params = state.params; | ||
this.queryParams = state.queryParams; | ||
this.handlerInfos = state.handlerInfos; | ||
_createClass(Transition, [{ | ||
key: 'isExiting', | ||
value: function isExiting(handler) { | ||
var handlerInfos = this.handlerInfos; | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = handlerInfos[i]; | ||
if (handlerInfo.name === handler || handlerInfo.handler === handler) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
The Transition's internal promise. Calling `.then` on this property | ||
is that same as calling `.then` on the Transition object itself, but | ||
this property is exposed for when you want to pass around a | ||
Transition's promise, but not the Transition object itself, since | ||
Transition object can be externally `abort`ed, while the promise | ||
cannot. | ||
@property promise | ||
@type {Object} | ||
@public | ||
*/ | ||
/** | ||
Custom state can be stored on a Transition's `data` object. | ||
This can be useful for decorating a Transition within an earlier | ||
hook and shared with a later hook. Properties set on `data` will | ||
be copied to new transitions generated by calling `retry` on this | ||
transition. | ||
@property data | ||
@type {Object} | ||
@public | ||
*/ | ||
/** | ||
A standard promise hook that resolves if the transition | ||
succeeds and rejects if it fails/redirects/aborts. | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method then | ||
@param {Function} onFulfilled | ||
@param {Function} onRejected | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
var len = state.handlerInfos.length; | ||
if (len) { | ||
this.targetName = state.handlerInfos[len - 1].name; | ||
} | ||
}, { | ||
key: 'then', | ||
value: function then(onFulfilled, onRejected, label) { | ||
return this.promise.then(onFulfilled, onRejected, label); | ||
} | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method catch | ||
@param {Function} onRejection | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
for (var i = 0; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
}, { | ||
key: 'catch', | ||
value: function _catch(onRejection, label) { | ||
return this.promise.catch(onRejection, label); | ||
} | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method finally | ||
@param {Function} callback | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
// TODO: this all seems hacky | ||
if (!handlerInfo.isResolved) { | ||
break; | ||
}, { | ||
key: 'finally', | ||
value: function _finally(callback, label) { | ||
return this.promise.finally(callback, label); | ||
} | ||
this.pivotHandler = handlerInfo.handler; | ||
} | ||
/** | ||
Aborts the Transition. Note you can also implicitly abort a transition | ||
by initiating another transition while a previous one is underway. | ||
@method abort | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
this.sequence = router.currentSequence++; | ||
this.promise = state.resolve(function () { | ||
if (_this.isAborted) { | ||
return _rsvp.Promise.reject(undefined, (0, _utils.promiseLabel)('Transition aborted - reject')); | ||
}, { | ||
key: 'abort', | ||
value: function abort() { | ||
if (this.isAborted) { | ||
return this; | ||
} | ||
(0, _utils.log)(this.router, this.sequence, this.targetName + ': transition was aborted'); | ||
this.intent.preTransitionState = this.router.state; | ||
this.isAborted = true; | ||
this.isActive = false; | ||
this.router.activeTransition = undefined; | ||
return this; | ||
} | ||
}, this).catch(function (result) { | ||
if (result.wasAborted || _this.isAborted) { | ||
return _rsvp.Promise.reject(logAbort(_this)); | ||
} else { | ||
_this.trigger('error', result.error, _this, result.handlerWithError); | ||
_this.abort(); | ||
return _rsvp.Promise.reject(result.error); | ||
/** | ||
Retries a previously-aborted transition (making sure to abort the | ||
transition if it's still active). Returns a new transition that | ||
represents the new attempt to transition. | ||
@method retry | ||
@return {Transition} new transition | ||
@public | ||
*/ | ||
}, { | ||
key: 'retry', | ||
value: function retry() { | ||
// TODO: add tests for merged state retry()s | ||
this.abort(); | ||
var newTransition = this.router.transitionByIntent(this.intent, false); | ||
// inheriting a `null` urlMethod is not valid | ||
// the urlMethod is only set to `null` when | ||
// the transition is initiated *after* the url | ||
// has been updated (i.e. `router.handleURL`) | ||
// | ||
// in that scenario, the url method cannot be | ||
// inherited for a new transition because then | ||
// the url would not update even though it should | ||
if (this.urlMethod !== null) { | ||
newTransition.method(this.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
}, (0, _utils.promiseLabel)('Handle Abort')); | ||
} else { | ||
this.promise = _rsvp.Promise.resolve(this.state); | ||
this.params = {}; | ||
} | ||
} | ||
/** | ||
Sets the URL-changing method to be employed at the end of a | ||
successful transition. By default, a new Transition will just | ||
use `updateURL`, but passing 'replace' to this method will | ||
cause the URL to update using 'replaceWith' instead. Omitting | ||
a parameter will disable the URL change, allowing for transitions | ||
that don't update the URL at completion (this is also used for | ||
handleURL, since the URL has already changed before the | ||
transition took place). | ||
@method method | ||
@param {String} method the type of URL-changing method to use | ||
at the end of a transition. Accepted values are 'replace', | ||
falsy values, or any other non-falsy value (which is | ||
interpreted as an updateURL transition). | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
_createClass(Transition, [{ | ||
key: 'isExiting', | ||
value: function isExiting(handler) { | ||
var handlerInfos = this.handlerInfos; | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = handlerInfos[i]; | ||
if (handlerInfo.name === handler || handlerInfo.handler === handler) { | ||
return false; | ||
}, { | ||
key: 'method', | ||
value: function method(_method) { | ||
this.urlMethod = _method; | ||
return this; | ||
} | ||
} | ||
return true; | ||
} | ||
// Alias 'trigger' as 'send' | ||
/** | ||
The Transition's internal promise. Calling `.then` on this property | ||
is that same as calling `.then` on the Transition object itself, but | ||
this property is exposed for when you want to pass around a | ||
Transition's promise, but not the Transition object itself, since | ||
Transition object can be externally `abort`ed, while the promise | ||
cannot. | ||
@property promise | ||
@type {Object} | ||
@public | ||
*/ | ||
}, { | ||
key: 'send', | ||
value: function send(ignoreFailure, _name, err, transition, handler) { | ||
this.trigger(ignoreFailure, _name, err, transition, handler); | ||
} | ||
/** | ||
Fires an event on the current list of resolved/resolving | ||
handlers within this transition. Useful for firing events | ||
on route hierarchies that haven't fully been entered yet. | ||
Note: This method is also aliased as `send` | ||
@method trigger | ||
@param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error | ||
@param {String} name the name of the event to fire | ||
@public | ||
*/ | ||
/** | ||
Custom state can be stored on a Transition's `data` object. | ||
This can be useful for decorating a Transition within an earlier | ||
hook and shared with a later hook. Properties set on `data` will | ||
be copied to new transitions generated by calling `retry` on this | ||
transition. | ||
@property data | ||
@type {Object} | ||
@public | ||
*/ | ||
}, { | ||
key: 'trigger', | ||
value: function trigger(ignoreFailure, _name) { | ||
for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
args[_key - 2] = arguments[_key]; | ||
} | ||
/** | ||
A standard promise hook that resolves if the transition | ||
succeeds and rejects if it fails/redirects/aborts. | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method then | ||
@param {Function} onFulfilled | ||
@param {Function} onRejected | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
_utils.trigger.apply(undefined, [this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, _name].concat(args)); | ||
} | ||
/** | ||
Transitions are aborted and their promises rejected | ||
when redirects occur; this method returns a promise | ||
that will follow any redirects that occur and fulfill | ||
with the value fulfilled by any redirecting transitions | ||
that occur. | ||
@method followRedirects | ||
@return {Promise} a promise that fulfills with the same | ||
value that the final redirecting transition fulfills with | ||
@public | ||
*/ | ||
}, { | ||
key: 'then', | ||
value: function then(onFulfilled, onRejected, label) { | ||
return this.promise.then(onFulfilled, onRejected, label); | ||
} | ||
}, { | ||
key: 'followRedirects', | ||
value: function followRedirects() { | ||
var router = this.router; | ||
return this.promise.catch(function (reason) { | ||
if (router.activeTransition) { | ||
return router.activeTransition.followRedirects(); | ||
} | ||
return _rsvp.Promise.reject(reason); | ||
}); | ||
} | ||
}, { | ||
key: 'toString', | ||
value: function toString() { | ||
return 'Transition (sequence ' + this.sequence + ')'; | ||
} | ||
/** | ||
@private | ||
*/ | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method catch | ||
@param {Function} onRejection | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
}, { | ||
key: 'catch', | ||
value: function _catch(onRejection, label) { | ||
return this.promise.catch(onRejection, label); | ||
} | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method finally | ||
@param {Function} callback | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
}, { | ||
key: 'finally', | ||
value: function _finally(callback, label) { | ||
return this.promise.finally(callback, label); | ||
} | ||
/** | ||
Aborts the Transition. Note you can also implicitly abort a transition | ||
by initiating another transition while a previous one is underway. | ||
@method abort | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
}, { | ||
key: 'abort', | ||
value: function abort() { | ||
if (this.isAborted) { | ||
return this; | ||
} | ||
(0, _utils.log)(this.router, this.sequence, this.targetName + ': transition was aborted'); | ||
this.intent.preTransitionState = this.router.state; | ||
this.isAborted = true; | ||
this.isActive = false; | ||
this.router.activeTransition = null; | ||
return this; | ||
} | ||
/** | ||
Retries a previously-aborted transition (making sure to abort the | ||
transition if it's still active). Returns a new transition that | ||
represents the new attempt to transition. | ||
@method retry | ||
@return {Transition} new transition | ||
@public | ||
*/ | ||
}, { | ||
key: 'retry', | ||
value: function retry() { | ||
// TODO: add tests for merged state retry()s | ||
this.abort(); | ||
var newTransition = this.router.transitionByIntent(this.intent, false); | ||
// inheriting a `null` urlMethod is not valid | ||
// the urlMethod is only set to `null` when | ||
// the transition is initiated *after* the url | ||
// has been updated (i.e. `router.handleURL`) | ||
// | ||
// in that scenario, the url method cannot be | ||
// inherited for a new transition because then | ||
// the url would not update even though it should | ||
if (this.urlMethod !== null) { | ||
newTransition.method(this.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
/** | ||
Sets the URL-changing method to be employed at the end of a | ||
successful transition. By default, a new Transition will just | ||
use `updateURL`, but passing 'replace' to this method will | ||
cause the URL to update using 'replaceWith' instead. Omitting | ||
a parameter will disable the URL change, allowing for transitions | ||
that don't update the URL at completion (this is also used for | ||
handleURL, since the URL has already changed before the | ||
transition took place). | ||
@method method | ||
@param {String} method the type of URL-changing method to use | ||
at the end of a transition. Accepted values are 'replace', | ||
falsy values, or any other non-falsy value (which is | ||
interpreted as an updateURL transition). | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
}, { | ||
key: 'method', | ||
value: function method(_method) { | ||
this.urlMethod = _method; | ||
return this; | ||
} | ||
/** | ||
Fires an event on the current list of resolved/resolving | ||
handlers within this transition. Useful for firing events | ||
on route hierarchies that haven't fully been entered yet. | ||
Note: This method is also aliased as `send` | ||
@method trigger | ||
@param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error | ||
@param {String} name the name of the event to fire | ||
@public | ||
*/ | ||
}, { | ||
key: 'trigger', | ||
value: function trigger(ignoreFailure) { | ||
var args = _utils.slice.call(arguments); | ||
if (typeof ignoreFailure === 'boolean') { | ||
args.shift(); | ||
} else { | ||
// Throw errors on unhandled trigger events by default | ||
ignoreFailure = false; | ||
} | ||
(0, _utils.trigger)(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, args); | ||
} | ||
/** | ||
Transitions are aborted and their promises rejected | ||
when redirects occur; this method returns a promise | ||
that will follow any redirects that occur and fulfill | ||
with the value fulfilled by any redirecting transitions | ||
that occur. | ||
@method followRedirects | ||
@return {Promise} a promise that fulfills with the same | ||
value that the final redirecting transition fulfills with | ||
@public | ||
*/ | ||
}, { | ||
key: 'followRedirects', | ||
value: function followRedirects() { | ||
var router = this.router; | ||
return this.promise.catch(function (reason) { | ||
if (router.activeTransition) { | ||
return router.activeTransition.followRedirects(); | ||
}, { | ||
key: 'log', | ||
value: function log(message) { | ||
(0, _utils.log)(this.router, this.sequence, message); | ||
} | ||
return _rsvp.Promise.reject(reason); | ||
}); | ||
} | ||
}, { | ||
key: 'toString', | ||
value: function toString() { | ||
return 'Transition (sequence ' + this.sequence + ')'; | ||
} | ||
}]); | ||
/** | ||
@private | ||
*/ | ||
}, { | ||
key: 'log', | ||
value: function log(message) { | ||
(0, _utils.log)(this.router, this.sequence, message); | ||
} | ||
}]); | ||
return Transition; | ||
return Transition; | ||
}(); | ||
// Alias 'trigger' as 'send' | ||
exports.Transition = Transition; | ||
Transition.prototype.send = Transition.prototype.trigger; | ||
/** | ||
@@ -376,5 +366,18 @@ @private | ||
*/ | ||
exports.Transition = Transition; | ||
function logAbort(transition) { | ||
(0, _utils.log)(transition.router, transition.sequence, 'detected abort.'); | ||
return new _transitionAbortedError2.default(); | ||
} | ||
(0, _utils.log)(transition.router, transition.sequence, 'detected abort.'); | ||
return new _transitionAbortedError2.default(); | ||
} | ||
function isTransition(obj) { | ||
return typeof obj === 'object' && obj instanceof Transition && obj.isTransition; | ||
} | ||
function prepareResult(obj) { | ||
if (isTransition(obj)) { | ||
return null; | ||
} | ||
return obj; | ||
} | ||
//# sourceMappingURL=transition.js.map |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
function UnrecognizedURLError(message) { | ||
if (!(this instanceof UnrecognizedURLError)) { | ||
return new UnrecognizedURLError(message); | ||
} | ||
var error = Error.call(this, message); | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, UnrecognizedURLError); | ||
} else { | ||
this.stack = error.stack; | ||
} | ||
this.description = error.description; | ||
this.fileName = error.fileName; | ||
this.lineNumber = error.lineNumber; | ||
this.message = error.message || 'UnrecognizedURL'; | ||
this.name = 'UnrecognizedURLError'; | ||
this.number = error.number; | ||
this.code = error.code; | ||
} | ||
UnrecognizedURLError.prototype = Object.create(Error.prototype); | ||
exports.default = UnrecognizedURLError; | ||
var UnrecognizedURLError = function () { | ||
UnrecognizedURLError.prototype = Object.create(Error.prototype); | ||
UnrecognizedURLError.prototype.constructor = UnrecognizedURLError; | ||
function UnrecognizedURLError(message) { | ||
var error = Error.call(this, message); | ||
this.name = 'UnrecognizedURLError'; | ||
this.message = message || 'UnrecognizedURL'; | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, UnrecognizedURLError); | ||
} else { | ||
this.stack = error.stack; | ||
} | ||
} | ||
return UnrecognizedURLError; | ||
}(); | ||
exports.default = UnrecognizedURLError; | ||
//# sourceMappingURL=unrecognized-url-error.js.map |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -16,23 +16,17 @@ exports.isPromise = isPromise; | ||
exports.promiseLabel = promiseLabel; | ||
exports.resolveHook = resolveHook; | ||
exports.callHook = callHook; | ||
exports.applyHook = applyHook; | ||
var slice = exports.slice = Array.prototype.slice; | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
/** | ||
Determines if an object is Promise by checking if it is "thenable". | ||
**/ | ||
function isPromise(obj) { | ||
return (typeof obj === 'object' && obj !== null || typeof obj === 'function') && typeof obj.then === 'function'; | ||
function isPromise(p) { | ||
return p !== null && typeof p === 'object' && typeof p.then === 'function'; | ||
} | ||
function merge(hash, other) { | ||
for (var prop in other) { | ||
if (hasOwnProperty.call(other, prop)) { | ||
hash[prop] = other[prop]; | ||
for (var prop in other) { | ||
if (hasOwnProperty.call(other, prop)) { | ||
hash[prop] = other[prop]; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
@@ -44,15 +38,18 @@ @private | ||
function extractQueryParams(array) { | ||
var len = array && array.length, | ||
head = void 0, | ||
queryParams = void 0; | ||
if (len && len > 0 && array[len - 1] && hasOwnProperty.call(array[len - 1], 'queryParams')) { | ||
queryParams = array[len - 1].queryParams; | ||
head = slice.call(array, 0, len - 1); | ||
return [head, queryParams]; | ||
} else { | ||
var len = array && array.length, | ||
head = void 0, | ||
queryParams = void 0; | ||
if (len && len > 0) { | ||
var obj = array[len - 1]; | ||
if (isQueryParams(obj)) { | ||
queryParams = obj.queryParams; | ||
head = slice.call(array, 0, len - 1); | ||
return [head, queryParams]; | ||
} | ||
} | ||
return [array, null]; | ||
} | ||
} | ||
function isQueryParams(obj) { | ||
return obj && hasOwnProperty.call(obj, 'queryParams'); | ||
} | ||
/** | ||
@@ -64,12 +61,12 @@ @private | ||
function coerceQueryParamsToString(queryParams) { | ||
for (var key in queryParams) { | ||
var val = queryParams[key]; | ||
if (typeof val === 'number') { | ||
queryParams[key] = '' + val; | ||
} else if (Array.isArray(val)) { | ||
for (var i = 0, l = val.length; i < l; i++) { | ||
val[i] = '' + val[i]; | ||
} | ||
for (var key in queryParams) { | ||
var val = queryParams[key]; | ||
if (typeof val === 'number') { | ||
queryParams[key] = '' + val; | ||
} else if (Array.isArray(val)) { | ||
for (var i = 0, l = val.length; i < l; i++) { | ||
val[i] = '' + val[i]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
@@ -79,154 +76,127 @@ /** | ||
*/ | ||
function log(router, sequence, msg) { | ||
if (!router.log) { | ||
return; | ||
} | ||
function log(router) { | ||
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
args[_key - 1] = arguments[_key]; | ||
} | ||
if (arguments.length === 3) { | ||
router.log('Transition #' + sequence + ': ' + msg); | ||
} else { | ||
msg = sequence; | ||
router.log(msg); | ||
} | ||
if (!router.log) { | ||
return; | ||
} | ||
if (arguments.length === 2) { | ||
var sequence = args[0], | ||
msg = args[1]; | ||
router.log('Transition #' + sequence + ': ' + msg); | ||
} else { | ||
var _msg = args[0]; | ||
router.log(_msg); | ||
} | ||
} | ||
function isParam(object) { | ||
return typeof object === 'string' || object instanceof String || typeof object === 'number' || object instanceof Number; | ||
return typeof object === 'string' || object instanceof String || typeof object === 'number' || object instanceof Number; | ||
} | ||
function forEach(array, callback) { | ||
for (var i = 0, l = array.length; i < l && false !== callback(array[i]); i++) { | ||
// empty intentionally | ||
} | ||
for (var i = 0, l = array.length; i < l && callback(array[i]) !== false; i++) { | ||
// empty intentionally | ||
} | ||
} | ||
function trigger(router, handlerInfos, ignoreFailure, args) { | ||
if (router.triggerEvent) { | ||
router.triggerEvent(handlerInfos, ignoreFailure, args); | ||
return; | ||
} | ||
var name = args.shift(); | ||
if (!handlerInfos) { | ||
if (ignoreFailure) { | ||
return; | ||
function trigger(router, handlerInfos, ignoreFailure, name) { | ||
for (var _len2 = arguments.length, args = Array(_len2 > 4 ? _len2 - 4 : 0), _key2 = 4; _key2 < _len2; _key2++) { | ||
args[_key2 - 4] = arguments[_key2]; | ||
} | ||
throw new Error("Could not trigger event '" + name + "'. There are no active handlers"); | ||
} | ||
var eventWasHandled = false; | ||
function delayedEvent(name, args, handler) { | ||
handler.events[name].apply(handler, args); | ||
} | ||
for (var i = handlerInfos.length - 1; i >= 0; i--) { | ||
var handlerInfo = handlerInfos[i], | ||
handler = handlerInfo.handler; | ||
// If there is no handler, it means the handler hasn't resolved yet which | ||
// means that we should trigger the event later when the handler is available | ||
if (!handler) { | ||
handlerInfo.handlerPromise.then(delayedEvent.bind(null, name, args)); | ||
continue; | ||
} | ||
if (handler.events && handler.events[name]) { | ||
if (handler.events[name].apply(handler, args) === true) { | ||
eventWasHandled = true; | ||
} else { | ||
if (router.triggerEvent) { | ||
router.triggerEvent(handlerInfos, ignoreFailure, [name].concat(args)); | ||
return; | ||
} | ||
} | ||
} | ||
// In the case that we got an UnrecognizedURLError as an event with no handler, | ||
// let it bubble up | ||
if (name === 'error' && args[0].name === 'UnrecognizedURLError') { | ||
throw args[0]; | ||
} else if (!eventWasHandled && !ignoreFailure) { | ||
throw new Error("Nothing handled the event '" + name + "'."); | ||
} | ||
if (!handlerInfos) { | ||
if (ignoreFailure) { | ||
return; | ||
} | ||
throw new Error("Could not trigger event '" + name + "'. There are no active handlers"); | ||
} | ||
var eventWasHandled = false; | ||
for (var i = handlerInfos.length - 1; i >= 0; i--) { | ||
var currentHandlerInfo = handlerInfos[i], | ||
currentHandler = currentHandlerInfo.handler; | ||
// If there is no handler, it means the handler hasn't resolved yet which | ||
// means that we should trigger the event later when the handler is available | ||
if (!currentHandler) { | ||
currentHandlerInfo.handlerPromise.then(function (resolvedHandler) { | ||
resolvedHandler.events[name].apply(resolvedHandler, args); | ||
}); | ||
continue; | ||
} | ||
if (currentHandler.events && currentHandler.events[name]) { | ||
if (currentHandler.events[name].apply(currentHandler, args) === true) { | ||
eventWasHandled = true; | ||
} else { | ||
return; | ||
} | ||
} | ||
} | ||
// In the case that we got an UnrecognizedURLError as an event with no handler, | ||
// let it bubble up | ||
if (name === 'error' && args[0].name === 'UnrecognizedURLError') { | ||
throw args[0]; | ||
} else if (!eventWasHandled && !ignoreFailure) { | ||
throw new Error("Nothing handled the event '" + name + "'."); | ||
} | ||
} | ||
function getChangelist(oldObject, newObject) { | ||
var key = void 0; | ||
var results = { | ||
all: {}, | ||
changed: {}, | ||
removed: {} | ||
}; | ||
merge(results.all, newObject); | ||
var didChange = false; | ||
coerceQueryParamsToString(oldObject); | ||
coerceQueryParamsToString(newObject); | ||
// Calculate removals | ||
for (key in oldObject) { | ||
if (hasOwnProperty.call(oldObject, key)) { | ||
if (!hasOwnProperty.call(newObject, key)) { | ||
didChange = true; | ||
results.removed[key] = oldObject[key]; | ||
} | ||
var key = void 0; | ||
var results = { | ||
all: {}, | ||
changed: {}, | ||
removed: {} | ||
}; | ||
merge(results.all, newObject); | ||
var didChange = false; | ||
coerceQueryParamsToString(oldObject); | ||
coerceQueryParamsToString(newObject); | ||
// Calculate removals | ||
for (key in oldObject) { | ||
if (hasOwnProperty.call(oldObject, key)) { | ||
if (!hasOwnProperty.call(newObject, key)) { | ||
didChange = true; | ||
results.removed[key] = oldObject[key]; | ||
} | ||
} | ||
} | ||
} | ||
// Calculate changes | ||
for (key in newObject) { | ||
if (hasOwnProperty.call(newObject, key)) { | ||
if (Array.isArray(oldObject[key]) && Array.isArray(newObject[key])) { | ||
if (oldObject[key].length !== newObject[key].length) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} else { | ||
for (var i = 0, l = oldObject[key].length; i < l; i++) { | ||
if (oldObject[key][i] !== newObject[key][i]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
// Calculate changes | ||
for (key in newObject) { | ||
if (hasOwnProperty.call(newObject, key)) { | ||
var oldElement = oldObject[key]; | ||
var newElement = newObject[key]; | ||
if (isArray(oldElement) && isArray(newElement)) { | ||
if (oldElement.length !== newElement.length) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} else { | ||
for (var i = 0, l = oldElement.length; i < l; i++) { | ||
if (oldElement[i] !== newElement[i]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} | ||
} | ||
} else if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} | ||
} | ||
} else if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} | ||
} | ||
return didChange ? results : undefined; | ||
return didChange ? results : undefined; | ||
} | ||
function isArray(obj) { | ||
return Array.isArray(obj); | ||
} | ||
function promiseLabel(label) { | ||
return 'Router: ' + label; | ||
return 'Router: ' + label; | ||
} | ||
function resolveHook(obj, hookName) { | ||
if (!obj) { | ||
return; | ||
} | ||
var underscored = '_' + hookName; | ||
return obj[underscored] && underscored || obj[hookName] && hookName; | ||
} | ||
function callHook(obj, _hookName, arg1, arg2) { | ||
var hookName = resolveHook(obj, _hookName); | ||
return hookName && obj[hookName].call(obj, arg1, arg2); | ||
} | ||
function applyHook(obj, _hookName, args) { | ||
var hookName = resolveHook(obj, _hookName); | ||
if (hookName) { | ||
if (args.length === 0) { | ||
return obj[hookName].call(obj); | ||
} else if (args.length === 1) { | ||
return obj[hookName].call(obj, args[0]); | ||
} else if (args.length === 2) { | ||
return obj[hookName].call(obj, args[0], args[1]); | ||
} else { | ||
return obj[hookName].apply(obj, args); | ||
} | ||
} | ||
} | ||
// export function callHook(obj: any, _hookName: string, arg1?: unknown, arg2?: unknown) { | ||
// let hookName = resolveHook(obj, _hookName); | ||
// return hookName && obj[hookName].call(obj, arg1, arg2); | ||
// } | ||
//# sourceMappingURL=utils.js.map |
@@ -1,267 +0,325 @@ | ||
import { promiseLabel, applyHook, isPromise } from './utils'; | ||
import { Promise } from 'rsvp'; | ||
const DEFAULT_HANDLER = Object.freeze({}); | ||
import { isTransition, prepareResult } from './transition'; | ||
import { isParam, isPromise, merge, promiseLabel } from './utils'; | ||
const stubHandler = { | ||
_handlerName: '', | ||
context: undefined, | ||
handler: '', | ||
names: [], | ||
}; | ||
export const noopGetHandler = () => { | ||
return Promise.resolve(stubHandler); | ||
}; | ||
export const DEFAULT_HANDLER = Object.freeze({ | ||
_handlerName: '', | ||
context: undefined, | ||
handler: '', | ||
names: [], | ||
}); | ||
export default class HandlerInfo { | ||
constructor(props = {}) { | ||
// initialize local properties to ensure consistent object shape | ||
this._handler = DEFAULT_HANDLER; | ||
this._handlerPromise = null; | ||
this.factory = null; // Injected by the handler info factory | ||
this.name = props.name; | ||
for (let prop in props) { | ||
if (prop === 'handler') { | ||
this._processHandler(props.handler); | ||
} else { | ||
this[prop] = props[prop]; | ||
} | ||
constructor(name, handler) { | ||
this.params = {}; | ||
this.isResolved = false; | ||
// initialize local properties to ensure consistent object shape | ||
this._handler = DEFAULT_HANDLER; | ||
this._handlerPromise = undefined; | ||
this.name = name; | ||
if (handler) { | ||
this._processHandler(handler); | ||
} | ||
} | ||
} | ||
getHandler() {} | ||
fetchHandler() { | ||
let handler = this.getHandler(this.name); | ||
return this._processHandler(handler); | ||
} | ||
_processHandler(handler) { | ||
// Setup a handlerPromise so that we can wait for asynchronously loaded handlers | ||
this.handlerPromise = Promise.resolve(handler); | ||
// Wait until the 'handler' property has been updated when chaining to a handler | ||
// that is a promise | ||
if (isPromise(handler)) { | ||
this.handlerPromise = this.handlerPromise.then(h => { | ||
return this.updateHandler(h); | ||
}); | ||
// set to undefined to avoid recursive loop in the handler getter | ||
return (this.handler = undefined); | ||
} else if (handler) { | ||
return this.updateHandler(handler); | ||
serialize(_context) { | ||
return this.params || {}; | ||
} | ||
} | ||
log(payload, message) { | ||
if (payload.log) { | ||
payload.log(this.name + ': ' + message); | ||
resolve(shouldContinue, transition) { | ||
return Promise.resolve(this.handlerPromise, this.promiseLabel('Start handler')) | ||
.then((handler) => this.checkForAbort(shouldContinue, handler), null, this.promiseLabel('Check for abort')) | ||
.then(() => { | ||
return this.runBeforeModelHook(transition); | ||
}, null, this.promiseLabel('Before model')) | ||
.then(() => this.checkForAbort(shouldContinue, null), null, this.promiseLabel("Check if aborted during 'beforeModel' hook")) | ||
.then(() => this.getModel(transition)) | ||
.then(resolvedModel => this.checkForAbort(shouldContinue, resolvedModel), null, this.promiseLabel("Check if aborted in 'model' hook")) | ||
.then(resolvedModel => this.runAfterModelHook(transition, resolvedModel)) | ||
.then(resolvedModel => this.becomeResolved(transition, resolvedModel)); | ||
} | ||
} | ||
promiseLabel(label) { | ||
return promiseLabel("'" + this.name + "' " + label); | ||
} | ||
getUnresolved() { | ||
return this; | ||
} | ||
serialize() { | ||
return this.params || {}; | ||
} | ||
updateHandler(handler) { | ||
// Store the name of the handler on the handler for easy checks later | ||
handler._handlerName = this.name; | ||
return (this.handler = handler); | ||
} | ||
resolve(shouldContinue, payload) { | ||
let checkForAbort = this.checkForAbort.bind(this, shouldContinue); | ||
let beforeModel = this.runBeforeModelHook.bind(this, payload); | ||
let model = this.getModel.bind(this, payload); | ||
let afterModel = this.runAfterModelHook.bind(this, payload); | ||
let becomeResolved = this.becomeResolved.bind(this, payload); | ||
return Promise.resolve( | ||
this.handlerPromise, | ||
this.promiseLabel('Start handler') | ||
) | ||
.then(checkForAbort, null, this.promiseLabel('Check for abort')) | ||
.then(beforeModel, null, this.promiseLabel('Before model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
this.promiseLabel("Check if aborted during 'beforeModel' hook") | ||
) | ||
.then(model, null, this.promiseLabel('Model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
this.promiseLabel("Check if aborted in 'model' hook") | ||
) | ||
.then(afterModel, null, this.promiseLabel('After model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
this.promiseLabel("Check if aborted in 'afterModel' hook") | ||
) | ||
.then(becomeResolved, null, this.promiseLabel('Become resolved')); | ||
} | ||
runBeforeModelHook(payload) { | ||
if (payload.trigger) { | ||
payload.trigger(true, 'willResolveModel', payload, this.handler); | ||
becomeResolved(transition, resolvedContext) { | ||
let params = this.serialize(resolvedContext); | ||
if (transition) { | ||
this.stashResolvedModel(transition, resolvedContext); | ||
transition.params = transition.params || {}; | ||
transition.params[this.name] = params; | ||
} | ||
let context; | ||
let contextsMatch = resolvedContext === this.context; | ||
if ('context' in this || !contextsMatch) { | ||
context = resolvedContext; | ||
} | ||
return new ResolvedHandlerInfo(this.name, this.handler, params, context); | ||
} | ||
return this.runSharedModelHook(payload, 'beforeModel', []); | ||
} | ||
runAfterModelHook(payload, resolvedModel) { | ||
// Stash the resolved model on the payload. | ||
// This makes it possible for users to swap out | ||
// the resolved model in afterModel. | ||
let name = this.name; | ||
this.stashResolvedModel(payload, resolvedModel); | ||
return this.runSharedModelHook(payload, 'afterModel', [resolvedModel]).then( | ||
function() { | ||
// Ignore the fulfilled value returned from afterModel. | ||
// Return the value stashed in resolvedModels, which | ||
// might have been swapped out in afterModel. | ||
return payload.resolvedModels[name]; | ||
}, | ||
null, | ||
this.promiseLabel('Ignore fulfillment value and return model value') | ||
); | ||
} | ||
runSharedModelHook(payload, hookName, args) { | ||
this.log(payload, 'calling ' + hookName + ' hook'); | ||
if (this.queryParams) { | ||
args.push(this.queryParams); | ||
shouldSupercede(other) { | ||
// Prefer this newer handlerInfo over `other` if: | ||
// 1) The other one doesn't exist | ||
// 2) The names don't match | ||
// 3) This handler has a context that doesn't match | ||
// the other one (or the other one doesn't have one). | ||
// 4) This handler has parameters that don't match the other. | ||
if (!other) { | ||
return true; | ||
} | ||
let contextsMatch = other.context === this.context; | ||
return (other.name !== this.name || | ||
('context' in this && !contextsMatch) || | ||
(this.hasOwnProperty('params') && !paramsMatch(this.params, other.params))); | ||
} | ||
args.push(payload); | ||
var result = applyHook(this.handler, hookName, args); | ||
if (result && result.isTransition) { | ||
result = null; | ||
get handler() { | ||
// _handler could be set to either a handler object or undefined, so we | ||
// compare against a default reference to know when it's been set | ||
if (this._handler !== DEFAULT_HANDLER) { | ||
return this._handler; | ||
} | ||
return this.fetchHandler(); | ||
} | ||
return Promise.resolve( | ||
result, | ||
this.promiseLabel('Resolve value returned from one of the model hooks') | ||
); | ||
} | ||
// overridden by subclasses | ||
getModel() {} | ||
checkForAbort(shouldContinue, promiseValue) { | ||
return Promise.resolve( | ||
shouldContinue(), | ||
this.promiseLabel('Check for abort') | ||
).then( | ||
function() { | ||
// We don't care about shouldContinue's resolve value; | ||
// pass along the original value passed to this fn. | ||
return promiseValue; | ||
}, | ||
null, | ||
this.promiseLabel('Ignore fulfillment value and continue') | ||
); | ||
} | ||
stashResolvedModel(payload, resolvedModel) { | ||
payload.resolvedModels = payload.resolvedModels || {}; | ||
payload.resolvedModels[this.name] = resolvedModel; | ||
} | ||
becomeResolved(payload, resolvedContext) { | ||
var params = this.serialize(resolvedContext); | ||
if (payload) { | ||
this.stashResolvedModel(payload, resolvedContext); | ||
payload.params = payload.params || {}; | ||
payload.params[this.name] = params; | ||
set handler(handler) { | ||
this._handler = handler; | ||
} | ||
var resolution = { | ||
name: this.name, | ||
handler: this.handler, | ||
params: params, | ||
}; | ||
// Don't set a context on the resolution unless we actually have one. | ||
var contextsMatch = resolvedContext === this.context; | ||
if ('context' in this || !contextsMatch) { | ||
resolution.context = resolvedContext; | ||
get handlerPromise() { | ||
if (this._handlerPromise) { | ||
return this._handlerPromise; | ||
} | ||
this.fetchHandler(); | ||
return this._handlerPromise; | ||
} | ||
return this.factory('resolved', resolution); | ||
} | ||
shouldSupercede(other) { | ||
// Prefer this newer handlerInfo over `other` if: | ||
// 1) The other one doesn't exist | ||
// 2) The names don't match | ||
// 3) This handler has a context that doesn't match | ||
// the other one (or the other one doesn't have one). | ||
// 4) This handler has parameters that don't match the other. | ||
if (!other) { | ||
return true; | ||
set handlerPromise(handlerPromise) { | ||
this._handlerPromise = handlerPromise; | ||
} | ||
var contextsMatch = other.context === this.context; | ||
return ( | ||
other.name !== this.name || | ||
('context' in this && !contextsMatch) || | ||
(this.hasOwnProperty('params') && !paramsMatch(this.params, other.params)) | ||
); | ||
} | ||
get handler() { | ||
// _handler could be set to either a handler object or undefined, so we | ||
// compare against a default reference to know when it's been set | ||
if (this._handler !== DEFAULT_HANDLER) { | ||
return this._handler; | ||
promiseLabel(label) { | ||
return promiseLabel("'" + this.name + "' " + label); | ||
} | ||
return this.fetchHandler(); | ||
} | ||
set handler(handler) { | ||
return (this._handler = handler); | ||
} | ||
get handlerPromise() { | ||
if (this._handlerPromise !== null) { | ||
return this._handlerPromise; | ||
log(transition, message) { | ||
if (transition.log) { | ||
transition.log(this.name + ': ' + message); | ||
} | ||
} | ||
this.fetchHandler(); | ||
return this._handlerPromise; | ||
} | ||
set handlerPromise(handlerPromise) { | ||
this._handlerPromise = handlerPromise; | ||
return handlerPromise; | ||
} | ||
updateHandler(handler) { | ||
// Store the name of the handler on the handler for easy checks later | ||
handler._handlerName = this.name; | ||
return (this.handler = handler); | ||
} | ||
runBeforeModelHook(transition) { | ||
if (transition.trigger) { | ||
transition.trigger(true, 'willResolveModel', transition, this.handler); | ||
} | ||
let result; | ||
if (this.handler) { | ||
if (this.handler._beforeModel !== undefined) { | ||
result = this.handler._beforeModel(transition); | ||
} | ||
else if (this.handler.beforeModel !== undefined) { | ||
result = this.handler.beforeModel(transition); | ||
} | ||
} | ||
if (isTransition(result)) { | ||
result = null; | ||
} | ||
return Promise.resolve(result); | ||
} | ||
runAfterModelHook(transition, resolvedModel) { | ||
// Stash the resolved model on the payload. | ||
// This makes it possible for users to swap out | ||
// the resolved model in afterModel. | ||
let name = this.name; | ||
this.stashResolvedModel(transition, resolvedModel); | ||
let result; | ||
if (this.handler !== undefined) { | ||
if (this.handler._afterModel !== undefined) { | ||
result = this.handler._afterModel(resolvedModel, transition); | ||
} | ||
else if (this.handler.afterModel !== undefined) { | ||
result = this.handler.afterModel(resolvedModel, transition); | ||
} | ||
} | ||
result = prepareResult(result); | ||
return Promise.resolve(result).then(() => { | ||
// Ignore the fulfilled value returned from afterModel. | ||
// Return the value stashed in resolvedModels, which | ||
// might have been swapped out in afterModel. | ||
return transition.resolvedModels[name]; | ||
}); | ||
} | ||
checkForAbort(shouldContinue, value) { | ||
return Promise.resolve(shouldContinue(), this.promiseLabel('Check for abort')).then(function () { | ||
// We don't care about shouldContinue's resolve value; | ||
// pass along the original value passed to this fn. | ||
return value; | ||
}, null, this.promiseLabel('Ignore fulfillment value and continue')); | ||
} | ||
stashResolvedModel(transition, resolvedModel) { | ||
transition.resolvedModels = transition.resolvedModels || {}; | ||
transition.resolvedModels[this.name] = resolvedModel; | ||
} | ||
fetchHandler() { | ||
let handler = this.getHandler(this.name); | ||
return this._processHandler(handler); | ||
} | ||
_processHandler(handler) { | ||
// Setup a handlerPromise so that we can wait for asynchronously loaded handlers | ||
this.handlerPromise = Promise.resolve(handler); | ||
// Wait until the 'handler' property has been updated when chaining to a handler | ||
// that is a promise | ||
if (isPromise(handler)) { | ||
this.handlerPromise = this.handlerPromise.then(h => { | ||
return this.updateHandler(h); | ||
}); | ||
// set to undefined to avoid recursive loop in the handler getter | ||
return (this.handler = undefined); | ||
} | ||
else if (handler) { | ||
return this.updateHandler(handler); | ||
} | ||
return undefined; | ||
} | ||
} | ||
export class ResolvedHandlerInfo extends HandlerInfo { | ||
constructor(name, handler, params, context) { | ||
super(name, handler); | ||
this.getHandler = (_name) => { | ||
throw new Error('Method not implemented.'); | ||
}; | ||
this.params = params; | ||
this.isResolved = true; | ||
this.context = context; | ||
} | ||
resolve(_shouldContinue, transition) { | ||
// A ResolvedHandlerInfo just resolved with itself. | ||
if (transition && transition.resolvedModels) { | ||
transition.resolvedModels[this.name] = this.context; | ||
} | ||
return Promise.resolve(this, this.promiseLabel('Resolve')); | ||
} | ||
getUnresolved() { | ||
return new UnresolvedHandlerInfoByParam(this.name, noopGetHandler, this.params, this.handler); | ||
} | ||
getModel() { | ||
throw new Error('Method not implemented.'); | ||
} | ||
} | ||
export class UnresolvedHandlerInfoByParam extends HandlerInfo { | ||
constructor(name, getHandler, params, handler) { | ||
super(name, handler); | ||
this.params = {}; | ||
this.params = params; | ||
this.getHandler = getHandler; | ||
} | ||
getUnresolved() { | ||
return this; | ||
} | ||
getModel(transition) { | ||
let fullParams = this.params; | ||
if (transition && transition.queryParams) { | ||
fullParams = {}; | ||
merge(fullParams, this.params); | ||
fullParams.queryParams = transition.queryParams; | ||
} | ||
let handler = this.handler; | ||
let result = undefined; | ||
if (handler._deserialize) { | ||
result = handler._deserialize(fullParams, transition); | ||
} | ||
else if (handler.deserialize) { | ||
result = handler.deserialize(fullParams, transition); | ||
} | ||
else if (handler._model) { | ||
result = handler._model(fullParams, transition); | ||
} | ||
else if (handler.model) { | ||
result = handler.model(fullParams, transition); | ||
} | ||
if (result && isTransition(result)) { | ||
result = undefined; | ||
} | ||
return Promise.resolve(result, this.promiseLabel('Resolve value returned from one of the model hooks')); | ||
} | ||
} | ||
export class UnresolvedHandlerInfoByObject extends HandlerInfo { | ||
constructor(name, names, getHandler, serializer, context) { | ||
super(name); | ||
this.names = []; | ||
this.names = names; | ||
this.getHandler = getHandler; | ||
this.serializer = serializer; | ||
this.context = context; | ||
this.names = this.names || []; | ||
} | ||
getModel(transition) { | ||
this.log(transition, this.name + ': resolving provided model'); | ||
return Promise.resolve(this.context); | ||
} | ||
getUnresolved() { | ||
return this; | ||
} | ||
/** | ||
@private | ||
Serializes a handler using its custom `serialize` method or | ||
by a default that looks up the expected property name from | ||
the dynamic segment. | ||
@param {Object} model the model to be serialized for this handler | ||
*/ | ||
serialize(model) { | ||
let { names, context } = this; | ||
if (!model) { | ||
model = context; | ||
} | ||
let object = {}; | ||
if (isParam(model)) { | ||
object[names[0]] = model; | ||
return object; | ||
} | ||
// Use custom serialize if it exists. | ||
if (this.serializer) { | ||
// invoke this.serializer unbound (getSerializer returns a stateless function) | ||
return this.serializer.call(null, model, names); | ||
} | ||
else if (this.handler) { | ||
if (this.handler._serialize) { | ||
return this.handler._serialize(model, names); | ||
} | ||
if (this.handler.serialize) { | ||
return this.handler.serialize(model, names); | ||
} | ||
} | ||
if (names.length !== 1) { | ||
return; | ||
} | ||
let name = names[0]; | ||
if (/_id$/.test(name)) { | ||
object[name] = model.id; | ||
} | ||
else { | ||
object[name] = model; | ||
} | ||
return object; | ||
} | ||
} | ||
function paramsMatch(a, b) { | ||
if (!a ^ !b) { | ||
// Only one is null. | ||
return false; | ||
} | ||
if (!a) { | ||
// Both must be null. | ||
if (!a !== !b) { | ||
// Only one is null. | ||
return false; | ||
} | ||
if (!a) { | ||
// Both must be null. | ||
return true; | ||
} | ||
// Note: this assumes that both params have the same | ||
// number of keys, but since we're comparing the | ||
// same handlers, they should. | ||
for (let k in a) { | ||
if (a.hasOwnProperty(k) && a[k] !== b[k]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
// Note: this assumes that both params have the same | ||
// number of keys, but since we're comparing the | ||
// same handlers, they should. | ||
for (var k in a) { | ||
if (a.hasOwnProperty(k) && a[k] !== b[k]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
//# sourceMappingURL=handler-info.js.map |
export { default } from './router'; | ||
export { Transition } from './transition'; | ||
//# sourceMappingURL=index.js.map |
import RouteRecognizer from 'route-recognizer'; | ||
import { Promise } from 'rsvp'; | ||
import { | ||
trigger, | ||
log, | ||
slice, | ||
forEach, | ||
merge, | ||
extractQueryParams, | ||
getChangelist, | ||
promiseLabel, | ||
callHook, | ||
} from './utils'; | ||
import TransitionState from './transition-state'; | ||
import { logAbort, Transition } from './transition'; | ||
@@ -19,455 +7,304 @@ import TransitionAbortedError from './transition-aborted-error'; | ||
import URLTransitionIntent from './transition-intent/url-transition-intent'; | ||
var pop = Array.prototype.pop; | ||
import TransitionState from './transition-state'; | ||
import { extractQueryParams, forEach, getChangelist, log, merge, promiseLabel, trigger, } from './utils'; | ||
// Todo: | ||
// this should just be an abstract class | ||
class Router { | ||
constructor(options = {}) { | ||
this.getHandler = options.getHandler || this.getHandler; | ||
this.getSerializer = options.getSerializer || this.getSerializer; | ||
this.updateURL = options.updateURL || this.updateURL; | ||
this.replaceURL = options.replaceURL || this.replaceURL; | ||
this.didTransition = options.didTransition || this.didTransition; | ||
this.willTransition = options.willTransition || this.willTransition; | ||
this.delegate = options.delegate || this.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.dslCallBacks = []; // NOTE: set by Ember | ||
this.state = undefined; | ||
this.activeTransition = undefined; | ||
this._changedQueryParams = undefined; | ||
this.oldState = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this.currentSequence = 0; | ||
this.recognizer = new RouteRecognizer(); | ||
this.reset(); | ||
} | ||
/** | ||
The main entry point into the router. The API is essentially | ||
the same as the `map` method in `route-recognizer`. | ||
This method extracts the String handler at the last `.to()` | ||
call and uses it as the name of the whole route. | ||
@param {Function} callback | ||
*/ | ||
map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
this.recognizer.map(callback, function(recognizer, routes) { | ||
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { | ||
var route = routes[i]; | ||
recognizer.add(routes, { as: route.handler }); | ||
proceed = | ||
route.path === '/' || | ||
route.path === '' || | ||
route.handler.slice(-6) === '.index'; | ||
} | ||
}); | ||
} | ||
hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
} | ||
getHandler() {} | ||
getSerializer() {} | ||
queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
let router = this; | ||
fireQueryParamDidChange(this, newState, changelist); | ||
if (!wasTransitioning && this.activeTransition) { | ||
// One of the handlers in queryParamsDidChange | ||
// caused a transition. Just return that transition. | ||
return this.activeTransition; | ||
} else { | ||
// Running queryParamsDidChange didn't change anything. | ||
// Just update query params and be on our way. | ||
// We have to return a noop transition that will | ||
// perform a URL update at the end. This gives | ||
// the user the ability to set the url update | ||
// method (default is replaceState). | ||
let newTransition = new Transition(this); | ||
newTransition.queryParamsOnly = true; | ||
oldState.queryParams = finalizeQueryParamChange( | ||
this, | ||
newState.handlerInfos, | ||
newState.queryParams, | ||
newTransition | ||
); | ||
newTransition.promise = newTransition.promise.then( | ||
function(result) { | ||
updateURL(newTransition, oldState, true); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
return result; | ||
}, | ||
null, | ||
promiseLabel('Transition complete') | ||
); | ||
return newTransition; | ||
constructor(options) { | ||
this.state = undefined; | ||
this.oldState = undefined; | ||
this.activeTransition = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this._changedQueryParams = undefined; | ||
this.currentSequence = 0; | ||
this.dslCallBacks = []; // TODO: set by ember. Please refactor. | ||
this.getHandler = options.getHandler; | ||
this.getSerializer = options.getSerializer; | ||
this.updateURL = options.updateURL; | ||
this.replaceURL = | ||
options.replaceURL || | ||
((url) => { | ||
this.updateURL(url); | ||
}); | ||
this.didTransition = options.didTransition; | ||
this.willTransition = options.willTransition; | ||
this.delegate = options.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.recognizer = new RouteRecognizer(); | ||
this.reset(); | ||
} | ||
} | ||
// NOTE: this doesn't really belong here, but here | ||
// it shall remain until our ES6 transpiler can | ||
// handle cyclical deps. | ||
transitionByIntent(intent /*, isIntermediate*/) { | ||
try { | ||
return getTransitionByIntent.apply(this, arguments); | ||
} catch (e) { | ||
return new Transition(this, intent, null, e); | ||
/** | ||
The main entry point into the router. The API is essentially | ||
the same as the `map` method in `route-recognizer`. | ||
This method extracts the String handler at the last `.to()` | ||
call and uses it as the name of the whole route. | ||
@param {Function} callback | ||
*/ | ||
map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
this.recognizer.map(callback, function (recognizer, routes) { | ||
for (let i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { | ||
let route = routes[i]; | ||
let handler = route.handler; | ||
recognizer.add(routes, { as: handler }); | ||
proceed = route.path === '/' || route.path === '' || handler.slice(-6) === '.index'; | ||
} | ||
}); | ||
} | ||
} | ||
/** | ||
Clears the current and target route handlers and triggers exit | ||
on each of them starting at the leaf and traversing up through | ||
its ancestors. | ||
*/ | ||
reset() { | ||
if (this.state) { | ||
forEach(this.state.handlerInfos.slice().reverse(), function(handlerInfo) { | ||
var handler = handlerInfo.handler; | ||
callHook(handler, 'exit'); | ||
}); | ||
hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
} | ||
this.oldState = undefined; | ||
this.state = new TransitionState(); | ||
this.currentHandlerInfos = null; | ||
} | ||
/** | ||
var handler = handlerInfo.handler; | ||
The entry point for handling a change to the URL (usually | ||
via the back and forward button). | ||
Returns an Array of handlers and the parameters associated | ||
with those parameters. | ||
@param {String} url a URL to process | ||
@return {Array} an Array of `[handler, parameter]` tuples | ||
*/ | ||
handleURL(...args) { | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
let url = args[0]; | ||
if (url.charAt(0) !== '/') { | ||
args[0] = '/' + url; | ||
queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
fireQueryParamDidChange(this, newState, changelist); | ||
if (!wasTransitioning && this.activeTransition) { | ||
// One of the handlers in queryParamsDidChange | ||
// caused a transition. Just return that transition. | ||
return this.activeTransition; | ||
} | ||
else { | ||
// Running queryParamsDidChange didn't change anything. | ||
// Just update query params and be on our way. | ||
// We have to return a noop transition that will | ||
// perform a URL update at the end. This gives | ||
// the user the ability to set the url update | ||
// method (default is replaceState). | ||
let newTransition = new Transition(this, undefined, undefined); | ||
newTransition.queryParamsOnly = true; | ||
oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition); | ||
newTransition.promise = newTransition.promise.then((result) => { | ||
updateURL(newTransition, oldState); | ||
if (this.didTransition) { | ||
this.didTransition(this.currentHandlerInfos); | ||
} | ||
return result; | ||
}, null, promiseLabel('Transition complete')); | ||
return newTransition; | ||
} | ||
} | ||
return doTransition(this, args).method(null); | ||
} | ||
/** | ||
Hook point for updating the URL. | ||
@param {String} url a URL to update to | ||
*/ | ||
updateURL() { | ||
throw new Error('updateURL is not implemented'); | ||
} | ||
/** | ||
Hook point for replacing the current URL, i.e. with replaceState | ||
By default this behaves the same as `updateURL` | ||
@param {String} url a URL to update to | ||
*/ | ||
replaceURL(url) { | ||
this.updateURL(url); | ||
} | ||
/** | ||
Transition into the specified named route. | ||
If necessary, trigger the exit callback on any handlers | ||
that are no longer represented by the target route. | ||
@param {String} name the name of the route | ||
*/ | ||
transitionTo(/*name*/) { | ||
return doTransition(this, arguments); | ||
} | ||
intermediateTransitionTo(/*name*/) { | ||
return doTransition(this, arguments, true); | ||
} | ||
refresh(pivotHandler) { | ||
let previousTransition = this.activeTransition; | ||
let state = previousTransition ? previousTransition.state : this.state; | ||
let handlerInfos = state.handlerInfos; | ||
log(this, 'Starting a refresh transition'); | ||
let intent = new NamedTransitionIntent({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
pivotHandler: pivotHandler || handlerInfos[0].handler, | ||
contexts: [], // TODO collect contexts...? | ||
queryParams: this._changedQueryParams || state.queryParams || {}, | ||
}); | ||
let newTransition = this.transitionByIntent(intent, false); | ||
// if the previous transition is a replace transition, that needs to be preserved | ||
if (previousTransition && previousTransition.urlMethod === 'replace') { | ||
newTransition.method(previousTransition.urlMethod); | ||
// NOTE: this doesn't really belong here, but here | ||
// it shall remain until our ES6 transpiler can | ||
// handle cyclical deps. | ||
transitionByIntent(intent, isIntermediate) { | ||
try { | ||
return getTransitionByIntent.apply(this, [intent, isIntermediate]); | ||
} | ||
catch (e) { | ||
return new Transition(this, intent, undefined, e, undefined); | ||
} | ||
} | ||
return newTransition; | ||
} | ||
/** | ||
Identical to `transitionTo` except that the current URL will be replaced | ||
if possible. | ||
This method is intended primarily for use with `replaceState`. | ||
@param {String} name the name of the route | ||
*/ | ||
replaceWith(/*name*/) { | ||
return doTransition(this, arguments).method('replace'); | ||
} | ||
/** | ||
Take a named route and context objects and generate a | ||
URL. | ||
@param {String} name the name of the route to generate | ||
a URL for | ||
@param {...Object} objects a list of objects to serialize | ||
@return {String} a URL | ||
*/ | ||
generate(handlerName) { | ||
let partitionedArgs = extractQueryParams(slice.call(arguments, 1)), | ||
suppliedParams = partitionedArgs[0], | ||
queryParams = partitionedArgs[1]; | ||
// Construct a TransitionIntent with the provided params | ||
// and apply it to the present state of the router. | ||
let intent = new NamedTransitionIntent({ | ||
name: handlerName, | ||
contexts: suppliedParams, | ||
}); | ||
let state = intent.applyToState( | ||
this.state, | ||
this.recognizer, | ||
this.getHandler, | ||
null, | ||
this.getSerializer | ||
); | ||
let params = {}; | ||
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
var handlerParams = handlerInfo.serialize(); | ||
merge(params, handlerParams); | ||
/** | ||
Clears the current and target route handlers and triggers exit | ||
on each of them starting at the leaf and traversing up through | ||
its ancestors. | ||
*/ | ||
reset() { | ||
if (this.state) { | ||
forEach(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { | ||
let handler = handlerInfo.handler; | ||
if (handler !== undefined) { | ||
if (handler._exit !== undefined) { | ||
handler._exit(); | ||
} | ||
else if (handler.exit !== undefined) { | ||
handler.exit(); | ||
} | ||
} | ||
return true; | ||
}); | ||
} | ||
this.oldState = undefined; | ||
this.state = new TransitionState(); | ||
this.currentHandlerInfos = undefined; | ||
} | ||
params.queryParams = queryParams; | ||
return this.recognizer.generate(handlerName, params); | ||
} | ||
applyIntent(handlerName, contexts) { | ||
let intent = new NamedTransitionIntent({ | ||
name: handlerName, | ||
contexts: contexts, | ||
}); | ||
let state = | ||
(this.activeTransition && this.activeTransition.state) || this.state; | ||
return intent.applyToState( | ||
state, | ||
this.recognizer, | ||
this.getHandler, | ||
null, | ||
this.getSerializer | ||
); | ||
} | ||
isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
let state = _state || this.state, | ||
targetHandlerInfos = state.handlerInfos, | ||
handlerInfo, | ||
len; | ||
if (!targetHandlerInfos.length) { | ||
return false; | ||
/** | ||
let handler = handlerInfo.handler; | ||
The entry point for handling a change to the URL (usually | ||
via the back and forward button). | ||
Returns an Array of handlers and the parameters associated | ||
with those parameters. | ||
@param {String} url a URL to process | ||
@return {Array} an Array of `[handler, parameter]` tuples | ||
*/ | ||
handleURL(url) { | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
if (url.charAt(0) !== '/') { | ||
url = '/' + url; | ||
} | ||
return doTransition(this, url).method(null); | ||
} | ||
let targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
let recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
let index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
handlerInfo = targetHandlerInfos[index]; | ||
if (handlerInfo.name === handlerName) { | ||
break; | ||
} | ||
/** | ||
Transition into the specified named route. | ||
If necessary, trigger the exit callback on any handlers | ||
that are no longer represented by the target route. | ||
@param {String} name the name of the route | ||
*/ | ||
transitionTo(name, ...contexts) { | ||
if (typeof name === 'object') { | ||
contexts.push(name); | ||
return doTransition(this, undefined, contexts, false); | ||
} | ||
return doTransition(this, name, contexts); | ||
} | ||
if (index === recogHandlers.length) { | ||
// The provided route name isn't even in the route hierarchy. | ||
return false; | ||
intermediateTransitionTo(name, ...args) { | ||
return doTransition(this, name, args, true); | ||
} | ||
let testState = new TransitionState(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
let intent = new NamedTransitionIntent({ | ||
name: targetHandler, | ||
contexts: contexts, | ||
}); | ||
let newState = intent.applyToHandlers( | ||
testState, | ||
recogHandlers, | ||
this.getHandler, | ||
targetHandler, | ||
true, | ||
true, | ||
this.getSerializer | ||
); | ||
let handlersEqual = handlerInfosEqual( | ||
newState.handlerInfos, | ||
testState.handlerInfos | ||
); | ||
if (!queryParams || !handlersEqual) { | ||
return handlersEqual; | ||
refresh(pivotHandler) { | ||
let previousTransition = this.activeTransition; | ||
let state = previousTransition ? previousTransition.state : this.state; | ||
let handlerInfos = state.handlerInfos; | ||
if (pivotHandler === undefined) { | ||
pivotHandler = handlerInfos[0].handler; | ||
} | ||
log(this, 'Starting a refresh transition'); | ||
let name = handlerInfos[handlerInfos.length - 1].name; | ||
let intent = new NamedTransitionIntent(name, pivotHandler, [], this._changedQueryParams || state.queryParams); | ||
let newTransition = this.transitionByIntent(intent, false); | ||
// if the previous transition is a replace transition, that needs to be preserved | ||
if (previousTransition && previousTransition.urlMethod === 'replace') { | ||
newTransition.method(previousTransition.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
// Get a hash of QPs that will still be active on new route | ||
let activeQPsOnNewHandler = {}; | ||
merge(activeQPsOnNewHandler, queryParams); | ||
let activeQueryParams = state.queryParams; | ||
for (var key in activeQueryParams) { | ||
if ( | ||
activeQueryParams.hasOwnProperty(key) && | ||
activeQPsOnNewHandler.hasOwnProperty(key) | ||
) { | ||
activeQPsOnNewHandler[key] = activeQueryParams[key]; | ||
} | ||
/** | ||
Identical to `transitionTo` except that the current URL will be replaced | ||
if possible. | ||
This method is intended primarily for use with `replaceState`. | ||
@param {String} name the name of the route | ||
*/ | ||
replaceWith(name) { | ||
return doTransition(this, name).method('replace'); | ||
} | ||
return handlersEqual && !getChangelist(activeQPsOnNewHandler, queryParams); | ||
} | ||
isActive(handlerName, ...args) { | ||
let partitionedArgs = extractQueryParams(args); | ||
return this.isActiveIntent( | ||
handlerName, | ||
partitionedArgs[0], | ||
partitionedArgs[1] | ||
); | ||
} | ||
trigger(...args) { | ||
trigger(this, this.currentHandlerInfos, false, args); | ||
} | ||
/** | ||
Take a named route and context objects and generate a | ||
URL. | ||
@param {String} name the name of the route to generate | ||
a URL for | ||
@param {...Object} objects a list of objects to serialize | ||
@return {String} a URL | ||
*/ | ||
generate(handlerName, ...args) { | ||
let partitionedArgs = extractQueryParams(args), suppliedParams = partitionedArgs[0], queryParams = partitionedArgs[1]; | ||
// Construct a TransitionIntent with the provided params | ||
// and apply it to the present state of the router. | ||
let intent = new NamedTransitionIntent(handlerName, undefined, suppliedParams); | ||
let state = intent.applyToState(this.state, this.recognizer, this.getHandler, false, this.getSerializer); | ||
let params = {}; | ||
for (let i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
let handlerInfo = state.handlerInfos[i]; | ||
let handlerParams = handlerInfo.serialize(); | ||
merge(params, handlerParams); | ||
} | ||
params.queryParams = queryParams; | ||
return this.recognizer.generate(handlerName, params); | ||
} | ||
applyIntent(handlerName, contexts) { | ||
let intent = new NamedTransitionIntent(handlerName, undefined, contexts); | ||
let state = (this.activeTransition && this.activeTransition.state) || this.state; | ||
return intent.applyToState(state, this.recognizer, this.getHandler, false, this.getSerializer); | ||
} | ||
isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
let state = _state || this.state, targetHandlerInfos = state.handlerInfos, handlerInfo, len; | ||
if (!targetHandlerInfos.length) { | ||
return false; | ||
} | ||
let targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
let recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
let index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
handlerInfo = targetHandlerInfos[index]; | ||
if (handlerInfo.name === handlerName) { | ||
break; | ||
} | ||
} | ||
if (index === recogHandlers.length) { | ||
// The provided route name isn't even in the route hierarchy. | ||
return false; | ||
} | ||
let testState = new TransitionState(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
let intent = new NamedTransitionIntent(targetHandler, undefined, contexts); | ||
let newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true, this.getSerializer); | ||
let handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos); | ||
if (!queryParams || !handlersEqual) { | ||
return handlersEqual; | ||
} | ||
// Get a hash of QPs that will still be active on new route | ||
let activeQPsOnNewHandler = {}; | ||
merge(activeQPsOnNewHandler, queryParams); | ||
let activeQueryParams = state.queryParams; | ||
for (let key in activeQueryParams) { | ||
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) { | ||
activeQPsOnNewHandler[key] = activeQueryParams[key]; | ||
} | ||
} | ||
return handlersEqual && !getChangelist(activeQPsOnNewHandler, queryParams); | ||
} | ||
isActive(handlerName, ...args) { | ||
let partitionedArgs = extractQueryParams(args); | ||
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); | ||
} | ||
trigger(name, ...args) { | ||
trigger(this, this.currentHandlerInfos, false, name, ...args); | ||
} | ||
} | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition; | ||
var newState = intent.applyToState( | ||
oldState, | ||
this.recognizer, | ||
this.getHandler, | ||
isIntermediate, | ||
this.getSerializer | ||
); | ||
var queryParamChangelist = getChangelist( | ||
oldState.queryParams, | ||
newState.queryParams | ||
); | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition( | ||
queryParamChangelist, | ||
wasTransitioning, | ||
oldState, | ||
newState | ||
); | ||
if (newTransition) { | ||
let wasTransitioning = !!this.activeTransition; | ||
let oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
let newTransition; | ||
let newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate, this.getSerializer); | ||
let queryParamChangelist = getChangelist(oldState.queryParams, newState.queryParams); | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState); | ||
if (newTransition) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new Transition(this, undefined, undefined); | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
// Create a new transition to the destination route. | ||
newTransition = new Transition(this, intent, newState, undefined, this.activeTransition); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if (handlerInfosSameExceptQueryParams(newState.handlerInfos, oldState.handlerInfos)) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new Transition(this); | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
// Create a new transition to the destination route. | ||
newTransition = new Transition( | ||
this, | ||
intent, | ||
newState, | ||
undefined, | ||
this.activeTransition | ||
); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if ( | ||
handlerInfosSameExceptQueryParams( | ||
newState.handlerInfos, | ||
oldState.handlerInfos | ||
) | ||
) { | ||
newTransition.queryParamsOnly = true; | ||
} | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then( | ||
function(result) { | ||
return finalizeTransition(newTransition, result.state); | ||
}, | ||
null, | ||
promiseLabel('Settle transition promise when transition is finalized') | ||
); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then((result) => { | ||
return finalizeTransition(newTransition, result); | ||
}, null, promiseLabel('Settle transition promise when transition is finalized')); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
} | ||
/** | ||
@@ -479,18 +316,12 @@ @private | ||
function fireQueryParamDidChange(router, newState, queryParamChangelist) { | ||
// If queryParams changed trigger event | ||
if (queryParamChangelist) { | ||
// This is a little hacky but we need some way of storing | ||
// changed query params given that no activeTransition | ||
// is guaranteed to have occurred. | ||
router._changedQueryParams = queryParamChangelist.all; | ||
trigger(router, newState.handlerInfos, true, [ | ||
'queryParamsDidChange', | ||
queryParamChangelist.changed, | ||
queryParamChangelist.all, | ||
queryParamChangelist.removed, | ||
]); | ||
router._changedQueryParams = null; | ||
} | ||
// If queryParams changed trigger event | ||
if (queryParamChangelist) { | ||
// This is a little hacky but we need some way of storing | ||
// changed query params given that no activeTransition | ||
// is guaranteed to have occurred. | ||
router._changedQueryParams = queryParamChangelist.all; | ||
trigger(router, newState.handlerInfos, true, 'queryParamsDidChange', queryParamChangelist.changed, queryParamChangelist.all, queryParamChangelist.removed); | ||
router._changedQueryParams = undefined; | ||
} | ||
} | ||
/** | ||
@@ -538,54 +369,51 @@ @private | ||
function setupContexts(router, newState, transition) { | ||
var partition = partitionHandlers(router.state, newState); | ||
var i, l, handler; | ||
for (i = 0, l = partition.exited.length; i < l; i++) { | ||
handler = partition.exited[i].handler; | ||
delete handler.context; | ||
callHook(handler, 'reset', true, transition); | ||
callHook(handler, 'exit', transition); | ||
} | ||
var oldState = (router.oldState = router.state); | ||
router.state = newState; | ||
var currentHandlerInfos = (router.currentHandlerInfos = partition.unchanged.slice()); | ||
try { | ||
for (i = 0, l = partition.reset.length; i < l; i++) { | ||
handler = partition.reset[i].handler; | ||
callHook(handler, 'reset', false, transition); | ||
let partition = partitionHandlers(router.state, newState); | ||
let i, l, handler; | ||
for (i = 0, l = partition.exited.length; i < l; i++) { | ||
handler = partition.exited[i].handler; | ||
delete handler.context; | ||
if (handler !== undefined) { | ||
if (handler._reset !== undefined) { | ||
handler._reset(true, transition); | ||
} | ||
else if (handler.reset !== undefined) { | ||
handler.reset(true, transition); | ||
} | ||
if (handler._exit !== undefined) { | ||
handler._exit(transition); | ||
} | ||
else if (handler.exit !== undefined) { | ||
handler.exit(transition); | ||
} | ||
} | ||
} | ||
for (i = 0, l = partition.updatedContext.length; i < l; i++) { | ||
handlerEnteredOrUpdated( | ||
currentHandlerInfos, | ||
partition.updatedContext[i], | ||
false, | ||
transition | ||
); | ||
let oldState = (router.oldState = router.state); | ||
router.state = newState; | ||
let currentHandlerInfos = (router.currentHandlerInfos = partition.unchanged.slice()); | ||
try { | ||
for (i = 0, l = partition.reset.length; i < l; i++) { | ||
handler = partition.reset[i].handler; | ||
if (handler !== undefined) { | ||
if (handler._reset !== undefined) { | ||
handler._reset(false, transition); | ||
} | ||
else if (handler.reset !== undefined) { | ||
handler.reset(false, transition); | ||
} | ||
} | ||
} | ||
for (i = 0, l = partition.updatedContext.length; i < l; i++) { | ||
handlerEnteredOrUpdated(currentHandlerInfos, partition.updatedContext[i], false, transition); | ||
} | ||
for (i = 0, l = partition.entered.length; i < l; i++) { | ||
handlerEnteredOrUpdated(currentHandlerInfos, partition.entered[i], true, transition); | ||
} | ||
} | ||
for (i = 0, l = partition.entered.length; i < l; i++) { | ||
handlerEnteredOrUpdated( | ||
currentHandlerInfos, | ||
partition.entered[i], | ||
true, | ||
transition | ||
); | ||
catch (e) { | ||
router.state = oldState; | ||
router.currentHandlerInfos = oldState.handlerInfos; | ||
throw e; | ||
} | ||
} catch (e) { | ||
router.state = oldState; | ||
router.currentHandlerInfos = oldState.handlerInfos; | ||
throw e; | ||
} | ||
router.state.queryParams = finalizeQueryParamChange( | ||
router, | ||
currentHandlerInfos, | ||
newState.queryParams, | ||
transition | ||
); | ||
router.state.queryParams = finalizeQueryParamChange(router, currentHandlerInfos, newState.queryParams, transition); | ||
} | ||
/** | ||
@@ -597,43 +425,44 @@ @private | ||
*/ | ||
function handlerEnteredOrUpdated( | ||
currentHandlerInfos, | ||
handlerInfo, | ||
enter, | ||
transition | ||
) { | ||
var handler = handlerInfo.handler, | ||
context = handlerInfo.context; | ||
function _handlerEnteredOrUpdated(handler) { | ||
if (enter) { | ||
callHook(handler, 'enter', transition); | ||
function handlerEnteredOrUpdated(currentHandlerInfos, handlerInfo, enter, transition) { | ||
let handler = handlerInfo.handler, context = handlerInfo.context; | ||
function _handlerEnteredOrUpdated(handler) { | ||
if (enter) { | ||
if (handler._enter !== undefined) { | ||
handler._enter(transition); | ||
} | ||
else if (handler.enter !== undefined) { | ||
handler.enter(transition); | ||
} | ||
} | ||
if (transition && transition.isAborted) { | ||
throw new TransitionAbortedError(); | ||
} | ||
handler.context = context; | ||
if (handler._contextDidChange !== undefined) { | ||
handler._contextDidChange(); | ||
} | ||
else if (handler.contextDidChange !== undefined) { | ||
handler.contextDidChange(); | ||
} | ||
if (handler._setup !== undefined) { | ||
handler._setup(context, transition); | ||
} | ||
else if (handler.setup !== undefined) { | ||
handler.setup(context, transition); | ||
} | ||
if (transition && transition.isAborted) { | ||
throw new TransitionAbortedError(); | ||
} | ||
currentHandlerInfos.push(handlerInfo); | ||
return handler; | ||
} | ||
if (transition && transition.isAborted) { | ||
throw new TransitionAbortedError(); | ||
// If the handler doesn't exist, it means we haven't resolved the handler promise yet | ||
if (!handler) { | ||
handlerInfo.handlerPromise = handlerInfo.handlerPromise.then(_handlerEnteredOrUpdated); | ||
} | ||
handler.context = context; | ||
callHook(handler, 'contextDidChange'); | ||
callHook(handler, 'setup', context, transition); | ||
if (transition && transition.isAborted) { | ||
throw new TransitionAbortedError(); | ||
else { | ||
_handlerEnteredOrUpdated(handler); | ||
} | ||
currentHandlerInfos.push(handlerInfo); | ||
} | ||
// If the handler doesn't exist, it means we haven't resolved the handler promise yet | ||
if (!handler) { | ||
handlerInfo.handlerPromise = handlerInfo.handlerPromise.then( | ||
_handlerEnteredOrUpdated | ||
); | ||
} else { | ||
_handlerEnteredOrUpdated(handler); | ||
} | ||
return true; | ||
return true; | ||
} | ||
/** | ||
@@ -681,119 +510,91 @@ @private | ||
function partitionHandlers(oldState, newState) { | ||
var oldHandlers = oldState.handlerInfos; | ||
var newHandlers = newState.handlerInfos; | ||
var handlers = { | ||
updatedContext: [], | ||
exited: [], | ||
entered: [], | ||
unchanged: [], | ||
reset: undefined, | ||
}; | ||
var handlerChanged, | ||
contextChanged = false, | ||
i, | ||
l; | ||
for (i = 0, l = newHandlers.length; i < l; i++) { | ||
var oldHandler = oldHandlers[i], | ||
newHandler = newHandlers[i]; | ||
if (!oldHandler || oldHandler.handler !== newHandler.handler) { | ||
handlerChanged = true; | ||
let oldHandlerInfos = oldState.handlerInfos; | ||
let newHandlerInfos = newState.handlerInfos; | ||
let handlers = { | ||
updatedContext: [], | ||
exited: [], | ||
entered: [], | ||
unchanged: [], | ||
reset: [], | ||
}; | ||
let handlerChanged, contextChanged = false, i, l; | ||
for (i = 0, l = newHandlerInfos.length; i < l; i++) { | ||
let oldHandlerInfo = oldHandlerInfos[i], newHandlerInfo = newHandlerInfos[i]; | ||
if (!oldHandlerInfo || oldHandlerInfo.handler !== newHandlerInfo.handler) { | ||
handlerChanged = true; | ||
} | ||
if (handlerChanged) { | ||
handlers.entered.push(newHandlerInfo); | ||
if (oldHandlerInfo) { | ||
handlers.exited.unshift(oldHandlerInfo); | ||
} | ||
} | ||
else if (contextChanged || oldHandlerInfo.context !== newHandlerInfo.context) { | ||
contextChanged = true; | ||
handlers.updatedContext.push(newHandlerInfo); | ||
} | ||
else { | ||
handlers.unchanged.push(oldHandlerInfo); | ||
} | ||
} | ||
if (handlerChanged) { | ||
handlers.entered.push(newHandler); | ||
if (oldHandler) { | ||
handlers.exited.unshift(oldHandler); | ||
} | ||
} else if (contextChanged || oldHandler.context !== newHandler.context) { | ||
contextChanged = true; | ||
handlers.updatedContext.push(newHandler); | ||
} else { | ||
handlers.unchanged.push(oldHandler); | ||
for (i = newHandlerInfos.length, l = oldHandlerInfos.length; i < l; i++) { | ||
handlers.exited.unshift(oldHandlerInfos[i]); | ||
} | ||
} | ||
for (i = newHandlers.length, l = oldHandlers.length; i < l; i++) { | ||
handlers.exited.unshift(oldHandlers[i]); | ||
} | ||
handlers.reset = handlers.updatedContext.slice(); | ||
handlers.reset.reverse(); | ||
return handlers; | ||
handlers.reset = handlers.updatedContext.slice(); | ||
handlers.reset.reverse(); | ||
return handlers; | ||
} | ||
function updateURL(transition, state /*, inputUrl*/) { | ||
var urlMethod = transition.urlMethod; | ||
if (!urlMethod) { | ||
return; | ||
} | ||
var router = transition.router, | ||
handlerInfos = state.handlerInfos, | ||
handlerName = handlerInfos[handlerInfos.length - 1].name, | ||
params = {}; | ||
for (var i = handlerInfos.length - 1; i >= 0; --i) { | ||
var handlerInfo = handlerInfos[i]; | ||
merge(params, handlerInfo.params); | ||
if (handlerInfo.handler.inaccessibleByURL) { | ||
urlMethod = null; | ||
function updateURL(transition, state, _inputUrl) { | ||
let urlMethod = transition.urlMethod; | ||
if (!urlMethod) { | ||
return; | ||
} | ||
} | ||
if (urlMethod) { | ||
params.queryParams = transition._visibleQueryParams || state.queryParams; | ||
var url = router.recognizer.generate(handlerName, params); | ||
// transitions during the initial transition must always use replaceURL. | ||
// When the app boots, you are at a url, e.g. /foo. If some handler | ||
// redirects to bar as part of the initial transition, you don't want to | ||
// add a history entry for /foo. If you do, pressing back will immediately | ||
// hit the redirect again and take you back to /bar, thus killing the back | ||
// button | ||
var initial = transition.isCausedByInitialTransition; | ||
// say you are at / and you click a link to route /foo. In /foo's | ||
// handler, the transition is aborted using replacewith('/bar'). | ||
// Because the current url is still /, the history entry for / is | ||
// removed from the history. Clicking back will take you to the page | ||
// you were on before /, which is often not even the app, thus killing | ||
// the back button. That's why updateURL is always correct for an | ||
// aborting transition that's not the initial transition | ||
var replaceAndNotAborting = | ||
urlMethod === 'replace' && !transition.isCausedByAbortingTransition; | ||
// because calling refresh causes an aborted transition, this needs to be | ||
// special cased - if the initial transition is a replace transition, the | ||
// urlMethod should be honored here. | ||
var isQueryParamsRefreshTransition = | ||
transition.queryParamsOnly && urlMethod === 'replace'; | ||
// say you are at / and you a `replaceWith(/foo)` is called. Then, that | ||
// transition is aborted with `replaceWith(/bar)`. At the end, we should | ||
// end up with /bar replacing /. We are replacing the replace. We only | ||
// will replace the initial route if all subsequent aborts are also | ||
// replaces. However, there is some ambiguity around the correct behavior | ||
// here. | ||
var replacingReplace = | ||
urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition; | ||
if ( | ||
initial || | ||
replaceAndNotAborting || | ||
isQueryParamsRefreshTransition || | ||
replacingReplace | ||
) { | ||
router.replaceURL(url); | ||
} else { | ||
router.updateURL(url); | ||
let { router } = transition; | ||
let { handlerInfos } = state; | ||
let { name: handlerName } = handlerInfos[handlerInfos.length - 1]; | ||
let params = {}; | ||
for (let i = handlerInfos.length - 1; i >= 0; --i) { | ||
let handlerInfo = handlerInfos[i]; | ||
merge(params, handlerInfo.params); | ||
if (handlerInfo.handler.inaccessibleByURL) { | ||
urlMethod = null; | ||
} | ||
} | ||
} | ||
if (urlMethod) { | ||
params.queryParams = transition._visibleQueryParams || state.queryParams; | ||
let url = router.recognizer.generate(handlerName, params); | ||
// transitions during the initial transition must always use replaceURL. | ||
// When the app boots, you are at a url, e.g. /foo. If some handler | ||
// redirects to bar as part of the initial transition, you don't want to | ||
// add a history entry for /foo. If you do, pressing back will immediately | ||
// hit the redirect again and take you back to /bar, thus killing the back | ||
// button | ||
let initial = transition.isCausedByInitialTransition; | ||
// say you are at / and you click a link to route /foo. In /foo's | ||
// handler, the transition is aborted using replacewith('/bar'). | ||
// Because the current url is still /, the history entry for / is | ||
// removed from the history. Clicking back will take you to the page | ||
// you were on before /, which is often not even the app, thus killing | ||
// the back button. That's why updateURL is always correct for an | ||
// aborting transition that's not the initial transition | ||
let replaceAndNotAborting = urlMethod === 'replace' && !transition.isCausedByAbortingTransition; | ||
// because calling refresh causes an aborted transition, this needs to be | ||
// special cased - if the initial transition is a replace transition, the | ||
// urlMethod should be honored here. | ||
let isQueryParamsRefreshTransition = transition.queryParamsOnly && urlMethod === 'replace'; | ||
// say you are at / and you a `replaceWith(/foo)` is called. Then, that | ||
// transition is aborted with `replaceWith(/bar)`. At the end, we should | ||
// end up with /bar replacing /. We are replacing the replace. We only | ||
// will replace the initial route if all subsequent aborts are also | ||
// replaces. However, there is some ambiguity around the correct behavior | ||
// here. | ||
let replacingReplace = urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition; | ||
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition || replacingReplace) { | ||
router.replaceURL(url); | ||
} | ||
else { | ||
router.updateURL(url); | ||
} | ||
} | ||
} | ||
/** | ||
@@ -806,55 +607,34 @@ @private | ||
function finalizeTransition(transition, newState) { | ||
try { | ||
log( | ||
transition.router, | ||
transition.sequence, | ||
'Resolved all models on destination route; finalizing transition.' | ||
); | ||
var router = transition.router, | ||
handlerInfos = newState.handlerInfos; | ||
// Run all the necessary enter/setup/exit hooks | ||
setupContexts(router, newState, transition); | ||
// Check if a redirect occurred in enter/setup | ||
if (transition.isAborted) { | ||
// TODO: cleaner way? distinguish b/w targetHandlerInfos? | ||
router.state.handlerInfos = router.currentHandlerInfos; | ||
return Promise.reject(logAbort(transition)); | ||
try { | ||
log(transition.router, transition.sequence, 'Resolved all models on destination route; finalizing transition.'); | ||
let router = transition.router, handlerInfos = newState.handlerInfos; | ||
// Run all the necessary enter/setup/exit hooks | ||
setupContexts(router, newState, transition); | ||
// Check if a redirect occurred in enter/setup | ||
if (transition.isAborted) { | ||
// TODO: cleaner way? distinguish b/w targetHandlerInfos? | ||
router.state.handlerInfos = router.currentHandlerInfos; | ||
return Promise.reject(logAbort(transition)); | ||
} | ||
updateURL(transition, newState, transition.intent.url); | ||
transition.isActive = false; | ||
router.activeTransition = undefined; | ||
trigger(router, router.currentHandlerInfos, true, 'didTransition'); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
log(router, transition.sequence, 'TRANSITION COMPLETE.'); | ||
// Resolve with the final handler. | ||
return handlerInfos[handlerInfos.length - 1].handler; | ||
} | ||
updateURL(transition, newState, transition.intent.url); | ||
transition.isActive = false; | ||
router.activeTransition = null; | ||
trigger(router, router.currentHandlerInfos, true, ['didTransition']); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
catch (e) { | ||
if (!(e instanceof TransitionAbortedError)) { | ||
//let erroneousHandler = handlerInfos.pop(); | ||
let infos = transition.state.handlerInfos; | ||
transition.trigger(true, 'error', e, transition, infos[infos.length - 1].handler); | ||
transition.abort(); | ||
} | ||
throw e; | ||
} | ||
log(router, transition.sequence, 'TRANSITION COMPLETE.'); | ||
// Resolve with the final handler. | ||
return handlerInfos[handlerInfos.length - 1].handler; | ||
} catch (e) { | ||
if (!(e instanceof TransitionAbortedError)) { | ||
//var erroneousHandler = handlerInfos.pop(); | ||
var infos = transition.state.handlerInfos; | ||
transition.trigger( | ||
true, | ||
'error', | ||
e, | ||
transition, | ||
infos[infos.length - 1].handler | ||
); | ||
transition.abort(); | ||
} | ||
throw e; | ||
} | ||
} | ||
/** | ||
@@ -871,166 +651,119 @@ @private | ||
*/ | ||
function doTransition(router, args, isIntermediate) { | ||
// Normalize blank transitions to root URL transitions. | ||
var name = args[0] || '/'; | ||
var lastArg = args[args.length - 1]; | ||
var queryParams = {}; | ||
if (lastArg && lastArg.hasOwnProperty('queryParams')) { | ||
queryParams = pop.call(args).queryParams; | ||
} | ||
var intent; | ||
if (args.length === 0) { | ||
log(router, 'Updating query params'); | ||
// A query param update is really just a transition | ||
// into the route you're already on. | ||
var handlerInfos = router.state.handlerInfos; | ||
intent = new NamedTransitionIntent({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
contexts: [], | ||
queryParams: queryParams, | ||
}); | ||
} else if (name.charAt(0) === '/') { | ||
log(router, 'Attempting URL transition to ' + name); | ||
intent = new URLTransitionIntent({ url: name }); | ||
} else { | ||
log(router, 'Attempting transition to ' + name); | ||
intent = new NamedTransitionIntent({ | ||
name: args[0], | ||
contexts: slice.call(args, 1), | ||
queryParams: queryParams, | ||
}); | ||
} | ||
return router.transitionByIntent(intent, isIntermediate); | ||
function doTransition(router, name, modelsArray = [], isIntermediate = false) { | ||
let lastArg = modelsArray[modelsArray.length - 1]; | ||
let queryParams = {}; | ||
if (lastArg !== undefined && lastArg.hasOwnProperty('queryParams')) { | ||
queryParams = modelsArray.pop().queryParams; | ||
} | ||
let intent; | ||
if (name === undefined) { | ||
log(router, 'Updating query params'); | ||
// A query param update is really just a transition | ||
// into the route you're already on. | ||
let { handlerInfos } = router.state; | ||
intent = new NamedTransitionIntent(handlerInfos[handlerInfos.length - 1].name, undefined, [], queryParams); | ||
} | ||
else if (name.charAt(0) === '/') { | ||
log(router, 'Attempting URL transition to ' + name); | ||
intent = new URLTransitionIntent(name); | ||
} | ||
else { | ||
log(router, 'Attempting transition to ' + name); | ||
intent = new NamedTransitionIntent(name, undefined, modelsArray, queryParams); | ||
} | ||
return router.transitionByIntent(intent, isIntermediate); | ||
} | ||
function handlerInfosEqual(handlerInfos, otherHandlerInfos) { | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i] !== otherHandlerInfos[i]) { | ||
return false; | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
for (let i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i] !== otherHandlerInfos[i]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function handlerInfosSameExceptQueryParams(handlerInfos, otherHandlerInfos) { | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i].name !== otherHandlerInfos[i].name) { | ||
return false; | ||
if (handlerInfos.length !== otherHandlerInfos.length) { | ||
return false; | ||
} | ||
if (!paramsEqual(handlerInfos[i].params, otherHandlerInfos[i].params)) { | ||
return false; | ||
for (let i = 0, len = handlerInfos.length; i < len; ++i) { | ||
if (handlerInfos[i].name !== otherHandlerInfos[i].name) { | ||
return false; | ||
} | ||
if (!paramsEqual(handlerInfos[i].params, otherHandlerInfos[i].params)) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
return true; | ||
} | ||
function paramsEqual(params, otherParams) { | ||
if (!params && !otherParams) { | ||
if (!params && !otherParams) { | ||
return true; | ||
} | ||
else if ((!params && !!otherParams) || (!!params && !otherParams)) { | ||
// one is falsy but other is not; | ||
return false; | ||
} | ||
let keys = Object.keys(params); | ||
let otherKeys = Object.keys(otherParams); | ||
if (keys.length !== otherKeys.length) { | ||
return false; | ||
} | ||
for (let i = 0, len = keys.length; i < len; ++i) { | ||
let key = keys[i]; | ||
if (params[key] !== otherParams[key]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} else if ((!params && !!otherParams) || (!!params && !otherParams)) { | ||
// one is falsy but other is not; | ||
return false; | ||
} | ||
var keys = Object.keys(params); | ||
var otherKeys = Object.keys(otherParams); | ||
if (keys.length !== otherKeys.length) { | ||
return false; | ||
} | ||
for (var i = 0, len = keys.length; i < len; ++i) { | ||
var key = keys[i]; | ||
if (params[key] !== otherParams[key]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function finalizeQueryParamChange( | ||
router, | ||
resolvedHandlers, | ||
newQueryParams, | ||
transition | ||
) { | ||
// We fire a finalizeQueryParamChange event which | ||
// gives the new route hierarchy a chance to tell | ||
// us which query params it's consuming and what | ||
// their final values are. If a query param is | ||
// no longer consumed in the final route hierarchy, | ||
// its serialized segment will be removed | ||
// from the URL. | ||
for (var k in newQueryParams) { | ||
if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) { | ||
delete newQueryParams[k]; | ||
function finalizeQueryParamChange(router, resolvedHandlers, newQueryParams, transition) { | ||
// We fire a finalizeQueryParamChange event which | ||
// gives the new route hierarchy a chance to tell | ||
// us which query params it's consuming and what | ||
// their final values are. If a query param is | ||
// no longer consumed in the final route hierarchy, | ||
// its serialized segment will be removed | ||
// from the URL. | ||
for (let k in newQueryParams) { | ||
if (newQueryParams.hasOwnProperty(k) && newQueryParams[k] === null) { | ||
delete newQueryParams[k]; | ||
} | ||
} | ||
} | ||
var finalQueryParamsArray = []; | ||
trigger(router, resolvedHandlers, true, [ | ||
'finalizeQueryParamChange', | ||
newQueryParams, | ||
finalQueryParamsArray, | ||
transition, | ||
]); | ||
if (transition) { | ||
transition._visibleQueryParams = {}; | ||
} | ||
var finalQueryParams = {}; | ||
for (var i = 0, len = finalQueryParamsArray.length; i < len; ++i) { | ||
var qp = finalQueryParamsArray[i]; | ||
finalQueryParams[qp.key] = qp.value; | ||
if (transition && qp.visible !== false) { | ||
transition._visibleQueryParams[qp.key] = qp.value; | ||
let finalQueryParamsArray = []; | ||
trigger(router, resolvedHandlers, true, 'finalizeQueryParamChange', newQueryParams, finalQueryParamsArray, transition); | ||
if (transition) { | ||
transition._visibleQueryParams = {}; | ||
} | ||
} | ||
return finalQueryParams; | ||
let finalQueryParams = {}; | ||
for (let i = 0, len = finalQueryParamsArray.length; i < len; ++i) { | ||
let qp = finalQueryParamsArray[i]; | ||
finalQueryParams[qp.key] = qp.value; | ||
if (transition && qp.visible !== false) { | ||
transition._visibleQueryParams[qp.key] = qp.value; | ||
} | ||
} | ||
return finalQueryParams; | ||
} | ||
function notifyExistingHandlers(router, newState, newTransition) { | ||
var oldHandlers = router.state.handlerInfos, | ||
changing = [], | ||
i, | ||
oldHandlerLen, | ||
oldHandler, | ||
newHandler; | ||
oldHandlerLen = oldHandlers.length; | ||
for (i = 0; i < oldHandlerLen; i++) { | ||
oldHandler = oldHandlers[i]; | ||
newHandler = newState.handlerInfos[i]; | ||
if (!newHandler || oldHandler.name !== newHandler.name) { | ||
break; | ||
let oldHandlers = router.state.handlerInfos, changing = [], i, oldHandlerLen, oldHandler, newHandler; | ||
oldHandlerLen = oldHandlers.length; | ||
for (i = 0; i < oldHandlerLen; i++) { | ||
oldHandler = oldHandlers[i]; | ||
newHandler = newState.handlerInfos[i]; | ||
if (!newHandler || oldHandler.name !== newHandler.name) { | ||
break; | ||
} | ||
if (!newHandler.isResolved) { | ||
changing.push(oldHandler); | ||
} | ||
} | ||
if (!newHandler.isResolved) { | ||
changing.push(oldHandler); | ||
trigger(router, oldHandlers, true, 'willTransition', newTransition); | ||
if (router.willTransition) { | ||
router.willTransition(oldHandlers, newState.handlerInfos, newTransition); | ||
} | ||
} | ||
trigger(router, oldHandlers, true, ['willTransition', newTransition]); | ||
if (router.willTransition) { | ||
router.willTransition(oldHandlers, newState.handlerInfos, newTransition); | ||
} | ||
} | ||
export default Router; | ||
//# sourceMappingURL=router.js.map |
@@ -1,25 +0,18 @@ | ||
function TransitionAbortedError(message) { | ||
if (!(this instanceof TransitionAbortedError)) { | ||
return new TransitionAbortedError(message); | ||
} | ||
var error = Error.call(this, message); | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, TransitionAbortedError); | ||
} else { | ||
this.stack = error.stack; | ||
} | ||
this.description = error.description; | ||
this.fileName = error.fileName; | ||
this.lineNumber = error.lineNumber; | ||
this.message = error.message || 'TransitionAborted'; | ||
this.name = 'TransitionAborted'; | ||
this.number = error.number; | ||
this.code = error.code; | ||
} | ||
TransitionAbortedError.prototype = Object.create(Error.prototype); | ||
const TransitionAbortedError = (function () { | ||
TransitionAbortedError.prototype = Object.create(Error.prototype); | ||
TransitionAbortedError.prototype.constructor = TransitionAbortedError; | ||
function TransitionAbortedError(message) { | ||
let error = Error.call(this, message); | ||
this.name = 'TransitionAborted'; | ||
this.message = message || 'TransitionAborted'; | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, TransitionAbortedError); | ||
} | ||
else { | ||
this.stack = error.stack; | ||
} | ||
} | ||
return TransitionAbortedError; | ||
})(); | ||
export default TransitionAbortedError; | ||
//# sourceMappingURL=transition-aborted-error.js.map |
@@ -1,5 +0,6 @@ | ||
export default class TransitionIntent { | ||
constructor() { | ||
this.data = this.data || {}; | ||
} | ||
export class TransitionIntent { | ||
constructor(data) { | ||
this.data = data || {}; | ||
} | ||
} | ||
//# sourceMappingURL=transition-intent.js.map |
@@ -1,261 +0,159 @@ | ||
import TransitionIntent from '../transition-intent'; | ||
import { UnresolvedHandlerInfoByObject, UnresolvedHandlerInfoByParam, } from '../handler-info'; | ||
import { TransitionIntent } from '../transition-intent'; | ||
import TransitionState from '../transition-state'; | ||
import handlerInfoFactory from '../handler-info/factory'; | ||
import { isParam, extractQueryParams, merge } from '../utils'; | ||
import { extractQueryParams, isParam, merge } from '../utils'; | ||
export default class NamedTransitionIntent extends TransitionIntent { | ||
constructor(props) { | ||
super(props); | ||
this.name = props.name; | ||
this.pivotHandler = props.pivotHandler; | ||
this.contexts = props.contexts || []; | ||
this.queryParams = props.queryParams; | ||
} | ||
applyToState( | ||
oldState, | ||
recognizer, | ||
getHandler, | ||
isIntermediate, | ||
getSerializer | ||
) { | ||
var partitionedArgs = extractQueryParams([this.name].concat(this.contexts)), | ||
pureArgs = partitionedArgs[0], | ||
handlers = recognizer.handlersFor(pureArgs[0]); | ||
var targetRouteName = handlers[handlers.length - 1].handler; | ||
return this.applyToHandlers( | ||
oldState, | ||
handlers, | ||
getHandler, | ||
targetRouteName, | ||
isIntermediate, | ||
null, | ||
getSerializer | ||
); | ||
} | ||
applyToHandlers( | ||
oldState, | ||
handlers, | ||
getHandler, | ||
targetRouteName, | ||
isIntermediate, | ||
checkingIfActive, | ||
getSerializer | ||
) { | ||
var i, len; | ||
var newState = new TransitionState(); | ||
var objects = this.contexts.slice(0); | ||
var invalidateIndex = handlers.length; | ||
// Pivot handlers are provided for refresh transitions | ||
if (this.pivotHandler) { | ||
for (i = 0, len = handlers.length; i < len; ++i) { | ||
if (handlers[i].handler === this.pivotHandler._handlerName) { | ||
invalidateIndex = i; | ||
break; | ||
} | ||
} | ||
constructor(name, pivotHandler, contexts = [], queryParams = {}) { | ||
super(); | ||
this.preTransitionState = undefined; | ||
this.name = name; | ||
this.pivotHandler = pivotHandler; | ||
this.contexts = contexts; | ||
this.queryParams = queryParams; | ||
} | ||
for (i = handlers.length - 1; i >= 0; --i) { | ||
var result = handlers[i]; | ||
var name = result.handler; | ||
var oldHandlerInfo = oldState.handlerInfos[i]; | ||
var newHandlerInfo = null; | ||
if (result.names.length > 0) { | ||
if (i >= invalidateIndex) { | ||
newHandlerInfo = this.createParamHandlerInfo( | ||
name, | ||
getHandler, | ||
result.names, | ||
objects, | ||
oldHandlerInfo | ||
); | ||
} else { | ||
var serializer = getSerializer(name); | ||
newHandlerInfo = this.getHandlerInfoForDynamicSegment( | ||
name, | ||
getHandler, | ||
result.names, | ||
objects, | ||
oldHandlerInfo, | ||
targetRouteName, | ||
i, | ||
serializer | ||
); | ||
applyToState(oldState, recognizer, getHandler, isIntermediate, getSerializer) { | ||
// TODO: WTF fix me | ||
let partitionedArgs = extractQueryParams([this.name].concat(this.contexts)), pureArgs = partitionedArgs[0], handlers = recognizer.handlersFor(pureArgs[0]); | ||
let targetRouteName = handlers[handlers.length - 1].handler; | ||
return this.applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate, false, getSerializer); | ||
} | ||
applyToHandlers(oldState, handlers, getHandler, targetRouteName, isIntermediate, checkingIfActive, getSerializer) { | ||
let i, len; | ||
let newState = new TransitionState(); | ||
let objects = this.contexts.slice(0); | ||
let invalidateIndex = handlers.length; | ||
// Pivot handlers are provided for refresh transitions | ||
if (this.pivotHandler) { | ||
for (i = 0, len = handlers.length; i < len; ++i) { | ||
if (handlers[i].handler === this.pivotHandler._handlerName) { | ||
invalidateIndex = i; | ||
break; | ||
} | ||
} | ||
} | ||
} else { | ||
// This route has no dynamic segment. | ||
// Therefore treat as a param-based handlerInfo | ||
// with empty params. This will cause the `model` | ||
// hook to be called with empty params, which is desirable. | ||
newHandlerInfo = this.createParamHandlerInfo( | ||
name, | ||
getHandler, | ||
result.names, | ||
objects, | ||
oldHandlerInfo | ||
); | ||
} | ||
if (checkingIfActive) { | ||
// If we're performing an isActive check, we want to | ||
// serialize URL params with the provided context, but | ||
// ignore mismatches between old and new context. | ||
newHandlerInfo = newHandlerInfo.becomeResolved( | ||
null, | ||
newHandlerInfo.context | ||
); | ||
var oldContext = oldHandlerInfo && oldHandlerInfo.context; | ||
if ( | ||
result.names.length > 0 && | ||
'context' in oldHandlerInfo && | ||
newHandlerInfo.context === oldContext | ||
) { | ||
// If contexts match in isActive test, assume params also match. | ||
// This allows for flexibility in not requiring that every last | ||
// handler provide a `serialize` method | ||
newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params; | ||
for (i = handlers.length - 1; i >= 0; --i) { | ||
let result = handlers[i]; | ||
let name = result.handler; | ||
let oldHandlerInfo = oldState.handlerInfos[i]; | ||
let newHandlerInfo = null; | ||
if (result.names.length > 0) { | ||
if (i >= invalidateIndex) { | ||
newHandlerInfo = this.createParamHandlerInfo(name, getHandler, result.names, objects, oldHandlerInfo); | ||
} | ||
else { | ||
let serializer = getSerializer(name); | ||
newHandlerInfo = this.getHandlerInfoForDynamicSegment(name, getHandler, result.names, objects, oldHandlerInfo, targetRouteName, i, serializer); | ||
} | ||
} | ||
else { | ||
// This route has no dynamic segment. | ||
// Therefore treat as a param-based handlerInfo | ||
// with empty params. This will cause the `model` | ||
// hook to be called with empty params, which is desirable. | ||
newHandlerInfo = this.createParamHandlerInfo(name, getHandler, result.names, objects, oldHandlerInfo); | ||
} | ||
if (checkingIfActive) { | ||
// If we're performing an isActive check, we want to | ||
// serialize URL params with the provided context, but | ||
// ignore mismatches between old and new context. | ||
newHandlerInfo = newHandlerInfo.becomeResolved(null, newHandlerInfo.context); | ||
let oldContext = oldHandlerInfo && oldHandlerInfo.context; | ||
if (result.names.length > 0 && | ||
oldHandlerInfo.context !== undefined && | ||
newHandlerInfo.context === oldContext) { | ||
// If contexts match in isActive test, assume params also match. | ||
// This allows for flexibility in not requiring that every last | ||
// handler provide a `serialize` method | ||
newHandlerInfo.params = oldHandlerInfo && oldHandlerInfo.params; | ||
} | ||
newHandlerInfo.context = oldContext; | ||
} | ||
let handlerToUse = oldHandlerInfo; | ||
if (i >= invalidateIndex || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
invalidateIndex = Math.min(i, invalidateIndex); | ||
handlerToUse = newHandlerInfo; | ||
} | ||
if (isIntermediate && !checkingIfActive) { | ||
handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context); | ||
} | ||
newState.handlerInfos.unshift(handlerToUse); | ||
} | ||
newHandlerInfo.context = oldContext; | ||
} | ||
var handlerToUse = oldHandlerInfo; | ||
if ( | ||
i >= invalidateIndex || | ||
newHandlerInfo.shouldSupercede(oldHandlerInfo) | ||
) { | ||
invalidateIndex = Math.min(i, invalidateIndex); | ||
handlerToUse = newHandlerInfo; | ||
} | ||
if (isIntermediate && !checkingIfActive) { | ||
handlerToUse = handlerToUse.becomeResolved(null, handlerToUse.context); | ||
} | ||
newState.handlerInfos.unshift(handlerToUse); | ||
if (objects.length > 0) { | ||
throw new Error('More context objects were passed than there are dynamic segments for the route: ' + | ||
targetRouteName); | ||
} | ||
if (!isIntermediate) { | ||
this.invalidateChildren(newState.handlerInfos, invalidateIndex); | ||
} | ||
merge(newState.queryParams, this.queryParams || {}); | ||
return newState; | ||
} | ||
if (objects.length > 0) { | ||
throw new Error( | ||
'More context objects were passed than there are dynamic segments for the route: ' + | ||
targetRouteName | ||
); | ||
invalidateChildren(handlerInfos, invalidateIndex) { | ||
for (let i = invalidateIndex, l = handlerInfos.length; i < l; ++i) { | ||
let handlerInfo = handlerInfos[i]; | ||
handlerInfos[i] = handlerInfo.getUnresolved(); | ||
} | ||
} | ||
if (!isIntermediate) { | ||
this.invalidateChildren(newState.handlerInfos, invalidateIndex); | ||
getHandlerInfoForDynamicSegment(name, getHandler, names, objects, oldHandlerInfo, _targetRouteName, i, serializer) { | ||
let objectToUse; | ||
if (objects.length > 0) { | ||
// Use the objects provided for this transition. | ||
objectToUse = objects[objects.length - 1]; | ||
if (isParam(objectToUse)) { | ||
return this.createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo); | ||
} | ||
else { | ||
objects.pop(); | ||
} | ||
} | ||
else if (oldHandlerInfo && oldHandlerInfo.name === name) { | ||
// Reuse the matching oldHandlerInfo | ||
return oldHandlerInfo; | ||
} | ||
else { | ||
if (this.preTransitionState) { | ||
let preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i]; | ||
objectToUse = preTransitionHandlerInfo && preTransitionHandlerInfo.context; | ||
} | ||
else { | ||
// Ideally we should throw this error to provide maximal | ||
// information to the user that not enough context objects | ||
// were provided, but this proves too cumbersome in Ember | ||
// in cases where inner template helpers are evaluated | ||
// before parent helpers un-render, in which cases this | ||
// error somewhat prematurely fires. | ||
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]"); | ||
return oldHandlerInfo; | ||
} | ||
} | ||
return new UnresolvedHandlerInfoByObject(name, names, getHandler, serializer, objectToUse); | ||
} | ||
merge(newState.queryParams, this.queryParams || {}); | ||
return newState; | ||
} | ||
invalidateChildren(handlerInfos, invalidateIndex) { | ||
for (var i = invalidateIndex, l = handlerInfos.length; i < l; ++i) { | ||
var handlerInfo = handlerInfos[i]; | ||
handlerInfos[i] = handlerInfo.getUnresolved(); | ||
} | ||
} | ||
getHandlerInfoForDynamicSegment( | ||
name, | ||
getHandler, | ||
names, | ||
objects, | ||
oldHandlerInfo, | ||
targetRouteName, | ||
i, | ||
serializer | ||
) { | ||
var objectToUse; | ||
if (objects.length > 0) { | ||
// Use the objects provided for this transition. | ||
objectToUse = objects[objects.length - 1]; | ||
if (isParam(objectToUse)) { | ||
return this.createParamHandlerInfo( | ||
name, | ||
getHandler, | ||
names, | ||
objects, | ||
oldHandlerInfo | ||
); | ||
} else { | ||
objects.pop(); | ||
} | ||
} else if (oldHandlerInfo && oldHandlerInfo.name === name) { | ||
// Reuse the matching oldHandlerInfo | ||
return oldHandlerInfo; | ||
} else { | ||
if (this.preTransitionState) { | ||
var preTransitionHandlerInfo = this.preTransitionState.handlerInfos[i]; | ||
objectToUse = | ||
preTransitionHandlerInfo && preTransitionHandlerInfo.context; | ||
} else { | ||
// Ideally we should throw this error to provide maximal | ||
// information to the user that not enough context objects | ||
// were provided, but this proves too cumbersome in Ember | ||
// in cases where inner template helpers are evaluated | ||
// before parent helpers un-render, in which cases this | ||
// error somewhat prematurely fires. | ||
//throw new Error("Not enough context objects were provided to complete a transition to " + targetRouteName + ". Specifically, the " + name + " route needs an object that can be serialized into its dynamic URL segments [" + names.join(', ') + "]"); | ||
return oldHandlerInfo; | ||
} | ||
} | ||
return handlerInfoFactory('object', { | ||
name: name, | ||
getHandler: getHandler, | ||
serializer: serializer, | ||
context: objectToUse, | ||
names: names, | ||
}); | ||
} | ||
createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo) { | ||
var params = {}; | ||
// Soak up all the provided string/numbers | ||
var numNames = names.length; | ||
while (numNames--) { | ||
// Only use old params if the names match with the new handler | ||
var oldParams = | ||
(oldHandlerInfo && | ||
name === oldHandlerInfo.name && | ||
oldHandlerInfo.params) || | ||
{}; | ||
var peek = objects[objects.length - 1]; | ||
var paramName = names[numNames]; | ||
if (isParam(peek)) { | ||
params[paramName] = '' + objects.pop(); | ||
} else { | ||
// If we're here, this means only some of the params | ||
// were string/number params, so try and use a param | ||
// value from a previous handler. | ||
if (oldParams.hasOwnProperty(paramName)) { | ||
params[paramName] = oldParams[paramName]; | ||
} else { | ||
throw new Error( | ||
"You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + | ||
name | ||
); | ||
createParamHandlerInfo(name, getHandler, names, objects, oldHandlerInfo) { | ||
let params = {}; | ||
// Soak up all the provided string/numbers | ||
let numNames = names.length; | ||
while (numNames--) { | ||
// Only use old params if the names match with the new handler | ||
let oldParams = (oldHandlerInfo && name === oldHandlerInfo.name && oldHandlerInfo.params) || {}; | ||
let peek = objects[objects.length - 1]; | ||
let paramName = names[numNames]; | ||
if (isParam(peek)) { | ||
params[paramName] = '' + objects.pop(); | ||
} | ||
else { | ||
// If we're here, this means only some of the params | ||
// were string/number params, so try and use a param | ||
// value from a previous handler. | ||
if (oldParams.hasOwnProperty(paramName)) { | ||
params[paramName] = oldParams[paramName]; | ||
} | ||
else { | ||
throw new Error("You didn't provide enough string/numeric parameters to satisfy all of the dynamic segments for route " + | ||
name); | ||
} | ||
} | ||
} | ||
} | ||
return new UnresolvedHandlerInfoByParam(name, getHandler, params); | ||
} | ||
return handlerInfoFactory('param', { | ||
name: name, | ||
getHandler: getHandler, | ||
params: params, | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=named-transition-intent.js.map |
@@ -1,71 +0,55 @@ | ||
import TransitionIntent from '../transition-intent'; | ||
import { UnresolvedHandlerInfoByParam } from '../handler-info'; | ||
import { TransitionIntent } from '../transition-intent'; | ||
import TransitionState from '../transition-state'; | ||
import handlerInfoFactory from '../handler-info/factory'; | ||
import UnrecognizedURLError from '../unrecognized-url-error'; | ||
import { merge } from '../utils'; | ||
import UnrecognizedURLError from '../unrecognized-url-error'; | ||
export default class URLTransitionIntent extends TransitionIntent { | ||
constructor(props) { | ||
super(props); | ||
this.url = props.url; | ||
} | ||
applyToState(oldState, recognizer, getHandler) { | ||
var newState = new TransitionState(); | ||
var results = recognizer.recognize(this.url), | ||
i, | ||
len; | ||
if (!results) { | ||
throw new UnrecognizedURLError(this.url); | ||
constructor(url) { | ||
super(); | ||
this.url = url; | ||
this.preTransitionState = undefined; | ||
} | ||
var statesDiffer = false; | ||
var url = this.url; | ||
// Checks if a handler is accessible by URL. If it is not, an error is thrown. | ||
// For the case where the handler is loaded asynchronously, the error will be | ||
// thrown once it is loaded. | ||
function checkHandlerAccessibility(handler) { | ||
if (handler && handler.inaccessibleByURL) { | ||
throw new UnrecognizedURLError(url); | ||
} | ||
return handler; | ||
applyToState(oldState, recognizer, getHandler) { | ||
let newState = new TransitionState(); | ||
let results = recognizer.recognize(this.url), i, len; | ||
if (!results) { | ||
throw new UnrecognizedURLError(this.url); | ||
} | ||
let statesDiffer = false; | ||
let url = this.url; | ||
// Checks if a handler is accessible by URL. If it is not, an error is thrown. | ||
// For the case where the handler is loaded asynchronously, the error will be | ||
// thrown once it is loaded. | ||
function checkHandlerAccessibility(handler) { | ||
if (handler && handler.inaccessibleByURL) { | ||
throw new UnrecognizedURLError(url); | ||
} | ||
return handler; | ||
} | ||
for (i = 0, len = results.length; i < len; ++i) { | ||
let result = results[i]; | ||
let name = result.handler; | ||
let newHandlerInfo = new UnresolvedHandlerInfoByParam(name, getHandler, result.params); | ||
let handler = newHandlerInfo.handler; | ||
if (handler) { | ||
checkHandlerAccessibility(handler); | ||
} | ||
else { | ||
// If the hanlder is being loaded asynchronously, check if we can | ||
// access it after it has resolved | ||
newHandlerInfo.handlerPromise = newHandlerInfo.handlerPromise.then(checkHandlerAccessibility); | ||
} | ||
let oldHandlerInfo = oldState.handlerInfos[i]; | ||
if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
statesDiffer = true; | ||
newState.handlerInfos[i] = newHandlerInfo; | ||
} | ||
else { | ||
newState.handlerInfos[i] = oldHandlerInfo; | ||
} | ||
} | ||
merge(newState.queryParams, results.queryParams); | ||
return newState; | ||
} | ||
for (i = 0, len = results.length; i < len; ++i) { | ||
var result = results[i]; | ||
var name = result.handler; | ||
var newHandlerInfo = handlerInfoFactory('param', { | ||
name: name, | ||
getHandler: getHandler, | ||
params: result.params, | ||
}); | ||
var handler = newHandlerInfo.handler; | ||
if (handler) { | ||
checkHandlerAccessibility(handler); | ||
} else { | ||
// If the hanlder is being loaded asynchronously, check if we can | ||
// access it after it has resolved | ||
newHandlerInfo.handlerPromise = newHandlerInfo.handlerPromise.then( | ||
checkHandlerAccessibility | ||
); | ||
} | ||
var oldHandlerInfo = oldState.handlerInfos[i]; | ||
if (statesDiffer || newHandlerInfo.shouldSupercede(oldHandlerInfo)) { | ||
statesDiffer = true; | ||
newState.handlerInfos[i] = newHandlerInfo; | ||
} else { | ||
newState.handlerInfos[i] = oldHandlerInfo; | ||
} | ||
} | ||
merge(newState.queryParams, results.queryParams); | ||
return newState; | ||
} | ||
} | ||
//# sourceMappingURL=url-transition-intent.js.map |
@@ -1,112 +0,98 @@ | ||
import { forEach, promiseLabel, callHook } from './utils'; | ||
import { Promise } from 'rsvp'; | ||
import { forEach, promiseLabel } from './utils'; | ||
export default class TransitionState { | ||
constructor() { | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
promiseLabel(label) { | ||
let targetName = ''; | ||
forEach(this.handlerInfos, function(handlerInfo) { | ||
if (targetName !== '') { | ||
targetName += '.'; | ||
} | ||
targetName += handlerInfo.name; | ||
}); | ||
return promiseLabel("'" + targetName + "': " + label); | ||
} | ||
resolve(shouldContinue, payload = {}) { | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
let params = this.params; | ||
forEach(this.handlerInfos, function(handlerInfo) { | ||
params[handlerInfo.name] = handlerInfo.params || {}; | ||
}); | ||
payload.resolveIndex = 0; | ||
let currentState = this; | ||
let wasAborted = false; | ||
// The prelude RSVP.resolve() asyncs us into the promise land. | ||
return Promise.resolve(null, this.promiseLabel('Start transition')) | ||
.then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler')) | ||
.catch(handleError, this.promiseLabel('Handle error')); | ||
function innerShouldContinue() { | ||
return Promise.resolve( | ||
shouldContinue(), | ||
currentState.promiseLabel('Check if should continue') | ||
).catch(function(reason) { | ||
// We distinguish between errors that occurred | ||
// during resolution (e.g. before"Model/model/afterModel), | ||
// and aborts due to a rejecting promise from shouldContinue(). | ||
wasAborted = true; | ||
return Promise.reject(reason); | ||
}, currentState.promiseLabel('Handle abort')); | ||
constructor() { | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
function handleError(error) { | ||
// This is the only possible | ||
// reject value of TransitionState#resolve | ||
let handlerInfos = currentState.handlerInfos; | ||
let errorHandlerIndex = | ||
payload.resolveIndex >= handlerInfos.length | ||
? handlerInfos.length - 1 | ||
: payload.resolveIndex; | ||
return Promise.reject({ | ||
error: error, | ||
handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler, | ||
wasAborted: wasAborted, | ||
state: currentState, | ||
}); | ||
promiseLabel(label) { | ||
let targetName = ''; | ||
forEach(this.handlerInfos, function (handlerInfo) { | ||
if (targetName !== '') { | ||
targetName += '.'; | ||
} | ||
targetName += handlerInfo.name; | ||
return true; | ||
}); | ||
return promiseLabel("'" + targetName + "': " + label); | ||
} | ||
function proceed(resolvedHandlerInfo) { | ||
let wasAlreadyResolved = | ||
currentState.handlerInfos[payload.resolveIndex].isResolved; | ||
// Swap the previously unresolved handlerInfo with | ||
// the resolved handlerInfo | ||
currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo; | ||
if (!wasAlreadyResolved) { | ||
// Call the redirect hook. The reason we call it here | ||
// vs. afterModel is so that redirects into child | ||
// routes don't re-run the model hooks for this | ||
// already-resolved route. | ||
let handler = resolvedHandlerInfo.handler; | ||
callHook(handler, 'redirect', resolvedHandlerInfo.context, payload); | ||
} | ||
// Proceed after ensuring that the redirect hook | ||
// didn't abort this transition by transitioning elsewhere. | ||
return innerShouldContinue().then( | ||
resolveOneHandlerInfo, | ||
null, | ||
currentState.promiseLabel('Resolve handler') | ||
); | ||
resolve(shouldContinue, transition) { | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
let params = this.params; | ||
forEach(this.handlerInfos, handlerInfo => { | ||
params[handlerInfo.name] = handlerInfo.params || {}; | ||
return true; | ||
}); | ||
transition.resolveIndex = 0; | ||
let currentState = this; | ||
let wasAborted = false; | ||
// The prelude RSVP.resolve() asyncs us into the promise land. | ||
return Promise.resolve(null, this.promiseLabel('Start transition')) | ||
.then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler')) | ||
.catch(handleError, this.promiseLabel('Handle error')); | ||
function innerShouldContinue() { | ||
return Promise.resolve(shouldContinue(), currentState.promiseLabel('Check if should continue')).catch(function (reason) { | ||
// We distinguish between errors that occurred | ||
// during resolution (e.g. before"Model/model/afterModel), | ||
// and aborts due to a rejecting promise from shouldContinue(). | ||
wasAborted = true; | ||
return Promise.reject(reason); | ||
}, currentState.promiseLabel('Handle abort')); | ||
} | ||
function handleError(error) { | ||
// This is the only possible | ||
// reject value of TransitionState#resolve | ||
let handlerInfos = currentState.handlerInfos; | ||
let errorHandlerIndex = transition.resolveIndex >= handlerInfos.length | ||
? handlerInfos.length - 1 | ||
: transition.resolveIndex; | ||
return Promise.reject(new TransitionError(error, currentState.handlerInfos[errorHandlerIndex].handler, wasAborted, currentState)); | ||
} | ||
function proceed(resolvedHandlerInfo) { | ||
let wasAlreadyResolved = currentState.handlerInfos[transition.resolveIndex].isResolved; | ||
// Swap the previously unresolved handlerInfo with | ||
// the resolved handlerInfo | ||
currentState.handlerInfos[transition.resolveIndex++] = resolvedHandlerInfo; | ||
if (!wasAlreadyResolved) { | ||
// Call the redirect hook. The reason we call it here | ||
// vs. afterModel is so that redirects into child | ||
// routes don't re-run the model hooks for this | ||
// already-resolved route. | ||
let handler = resolvedHandlerInfo.handler; | ||
if (handler !== undefined) { | ||
if (handler._redirect) { | ||
handler._redirect(resolvedHandlerInfo.context, transition); | ||
} | ||
else if (handler.redirect) { | ||
handler.redirect(resolvedHandlerInfo.context, transition); | ||
} | ||
} | ||
} | ||
// Proceed after ensuring that the redirect hook | ||
// didn't abort this transition by transitioning elsewhere. | ||
return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler')); | ||
} | ||
function resolveOneHandlerInfo() { | ||
if (transition.resolveIndex === currentState.handlerInfos.length) { | ||
// This is is the only possible | ||
// fulfill value of TransitionState#resolve | ||
return currentState; | ||
} | ||
let handlerInfo = currentState.handlerInfos[transition.resolveIndex]; | ||
return handlerInfo | ||
.resolve(innerShouldContinue, transition) | ||
.then(proceed, null, currentState.promiseLabel('Proceed')); | ||
} | ||
} | ||
function resolveOneHandlerInfo() { | ||
if (payload.resolveIndex === currentState.handlerInfos.length) { | ||
// This is is the only possible | ||
// fulfill value of TransitionState#resolve | ||
return { | ||
error: null, | ||
state: currentState, | ||
}; | ||
} | ||
let handlerInfo = currentState.handlerInfos[payload.resolveIndex]; | ||
return handlerInfo | ||
.resolve(innerShouldContinue, payload) | ||
.then(proceed, null, currentState.promiseLabel('Proceed')); | ||
} | ||
export class TransitionError { | ||
constructor(error, handler, wasAborted, state) { | ||
this.error = error; | ||
this.handler = handler; | ||
this.wasAborted = wasAborted; | ||
this.state = state; | ||
} | ||
} | ||
} | ||
//# sourceMappingURL=transition-state.js.map |
import { Promise } from 'rsvp'; | ||
import { trigger, slice, log, promiseLabel } from './utils'; | ||
import TransitionAborted from './transition-aborted-error'; | ||
import { log, promiseLabel, trigger } from './utils'; | ||
export { default as TransitionAborted } from './transition-aborted-error'; | ||
/** | ||
@@ -23,324 +21,291 @@ A Transition is a thennable (a promise-like object) that represents | ||
export class Transition { | ||
constructor(router, intent, state, error, previousTransition) { | ||
this.state = state || router.state; | ||
this.intent = intent; | ||
this.router = router; | ||
this.data = (this.intent && this.intent.data) || {}; | ||
this.resolvedModels = {}; | ||
this.queryParams = {}; | ||
this.promise = undefined; | ||
this.error = undefined; | ||
this.params = undefined; | ||
this.handlerInfos = undefined; | ||
this.targetName = undefined; | ||
this.pivotHandler = undefined; | ||
this.sequence = undefined; | ||
this.isAborted = false; | ||
this.isActive = true; | ||
this.urlMethod = 'update'; | ||
this.resolveIndex = 0; | ||
this.queryParamsOnly = false; | ||
this.isTransition = true; | ||
if (error) { | ||
this.promise = Promise.reject(error); | ||
this.error = error; | ||
return; | ||
constructor(router, intent, state, error = undefined, previousTransition = undefined) { | ||
this.isAborted = false; | ||
this.isActive = true; | ||
this.urlMethod = 'update'; | ||
this.resolveIndex = 0; | ||
this.queryParamsOnly = false; | ||
this.isTransition = true; | ||
this.isCausedByAbortingTransition = false; | ||
this.isCausedByInitialTransition = false; | ||
this.isCausedByAbortingReplaceTransition = false; | ||
this._visibleQueryParams = {}; | ||
this.state = state || router.state; | ||
this.intent = intent; | ||
this.router = router; | ||
this.data = (intent && intent.data) || {}; | ||
this.resolvedModels = {}; | ||
this.queryParams = {}; | ||
this.promise = undefined; | ||
this.error = undefined; | ||
this.params = {}; | ||
this.handlerInfos = []; | ||
this.targetName = undefined; | ||
this.pivotHandler = undefined; | ||
this.sequence = -1; | ||
if (error) { | ||
this.promise = Promise.reject(error); | ||
this.error = error; | ||
return; | ||
} | ||
// if you're doing multiple redirects, need the new transition to know if it | ||
// is actually part of the first transition or not. Any further redirects | ||
// in the initial transition also need to know if they are part of the | ||
// initial transition | ||
this.isCausedByAbortingTransition = !!previousTransition; | ||
this.isCausedByInitialTransition = | ||
!!previousTransition && | ||
(previousTransition.isCausedByInitialTransition || previousTransition.sequence === 0); | ||
// Every transition in the chain is a replace | ||
this.isCausedByAbortingReplaceTransition = | ||
!!previousTransition && | ||
(previousTransition.urlMethod === 'replace' && | ||
(!previousTransition.isCausedByAbortingTransition || | ||
previousTransition.isCausedByAbortingReplaceTransition)); | ||
if (state) { | ||
this.params = state.params; | ||
this.queryParams = state.queryParams; | ||
this.handlerInfos = state.handlerInfos; | ||
let len = state.handlerInfos.length; | ||
if (len) { | ||
this.targetName = state.handlerInfos[len - 1].name; | ||
} | ||
for (let i = 0; i < len; ++i) { | ||
let handlerInfo = state.handlerInfos[i]; | ||
// TODO: this all seems hacky | ||
if (!handlerInfo.isResolved) { | ||
break; | ||
} | ||
this.pivotHandler = handlerInfo.handler; | ||
} | ||
this.sequence = router.currentSequence++; | ||
this.promise = state | ||
.resolve(() => { | ||
if (this.isAborted) { | ||
return Promise.reject(false, promiseLabel('Transition aborted - reject')); | ||
} | ||
return Promise.resolve(true); | ||
}, this) | ||
.catch((result) => { | ||
if (result.wasAborted || this.isAborted) { | ||
return Promise.reject(logAbort(this)); | ||
} | ||
else { | ||
this.trigger(false, 'error', result.error, this, result.handler); | ||
this.abort(); | ||
return Promise.reject(result.error); | ||
} | ||
}, promiseLabel('Handle Abort')); | ||
} | ||
else { | ||
this.promise = Promise.resolve(this.state); | ||
this.params = {}; | ||
} | ||
} | ||
// if you're doing multiple redirects, need the new transition to know if it | ||
// is actually part of the first transition or not. Any further redirects | ||
// in the initial transition also need to know if they are part of the | ||
// initial transition | ||
this.isCausedByAbortingTransition = !!previousTransition; | ||
this.isCausedByInitialTransition = | ||
previousTransition && | ||
(previousTransition.isCausedByInitialTransition || | ||
previousTransition.sequence === 0); | ||
// Every transition in the chain is a replace | ||
this.isCausedByAbortingReplaceTransition = | ||
previousTransition && | ||
(previousTransition.urlMethod == 'replace' && | ||
(!previousTransition.isCausedByAbortingTransition || | ||
previousTransition.isCausedByAbortingReplaceTransition)); | ||
if (state) { | ||
this.params = state.params; | ||
this.queryParams = state.queryParams; | ||
this.handlerInfos = state.handlerInfos; | ||
let len = state.handlerInfos.length; | ||
if (len) { | ||
this.targetName = state.handlerInfos[len - 1].name; | ||
} | ||
for (let i = 0; i < len; ++i) { | ||
let handlerInfo = state.handlerInfos[i]; | ||
// TODO: this all seems hacky | ||
if (!handlerInfo.isResolved) { | ||
break; | ||
// Todo Delete? | ||
isExiting(handler) { | ||
let handlerInfos = this.handlerInfos; | ||
for (let i = 0, len = handlerInfos.length; i < len; ++i) { | ||
let handlerInfo = handlerInfos[i]; | ||
if (handlerInfo.name === handler || handlerInfo.handler === handler) { | ||
return false; | ||
} | ||
} | ||
this.pivotHandler = handlerInfo.handler; | ||
} | ||
this.sequence = router.currentSequence++; | ||
this.promise = state | ||
.resolve(() => { | ||
if (this.isAborted) { | ||
return Promise.reject( | ||
undefined, | ||
promiseLabel('Transition aborted - reject') | ||
); | ||
} | ||
}, this) | ||
.catch(result => { | ||
if (result.wasAborted || this.isAborted) { | ||
return Promise.reject(logAbort(this)); | ||
} else { | ||
this.trigger('error', result.error, this, result.handlerWithError); | ||
this.abort(); | ||
return Promise.reject(result.error); | ||
} | ||
}, promiseLabel('Handle Abort')); | ||
} else { | ||
this.promise = Promise.resolve(this.state); | ||
this.params = {}; | ||
return true; | ||
} | ||
} | ||
isExiting(handler) { | ||
let handlerInfos = this.handlerInfos; | ||
for (let i = 0, len = handlerInfos.length; i < len; ++i) { | ||
let handlerInfo = handlerInfos[i]; | ||
if (handlerInfo.name === handler || handlerInfo.handler === handler) { | ||
return false; | ||
} | ||
/** | ||
The Transition's internal promise. Calling `.then` on this property | ||
is that same as calling `.then` on the Transition object itself, but | ||
this property is exposed for when you want to pass around a | ||
Transition's promise, but not the Transition object itself, since | ||
Transition object can be externally `abort`ed, while the promise | ||
cannot. | ||
@property promise | ||
@type {Object} | ||
@public | ||
*/ | ||
/** | ||
Custom state can be stored on a Transition's `data` object. | ||
This can be useful for decorating a Transition within an earlier | ||
hook and shared with a later hook. Properties set on `data` will | ||
be copied to new transitions generated by calling `retry` on this | ||
transition. | ||
@property data | ||
@type {Object} | ||
@public | ||
*/ | ||
/** | ||
A standard promise hook that resolves if the transition | ||
succeeds and rejects if it fails/redirects/aborts. | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method then | ||
@param {Function} onFulfilled | ||
@param {Function} onRejected | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
then(onFulfilled, onRejected, label) { | ||
return this.promise.then(onFulfilled, onRejected, label); | ||
} | ||
return true; | ||
} | ||
/** | ||
The Transition's internal promise. Calling `.then` on this property | ||
is that same as calling `.then` on the Transition object itself, but | ||
this property is exposed for when you want to pass around a | ||
Transition's promise, but not the Transition object itself, since | ||
Transition object can be externally `abort`ed, while the promise | ||
cannot. | ||
@property promise | ||
@type {Object} | ||
@public | ||
*/ | ||
/** | ||
Custom state can be stored on a Transition's `data` object. | ||
This can be useful for decorating a Transition within an earlier | ||
hook and shared with a later hook. Properties set on `data` will | ||
be copied to new transitions generated by calling `retry` on this | ||
transition. | ||
@property data | ||
@type {Object} | ||
@public | ||
*/ | ||
/** | ||
A standard promise hook that resolves if the transition | ||
succeeds and rejects if it fails/redirects/aborts. | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method then | ||
@param {Function} onFulfilled | ||
@param {Function} onRejected | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
then(onFulfilled, onRejected, label) { | ||
return this.promise.then(onFulfilled, onRejected, label); | ||
} | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method catch | ||
@param {Function} onRejection | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
catch(onRejection, label) { | ||
return this.promise.catch(onRejection, label); | ||
} | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method finally | ||
@param {Function} callback | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
finally(callback, label) { | ||
return this.promise.finally(callback, label); | ||
} | ||
/** | ||
Aborts the Transition. Note you can also implicitly abort a transition | ||
by initiating another transition while a previous one is underway. | ||
@method abort | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
abort() { | ||
if (this.isAborted) { | ||
return this; | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method catch | ||
@param {Function} onRejection | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
catch(onRejection, label) { | ||
return this.promise.catch(onRejection, label); | ||
} | ||
log( | ||
this.router, | ||
this.sequence, | ||
this.targetName + ': transition was aborted' | ||
); | ||
this.intent.preTransitionState = this.router.state; | ||
this.isAborted = true; | ||
this.isActive = false; | ||
this.router.activeTransition = null; | ||
return this; | ||
} | ||
/** | ||
Retries a previously-aborted transition (making sure to abort the | ||
transition if it's still active). Returns a new transition that | ||
represents the new attempt to transition. | ||
@method retry | ||
@return {Transition} new transition | ||
@public | ||
*/ | ||
retry() { | ||
// TODO: add tests for merged state retry()s | ||
this.abort(); | ||
let newTransition = this.router.transitionByIntent(this.intent, false); | ||
// inheriting a `null` urlMethod is not valid | ||
// the urlMethod is only set to `null` when | ||
// the transition is initiated *after* the url | ||
// has been updated (i.e. `router.handleURL`) | ||
// | ||
// in that scenario, the url method cannot be | ||
// inherited for a new transition because then | ||
// the url would not update even though it should | ||
if (this.urlMethod !== null) { | ||
newTransition.method(this.urlMethod); | ||
/** | ||
Forwards to the internal `promise` property which you can | ||
use in situations where you want to pass around a thennable, | ||
but not the Transition itself. | ||
@method finally | ||
@param {Function} callback | ||
@param {String} label optional string for labeling the promise. | ||
Useful for tooling. | ||
@return {Promise} | ||
@public | ||
*/ | ||
finally(callback, label) { | ||
return this.promise.finally(callback, label); | ||
} | ||
return newTransition; | ||
} | ||
/** | ||
Sets the URL-changing method to be employed at the end of a | ||
successful transition. By default, a new Transition will just | ||
use `updateURL`, but passing 'replace' to this method will | ||
cause the URL to update using 'replaceWith' instead. Omitting | ||
a parameter will disable the URL change, allowing for transitions | ||
that don't update the URL at completion (this is also used for | ||
handleURL, since the URL has already changed before the | ||
transition took place). | ||
@method method | ||
@param {String} method the type of URL-changing method to use | ||
at the end of a transition. Accepted values are 'replace', | ||
falsy values, or any other non-falsy value (which is | ||
interpreted as an updateURL transition). | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
method(method) { | ||
this.urlMethod = method; | ||
return this; | ||
} | ||
/** | ||
Fires an event on the current list of resolved/resolving | ||
handlers within this transition. Useful for firing events | ||
on route hierarchies that haven't fully been entered yet. | ||
Note: This method is also aliased as `send` | ||
@method trigger | ||
@param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error | ||
@param {String} name the name of the event to fire | ||
@public | ||
*/ | ||
trigger(ignoreFailure) { | ||
let args = slice.call(arguments); | ||
if (typeof ignoreFailure === 'boolean') { | ||
args.shift(); | ||
} else { | ||
// Throw errors on unhandled trigger events by default | ||
ignoreFailure = false; | ||
/** | ||
Aborts the Transition. Note you can also implicitly abort a transition | ||
by initiating another transition while a previous one is underway. | ||
@method abort | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
abort() { | ||
if (this.isAborted) { | ||
return this; | ||
} | ||
log(this.router, this.sequence, this.targetName + ': transition was aborted'); | ||
this.intent.preTransitionState = this.router.state; | ||
this.isAborted = true; | ||
this.isActive = false; | ||
this.router.activeTransition = undefined; | ||
return this; | ||
} | ||
trigger( | ||
this.router, | ||
this.state.handlerInfos.slice(0, this.resolveIndex + 1), | ||
ignoreFailure, | ||
args | ||
); | ||
} | ||
/** | ||
Transitions are aborted and their promises rejected | ||
when redirects occur; this method returns a promise | ||
that will follow any redirects that occur and fulfill | ||
with the value fulfilled by any redirecting transitions | ||
that occur. | ||
@method followRedirects | ||
@return {Promise} a promise that fulfills with the same | ||
value that the final redirecting transition fulfills with | ||
@public | ||
*/ | ||
followRedirects() { | ||
let router = this.router; | ||
return this.promise.catch(function(reason) { | ||
if (router.activeTransition) { | ||
return router.activeTransition.followRedirects(); | ||
} | ||
return Promise.reject(reason); | ||
}); | ||
} | ||
toString() { | ||
return 'Transition (sequence ' + this.sequence + ')'; | ||
} | ||
/** | ||
@private | ||
*/ | ||
log(message) { | ||
log(this.router, this.sequence, message); | ||
} | ||
/** | ||
Retries a previously-aborted transition (making sure to abort the | ||
transition if it's still active). Returns a new transition that | ||
represents the new attempt to transition. | ||
@method retry | ||
@return {Transition} new transition | ||
@public | ||
*/ | ||
retry() { | ||
// TODO: add tests for merged state retry()s | ||
this.abort(); | ||
let newTransition = this.router.transitionByIntent(this.intent, false); | ||
// inheriting a `null` urlMethod is not valid | ||
// the urlMethod is only set to `null` when | ||
// the transition is initiated *after* the url | ||
// has been updated (i.e. `router.handleURL`) | ||
// | ||
// in that scenario, the url method cannot be | ||
// inherited for a new transition because then | ||
// the url would not update even though it should | ||
if (this.urlMethod !== null) { | ||
newTransition.method(this.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
/** | ||
Sets the URL-changing method to be employed at the end of a | ||
successful transition. By default, a new Transition will just | ||
use `updateURL`, but passing 'replace' to this method will | ||
cause the URL to update using 'replaceWith' instead. Omitting | ||
a parameter will disable the URL change, allowing for transitions | ||
that don't update the URL at completion (this is also used for | ||
handleURL, since the URL has already changed before the | ||
transition took place). | ||
@method method | ||
@param {String} method the type of URL-changing method to use | ||
at the end of a transition. Accepted values are 'replace', | ||
falsy values, or any other non-falsy value (which is | ||
interpreted as an updateURL transition). | ||
@return {Transition} this transition | ||
@public | ||
*/ | ||
method(method) { | ||
this.urlMethod = method; | ||
return this; | ||
} | ||
// Alias 'trigger' as 'send' | ||
send(ignoreFailure, _name, err, transition, handler) { | ||
this.trigger(ignoreFailure, _name, err, transition, handler); | ||
} | ||
/** | ||
Fires an event on the current list of resolved/resolving | ||
handlers within this transition. Useful for firing events | ||
on route hierarchies that haven't fully been entered yet. | ||
Note: This method is also aliased as `send` | ||
@method trigger | ||
@param {Boolean} [ignoreFailure=false] a boolean specifying whether unhandled events throw an error | ||
@param {String} name the name of the event to fire | ||
@public | ||
*/ | ||
trigger(ignoreFailure, _name, ...args) { | ||
trigger(this.router, this.state.handlerInfos.slice(0, this.resolveIndex + 1), ignoreFailure, _name, ...args); | ||
} | ||
/** | ||
Transitions are aborted and their promises rejected | ||
when redirects occur; this method returns a promise | ||
that will follow any redirects that occur and fulfill | ||
with the value fulfilled by any redirecting transitions | ||
that occur. | ||
@method followRedirects | ||
@return {Promise} a promise that fulfills with the same | ||
value that the final redirecting transition fulfills with | ||
@public | ||
*/ | ||
followRedirects() { | ||
let router = this.router; | ||
return this.promise.catch(function (reason) { | ||
if (router.activeTransition) { | ||
return router.activeTransition.followRedirects(); | ||
} | ||
return Promise.reject(reason); | ||
}); | ||
} | ||
toString() { | ||
return 'Transition (sequence ' + this.sequence + ')'; | ||
} | ||
/** | ||
@private | ||
*/ | ||
log(message) { | ||
log(this.router, this.sequence, message); | ||
} | ||
} | ||
// Alias 'trigger' as 'send' | ||
Transition.prototype.send = Transition.prototype.trigger; | ||
/** | ||
@@ -352,4 +317,14 @@ @private | ||
export function logAbort(transition) { | ||
log(transition.router, transition.sequence, 'detected abort.'); | ||
return new TransitionAborted(); | ||
log(transition.router, transition.sequence, 'detected abort.'); | ||
return new TransitionAborted(); | ||
} | ||
export function isTransition(obj) { | ||
return typeof obj === 'object' && obj instanceof Transition && obj.isTransition; | ||
} | ||
export function prepareResult(obj) { | ||
if (isTransition(obj)) { | ||
return null; | ||
} | ||
return obj; | ||
} | ||
//# sourceMappingURL=transition.js.map |
@@ -1,25 +0,18 @@ | ||
function UnrecognizedURLError(message) { | ||
if (!(this instanceof UnrecognizedURLError)) { | ||
return new UnrecognizedURLError(message); | ||
} | ||
var error = Error.call(this, message); | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, UnrecognizedURLError); | ||
} else { | ||
this.stack = error.stack; | ||
} | ||
this.description = error.description; | ||
this.fileName = error.fileName; | ||
this.lineNumber = error.lineNumber; | ||
this.message = error.message || 'UnrecognizedURL'; | ||
this.name = 'UnrecognizedURLError'; | ||
this.number = error.number; | ||
this.code = error.code; | ||
} | ||
UnrecognizedURLError.prototype = Object.create(Error.prototype); | ||
const UnrecognizedURLError = (function () { | ||
UnrecognizedURLError.prototype = Object.create(Error.prototype); | ||
UnrecognizedURLError.prototype.constructor = UnrecognizedURLError; | ||
function UnrecognizedURLError(message) { | ||
let error = Error.call(this, message); | ||
this.name = 'UnrecognizedURLError'; | ||
this.message = message || 'UnrecognizedURL'; | ||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, UnrecognizedURLError); | ||
} | ||
else { | ||
this.stack = error.stack; | ||
} | ||
} | ||
return UnrecognizedURLError; | ||
})(); | ||
export default UnrecognizedURLError; | ||
//# sourceMappingURL=unrecognized-url-error.js.map |
export const slice = Array.prototype.slice; | ||
const hasOwnProperty = Object.prototype.hasOwnProperty; | ||
/** | ||
Determines if an object is Promise by checking if it is "thenable". | ||
**/ | ||
export function isPromise(obj) { | ||
return ( | ||
((typeof obj === 'object' && obj !== null) || typeof obj === 'function') && | ||
typeof obj.then === 'function' | ||
); | ||
export function isPromise(p) { | ||
return p !== null && typeof p === 'object' && typeof p.then === 'function'; | ||
} | ||
export function merge(hash, other) { | ||
for (let prop in other) { | ||
if (hasOwnProperty.call(other, prop)) { | ||
hash[prop] = other[prop]; | ||
for (let prop in other) { | ||
if (hasOwnProperty.call(other, prop)) { | ||
hash[prop] = other[prop]; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
@@ -28,20 +22,16 @@ @private | ||
export function extractQueryParams(array) { | ||
let len = array && array.length, | ||
head, | ||
queryParams; | ||
if ( | ||
len && | ||
len > 0 && | ||
array[len - 1] && | ||
hasOwnProperty.call(array[len - 1], 'queryParams') | ||
) { | ||
queryParams = array[len - 1].queryParams; | ||
head = slice.call(array, 0, len - 1); | ||
return [head, queryParams]; | ||
} else { | ||
let len = array && array.length, head, queryParams; | ||
if (len && len > 0) { | ||
let obj = array[len - 1]; | ||
if (isQueryParams(obj)) { | ||
queryParams = obj.queryParams; | ||
head = slice.call(array, 0, len - 1); | ||
return [head, queryParams]; | ||
} | ||
} | ||
return [array, null]; | ||
} | ||
} | ||
function isQueryParams(obj) { | ||
return obj && hasOwnProperty.call(obj, 'queryParams'); | ||
} | ||
/** | ||
@@ -53,12 +43,13 @@ @private | ||
export function coerceQueryParamsToString(queryParams) { | ||
for (let key in queryParams) { | ||
let val = queryParams[key]; | ||
if (typeof val === 'number') { | ||
queryParams[key] = '' + val; | ||
} else if (Array.isArray(val)) { | ||
for (let i = 0, l = val.length; i < l; i++) { | ||
val[i] = '' + val[i]; | ||
} | ||
for (let key in queryParams) { | ||
let val = queryParams[key]; | ||
if (typeof val === 'number') { | ||
queryParams[key] = '' + val; | ||
} | ||
else if (Array.isArray(val)) { | ||
for (let i = 0, l = val.length; i < l; i++) { | ||
val[i] = '' + val[i]; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
@@ -68,165 +59,123 @@ /** | ||
*/ | ||
export function log(router, sequence, msg) { | ||
if (!router.log) { | ||
return; | ||
} | ||
if (arguments.length === 3) { | ||
router.log('Transition #' + sequence + ': ' + msg); | ||
} else { | ||
msg = sequence; | ||
router.log(msg); | ||
} | ||
export function log(router, ...args) { | ||
if (!router.log) { | ||
return; | ||
} | ||
if (arguments.length === 2) { | ||
let [sequence, msg] = args; | ||
router.log('Transition #' + sequence + ': ' + msg); | ||
} | ||
else { | ||
let [msg] = args; | ||
router.log(msg); | ||
} | ||
} | ||
export function isParam(object) { | ||
return ( | ||
typeof object === 'string' || | ||
object instanceof String || | ||
typeof object === 'number' || | ||
object instanceof Number | ||
); | ||
return (typeof object === 'string' || | ||
object instanceof String || | ||
typeof object === 'number' || | ||
object instanceof Number); | ||
} | ||
export function forEach(array, callback) { | ||
for ( | ||
let i = 0, l = array.length; | ||
i < l && false !== callback(array[i]); | ||
i++ | ||
) { | ||
// empty intentionally | ||
} | ||
for (let i = 0, l = array.length; i < l && callback(array[i]) !== false; i++) { | ||
// empty intentionally | ||
} | ||
} | ||
export function trigger(router, handlerInfos, ignoreFailure, args) { | ||
if (router.triggerEvent) { | ||
router.triggerEvent(handlerInfos, ignoreFailure, args); | ||
return; | ||
} | ||
let name = args.shift(); | ||
if (!handlerInfos) { | ||
if (ignoreFailure) { | ||
return; | ||
export function trigger(router, handlerInfos, ignoreFailure, name, ...args) { | ||
if (router.triggerEvent) { | ||
router.triggerEvent(handlerInfos, ignoreFailure, [name, ...args]); | ||
return; | ||
} | ||
throw new Error( | ||
"Could not trigger event '" + name + "'. There are no active handlers" | ||
); | ||
} | ||
let eventWasHandled = false; | ||
function delayedEvent(name, args, handler) { | ||
handler.events[name].apply(handler, args); | ||
} | ||
for (let i = handlerInfos.length - 1; i >= 0; i--) { | ||
let handlerInfo = handlerInfos[i], | ||
handler = handlerInfo.handler; | ||
// If there is no handler, it means the handler hasn't resolved yet which | ||
// means that we should trigger the event later when the handler is available | ||
if (!handler) { | ||
handlerInfo.handlerPromise.then(delayedEvent.bind(null, name, args)); | ||
continue; | ||
if (!handlerInfos) { | ||
if (ignoreFailure) { | ||
return; | ||
} | ||
throw new Error("Could not trigger event '" + name + "'. There are no active handlers"); | ||
} | ||
if (handler.events && handler.events[name]) { | ||
if (handler.events[name].apply(handler, args) === true) { | ||
eventWasHandled = true; | ||
} else { | ||
return; | ||
} | ||
let eventWasHandled = false; | ||
for (let i = handlerInfos.length - 1; i >= 0; i--) { | ||
let currentHandlerInfo = handlerInfos[i], currentHandler = currentHandlerInfo.handler; | ||
// If there is no handler, it means the handler hasn't resolved yet which | ||
// means that we should trigger the event later when the handler is available | ||
if (!currentHandler) { | ||
currentHandlerInfo.handlerPromise.then(function (resolvedHandler) { | ||
resolvedHandler.events[name].apply(resolvedHandler, args); | ||
}); | ||
continue; | ||
} | ||
if (currentHandler.events && currentHandler.events[name]) { | ||
if (currentHandler.events[name].apply(currentHandler, args) === true) { | ||
eventWasHandled = true; | ||
} | ||
else { | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
// In the case that we got an UnrecognizedURLError as an event with no handler, | ||
// let it bubble up | ||
if (name === 'error' && args[0].name === 'UnrecognizedURLError') { | ||
throw args[0]; | ||
} else if (!eventWasHandled && !ignoreFailure) { | ||
throw new Error("Nothing handled the event '" + name + "'."); | ||
} | ||
// In the case that we got an UnrecognizedURLError as an event with no handler, | ||
// let it bubble up | ||
if (name === 'error' && args[0].name === 'UnrecognizedURLError') { | ||
throw args[0]; | ||
} | ||
else if (!eventWasHandled && !ignoreFailure) { | ||
throw new Error("Nothing handled the event '" + name + "'."); | ||
} | ||
} | ||
export function getChangelist(oldObject, newObject) { | ||
let key; | ||
let results = { | ||
all: {}, | ||
changed: {}, | ||
removed: {}, | ||
}; | ||
merge(results.all, newObject); | ||
let didChange = false; | ||
coerceQueryParamsToString(oldObject); | ||
coerceQueryParamsToString(newObject); | ||
// Calculate removals | ||
for (key in oldObject) { | ||
if (hasOwnProperty.call(oldObject, key)) { | ||
if (!hasOwnProperty.call(newObject, key)) { | ||
didChange = true; | ||
results.removed[key] = oldObject[key]; | ||
} | ||
let key; | ||
let results = { | ||
all: {}, | ||
changed: {}, | ||
removed: {}, | ||
}; | ||
merge(results.all, newObject); | ||
let didChange = false; | ||
coerceQueryParamsToString(oldObject); | ||
coerceQueryParamsToString(newObject); | ||
// Calculate removals | ||
for (key in oldObject) { | ||
if (hasOwnProperty.call(oldObject, key)) { | ||
if (!hasOwnProperty.call(newObject, key)) { | ||
didChange = true; | ||
results.removed[key] = oldObject[key]; | ||
} | ||
} | ||
} | ||
} | ||
// Calculate changes | ||
for (key in newObject) { | ||
if (hasOwnProperty.call(newObject, key)) { | ||
if (Array.isArray(oldObject[key]) && Array.isArray(newObject[key])) { | ||
if (oldObject[key].length !== newObject[key].length) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} else { | ||
for (let i = 0, l = oldObject[key].length; i < l; i++) { | ||
if (oldObject[key][i] !== newObject[key][i]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
// Calculate changes | ||
for (key in newObject) { | ||
if (hasOwnProperty.call(newObject, key)) { | ||
let oldElement = oldObject[key]; | ||
let newElement = newObject[key]; | ||
if (isArray(oldElement) && isArray(newElement)) { | ||
if (oldElement.length !== newElement.length) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
else { | ||
for (let i = 0, l = oldElement.length; i < l; i++) { | ||
if (oldElement[i] !== newElement[i]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
else if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} | ||
} else if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} | ||
} | ||
return didChange ? results : undefined; | ||
return didChange ? results : undefined; | ||
} | ||
function isArray(obj) { | ||
return Array.isArray(obj); | ||
} | ||
export function promiseLabel(label) { | ||
return 'Router: ' + label; | ||
return 'Router: ' + label; | ||
} | ||
export function resolveHook(obj, hookName) { | ||
if (!obj) { | ||
return; | ||
} | ||
let underscored = '_' + hookName; | ||
return (obj[underscored] && underscored) || (obj[hookName] && hookName); | ||
} | ||
export function callHook(obj, _hookName, arg1, arg2) { | ||
let hookName = resolveHook(obj, _hookName); | ||
return hookName && obj[hookName].call(obj, arg1, arg2); | ||
} | ||
export function applyHook(obj, _hookName, args) { | ||
let hookName = resolveHook(obj, _hookName); | ||
if (hookName) { | ||
if (args.length === 0) { | ||
return obj[hookName].call(obj); | ||
} else if (args.length === 1) { | ||
return obj[hookName].call(obj, args[0]); | ||
} else if (args.length === 2) { | ||
return obj[hookName].call(obj, args[0], args[1]); | ||
} else { | ||
return obj[hookName].apply(obj, args); | ||
} | ||
} | ||
} | ||
// export function callHook(obj: any, _hookName: string, arg1?: unknown, arg2?: unknown) { | ||
// let hookName = resolveHook(obj, _hookName); | ||
// return hookName && obj[hookName].call(obj, arg1, arg2); | ||
// } | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "router_js", | ||
"version": "2.0.0-beta.4", | ||
"version": "3.0.0", | ||
"description": "A lightweight JavaScript library is built on top of route-recognizer and rsvp.js to provide an API for handling routes", | ||
@@ -30,4 +30,7 @@ "keywords": [ | ||
], | ||
"dependencies": {}, | ||
"dependencies": { | ||
"@types/node": "^10.5.5" | ||
}, | ||
"devDependencies": { | ||
"@types/qunit": "^2.5.2", | ||
"babel-preset-env": "^1.6.1", | ||
@@ -38,15 +41,18 @@ "backburner.js": "^2.1.0", | ||
"broccoli-funnel": "^2.0.1", | ||
"broccoli-lint-eslint": "^4.1.0", | ||
"broccoli-merge-trees": "^2.0.0", | ||
"broccoli-tslinter": "^3.0.1", | ||
"broccoli-typescript-compiler": "^4.0.1", | ||
"ember-cli": "2.18.1", | ||
"eslint-config-prettier": "2.9.0", | ||
"eslint-plugin-prettier": "2.6.0", | ||
"loader.js": "^4.6.0", | ||
"prettier": "1.10.2", | ||
"qunit": "^2.5.0", | ||
"route-recognizer": "^0.3.3", | ||
"rsvp": "^4.8.1" | ||
"route-recognizer": "^0.3.4", | ||
"rsvp": "^4.8.1", | ||
"tslint": "^5.11.0", | ||
"tslint-config-prettier": "^1.14.0", | ||
"tslint-plugin-prettier": "^1.3.0", | ||
"typescript": "^3.0.1" | ||
}, | ||
"peerDependencies": { | ||
"route-recognizer": "^0.3.3", | ||
"route-recognizer": "^0.3.4", | ||
"rsvp": "^4.8.1" | ||
@@ -53,0 +59,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
356268
75
5111
1
3
19
+ Added@types/node@^10.5.5
+ Added@types/node@10.17.60(transitive)