vendor/assets/javascripts/vue-router2.js in vuejs-1.0.31 vs vendor/assets/javascripts/vue-router2.js in vuejs-1.0.33

- old
+ new

@@ -1,15 +1,11 @@ /** - * vue-router v2.0.0 - * (c) 2016 Evan You - * @license MIT - */ -(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'; + * vue-router v2.1.1 + * (c) 2016 Evan You + * @license MIT + */ +'use strict'; var View = { name: 'router-view', functional: true, props: { @@ -45,98 +41,36 @@ var matched = route.matched[depth] if (!matched) { return h() } + var name = props.name var component = inactive - ? cache[props.name] - : (cache[props.name] = matched.components[props.name]) + ? cache[name] + : (cache[name] = matched.components[name]) if (!inactive) { - (data.hook || (data.hook = {})).init = function (vnode) { - matched.instances[props.name] = vnode.child + var hooks = data.hook || (data.hook = {}) + hooks.init = function (vnode) { + matched.instances[name] = vnode.child } + hooks.prepatch = function (oldVnode, vnode) { + matched.instances[name] = vnode.child + } + hooks.destroy = function (vnode) { + if (matched.instances[name] === vnode.child) { + matched.instances[name] = undefined + } + } } return h(component, data, children) } } /* */ -function resolvePath ( - relative, - base, - append -) { - if (relative.charAt(0) === '/') { - return relative - } - - if (relative.charAt(0) === '?' || 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('/') -} - -function parsePath (path) { - var hash = '' - var query = '' - - var hashIndex = path.indexOf('#') - if (hashIndex >= 0) { - hash = path.slice(hashIndex) - path = path.slice(0, hashIndex) - } - - var queryIndex = path.indexOf('?') - if (queryIndex >= 0) { - query = path.slice(queryIndex + 1) - path = path.slice(0, queryIndex) - } - - return { - path: path, - query: query, - hash: hash - } -} - -function cleanPath (path) { - return path.replace(/\/\//g, '/') -} - -/* */ - function assert (condition, message) { if (!condition) { throw new Error(("[vue-router] " + message)) } } @@ -161,11 +95,11 @@ if (query) { var parsedQuery try { parsedQuery = parseQuery(query) } catch (e) { - warn(false, e.message) + process.env.NODE_ENV !== 'production' && warn(false, e.message) parsedQuery = {} } for (var key in extraQuery) { parsedQuery[key] = extraQuery[key] } @@ -174,11 +108,11 @@ return extraQuery } } function parseQuery (query) { - var res = Object.create(null) + var res = {} query = query.trim().replace(/^(\?|#|&)/, '') if (!query) { return res @@ -202,11 +136,11 @@ return res } function stringifyQuery (obj) { - var res = obj ? Object.keys(obj).sort().map(function (key) { + var res = obj ? Object.keys(obj).map(function (key) { var val = obj[key] if (val === undefined) { return '' } @@ -316,11 +250,11 @@ return aKeys.every(function (key) { return String(a[key]) === String(b[key]); }) } function isIncludedRoute (current, target) { return ( - current.path.indexOf(target.path) === 0 && + current.path.indexOf(target.path.replace(/\/$/, '')) === 0 && (!target.hash || current.hash === target.hash) && queryIncludes(current.query, target.query) ) } @@ -333,41 +267,10 @@ return true } /* */ -function normalizeLocation ( - raw, - current, - append -) { - var next = typeof raw === 'string' ? { path: raw } : raw - if (next.name || next._normalized) { - return next - } - - var parsedPath = parsePath(next.path || '') - var basePath = (current && current.path) || '/' - var path = parsedPath.path - ? resolvePath(parsedPath.path, basePath, append) - : (current && current.path) || '/' - var query = resolveQuery(parsedPath.query, next.query) - var hash = next.hash || parsedPath.hash - if (hash && hash.charAt(0) !== '#') { - hash = "#" + hash - } - - return { - _normalized: true, - path: path, - query: query, - hash: hash - } -} - -/* */ - // work around weird flow bug var toTypes = [String, Object] var Link = { name: 'router-link', @@ -381,40 +284,49 @@ default: 'a' }, exact: Boolean, append: Boolean, replace: Boolean, - activeClass: String + activeClass: String, + event: { + type: [String, Array], + default: 'click' + } }, render: function render (h) { var this$1 = this; var router = this.$router var current = this.$route - var to = normalizeLocation(this.to, current, this.append) - var resolved = router.match(to) - var fullPath = resolved.redirectedFrom || resolved.fullPath - var base = router.history.base - var href = base ? cleanPath(base + fullPath) : fullPath + var ref = router.resolve(this.to, current, this.append); + var normalizedTo = ref.normalizedTo; + var resolved = ref.resolved; + var href = ref.href; var classes = {} var activeClass = this.activeClass || router.options.linkActiveClass || 'router-link-active' - var compareTarget = to.path ? createRoute(null, to) : resolved + var compareTarget = normalizedTo.path ? createRoute(null, normalizedTo) : resolved classes[activeClass] = this.exact ? isSameRoute(current, compareTarget) : isIncludedRoute(current, compareTarget) - var on = { - click: function (e) { - e.preventDefault() + var handler = function (e) { + if (guardEvent(e)) { if (this$1.replace) { - router.replace(to) + router.replace(normalizedTo) } else { - router.push(to) + router.push(normalizedTo) } } } + var on = { click: guardEvent } + if (Array.isArray(this.event)) { + this.event.forEach(function (e) { on[e] = handler }) + } else { + on[this.event] = handler + } + var data = { class: classes } if (this.tag === 'a') { @@ -422,21 +334,46 @@ data.attrs = { href: href } } else { // find the first <a> child and apply listener and href var a = findAnchor(this.$slots.default) if (a) { - var aData = a.data || (a.data = {}) + // in case the <a> is a static node + a.isStatic = false + var extend = _Vue.util.extend + var aData = a.data = extend({}, a.data) aData.on = on - var aAttrs = aData.attrs || (aData.attrs = {}) + var aAttrs = a.data.attrs = extend({}, a.data.attrs) aAttrs.href = href + } else { + // doesn't have <a> child, apply listener to self + data.on = on } } return h(this.tag, data, this.$slots.default) } } +function guardEvent (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 } + // don't redirect if `target="_blank"` + /* istanbul ignore if */ + var target = e.target.getAttribute('target') + if (/\b_blank\b/i.test(target)) { return } + + e.preventDefault() + return true +} + function findAnchor (children) { if (children) { var child for (var i = 0; i < children.length; i++) { child = children[i] @@ -448,14 +385,18 @@ } } } } +var _Vue + function install (Vue) { if (install.installed) { return } install.installed = true + _Vue = Vue + Object.defineProperty(Vue.prototype, '$router', { get: function get () { return this.$root._router } }) Object.defineProperty(Vue.prototype, '$route', { @@ -472,12 +413,178 @@ } }) Vue.component('router-view', View) Vue.component('router-link', Link) + + var strats = Vue.config.optionMergeStrategies + // use the same hook merging strategy for route hooks + strats.beforeRouteEnter = strats.beforeRouteLeave = strats.created } +/* */ + +function resolvePath ( + relative, + base, + append +) { + if (relative.charAt(0) === '/') { + return relative + } + + if (relative.charAt(0) === '?' || 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('/') +} + +function parsePath (path) { + var hash = '' + var query = '' + + var hashIndex = path.indexOf('#') + if (hashIndex >= 0) { + hash = path.slice(hashIndex) + path = path.slice(0, hashIndex) + } + + var queryIndex = path.indexOf('?') + if (queryIndex >= 0) { + query = path.slice(queryIndex + 1) + path = path.slice(0, queryIndex) + } + + return { + path: path, + query: query, + hash: hash + } +} + +function cleanPath (path) { + return path.replace(/\/\//g, '/') +} + +/* */ + +function createRouteMap (routes) { + var pathMap = Object.create(null) + var nameMap = Object.create(null) + + routes.forEach(function (route) { + addRouteRecord(pathMap, nameMap, route) + }) + + return { + pathMap: pathMap, + nameMap: nameMap + } +} + +function addRouteRecord ( + pathMap, + nameMap, + route, + parent, + matchAs +) { + var path = route.path; + var name = route.name; + if (process.env.NODE_ENV !== 'production') { + assert(path != null, "\"path\" is required in a route configuration.") + assert( + typeof route.component !== 'string', + "route config \"component\" for path: " + (String(path || name)) + " cannot be a " + + "string id. Use an actual component instead." + ) + } + + var record = { + path: normalizePath(path, parent), + components: route.components || { default: route.component }, + instances: {}, + name: name, + parent: parent, + matchAs: matchAs, + redirect: route.redirect, + beforeEnter: route.beforeEnter, + meta: route.meta || {} + } + + if (route.children) { + // Warn if route is named and has a default child route. + // If users navigate to this route by name, the default child will + // not be rendered (GH Issue #629) + if (process.env.NODE_ENV !== 'production') { + if (route.name && route.children.some(function (child) { return /^\/?$/.test(child.path); })) { + warn(false, ("Named Route '" + (route.name) + "' has a default child route.\n When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), the default child route will not be rendered.\n Remove the name from this route and use the name of the default child route for named links instead.") + ) + } + } + route.children.forEach(function (child) { + addRouteRecord(pathMap, nameMap, child, record) + }) + } + + if (route.alias !== undefined) { + if (Array.isArray(route.alias)) { + route.alias.forEach(function (alias) { + addRouteRecord(pathMap, nameMap, { path: alias }, parent, record.path) + }) + } else { + addRouteRecord(pathMap, nameMap, { path: route.alias }, parent, record.path) + } + } + + if (!pathMap[record.path]) { + pathMap[record.path] = record + } + if (name) { + if (!nameMap[name]) { + nameMap[name] = record + } else if (process.env.NODE_ENV !== 'production') { + warn(false, ("Duplicate named routes definition: { name: \"" + name + "\", path: \"" + (record.path) + "\" }")) + } + } +} + +function normalizePath (path, parent) { + path = path.replace(/\/$/, '') + if (path[0] === '/') { return path } + if (parent == null) { return path } + return cleanPath(((parent.path) + "/" + path)) +} + var __moduleExports = Array.isArray || function (arr) { return Object.prototype.toString.call(arr) == '[object Array]'; }; var isarray = __moduleExports @@ -510,18 +617,20 @@ ].join('|'), 'g') /** * Parse a string for the raw tokens. * - * @param {string} str + * @param {string} str + * @param {Object=} options * @return {!Array} */ -function parse (str) { +function parse (str, options) { var tokens = [] var key = 0 var index = 0 var path = '' + var defaultDelimiter = options && options.delimiter || '/' var res while ((res = PATH_REGEXP.exec(str)) != null) { var m = res[0] var escaped = res[1] @@ -550,22 +659,22 @@ } var partial = prefix != null && next != null && next !== prefix var repeat = modifier === '+' || modifier === '*' var optional = modifier === '?' || modifier === '*' - var delimiter = res[2] || '/' - var pattern = capture || group || (asterisk ? '.*' : '[^' + delimiter + ']+?') + var delimiter = res[2] || defaultDelimiter + var pattern = capture || group tokens.push({ name: name || key++, prefix: prefix || '', delimiter: delimiter, optional: optional, repeat: repeat, partial: partial, asterisk: !!asterisk, - pattern: escapeGroup(pattern) + pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') }) } // Match any characters still remaining. if (index < str.length) { @@ -582,14 +691,15 @@ /** * Compile a string to a template function for the path. * * @param {string} str + * @param {Object=} options * @return {!function(Object=, Object=)} */ -function compile (str) { - return tokensToFunction(parse(str)) +function compile (str, options) { + return tokensToFunction(parse(str, options)) } /** * Prettier encoding of URI path segments. * @@ -796,38 +906,32 @@ * @param {!Array} keys * @param {!Object} options * @return {!RegExp} */ function stringToRegexp (path, keys, options) { - var tokens = parse(path) - var re = tokensToRegExp(tokens, options) - - // Attach keys back to the regexp. - for (var i = 0; i < tokens.length; i++) { - if (typeof tokens[i] !== 'string') { - keys.push(tokens[i]) - } - } - - return attachKeys(re, keys) + return tokensToRegExp(parse(path, options), keys, options) } /** * Expose a function for taking tokens and returning a RegExp. * - * @param {!Array} tokens - * @param {Object=} options + * @param {!Array} tokens + * @param {(Array|Object)=} keys + * @param {Object=} options * @return {!RegExp} */ -function tokensToRegExp (tokens, options) { +function tokensToRegExp (tokens, keys, options) { + if (!isarray(keys)) { + options = /** @type {!Object} */ (keys || options) + keys = [] + } + options = options || {} var strict = options.strict var end = options.end !== false var route = '' - var lastToken = tokens[tokens.length - 1] - var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken) // Iterate over the tokens and create our regexp string. for (var i = 0; i < tokens.length; i++) { var token = tokens[i] @@ -835,10 +939,12 @@ route += escapeString(token) } else { var prefix = escapeString(token.prefix) var capture = '(?:' + token.pattern + ')' + keys.push(token) + if (token.repeat) { capture += '(?:' + prefix + capture + ')*' } if (token.optional) { @@ -853,27 +959,30 @@ route += capture } } + var delimiter = escapeString(options.delimiter || '/') + var endsWithDelimiter = route.slice(-delimiter.length) === delimiter + // In non-strict mode we allow a slash at the end of match. If the path to // match already ends with a slash, we remove it for consistency. The slash // is valid at the end of a path match, not in the middle. This is important // in non-ending mode, where "/test/" shouldn't match "/test//route". if (!strict) { - route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?' + route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?' } if (end) { route += '$' } else { // In non-ending mode, we need the capturing groups to match as much as // possible by using a positive lookahead to the end or next path segment. - route += strict && endsWithSlash ? '' : '(?=\\/|$)' + route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)' } - return new RegExp('^' + route, flags(options)) + return attachKeys(new RegExp('^' + route, flags(options)), keys) } /** * Normalize the given path string, returning a regular expression. * @@ -885,19 +994,17 @@ * @param {(Array|Object)=} keys * @param {Object=} options * @return {!RegExp} */ function pathToRegexp (path, keys, options) { - keys = keys || [] - if (!isarray(keys)) { - options = /** @type {!Object} */ (keys) + options = /** @type {!Object} */ (keys || options) keys = [] - } else if (!options) { - options = {} } + options = options || {} + if (path instanceof RegExp) { return regexpToRegexp(path, /** @type {!Array} */ (keys)) } if (isarray(path)) { @@ -912,84 +1019,106 @@ index.tokensToFunction = tokensToFunction_1; index.tokensToRegExp = tokensToRegExp_1; /* */ -function createRouteMap (routes) { - var pathMap = Object.create(null) - var nameMap = Object.create(null) +var regexpCache = Object.create(null) - routes.forEach(function (route) { - addRouteRecord(pathMap, nameMap, route) - }) +function getRouteRegex (path) { + var hit = regexpCache[path] + var keys, regexp - return { - pathMap: pathMap, - nameMap: nameMap + if (hit) { + keys = hit.keys + regexp = hit.regexp + } else { + keys = [] + regexp = index(path, keys) + regexpCache[path] = { keys: keys, regexp: regexp } } + + return { keys: keys, regexp: regexp } } -function addRouteRecord ( - pathMap, - nameMap, - route, - parent, - matchAs -) { - var path = route.path; - var name = route.name; - assert(path != null, "\"path\" is required in a route configuration.") +var regexpCompileCache = Object.create(null) - var record = { - path: normalizePath(path, parent), - components: route.components || { default: route.component }, - instances: {}, - name: name, - parent: parent, - matchAs: matchAs, - redirect: route.redirect, - beforeEnter: route.beforeEnter, - meta: route.meta || {} +function fillParams ( + path, + params, + routeMsg +) { + try { + var filler = + regexpCompileCache[path] || + (regexpCompileCache[path] = index.compile(path)) + return filler(params || {}, { pretty: true }) + } catch (e) { + if (process.env.NODE_ENV !== 'production') { + warn(false, ("missing param for " + routeMsg + ": " + (e.message))) + } + return '' } +} - if (route.children) { - // Warn if route is named and has a default child route. - // If users navigate to this route by name, the default child will - // not be rendered (GH Issue #629) - if ("production" !== 'production') {} - route.children.forEach(function (child) { - addRouteRecord(pathMap, nameMap, child, record) - }) +/* */ + +function normalizeLocation ( + raw, + current, + append +) { + var next = typeof raw === 'string' ? { path: raw } : raw + // named target + if (next.name || next._normalized) { + return next } - if (route.alias) { - if (Array.isArray(route.alias)) { - route.alias.forEach(function (alias) { - addRouteRecord(pathMap, nameMap, { path: alias }, parent, record.path) - }) - } else { - addRouteRecord(pathMap, nameMap, { path: route.alias }, parent, record.path) + // relative params + if (!next.path && next.params && current) { + next = assign({}, next) + next._normalized = true + var params = assign(assign({}, current.params), next.params) + if (current.name) { + next.name = current.name + next.params = params + } else if (current.matched) { + var rawPath = current.matched[current.matched.length - 1].path + next.path = fillParams(rawPath, params, ("path " + (current.path))) + } else if (process.env.NODE_ENV !== 'production') { + warn(false, "relative params navigation requires a current route.") } + return next } - pathMap[record.path] = record - if (name) { nameMap[name] = record } + var parsedPath = parsePath(next.path || '') + var basePath = (current && current.path) || '/' + var path = parsedPath.path + ? resolvePath(parsedPath.path, basePath, append || next.append) + : (current && current.path) || '/' + var query = resolveQuery(parsedPath.query, next.query) + var hash = next.hash || parsedPath.hash + if (hash && hash.charAt(0) !== '#') { + hash = "#" + hash + } + + return { + _normalized: true, + path: path, + query: query, + hash: hash + } } -function normalizePath (path, parent) { - path = path.replace(/\/$/, '') - if (path[0] === '/') { return path } - if (parent == null) { return path } - return cleanPath(((parent.path) + "/" + path)) +function assign (a, b) { + for (var key in b) { + a[key] = b[key] + } + return a } /* */ -var regexpCache = Object.create(null) - -var regexpCompileCache = Object.create(null) - function createMatcher (routes) { var ref = createRouteMap(routes); var pathMap = ref.pathMap; var nameMap = ref.nameMap; @@ -1001,10 +1130,26 @@ var location = normalizeLocation(raw, currentRoute) var name = location.name; if (name) { var record = nameMap[name] + var paramNames = getRouteRegex(record.path).keys + .filter(function (key) { return !key.optional; }) + .map(function (key) { return key.name; }) + + if (typeof location.params !== 'object') { + location.params = {} + } + + if (currentRoute && typeof currentRoute.params === 'object') { + for (var key in currentRoute.params) { + if (!(key in location.params) && paramNames.indexOf(key) > -1) { + location.params[key] = currentRoute.params[key] + } + } + } + if (record) { location.path = fillParams(record.path, location.params, ("named route \"" + name + "\"")) return _createRoute(record, location, redirectedFrom) } } else if (location.path) { @@ -1031,11 +1176,13 @@ if (typeof redirect === 'string') { redirect = { path: redirect } } if (!redirect || typeof redirect !== 'object') { - warn(false, ("invalid redirect option: " + (JSON.stringify(redirect)))) + process.env.NODE_ENV !== 'production' && warn( + false, ("invalid redirect option: " + (JSON.stringify(redirect))) + ) return _createRoute(null, location) } var re = redirect var name = re.name; @@ -1048,11 +1195,13 @@ params = re.hasOwnProperty('params') ? re.params : params if (name) { // resolved named direct var targetRecord = nameMap[name] - assert(targetRecord, ("redirect failed: named route \"" + name + "\" not found.")) + if (process.env.NODE_ENV !== 'production') { + assert(targetRecord, ("redirect failed: named route \"" + name + "\" not found.")) + } return match({ _normalized: true, name: name, query: query, hash: hash, @@ -1115,20 +1264,13 @@ function matchRoute ( path, params, pathname ) { - var keys, regexp - var hit = regexpCache[path] - if (hit) { - keys = hit.keys - regexp = hit.regexp - } else { - keys = [] - regexp = index(path, keys) - regexpCache[path] = { keys: keys, regexp: regexp } - } + var ref = getRouteRegex(path); + var regexp = ref.regexp; + var keys = ref.keys; var m = pathname.match(regexp) if (!m) { return false } else if (!params) { @@ -1142,26 +1284,10 @@ } return true } -function fillParams ( - path, - params, - routeMsg -) { - try { - var filler = - regexpCompileCache[path] || - (regexpCompileCache[path] = index.compile(path)) - return filler(params || {}, { pretty: true }) - } catch (e) { - assert(false, ("missing param for " + routeMsg + ": " + (e.message))) - return '' - } -} - function resolveRecordPath (path, record) { return resolvePath(path, record.parent ? record.parent.path : '/', true) } /* */ @@ -1215,28 +1341,29 @@ History.prototype.listen = function listen (cb) { this.cb = cb }; -History.prototype.transitionTo = function transitionTo (location, cb) { +History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) { var this$1 = this; var route = this.router.match(location, this.current) this.confirmTransition(route, function () { this$1.updateRoute(route) - cb && cb(route) + onComplete && onComplete(route) this$1.ensureURL() - }) + }, onAbort) }; -History.prototype.confirmTransition = function confirmTransition (route, cb) { +History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) { var this$1 = this; var current = this.current + var abort = function () { onAbort && onAbort() } if (isSameRoute(route, current)) { this.ensureURL() - return + return abort() } var ref = resolveQueue(this.current.matched, route.matched); var deactivated = ref.deactivated; var activated = ref.activated; @@ -1252,33 +1379,43 @@ resolveAsyncComponents(activated) ) this.pending = route var iterator = function (hook, next) { - if (this$1.pending !== route) { return } + if (this$1.pending !== route) { + return abort() + } hook(route, current, function (to) { if (to === false) { // next(false) -> abort navigation, ensure current URL - this$1.ensureURL() + this$1.ensureURL(true) + abort() } else if (typeof to === 'string' || typeof to === 'object') { // next('/') or next({ path: '/' }) -> redirect - this$1.push(to) + (typeof to === 'object' && to.replace) ? this$1.replace(to) : this$1.push(to) + abort() } else { // confirm transition and pass on the value next(to) } }) } runQueue(queue, iterator, function () { var postEnterCbs = [] + var enterGuards = extractEnterGuards(activated, postEnterCbs, function () { + return this$1.current === route + }) // wait until async components are resolved before // extracting in-component enter guards - runQueue(extractEnterGuards(activated, postEnterCbs), iterator, function () { - if (this$1.pending === route) { - this$1.pending = null - cb(route) + runQueue(enterGuards, iterator, function () { + if (this$1.pending !== route) { + return abort() + } + this$1.pending = null + onComplete(route) + if (this$1.router.app) { this$1.router.app.$nextTick(function () { postEnterCbs.forEach(function (cb) { return cb(); }) }) } }) @@ -1327,39 +1464,95 @@ activated: next.slice(i), deactivated: current.slice(i) } } +function extractGuard ( + def, + key +) { + if (typeof def !== 'function') { + // extend now so that global mixins are applied. + def = _Vue.extend(def) + } + return def.options[key] +} + function extractLeaveGuards (matched) { - return flatMapComponents(matched, function (def, instance) { - var guard = def && def.beforeRouteLeave + return flatten(flatMapComponents(matched, function (def, instance) { + var guard = extractGuard(def, 'beforeRouteLeave') if (guard) { - return function routeLeaveGuard () { - return guard.apply(instance, arguments) - } + return Array.isArray(guard) + ? guard.map(function (guard) { return wrapLeaveGuard(guard, instance); }) + : wrapLeaveGuard(guard, instance) } - }).reverse() + }).reverse()) } -function extractEnterGuards (matched, cbs) { - return flatMapComponents(matched, function (def, _, match, key) { - var guard = def && def.beforeRouteEnter +function wrapLeaveGuard ( + guard, + instance +) { + return function routeLeaveGuard () { + return guard.apply(instance, arguments) + } +} + +function extractEnterGuards ( + matched, + cbs, + isValid +) { + return flatten(flatMapComponents(matched, function (def, _, match, key) { + var guard = extractGuard(def, 'beforeRouteEnter') if (guard) { - return function routeEnterGuard (to, from, next) { - return guard(to, from, function (cb) { - next(cb) - if (typeof cb === 'function') { - cbs.push(function () { - cb(match.instances[key]) - }) - } + return Array.isArray(guard) + ? guard.map(function (guard) { return wrapEnterGuard(guard, cbs, match, key, isValid); }) + : wrapEnterGuard(guard, cbs, match, key, isValid) + } + })) +} + +function wrapEnterGuard ( + guard, + cbs, + match, + key, + isValid +) { + return function routeEnterGuard (to, from, next) { + return guard(to, from, function (cb) { + next(cb) + if (typeof cb === 'function') { + cbs.push(function () { + // #750 + // if a router-view is wrapped with an out-in transition, + // the instance may not have been registered at this time. + // we will need to poll for registration until current route + // is no longer valid. + poll(cb, match.instances, key, isValid) }) } - } - }) + }) + } } +function poll ( + cb, // somehow flow cannot infer this is a function + instances, + key, + isValid +) { + if (instances[key]) { + cb(instances[key]) + } else if (isValid()) { + setTimeout(function () { + poll(cb, instances, key, isValid) + }, 16) + } +} + function resolveAsyncComponents (matched) { return flatMapComponents(matched, function (def, _, match, key) { // if it's a function and doesn't have Vue options attached, // assume it's an async component resolve function. // we are not using Vue's default async resolving mechanism because @@ -1388,32 +1581,38 @@ function flatMapComponents ( matched, fn ) { - return Array.prototype.concat.apply([], matched.map(function (m) { + return flatten(matched.map(function (m) { return Object.keys(m.components).map(function (key) { return fn( m.components[key], m.instances[key], m, key ); }) })) } +function flatten (arr) { + return Array.prototype.concat.apply([], arr) +} + /* */ +var positionStore = Object.create(null) + function saveScrollPosition (key) { if (!key) { return } - window.sessionStorage.setItem(key, JSON.stringify({ + positionStore[key] = { x: window.pageXOffset, y: window.pageYOffset - })) + } } function getScrollPosition (key) { if (!key) { return } - return JSON.parse(window.sessionStorage.getItem(key)) + return positionStore[key] } function getElementPosition (el) { var docRect = document.documentElement.getBoundingClientRect() var elRect = el.getBoundingClientRect() @@ -1448,12 +1647,10 @@ function HTML5History (router, base) { var this$1 = this; History.call(this, router, base) - this.transitionTo(getLocation(this.base)) - var expectScroll = router.options.scrollBehavior window.addEventListener('popstate', function (e) { _key = e.state && e.state.key var current = this$1.current this$1.transitionTo(getLocation(this$1.base), function (next) { @@ -1496,13 +1693,14 @@ replaceState(cleanPath(this$1.base + route.fullPath)) this$1.handleScroll(route, current, false) }) }; - HTML5History.prototype.ensureURL = function ensureURL () { + HTML5History.prototype.ensureURL = function ensureURL (push) { if (getLocation(this.base) !== this.current.fullPath) { - replaceState(cleanPath(this.base + this.current.fullPath)) + var current = cleanPath(this.base + this.current.fullPath) + push ? pushState(current) : replaceState(current) } }; HTML5History.prototype.handleScroll = function handleScroll (to, from, isPop) { var router = this.router @@ -1512,11 +1710,13 @@ var behavior = router.options.scrollBehavior if (!behavior) { return } - assert(typeof behavior === 'function', "scrollBehavior must be a function") + if (process.env.NODE_ENV !== 'production') { + assert(typeof behavior === 'function', "scrollBehavior must be a function") + } // wait until re-render finishes before scrolling router.app.$nextTick(function () { var position = getScrollPosition(_key) var shouldScroll = behavior(to, from, isPop ? position : null) @@ -1576,25 +1776,16 @@ /* */ var HashHistory = (function (History) { function HashHistory (router, base, fallback) { - var this$1 = this; - History.call(this, router, base) - // check history fallback deeplinking if (fallback && this.checkFallback()) { return } - ensureSlash() - this.transitionTo(getHash()) - - window.addEventListener('hashchange', function () { - this$1.onHashChange() - }) } if ( History ) HashHistory.__proto__ = History; HashHistory.prototype = Object.create( History && History.prototype ); HashHistory.prototype.constructor = HashHistory; @@ -1617,28 +1808,29 @@ replaceHash(route.fullPath) }) }; HashHistory.prototype.push = function push (location) { - History.prototype.transitionTo.call(this, location, function (route) { + this.transitionTo(location, function (route) { pushHash(route.fullPath) }) }; HashHistory.prototype.replace = function replace (location) { - History.prototype.transitionTo.call(this, location, function (route) { + this.transitionTo(location, function (route) { replaceHash(route.fullPath) }) }; HashHistory.prototype.go = function go (n) { window.history.go(n) }; - HashHistory.prototype.ensureURL = function ensureURL () { - if (getHash() !== this.current.fullPath) { - replaceHash(this.current.fullPath) + HashHistory.prototype.ensureURL = function ensureURL (push) { + var current = this.current.fullPath + if (getHash() !== current) { + push ? pushHash(current) : replaceHash(current) } }; return HashHistory; }(History)); @@ -1676,30 +1868,30 @@ var AbstractHistory = (function (History) { function AbstractHistory (router) { History.call(this, router) this.stack = [] - this.index = 0 + this.index = -1 } if ( History ) AbstractHistory.__proto__ = History; AbstractHistory.prototype = Object.create( History && History.prototype ); AbstractHistory.prototype.constructor = AbstractHistory; AbstractHistory.prototype.push = function push (location) { var this$1 = this; - History.prototype.transitionTo.call(this, location, function (route) { + this.transitionTo(location, function (route) { this$1.stack = this$1.stack.slice(0, this$1.index + 1).concat(route) this$1.index++ }) }; AbstractHistory.prototype.replace = function replace (location) { var this$1 = this; - History.prototype.transitionTo.call(this, location, function (route) { + this.transitionTo(location, function (route) { this$1.stack = this$1.stack.slice(0, this$1.index).concat(route) }) }; AbstractHistory.prototype.go = function go (n) { @@ -1707,14 +1899,14 @@ var targetIndex = this.index + n if (targetIndex < 0 || targetIndex >= this.stack.length) { return } - var location = this.stack[targetIndex] - this.confirmTransition(location, function () { + var route = this.stack[targetIndex] + this.confirmTransition(route, function () { this$1.index = targetIndex - this$1.updateRoute(location) + this$1.updateRoute(route) }) }; AbstractHistory.prototype.ensureURL = function ensureURL () { // noop @@ -1741,10 +1933,24 @@ } if (!inBrowser) { mode = 'abstract' } this.mode = mode + + switch (mode) { + case 'history': + this.history = new HTML5History(this, options.base) + break + case 'hash': + this.history = new HashHistory(this, options.base, this.fallback) + break + case 'abstract': + this.history = new AbstractHistory(this) + break + default: + process.env.NODE_ENV !== 'production' && assert(false, ("invalid mode: " + mode)) + } }; var prototypeAccessors = { currentRoute: {} }; prototypeAccessors.currentRoute.get = function () { @@ -1752,37 +1958,32 @@ }; VueRouter.prototype.init = function init (app /* Vue component instance */) { var this$1 = this; - assert( + process.env.NODE_ENV !== 'production' && assert( install.installed, "not installed. Make sure to call `Vue.use(VueRouter)` " + "before creating root instance." ) this.app = app - var ref = this; - var mode = ref.mode; - var options = ref.options; - var fallback = ref.fallback; - switch (mode) { - case 'history': - this.history = new HTML5History(this, options.base) - break - case 'hash': - this.history = new HashHistory(this, options.base, fallback) - break - case 'abstract': - this.history = new AbstractHistory(this) - break - default: - assert(false, ("invalid mode: " + mode)) + var history = this.history + + if (history instanceof HTML5History) { + history.transitionTo(getLocation(history.base)) + } else if (history instanceof HashHistory) { + var setupHashListener = function () { + window.addEventListener('hashchange', function () { + history.onHashChange() + }) + } + history.transitionTo(getHash(), setupHashListener, setupHashListener) } - this.history.listen(function (route) { + history.listen(function (route) { this$1.app._route = route }) }; VueRouter.prototype.beforeEach = function beforeEach (fn) { @@ -1811,27 +2012,51 @@ VueRouter.prototype.forward = function forward () { this.go(1) }; -VueRouter.prototype.getMatchedComponents = function getMatchedComponents () { - if (!this.currentRoute) { +VueRouter.prototype.getMatchedComponents = function getMatchedComponents (to) { + var route = to + ? this.resolve(to).resolved + : this.currentRoute + if (!route) { return [] } - return [].concat.apply([], this.currentRoute.matched.map(function (m) { + return [].concat.apply([], route.matched.map(function (m) { return Object.keys(m.components).map(function (key) { return m.components[key] }) })) }; +VueRouter.prototype.resolve = function resolve ( + to, + current, + append +) { + var normalizedTo = normalizeLocation(to, current || this.history.current, append) + var resolved = this.match(normalizedTo, current) + var fullPath = resolved.redirectedFrom || resolved.fullPath + var base = this.history.base + var href = createHref(base, fullPath, this.mode) + return { + normalizedTo: normalizedTo, + resolved: resolved, + href: href + } +}; + Object.defineProperties( VueRouter.prototype, prototypeAccessors ); -VueRouter.install = install +function createHref (base, fullPath, mode) { + var path = mode === 'hash' ? '#' + fullPath : fullPath + return base ? cleanPath(base + '/' + path) : path +} +VueRouter.install = install; +VueRouter.version = 'v2.1.1' + if (inBrowser && window.Vue) { window.Vue.use(VueRouter) } -return VueRouter; - -}))); \ No newline at end of file +module.exports = VueRouter;