/*! * vue-router v0.7.13 * (c) 2016 Evan You * Released under the MIT License. */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.VueRouter = factory(); }(this, function () { 'use strict'; var babelHelpers = {}; babelHelpers.classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; function Target(path, matcher, delegate) { this.path = path; this.matcher = matcher; this.delegate = delegate; } Target.prototype = { to: function to(target, callback) { var delegate = this.delegate; if (delegate && delegate.willAddRoute) { target = delegate.willAddRoute(this.matcher.target, target); } this.matcher.add(this.path, target); if (callback) { if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); } this.matcher.addChild(this.path, target, callback, this.delegate); } return this; } }; function Matcher(target) { this.routes = {}; this.children = {}; this.target = target; } Matcher.prototype = { add: function add(path, handler) { this.routes[path] = handler; }, addChild: function addChild(path, target, callback, delegate) { var matcher = new Matcher(target); this.children[path] = matcher; var match = generateMatch(path, matcher, delegate); if (delegate && delegate.contextEntered) { delegate.contextEntered(target, match); } callback(match); } }; function generateMatch(startingPath, matcher, delegate) { return function (path, nestedCallback) { var fullPath = startingPath + path; if (nestedCallback) { nestedCallback(generateMatch(fullPath, matcher, delegate)); } else { return new Target(startingPath + path, matcher, delegate); } }; } function addRoute(routeArray, path, handler) { var len = 0; for (var i = 0, l = routeArray.length; i < l; i++) { len += routeArray[i].path.length; } path = path.substr(len); var route = { path: path, handler: handler }; routeArray.push(route); } function eachRoute(baseRoute, matcher, callback, binding) { var routes = matcher.routes; for (var path in routes) { if (routes.hasOwnProperty(path)) { var routeArray = baseRoute.slice(); addRoute(routeArray, path, routes[path]); if (matcher.children[path]) { eachRoute(routeArray, matcher.children[path], callback, binding); } else { callback.call(binding, routeArray); } } } } function map (callback, addRouteCallback) { var matcher = new Matcher(); callback(generateMatch("", matcher, this.delegate)); eachRoute([], matcher, function (route) { if (addRouteCallback) { addRouteCallback(this, route); } else { this.add(route); } }, this); } var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']; var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); var noWarning = false; function warn(msg) { if (!noWarning && typeof console !== 'undefined') { console.error('[vue-router] ' + msg); } } function tryDecode(uri, asComponent) { try { return asComponent ? decodeURIComponent(uri) : decodeURI(uri); } catch (e) { warn('malformed URI' + (asComponent ? ' component: ' : ': ') + uri); } } function isArray(test) { return Object.prototype.toString.call(test) === "[object Array]"; } // A Segment represents a segment in the original route description. // Each Segment type provides an `eachChar` and `regex` method. // // The `eachChar` method invokes the callback with one or more character // specifications. A character specification consumes one or more input // characters. // // The `regex` method returns a regex fragment for the segment. If the // segment is a dynamic of star segment, the regex fragment also includes // a capture. // // A character specification contains: // // * `validChars`: a String with a list of all valid characters, or // * `invalidChars`: a String with a list of all invalid characters // * `repeat`: true if the character specification can repeat function StaticSegment(string) { this.string = string; } StaticSegment.prototype = { eachChar: function eachChar(callback) { var string = this.string, ch; for (var i = 0, l = string.length; i < l; i++) { ch = string.charAt(i); callback({ validChars: ch }); } }, regex: function regex() { return this.string.replace(escapeRegex, '\\$1'); }, generate: function generate() { return this.string; } }; function DynamicSegment(name) { this.name = name; } DynamicSegment.prototype = { eachChar: function eachChar(callback) { callback({ invalidChars: "/", repeat: true }); }, regex: function regex() { return "([^/]+)"; }, generate: function generate(params) { var val = params[this.name]; return val == null ? ":" + this.name : val; } }; function StarSegment(name) { this.name = name; } StarSegment.prototype = { eachChar: function eachChar(callback) { callback({ invalidChars: "", repeat: true }); }, regex: function regex() { return "(.+)"; }, generate: function generate(params) { var val = params[this.name]; return val == null ? ":" + this.name : val; } }; function EpsilonSegment() {} EpsilonSegment.prototype = { eachChar: function eachChar() {}, regex: function regex() { return ""; }, generate: function generate() { return ""; } }; function parse(route, names, specificity) { // normalize route as not starting with a "/". Recognition will // also normalize. if (route.charAt(0) === "/") { route = route.substr(1); } var segments = route.split("/"), results = []; // A routes has specificity determined by the order that its different segments // appear in. This system mirrors how the magnitude of numbers written as strings // works. // Consider a number written as: "abc". An example would be "200". Any other number written // "xyz" will be smaller than "abc" so long as `a > z`. For instance, "199" is smaller // then "200", even though "y" and "z" (which are both 9) are larger than "0" (the value // of (`b` and `c`). This is because the leading symbol, "2", is larger than the other // leading symbol, "1". // The rule is that symbols to the left carry more weight than symbols to the right // when a number is written out as a string. In the above strings, the leading digit // represents how many 100's are in the number, and it carries more weight than the middle // number which represents how many 10's are in the number. // This system of number magnitude works well for route specificity, too. A route written as // `a/b/c` will be more specific than `x/y/z` as long as `a` is more specific than // `x`, irrespective of the other parts. // Because of this similarity, we assign each type of segment a number value written as a // string. We can find the specificity of compound routes by concatenating these strings // together, from left to right. After we have looped through all of the segments, // we convert the string to a number. specificity.val = ''; for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], match; if (match = segment.match(/^:([^\/]+)$/)) { results.push(new DynamicSegment(match[1])); names.push(match[1]); specificity.val += '3'; } else if (match = segment.match(/^\*([^\/]+)$/)) { results.push(new StarSegment(match[1])); specificity.val += '2'; names.push(match[1]); } else if (segment === "") { results.push(new EpsilonSegment()); specificity.val += '1'; } else { results.push(new StaticSegment(segment)); specificity.val += '4'; } } specificity.val = +specificity.val; return results; } // A State has a character specification and (`charSpec`) and a list of possible // subsequent states (`nextStates`). // // If a State is an accepting state, it will also have several additional // properties: // // * `regex`: A regular expression that is used to extract parameters from paths // that reached this accepting state. // * `handlers`: Information on how to convert the list of captures into calls // to registered handlers with the specified parameters // * `types`: How many static, dynamic or star segments in this route. Used to // decide which route to use if multiple registered routes match a path. // // Currently, State is implemented naively by looping over `nextStates` and // comparing a character specification against a character. A more efficient // implementation would use a hash of keys pointing at one or more next states. function State(charSpec) { this.charSpec = charSpec; this.nextStates = []; } State.prototype = { get: function get(charSpec) { var nextStates = this.nextStates; for (var i = 0, l = nextStates.length; i < l; i++) { var child = nextStates[i]; var isEqual = child.charSpec.validChars === charSpec.validChars; isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars; if (isEqual) { return child; } } }, put: function put(charSpec) { var state; // If the character specification already exists in a child of the current // state, just return that state. if (state = this.get(charSpec)) { return state; } // Make a new state for the character spec state = new State(charSpec); // Insert the new state as a child of the current state this.nextStates.push(state); // If this character specification repeats, insert the new state as a child // of itself. Note that this will not trigger an infinite loop because each // transition during recognition consumes a character. if (charSpec.repeat) { state.nextStates.push(state); } // Return the new state return state; }, // Find a list of child states matching the next character match: function match(ch) { // DEBUG "Processing `" + ch + "`:" var nextStates = this.nextStates, child, charSpec, chars; // DEBUG " " + debugState(this) var returned = []; for (var i = 0, l = nextStates.length; i < l; i++) { child = nextStates[i]; charSpec = child.charSpec; if (typeof (chars = charSpec.validChars) !== 'undefined') { if (chars.indexOf(ch) !== -1) { returned.push(child); } } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') { if (chars.indexOf(ch) === -1) { returned.push(child); } } } return returned; } /** IF DEBUG , debug: function() { var charSpec = this.charSpec, debug = "[", chars = charSpec.validChars || charSpec.invalidChars; if (charSpec.invalidChars) { debug += "^"; } debug += chars; debug += "]"; if (charSpec.repeat) { debug += "+"; } return debug; } END IF **/ }; /** IF DEBUG function debug(log) { console.log(log); } function debugState(state) { return state.nextStates.map(function(n) { if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; } return "( " + n.debug() + " " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )"; }).join(", ") } END IF **/ // Sort the routes by specificity function sortSolutions(states) { return states.sort(function (a, b) { return b.specificity.val - a.specificity.val; }); } function recognizeChar(states, ch) { var nextStates = []; for (var i = 0, l = states.length; i < l; i++) { var state = states[i]; nextStates = nextStates.concat(state.match(ch)); } return nextStates; } var oCreate = Object.create || function (proto) { function F() {} F.prototype = proto; return new F(); }; function RecognizeResults(queryParams) { this.queryParams = queryParams || {}; } RecognizeResults.prototype = oCreate({ splice: Array.prototype.splice, slice: Array.prototype.slice, push: Array.prototype.push, length: 0, queryParams: null }); function findHandler(state, path, queryParams) { var handlers = state.handlers, regex = state.regex; var captures = path.match(regex), currentCapture = 1; var result = new RecognizeResults(queryParams); for (var i = 0, l = handlers.length; i < l; i++) { var handler = handlers[i], names = handler.names, params = {}; for (var j = 0, m = names.length; j < m; j++) { params[names[j]] = captures[currentCapture++]; } result.push({ handler: handler.handler, params: params, isDynamic: !!names.length }); } return result; } function addSegment(currentState, segment) { segment.eachChar(function (ch) { var state; currentState = currentState.put(ch); }); return currentState; } function decodeQueryParamPart(part) { // http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 part = part.replace(/\+/gm, '%20'); return tryDecode(part, true); } // The main interface var RouteRecognizer = function RouteRecognizer() { this.rootState = new State(); this.names = {}; }; RouteRecognizer.prototype = { add: function add(routes, options) { var currentState = this.rootState, regex = "^", specificity = {}, handlers = [], allSegments = [], name; var isEmpty = true; for (var i = 0, l = routes.length; i < l; i++) { var route = routes[i], names = []; var segments = parse(route.path, names, specificity); allSegments = allSegments.concat(segments); for (var j = 0, m = segments.length; j < m; j++) { var segment = segments[j]; if (segment instanceof EpsilonSegment) { continue; } isEmpty = false; // Add a "/" for the new segment currentState = currentState.put({ validChars: "/" }); regex += "/"; // Add a representation of the segment to the NFA and regex currentState = addSegment(currentState, segment); regex += segment.regex(); } var handler = { handler: route.handler, names: names }; handlers.push(handler); } if (isEmpty) { currentState = currentState.put({ validChars: "/" }); regex += "/"; } currentState.handlers = handlers; currentState.regex = new RegExp(regex + "$"); currentState.specificity = specificity; if (name = options && options.as) { this.names[name] = { segments: allSegments, handlers: handlers }; } }, handlersFor: function handlersFor(name) { var route = this.names[name], result = []; if (!route) { throw new Error("There is no route named " + name); } for (var i = 0, l = route.handlers.length; i < l; i++) { result.push(route.handlers[i]); } return result; }, hasRoute: function hasRoute(name) { return !!this.names[name]; }, generate: function generate(name, params) { var route = this.names[name], output = ""; if (!route) { throw new Error("There is no route named " + name); } var segments = route.segments; for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i]; if (segment instanceof EpsilonSegment) { continue; } output += "/"; output += segment.generate(params); } if (output.charAt(0) !== '/') { output = '/' + output; } if (params && params.queryParams) { output += this.generateQueryString(params.queryParams); } return output; }, generateQueryString: function generateQueryString(params) { var pairs = []; var keys = []; for (var key in params) { if (params.hasOwnProperty(key)) { keys.push(key); } } keys.sort(); for (var i = 0, len = keys.length; i < len; i++) { key = keys[i]; var value = params[key]; if (value == null) { continue; } var pair = encodeURIComponent(key); if (isArray(value)) { for (var j = 0, l = value.length; j < l; j++) { var arrayPair = key + '[]' + '=' + encodeURIComponent(value[j]); pairs.push(arrayPair); } } else { pair += "=" + encodeURIComponent(value); pairs.push(pair); } } if (pairs.length === 0) { return ''; } return "?" + pairs.join("&"); }, parseQueryString: function parseQueryString(queryString) { var pairs = queryString.split("&"), queryParams = {}; for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='), key = decodeQueryParamPart(pair[0]), keyLength = key.length, isArray = false, value; if (pair.length === 1) { value = 'true'; } else { //Handle arrays if (keyLength > 2 && key.slice(keyLength - 2) === '[]') { isArray = true; key = key.slice(0, keyLength - 2); if (!queryParams[key]) { queryParams[key] = []; } } value = pair[1] ? decodeQueryParamPart(pair[1]) : ''; } if (isArray) { queryParams[key].push(value); } else { queryParams[key] = value; } } return queryParams; }, recognize: function recognize(path, silent) { noWarning = silent; var states = [this.rootState], pathLen, i, l, queryStart, queryParams = {}, isSlashDropped = false; queryStart = path.indexOf('?'); if (queryStart !== -1) { var queryString = path.substr(queryStart + 1, path.length); path = path.substr(0, queryStart); if (queryString) { queryParams = this.parseQueryString(queryString); } } path = tryDecode(path); if (!path) return; // DEBUG GROUP path if (path.charAt(0) !== "/") { path = "/" + path; } pathLen = path.length; if (pathLen > 1 && path.charAt(pathLen - 1) === "/") { path = path.substr(0, pathLen - 1); isSlashDropped = true; } for (i = 0, l = path.length; i < l; i++) { states = recognizeChar(states, path.charAt(i)); if (!states.length) { break; } } // END DEBUG GROUP var solutions = []; for (i = 0, l = states.length; i < l; i++) { if (states[i].handlers) { solutions.push(states[i]); } } states = sortSolutions(solutions); var state = solutions[0]; if (state && state.handlers) { // if a trailing slash was dropped and a star segment is the last segment // specified, put the trailing slash back if (isSlashDropped && state.regex.source.slice(-5) === "(.+)$") { path = path + "/"; } return findHandler(state, path, queryParams); } } }; RouteRecognizer.prototype.map = map; var genQuery = RouteRecognizer.prototype.generateQueryString; // export default for holding the Vue reference var exports$1 = {}; /** * Warn stuff. * * @param {String} msg */ function warn$1(msg) { /* istanbul ignore next */ if (typeof console !== 'undefined') { console.error('[vue-router] ' + msg); } } /** * Resolve a relative path. * * @param {String} base * @param {String} relative * @param {Boolean} append * @return {String} */ function resolvePath(base, relative, append) { var query = base.match(/(\?.*)$/); if (query) { query = query[1]; base = base.slice(0, -query.length); } // a query! if (relative.charAt(0) === '?') { return base + relative; } var stack = base.split('/'); // remove trailing segment if: // - not appending // - appending to trailing slash (last segment is empty) if (!append || !stack[stack.length - 1]) { stack.pop(); } // resolve relative path var segments = relative.replace(/^\//, '').split('/'); for (var i = 0; i < segments.length; i++) { var segment = segments[i]; if (segment === '.') { continue; } else if (segment === '..') { stack.pop(); } else { stack.push(segment); } } // ensure leading slash if (stack[0] !== '') { stack.unshift(''); } return stack.join('/'); } /** * Forgiving check for a promise * * @param {Object} p * @return {Boolean} */ function isPromise(p) { return p && typeof p.then === 'function'; } /** * Retrive a route config field from a component instance * OR a component contructor. * * @param {Function|Vue} component * @param {String} name * @return {*} */ function getRouteConfig(component, name) { var options = component && (component.$options || component.options); return options && options.route && options.route[name]; } /** * Resolve an async component factory. Have to do a dirty * mock here because of Vue core's internal API depends on * an ID check. * * @param {Object} handler * @param {Function} cb */ var resolver = undefined; function resolveAsyncComponent(handler, cb) { if (!resolver) { resolver = { resolve: exports$1.Vue.prototype._resolveComponent, $options: { components: { _: handler.component } } }; } else { resolver.$options.components._ = handler.component; } resolver.resolve('_', function (Component) { handler.component = Component; cb(Component); }); } /** * Map the dynamic segments in a path to params. * * @param {String} path * @param {Object} params * @param {Object} query */ function mapParams(path, params, query) { if (params === undefined) params = {}; path = path.replace(/:([^\/]+)/g, function (_, key) { var val = params[key]; /* istanbul ignore if */ if (!val) { warn$1('param "' + key + '" not found when generating ' + 'path for "' + path + '" with params ' + JSON.stringify(params)); } return val || ''; }); if (query) { path += genQuery(query); } return path; } var hashRE = /#.*$/; var HTML5History = (function () { function HTML5History(_ref) { var root = _ref.root; var onChange = _ref.onChange; babelHelpers.classCallCheck(this, HTML5History); if (root && root !== '/') { // make sure there's the starting slash if (root.charAt(0) !== '/') { root = '/' + root; } // remove trailing slash this.root = root.replace(/\/$/, ''); this.rootRE = new RegExp('^\\' + this.root); } else { this.root = null; } this.onChange = onChange; // check base tag var baseEl = document.querySelector('base'); this.base = baseEl && baseEl.getAttribute('href'); } HTML5History.prototype.start = function start() { var _this = this; this.listener = function (e) { var url = location.pathname + location.search; if (_this.root) { url = url.replace(_this.rootRE, ''); } _this.onChange(url, e && e.state, location.hash); }; window.addEventListener('popstate', this.listener); this.listener(); }; HTML5History.prototype.stop = function stop() { window.removeEventListener('popstate', this.listener); }; HTML5History.prototype.go = function go(path, replace, append) { var url = this.formatPath(path, append); if (replace) { history.replaceState({}, '', url); } else { // record scroll position by replacing current state history.replaceState({ pos: { x: window.pageXOffset, y: window.pageYOffset } }, '', location.href); // then push new state history.pushState({}, '', url); } var hashMatch = path.match(hashRE); var hash = hashMatch && hashMatch[0]; path = url // strip hash so it doesn't mess up params .replace(hashRE, '') // remove root before matching .replace(this.rootRE, ''); this.onChange(path, null, hash); }; HTML5History.prototype.formatPath = function formatPath(path, append) { return path.charAt(0) === '/' // absolute path ? this.root ? this.root + '/' + path.replace(/^\//, '') : path : resolvePath(this.base || location.pathname, path, append); }; return HTML5History; })(); var HashHistory = (function () { function HashHistory(_ref) { var hashbang = _ref.hashbang; var onChange = _ref.onChange; babelHelpers.classCallCheck(this, HashHistory); this.hashbang = hashbang; this.onChange = onChange; } HashHistory.prototype.start = function start() { var self = this; this.listener = function () { var path = location.hash; var raw = path.replace(/^#!?/, ''); // always if (raw.charAt(0) !== '/') { raw = '/' + raw; } var formattedPath = self.formatPath(raw); if (formattedPath !== path) { location.replace(formattedPath); return; } // determine query // note it's possible to have queries in both the actual URL // and the hash fragment itself. var query = location.search && path.indexOf('?') > -1 ? '&' + location.search.slice(1) : location.search; self.onChange(path.replace(/^#!?/, '') + query); }; window.addEventListener('hashchange', this.listener); this.listener(); }; HashHistory.prototype.stop = function stop() { window.removeEventListener('hashchange', this.listener); }; HashHistory.prototype.go = function go(path, replace, append) { path = this.formatPath(path, append); if (replace) { location.replace(path); } else { location.hash = path; } }; HashHistory.prototype.formatPath = function formatPath(path, append) { var isAbsoloute = path.charAt(0) === '/'; var prefix = '#' + (this.hashbang ? '!' : ''); return isAbsoloute ? prefix + path : prefix + resolvePath(location.hash.replace(/^#!?/, ''), path, append); }; return HashHistory; })(); var AbstractHistory = (function () { function AbstractHistory(_ref) { var onChange = _ref.onChange; babelHelpers.classCallCheck(this, AbstractHistory); this.onChange = onChange; this.currentPath = '/'; } AbstractHistory.prototype.start = function start() { this.onChange('/'); }; AbstractHistory.prototype.stop = function stop() { // noop }; AbstractHistory.prototype.go = function go(path, replace, append) { path = this.currentPath = this.formatPath(path, append); this.onChange(path); }; AbstractHistory.prototype.formatPath = function formatPath(path, append) { return path.charAt(0) === '/' ? path : resolvePath(this.currentPath, path, append); }; return AbstractHistory; })(); /** * Determine the reusability of an existing router view. * * @param {Directive} view * @param {Object} handler * @param {Transition} transition */ function canReuse(view, handler, transition) { var component = view.childVM; if (!component || !handler) { return false; } // important: check view.Component here because it may // have been changed in activate hook if (view.Component !== handler.component) { return false; } var canReuseFn = getRouteConfig(component, 'canReuse'); return typeof canReuseFn === 'boolean' ? canReuseFn : canReuseFn ? canReuseFn.call(component, { to: transition.to, from: transition.from }) : true; // defaults to true } /** * Check if a component can deactivate. * * @param {Directive} view * @param {Transition} transition * @param {Function} next */ function canDeactivate(view, transition, next) { var fromComponent = view.childVM; var hook = getRouteConfig(fromComponent, 'canDeactivate'); if (!hook) { next(); } else { transition.callHook(hook, fromComponent, next, { expectBoolean: true }); } } /** * Check if a component can activate. * * @param {Object} handler * @param {Transition} transition * @param {Function} next */ function canActivate(handler, transition, next) { resolveAsyncComponent(handler, function (Component) { // have to check due to async-ness if (transition.aborted) { return; } // determine if this component can be activated var hook = getRouteConfig(Component, 'canActivate'); if (!hook) { next(); } else { transition.callHook(hook, null, next, { expectBoolean: true }); } }); } /** * Call deactivate hooks for existing router-views. * * @param {Directive} view * @param {Transition} transition * @param {Function} next */ function deactivate(view, transition, next) { var component = view.childVM; var hook = getRouteConfig(component, 'deactivate'); if (!hook) { next(); } else { transition.callHooks(hook, component, next); } } /** * Activate / switch component for a router-view. * * @param {Directive} view * @param {Transition} transition * @param {Number} depth * @param {Function} [cb] */ function activate(view, transition, depth, cb, reuse) { var handler = transition.activateQueue[depth]; if (!handler) { saveChildView(view); if (view._bound) { view.setComponent(null); } cb && cb(); return; } var Component = view.Component = handler.component; var activateHook = getRouteConfig(Component, 'activate'); var dataHook = getRouteConfig(Component, 'data'); var waitForData = getRouteConfig(Component, 'waitForData'); view.depth = depth; view.activated = false; var component = undefined; var loading = !!(dataHook && !waitForData); // "reuse" is a flag passed down when the parent view is // either reused via keep-alive or as a child of a kept-alive view. // of course we can only reuse if the current kept-alive instance // is of the correct type. reuse = reuse && view.childVM && view.childVM.constructor === Component; if (reuse) { // just reuse component = view.childVM; component.$loadingRouteData = loading; } else { saveChildView(view); // unbuild current component. this step also destroys // and removes all nested child views. view.unbuild(true); // build the new component. this will also create the // direct child view of the current one. it will register // itself as view.childView. component = view.build({ _meta: { $loadingRouteData: loading }, created: function created() { this._routerView = view; } }); // handle keep-alive. // when a kept-alive child vm is restored, we need to // add its cached child views into the router's view list, // and also properly update current view's child view. if (view.keepAlive) { component.$loadingRouteData = loading; var cachedChildView = component._keepAliveRouterView; if (cachedChildView) { view.childView = cachedChildView; component._keepAliveRouterView = null; } } } // cleanup the component in case the transition is aborted // before the component is ever inserted. var cleanup = function cleanup() { component.$destroy(); }; // actually insert the component and trigger transition var insert = function insert() { if (reuse) { cb && cb(); return; } var router = transition.router; if (router._rendered || router._transitionOnLoad) { view.transition(component); } else { // no transition on first render, manual transition /* istanbul ignore if */ if (view.setCurrent) { // 0.12 compat view.setCurrent(component); } else { // 1.0 view.childVM = component; } component.$before(view.anchor, null, false); } cb && cb(); }; var afterData = function afterData() { // activate the child view if (view.childView) { activate(view.childView, transition, depth + 1, null, reuse || view.keepAlive); } insert(); }; // called after activation hook is resolved var afterActivate = function afterActivate() { view.activated = true; if (dataHook && waitForData) { // wait until data loaded to insert loadData(component, transition, dataHook, afterData, cleanup); } else { // load data and insert at the same time if (dataHook) { loadData(component, transition, dataHook); } afterData(); } }; if (activateHook) { transition.callHooks(activateHook, component, afterActivate, { cleanup: cleanup, postActivate: true }); } else { afterActivate(); } } /** * Reuse a view, just reload data if necessary. * * @param {Directive} view * @param {Transition} transition */ function reuse(view, transition) { var component = view.childVM; var dataHook = getRouteConfig(component, 'data'); if (dataHook) { loadData(component, transition, dataHook); } } /** * Asynchronously load and apply data to component. * * @param {Vue} component * @param {Transition} transition * @param {Function} hook * @param {Function} cb * @param {Function} cleanup */ function loadData(component, transition, hook, cb, cleanup) { component.$loadingRouteData = true; transition.callHooks(hook, component, function () { component.$loadingRouteData = false; component.$emit('route-data-loaded', component); cb && cb(); }, { cleanup: cleanup, postActivate: true, processData: function processData(data) { // handle promise sugar syntax var promises = []; if (isPlainObject(data)) { Object.keys(data).forEach(function (key) { var val = data[key]; if (isPromise(val)) { promises.push(val.then(function (resolvedVal) { component.$set(key, resolvedVal); })); } else { component.$set(key, val); } }); } if (promises.length) { return promises[0].constructor.all(promises); } } }); } /** * Save the child view for a kept-alive view so that * we can restore it when it is switched back to. * * @param {Directive} view */ function saveChildView(view) { if (view.keepAlive && view.childVM && view.childView) { view.childVM._keepAliveRouterView = view.childView; } view.childView = null; } /** * Check plain object. * * @param {*} val */ function isPlainObject(val) { return Object.prototype.toString.call(val) === '[object Object]'; } /** * A RouteTransition object manages the pipeline of a * router-view switching process. This is also the object * passed into user route hooks. * * @param {Router} router * @param {Route} to * @param {Route} from */ var RouteTransition = (function () { function RouteTransition(router, to, from) { babelHelpers.classCallCheck(this, RouteTransition); this.router = router; this.to = to; this.from = from; this.next = null; this.aborted = false; this.done = false; } /** * Abort current transition and return to previous location. */ RouteTransition.prototype.abort = function abort() { if (!this.aborted) { this.aborted = true; // if the root path throws an error during validation // on initial load, it gets caught in an infinite loop. var abortingOnLoad = !this.from.path && this.to.path === '/'; if (!abortingOnLoad) { this.router.replace(this.from.path || '/'); } } }; /** * Abort current transition and redirect to a new location. * * @param {String} path */ RouteTransition.prototype.redirect = function redirect(path) { if (!this.aborted) { this.aborted = true; if (typeof path === 'string') { path = mapParams(path, this.to.params, this.to.query); } else { path.params = path.params || this.to.params; path.query = path.query || this.to.query; } this.router.replace(path); } }; /** * A router view transition's pipeline can be described as * follows, assuming we are transitioning from an existing * chain [Component A, Component B] to a new * chain [Component A, Component C]: * * A A * | => | * B C * * 1. Reusablity phase: * -> canReuse(A, A) * -> canReuse(B, C) * -> determine new queues: * - deactivation: [B] * - activation: [C] * * 2. Validation phase: * -> canDeactivate(B) * -> canActivate(C) * * 3. Activation phase: * -> deactivate(B) * -> activate(C) * * Each of these steps can be asynchronous, and any * step can potentially abort the transition. * * @param {Function} cb */ RouteTransition.prototype.start = function start(cb) { var transition = this; // determine the queue of views to deactivate var deactivateQueue = []; var view = this.router._rootView; while (view) { deactivateQueue.unshift(view); view = view.childView; } var reverseDeactivateQueue = deactivateQueue.slice().reverse(); // determine the queue of route handlers to activate var activateQueue = this.activateQueue = toArray(this.to.matched).map(function (match) { return match.handler; }); // 1. Reusability phase var i = undefined, reuseQueue = undefined; for (i = 0; i < reverseDeactivateQueue.length; i++) { if (!canReuse(reverseDeactivateQueue[i], activateQueue[i], transition)) { break; } } if (i > 0) { reuseQueue = reverseDeactivateQueue.slice(0, i); deactivateQueue = reverseDeactivateQueue.slice(i).reverse(); activateQueue = activateQueue.slice(i); } // 2. Validation phase transition.runQueue(deactivateQueue, canDeactivate, function () { transition.runQueue(activateQueue, canActivate, function () { transition.runQueue(deactivateQueue, deactivate, function () { // 3. Activation phase // Update router current route transition.router._onTransitionValidated(transition); // trigger reuse for all reused views reuseQueue && reuseQueue.forEach(function (view) { return reuse(view, transition); }); // the root of the chain that needs to be replaced // is the top-most non-reusable view. if (deactivateQueue.length) { var _view = deactivateQueue[deactivateQueue.length - 1]; var depth = reuseQueue ? reuseQueue.length : 0; activate(_view, transition, depth, cb); } else { cb(); } }); }); }); }; /** * Asynchronously and sequentially apply a function to a * queue. * * @param {Array} queue * @param {Function} fn * @param {Function} cb */ RouteTransition.prototype.runQueue = function runQueue(queue, fn, cb) { var transition = this; step(0); function step(index) { if (index >= queue.length) { cb(); } else { fn(queue[index], transition, function () { step(index + 1); }); } } }; /** * Call a user provided route transition hook and handle * the response (e.g. if the user returns a promise). * * If the user neither expects an argument nor returns a * promise, the hook is assumed to be synchronous. * * @param {Function} hook * @param {*} [context] * @param {Function} [cb] * @param {Object} [options] * - {Boolean} expectBoolean * - {Boolean} postActive * - {Function} processData * - {Function} cleanup */ RouteTransition.prototype.callHook = function callHook(hook, context, cb) { var _ref = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; var _ref$expectBoolean = _ref.expectBoolean; var expectBoolean = _ref$expectBoolean === undefined ? false : _ref$expectBoolean; var _ref$postActivate = _ref.postActivate; var postActivate = _ref$postActivate === undefined ? false : _ref$postActivate; var processData = _ref.processData; var cleanup = _ref.cleanup; var transition = this; var nextCalled = false; // abort the transition var abort = function abort() { cleanup && cleanup(); transition.abort(); }; // handle errors var onError = function onError(err) { postActivate ? next() : abort(); if (err && !transition.router._suppress) { warn$1('Uncaught error during transition: '); throw err instanceof Error ? err : new Error(err); } }; // since promise swallows errors, we have to // throw it in the next tick... var onPromiseError = function onPromiseError(err) { try { onError(err); } catch (e) { setTimeout(function () { throw e; }, 0); } }; // advance the transition to the next step var next = function next() { if (nextCalled) { warn$1('transition.next() should be called only once.'); return; } nextCalled = true; if (transition.aborted) { cleanup && cleanup(); return; } cb && cb(); }; var nextWithBoolean = function nextWithBoolean(res) { if (typeof res === 'boolean') { res ? next() : abort(); } else if (isPromise(res)) { res.then(function (ok) { ok ? next() : abort(); }, onPromiseError); } else if (!hook.length) { next(); } }; var nextWithData = function nextWithData(data) { var res = undefined; try { res = processData(data); } catch (err) { return onError(err); } if (isPromise(res)) { res.then(next, onPromiseError); } else { next(); } }; // expose a clone of the transition object, so that each // hook gets a clean copy and prevent the user from // messing with the internals. var exposed = { to: transition.to, from: transition.from, abort: abort, next: processData ? nextWithData : next, redirect: function redirect() { transition.redirect.apply(transition, arguments); } }; // actually call the hook var res = undefined; try { res = hook.call(context, exposed); } catch (err) { return onError(err); } if (expectBoolean) { // boolean hooks nextWithBoolean(res); } else if (isPromise(res)) { // promise if (processData) { res.then(nextWithData, onPromiseError); } else { res.then(next, onPromiseError); } } else if (processData && isPlainOjbect(res)) { // data promise sugar nextWithData(res); } else if (!hook.length) { next(); } }; /** * Call a single hook or an array of async hooks in series. * * @param {Array} hooks * @param {*} context * @param {Function} cb * @param {Object} [options] */ RouteTransition.prototype.callHooks = function callHooks(hooks, context, cb, options) { var _this = this; if (Array.isArray(hooks)) { this.runQueue(hooks, function (hook, _, next) { if (!_this.aborted) { _this.callHook(hook, context, next, options); } }, cb); } else { this.callHook(hooks, context, cb, options); } }; return RouteTransition; })(); function isPlainOjbect(val) { return Object.prototype.toString.call(val) === '[object Object]'; } function toArray(val) { return val ? Array.prototype.slice.call(val) : []; } var internalKeysRE = /^(component|subRoutes|fullPath)$/; /** * Route Context Object * * @param {String} path * @param {Router} router */ var Route = function Route(path, router) { var _this = this; babelHelpers.classCallCheck(this, Route); var matched = router._recognizer.recognize(path); if (matched) { // copy all custom fields from route configs [].forEach.call(matched, function (match) { for (var key in match.handler) { if (!internalKeysRE.test(key)) { _this[key] = match.handler[key]; } } }); // set query and params this.query = matched.queryParams; this.params = [].reduce.call(matched, function (prev, cur) { if (cur.params) { for (var key in cur.params) { prev[key] = cur.params[key]; } } return prev; }, {}); } // expose path and router this.path = path; // for internal use this.matched = matched || router._notFoundHandler; // internal reference to router Object.defineProperty(this, 'router', { enumerable: false, value: router }); // Important: freeze self to prevent observation Object.freeze(this); }; function applyOverride (Vue) { var _Vue$util = Vue.util; var extend = _Vue$util.extend; var isArray = _Vue$util.isArray; var defineReactive = _Vue$util.defineReactive; // override Vue's init and destroy process to keep track of router instances var init = Vue.prototype._init; Vue.prototype._init = function (options) { options = options || {}; var root = options._parent || options.parent || this; var router = root.$router; var route = root.$route; if (router) { // expose router this.$router = router; router._children.push(this); /* istanbul ignore if */ if (this._defineMeta) { // 0.12 this._defineMeta('$route', route); } else { // 1.0 defineReactive(this, '$route', route); } } init.call(this, options); }; var destroy = Vue.prototype._destroy; Vue.prototype._destroy = function () { if (!this._isBeingDestroyed && this.$router) { this.$router._children.$remove(this); } destroy.apply(this, arguments); }; // 1.0 only: enable route mixins var strats = Vue.config.optionMergeStrategies; var hooksToMergeRE = /^(data|activate|deactivate)$/; if (strats) { strats.route = function (parentVal, childVal) { if (!childVal) return parentVal; if (!parentVal) return childVal; var ret = {}; extend(ret, parentVal); for (var key in childVal) { var a = ret[key]; var b = childVal[key]; // for data, activate and deactivate, we need to merge them into // arrays similar to lifecycle hooks. if (a && hooksToMergeRE.test(key)) { ret[key] = (isArray(a) ? a : [a]).concat(b); } else { ret[key] = b; } } return ret; }; } } function View (Vue) { var _ = Vue.util; var componentDef = // 0.12 Vue.directive('_component') || // 1.0 Vue.internalDirectives.component; // extends the internal component directive var viewDef = _.extend({}, componentDef); // with some overrides _.extend(viewDef, { _isRouterView: true, bind: function bind() { var route = this.vm.$route; /* istanbul ignore if */ if (!route) { warn$1(' can only be used inside a ' + 'router-enabled app.'); return; } // force dynamic directive so v-component doesn't // attempt to build right now this._isDynamicLiteral = true; // finally, init by delegating to v-component componentDef.bind.call(this); // locate the parent view var parentView = undefined; var parent = this.vm; while (parent) { if (parent._routerView) { parentView = parent._routerView; break; } parent = parent.$parent; } if (parentView) { // register self as a child of the parent view, // instead of activating now. This is so that the // child's activate hook is called after the // parent's has resolved. this.parentView = parentView; parentView.childView = this; } else { // this is the root view! var router = route.router; router._rootView = this; } // handle late-rendered view // two possibilities: // 1. root view rendered after transition has been // validated; // 2. child view rendered after parent view has been // activated. var transition = route.router._currentTransition; if (!parentView && transition.done || parentView && parentView.activated) { var depth = parentView ? parentView.depth + 1 : 0; activate(this, transition, depth); } }, unbind: function unbind() { if (this.parentView) { this.parentView.childView = null; } componentDef.unbind.call(this); } }); Vue.elementDirective('router-view', viewDef); } var trailingSlashRE = /\/$/; var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g; var queryStringRE = /\?.*$/; // install v-link, which provides navigation support for // HTML5 history mode function Link (Vue) { var _Vue$util = Vue.util; var _bind = _Vue$util.bind; var isObject = _Vue$util.isObject; var addClass = _Vue$util.addClass; var removeClass = _Vue$util.removeClass; var onPriority = Vue.directive('on').priority; var LINK_UPDATE = '__vue-router-link-update__'; var activeId = 0; Vue.directive('link-active', { priority: 9999, bind: function bind() { var _this = this; var id = String(activeId++); // collect v-links contained within this element. // we need do this here before the parent-child relationship // gets messed up by terminal directives (if, for, components) var childLinks = this.el.querySelectorAll('[v-link]'); for (var i = 0, l = childLinks.length; i < l; i++) { var link = childLinks[i]; var existingId = link.getAttribute(LINK_UPDATE); var value = existingId ? existingId + ',' + id : id; // leave a mark on the link element which can be persisted // through fragment clones. link.setAttribute(LINK_UPDATE, value); } this.vm.$on(LINK_UPDATE, this.cb = function (link, path) { if (link.activeIds.indexOf(id) > -1) { link.updateClasses(path, _this.el); } }); }, unbind: function unbind() { this.vm.$off(LINK_UPDATE, this.cb); } }); Vue.directive('link', { priority: onPriority - 2, bind: function bind() { var vm = this.vm; /* istanbul ignore if */ if (!vm.$route) { warn$1('v-link can only be used inside a router-enabled app.'); return; } this.router = vm.$route.router; // update things when the route changes this.unwatch = vm.$watch('$route', _bind(this.onRouteUpdate, this)); // check v-link-active ids var activeIds = this.el.getAttribute(LINK_UPDATE); if (activeIds) { this.el.removeAttribute(LINK_UPDATE); this.activeIds = activeIds.split(','); } // no need to handle click if link expects to be opened // in a new window/tab. /* istanbul ignore if */ if (this.el.tagName === 'A' && this.el.getAttribute('target') === '_blank') { return; } // handle click this.handler = _bind(this.onClick, this); this.el.addEventListener('click', this.handler); }, update: function update(target) { this.target = target; if (isObject(target)) { this.append = target.append; this.exact = target.exact; this.prevActiveClass = this.activeClass; this.activeClass = target.activeClass; } this.onRouteUpdate(this.vm.$route); }, onClick: function onClick(e) { // don't redirect with control keys /* istanbul ignore if */ if (e.metaKey || e.ctrlKey || e.shiftKey) return; // don't redirect when preventDefault called /* istanbul ignore if */ if (e.defaultPrevented) return; // don't redirect on right click /* istanbul ignore if */ if (e.button !== 0) return; var target = this.target; if (target) { // v-link with expression, just go e.preventDefault(); this.router.go(target); } else { // no expression, delegate for an inside var el = e.target; while (el.tagName !== 'A' && el !== this.el) { el = el.parentNode; } if (el.tagName === 'A' && sameOrigin(el)) { e.preventDefault(); var path = el.pathname; if (this.router.history.root) { path = path.replace(this.router.history.rootRE, ''); } this.router.go({ path: path, replace: target && target.replace, append: target && target.append }); } } }, onRouteUpdate: function onRouteUpdate(route) { // router.stringifyPath is dependent on current route // and needs to be called again whenver route changes. var newPath = this.router.stringifyPath(this.target); if (this.path !== newPath) { this.path = newPath; this.updateActiveMatch(); this.updateHref(); } if (this.activeIds) { this.vm.$emit(LINK_UPDATE, this, route.path); } else { this.updateClasses(route.path, this.el); } }, updateActiveMatch: function updateActiveMatch() { this.activeRE = this.path && !this.exact ? new RegExp('^' + this.path.replace(/\/$/, '').replace(queryStringRE, '').replace(regexEscapeRE, '\\$&') + '(\\/|$)') : null; }, updateHref: function updateHref() { if (this.el.tagName !== 'A') { return; } var path = this.path; var router = this.router; var isAbsolute = path.charAt(0) === '/'; // do not format non-hash relative paths var href = path && (router.mode === 'hash' || isAbsolute) ? router.history.formatPath(path, this.append) : path; if (href) { this.el.href = href; } else { this.el.removeAttribute('href'); } }, updateClasses: function updateClasses(path, el) { var activeClass = this.activeClass || this.router._linkActiveClass; // clear old class if (this.prevActiveClass && this.prevActiveClass !== activeClass) { toggleClasses(el, this.prevActiveClass, removeClass); } // remove query string before matching var dest = this.path.replace(queryStringRE, ''); path = path.replace(queryStringRE, ''); // add new class if (this.exact) { if (dest === path || // also allow additional trailing slash dest.charAt(dest.length - 1) !== '/' && dest === path.replace(trailingSlashRE, '')) { toggleClasses(el, activeClass, addClass); } else { toggleClasses(el, activeClass, removeClass); } } else { if (this.activeRE && this.activeRE.test(path)) { toggleClasses(el, activeClass, addClass); } else { toggleClasses(el, activeClass, removeClass); } } }, unbind: function unbind() { this.el.removeEventListener('click', this.handler); this.unwatch && this.unwatch(); } }); function sameOrigin(link) { return link.protocol === location.protocol && link.hostname === location.hostname && link.port === location.port; } // this function is copied from v-bind:class implementation until // we properly expose it... function toggleClasses(el, key, fn) { key = key.trim(); if (key.indexOf(' ') === -1) { fn(el, key); return; } var keys = key.split(/\s+/); for (var i = 0, l = keys.length; i < l; i++) { fn(el, keys[i]); } } } var historyBackends = { abstract: AbstractHistory, hash: HashHistory, html5: HTML5History }; // late bind during install var Vue = undefined; /** * Router constructor * * @param {Object} [options] */ var Router = (function () { function Router() { var _this = this; var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var _ref$hashbang = _ref.hashbang; var hashbang = _ref$hashbang === undefined ? true : _ref$hashbang; var _ref$abstract = _ref.abstract; var abstract = _ref$abstract === undefined ? false : _ref$abstract; var _ref$history = _ref.history; var history = _ref$history === undefined ? false : _ref$history; var _ref$saveScrollPosition = _ref.saveScrollPosition; var saveScrollPosition = _ref$saveScrollPosition === undefined ? false : _ref$saveScrollPosition; var _ref$transitionOnLoad = _ref.transitionOnLoad; var transitionOnLoad = _ref$transitionOnLoad === undefined ? false : _ref$transitionOnLoad; var _ref$suppressTransitionError = _ref.suppressTransitionError; var suppressTransitionError = _ref$suppressTransitionError === undefined ? false : _ref$suppressTransitionError; var _ref$root = _ref.root; var root = _ref$root === undefined ? null : _ref$root; var _ref$linkActiveClass = _ref.linkActiveClass; var linkActiveClass = _ref$linkActiveClass === undefined ? 'v-link-active' : _ref$linkActiveClass; babelHelpers.classCallCheck(this, Router); /* istanbul ignore if */ if (!Router.installed) { throw new Error('Please install the Router with Vue.use() before ' + 'creating an instance.'); } // Vue instances this.app = null; this._children = []; // route recognizer this._recognizer = new RouteRecognizer(); this._guardRecognizer = new RouteRecognizer(); // state this._started = false; this._startCb = null; this._currentRoute = {}; this._currentTransition = null; this._previousTransition = null; this._notFoundHandler = null; this._notFoundRedirect = null; this._beforeEachHooks = []; this._afterEachHooks = []; // trigger transition on initial render? this._rendered = false; this._transitionOnLoad = transitionOnLoad; // history mode this._root = root; this._abstract = abstract; this._hashbang = hashbang; // check if HTML5 history is available var hasPushState = typeof window !== 'undefined' && window.history && window.history.pushState; this._history = history && hasPushState; this._historyFallback = history && !hasPushState; // create history object var inBrowser = Vue.util.inBrowser; this.mode = !inBrowser || this._abstract ? 'abstract' : this._history ? 'html5' : 'hash'; var History = historyBackends[this.mode]; this.history = new History({ root: root, hashbang: this._hashbang, onChange: function onChange(path, state, anchor) { _this._match(path, state, anchor); } }); // other options this._saveScrollPosition = saveScrollPosition; this._linkActiveClass = linkActiveClass; this._suppress = suppressTransitionError; } /** * Allow directly passing components to a route * definition. * * @param {String} path * @param {Object} handler */ // API =================================================== /** * Register a map of top-level paths. * * @param {Object} map */ Router.prototype.map = function map(_map) { for (var route in _map) { this.on(route, _map[route]); } return this; }; /** * Register a single root-level path * * @param {String} rootPath * @param {Object} handler * - {String} component * - {Object} [subRoutes] * - {Boolean} [forceRefresh] * - {Function} [before] * - {Function} [after] */ Router.prototype.on = function on(rootPath, handler) { if (rootPath === '*') { this._notFound(handler); } else { this._addRoute(rootPath, handler, []); } return this; }; /** * Set redirects. * * @param {Object} map */ Router.prototype.redirect = function redirect(map) { for (var path in map) { this._addRedirect(path, map[path]); } return this; }; /** * Set aliases. * * @param {Object} map */ Router.prototype.alias = function alias(map) { for (var path in map) { this._addAlias(path, map[path]); } return this; }; /** * Set global before hook. * * @param {Function} fn */ Router.prototype.beforeEach = function beforeEach(fn) { this._beforeEachHooks.push(fn); return this; }; /** * Set global after hook. * * @param {Function} fn */ Router.prototype.afterEach = function afterEach(fn) { this._afterEachHooks.push(fn); return this; }; /** * Navigate to a given path. * The path can be an object describing a named path in * the format of { name: '...', params: {}, query: {}} * The path is assumed to be already decoded, and will * be resolved against root (if provided) * * @param {String|Object} path * @param {Boolean} [replace] */ Router.prototype.go = function go(path) { var replace = false; var append = false; if (Vue.util.isObject(path)) { replace = path.replace; append = path.append; } path = this.stringifyPath(path); if (path) { this.history.go(path, replace, append); } }; /** * Short hand for replacing current path * * @param {String} path */ Router.prototype.replace = function replace(path) { if (typeof path === 'string') { path = { path: path }; } path.replace = true; this.go(path); }; /** * Start the router. * * @param {VueConstructor} App * @param {String|Element} container * @param {Function} [cb] */ Router.prototype.start = function start(App, container, cb) { /* istanbul ignore if */ if (this._started) { warn$1('already started.'); return; } this._started = true; this._startCb = cb; if (!this.app) { /* istanbul ignore if */ if (!App || !container) { throw new Error('Must start vue-router with a component and a ' + 'root container.'); } /* istanbul ignore if */ if (App instanceof Vue) { throw new Error('Must start vue-router with a component, not a ' + 'Vue instance.'); } this._appContainer = container; var Ctor = this._appConstructor = typeof App === 'function' ? App : Vue.extend(App); // give it a name for better debugging Ctor.options.name = Ctor.options.name || 'RouterApp'; } // handle history fallback in browsers that do not // support HTML5 history API if (this._historyFallback) { var _location = window.location; var _history = new HTML5History({ root: this._root }); var path = _history.root ? _location.pathname.replace(_history.rootRE, '') : _location.pathname; if (path && path !== '/') { _location.assign((_history.root || '') + '/' + this.history.formatPath(path) + _location.search); return; } } this.history.start(); }; /** * Stop listening to route changes. */ Router.prototype.stop = function stop() { this.history.stop(); this._started = false; }; /** * Normalize named route object / string paths into * a string. * * @param {Object|String|Number} path * @return {String} */ Router.prototype.stringifyPath = function stringifyPath(path) { var generatedPath = ''; if (path && typeof path === 'object') { if (path.name) { var extend = Vue.util.extend; var currentParams = this._currentTransition && this._currentTransition.to.params; var targetParams = path.params || {}; var params = currentParams ? extend(extend({}, currentParams), targetParams) : targetParams; generatedPath = encodeURI(this._recognizer.generate(path.name, params)); } else if (path.path) { generatedPath = encodeURI(path.path); } if (path.query) { // note: the generated query string is pre-URL-encoded by the recognizer var query = this._recognizer.generateQueryString(path.query); if (generatedPath.indexOf('?') > -1) { generatedPath += '&' + query.slice(1); } else { generatedPath += query; } } } else { generatedPath = encodeURI(path ? path + '' : ''); } return generatedPath; }; // Internal methods ====================================== /** * Add a route containing a list of segments to the internal * route recognizer. Will be called recursively to add all * possible sub-routes. * * @param {String} path * @param {Object} handler * @param {Array} segments */ Router.prototype._addRoute = function _addRoute(path, handler, segments) { guardComponent(path, handler); handler.path = path; handler.fullPath = (segments.reduce(function (path, segment) { return path + segment.path; }, '') + path).replace('//', '/'); segments.push({ path: path, handler: handler }); this._recognizer.add(segments, { as: handler.name }); // add sub routes if (handler.subRoutes) { for (var subPath in handler.subRoutes) { // recursively walk all sub routes this._addRoute(subPath, handler.subRoutes[subPath], // pass a copy in recursion to avoid mutating // across branches segments.slice()); } } }; /** * Set the notFound route handler. * * @param {Object} handler */ Router.prototype._notFound = function _notFound(handler) { guardComponent('*', handler); this._notFoundHandler = [{ handler: handler }]; }; /** * Add a redirect record. * * @param {String} path * @param {String} redirectPath */ Router.prototype._addRedirect = function _addRedirect(path, redirectPath) { if (path === '*') { this._notFoundRedirect = redirectPath; } else { this._addGuard(path, redirectPath, this.replace); } }; /** * Add an alias record. * * @param {String} path * @param {String} aliasPath */ Router.prototype._addAlias = function _addAlias(path, aliasPath) { this._addGuard(path, aliasPath, this._match); }; /** * Add a path guard. * * @param {String} path * @param {String} mappedPath * @param {Function} handler */ Router.prototype._addGuard = function _addGuard(path, mappedPath, _handler) { var _this2 = this; this._guardRecognizer.add([{ path: path, handler: function handler(match, query) { var realPath = mapParams(mappedPath, match.params, query); _handler.call(_this2, realPath); } }]); }; /** * Check if a path matches any redirect records. * * @param {String} path * @return {Boolean} - if true, will skip normal match. */ Router.prototype._checkGuard = function _checkGuard(path) { var matched = this._guardRecognizer.recognize(path, true); if (matched) { matched[0].handler(matched[0], matched.queryParams); return true; } else if (this._notFoundRedirect) { matched = this._recognizer.recognize(path); if (!matched) { this.replace(this._notFoundRedirect); return true; } } }; /** * Match a URL path and set the route context on vm, * triggering view updates. * * @param {String} path * @param {Object} [state] * @param {String} [anchor] */ Router.prototype._match = function _match(path, state, anchor) { var _this3 = this; if (this._checkGuard(path)) { return; } var currentRoute = this._currentRoute; var currentTransition = this._currentTransition; if (currentTransition) { if (currentTransition.to.path === path) { // do nothing if we have an active transition going to the same path return; } else if (currentRoute.path === path) { // We are going to the same path, but we also have an ongoing but // not-yet-validated transition. Abort that transition and reset to // prev transition. currentTransition.aborted = true; this._currentTransition = this._prevTransition; return; } else { // going to a totally different path. abort ongoing transition. currentTransition.aborted = true; } } // construct new route and transition context var route = new Route(path, this); var transition = new RouteTransition(this, route, currentRoute); // current transition is updated right now. // however, current route will only be updated after the transition has // been validated. this._prevTransition = currentTransition; this._currentTransition = transition; if (!this.app) { (function () { // initial render var router = _this3; _this3.app = new _this3._appConstructor({ el: _this3._appContainer, created: function created() { this.$router = router; }, _meta: { $route: route } }); })(); } // check global before hook var beforeHooks = this._beforeEachHooks; var startTransition = function startTransition() { transition.start(function () { _this3._postTransition(route, state, anchor); }); }; if (beforeHooks.length) { transition.runQueue(beforeHooks, function (hook, _, next) { if (transition === _this3._currentTransition) { transition.callHook(hook, null, next, { expectBoolean: true }); } }, startTransition); } else { startTransition(); } if (!this._rendered && this._startCb) { this._startCb.call(null); } // HACK: // set rendered to true after the transition start, so // that components that are acitvated synchronously know // whether it is the initial render. this._rendered = true; }; /** * Set current to the new transition. * This is called by the transition object when the * validation of a route has succeeded. * * @param {Transition} transition */ Router.prototype._onTransitionValidated = function _onTransitionValidated(transition) { // set current route var route = this._currentRoute = transition.to; // update route context for all children if (this.app.$route !== route) { this.app.$route = route; this._children.forEach(function (child) { child.$route = route; }); } // call global after hook if (this._afterEachHooks.length) { this._afterEachHooks.forEach(function (hook) { return hook.call(null, { to: transition.to, from: transition.from }); }); } this._currentTransition.done = true; }; /** * Handle stuff after the transition. * * @param {Route} route * @param {Object} [state] * @param {String} [anchor] */ Router.prototype._postTransition = function _postTransition(route, state, anchor) { // handle scroll positions // saved scroll positions take priority // then we check if the path has an anchor var pos = state && state.pos; if (pos && this._saveScrollPosition) { Vue.nextTick(function () { window.scrollTo(pos.x, pos.y); }); } else if (anchor) { Vue.nextTick(function () { var el = document.getElementById(anchor.slice(1)); if (el) { window.scrollTo(window.scrollX, el.offsetTop); } }); } }; return Router; })(); function guardComponent(path, handler) { var comp = handler.component; if (Vue.util.isPlainObject(comp)) { comp = handler.component = Vue.extend(comp); } /* istanbul ignore if */ if (typeof comp !== 'function') { handler.component = null; warn$1('invalid component for route "' + path + '".'); } } /* Installation */ Router.installed = false; /** * Installation interface. * Install the necessary directives. */ Router.install = function (externalVue) { /* istanbul ignore if */ if (Router.installed) { warn$1('already installed.'); return; } Vue = externalVue; applyOverride(Vue); View(Vue); Link(Vue); exports$1.Vue = Vue; Router.installed = true; }; // auto install /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(Router); } return Router; }));