Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Unified Diff: polymer_0.5.4/bower_components/app-router/src/app-router.js

Issue 897763002: Updated app-router to 2.4.0 (Closed) Base URL: https://chromium.googlesource.com/infra/third_party/npm_modules.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « polymer_0.5.4/bower_components/app-router/src/app-router.html ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: polymer_0.5.4/bower_components/app-router/src/app-router.js
diff --git a/polymer_0.5.4/bower_components/app-router/src/app-router.js b/polymer_0.5.4/bower_components/app-router/src/app-router.js
index 912ba1cf31739e8fe30b80eb04d86b7b1b7c6c5b..f4f3df833d97ce94d77b3913214020b4fb62eed7 100644
--- a/polymer_0.5.4/bower_components/app-router/src/app-router.js
+++ b/polymer_0.5.4/bower_components/app-router/src/app-router.js
@@ -1,254 +1,479 @@
+// @license Copyright (C) 2015 Erik Ringsmuth - MIT license
(function(window, document) {
- // <app-route path="/path" [import="/page/cust-el.html"] [element="cust-el"] [template]></app-route>
- document.registerElement('app-route', {
- prototype: Object.create(HTMLElement.prototype)
- });
-
- // <active-route></active-route> holds the active route's content when `shadow` is not enabled
- document.registerElement('active-route', {
- prototype: Object.create(HTMLElement.prototype)
- });
-
- // <app-router [shadow] [trailingSlash="strict|ignore"] [init="auto|manual"]></app-router>
- var router = Object.create(HTMLElement.prototype);
-
+ var utilities = {};
var importedURIs = {};
var isIE = 'ActiveXObject' in window;
+ var previousUrl = {};
- // fire(type, detail, node) - Fire a new CustomEvent(type, detail) on the node
- //
- // listen with document.querySelector('app-router').addEventListener(type, function(event) {
- // event.detail, event.preventDefault()
- // })
- function fire(type, detail, node) {
- // create a CustomEvent the old way for IE9/10 support
- var event = document.createEvent('CustomEvent');
-
- // initCustomEvent(type, bubbles, cancelable, detail)
- event.initCustomEvent(type, false, true, detail);
+ // <app-router [init="auto|manual"] [mode="auto|hash|pushstate"] [trailingSlash="strict|ignore"] [shadow]></app-router>
+ var AppRouter = Object.create(HTMLElement.prototype);
+ AppRouter.util = utilities;
- // returns false when event.preventDefault() is called, true otherwise
- return node.dispatchEvent(event);
- }
+ // <app-route path="/path" [import="/page/cust-el.html"] [element="cust-el"] [template]></app-route>
+ document.registerElement('app-route', {
+ prototype: Object.create(HTMLElement.prototype)
+ });
// Initial set up when attached
- router.attachedCallback = function() {
+ AppRouter.attachedCallback = function() {
+ // init="auto|manual"
if(this.getAttribute('init') !== 'manual') {
this.init();
}
};
// Initialize the router
- router.init = function() {
- if (this.isInitialized) {
+ AppRouter.init = function() {
+ var router = this;
+ if (router.isInitialized) {
return;
}
- this.isInitialized = true;
- this.activeRoute = document.createElement('app-route');
+ router.isInitialized = true;
- // Listen for URL change events.
- this.stateChangeHandler = this.go.bind(this);
- window.addEventListener('popstate', this.stateChangeHandler, false);
- if (isIE) {
- // IE is truly special! A hashchange is supposed to trigger a popstate, making popstate the only
- // even you should need to listen to. Not the case in IE! Make another event listener for it!
- window.addEventListener('hashchange', this.stateChangeHandler, false);
+ // trailingSlash="strict|ignore"
+ if (!router.hasAttribute('trailingSlash')) {
+ router.setAttribute('trailingSlash', 'strict');
+ }
+
+ // mode="auto|hash|pushstate"
+ if (!router.hasAttribute('mode')) {
+ router.setAttribute('mode', 'auto');
}
- // set up an <active-route> element for the active route's content
- this.activeRouteContent = document.createElement('active-route');
- this.appendChild(this.activeRouteContent);
- if (this.hasAttribute('shadow')) {
- this.activeRouteContent = this.activeRouteContent.createShadowRoot();
+ // typecast="auto|string"
+ if (!router.hasAttribute('typecast')) {
+ router.setAttribute('typecast', 'auto');
}
- // load the web component for the active route
- this.go();
+ // <app-router core-animated-pages transitions="hero-transition cross-fade">
+ if (router.hasAttribute('core-animated-pages')) {
+ // use shadow DOM to wrap the <app-route> elements in a <core-animated-pages> element
+ // <app-router>
+ // # shadowRoot
+ // <core-animated-pages>
+ // # content in the light DOM
+ // <app-route element="home-page">
+ // <home-page>
+ // </home-page>
+ // </app-route>
+ // </core-animated-pages>
+ // </app-router>
+ router.createShadowRoot();
+ router.coreAnimatedPages = document.createElement('core-animated-pages');
+ router.coreAnimatedPages.appendChild(document.createElement('content'));
+
+ // don't know why it needs to be static, but absolute doesn't display the page
+ router.coreAnimatedPages.style.position = 'static';
+
+ // toggle the selected page using selected="path" instead of selected="integer"
+ router.coreAnimatedPages.setAttribute('valueattr', 'path');
+
+ // pass the transitions attribute from <app-router core-animated-pages transitions="hero-transition cross-fade">
+ // to <core-animated-pages transitions="hero-transition cross-fade">
+ router.coreAnimatedPages.setAttribute('transitions', router.getAttribute('transitions'));
+
+ // set the shadow DOM's content
+ router.shadowRoot.appendChild(router.coreAnimatedPages);
+
+ // when a transition finishes, remove the previous route's content. there is a temporary overlap where both
+ // the new and old route's content is in the DOM to animate the transition.
+ router.coreAnimatedPages.addEventListener('core-animated-pages-transition-end', function() {
+ transitionAnimationEnd(router.previousRoute);
+ });
+ }
+
+ // listen for URL change events
+ router.stateChangeHandler = stateChange.bind(null, router);
+ window.addEventListener('popstate', router.stateChangeHandler, false);
+ if (isIE) {
+ // IE bug. A hashchange is supposed to trigger a popstate event, making popstate the only event you
+ // need to listen to. That's not the case in IE so we make another event listener for it.
+ window.addEventListener('hashchange', router.stateChangeHandler, false);
+ }
+
+ // load the web component for the current route
+ stateChange(router);
};
// clean up global event listeners
- router.detachedCallback = function() {
+ AppRouter.detachedCallback = function() {
window.removeEventListener('popstate', this.stateChangeHandler, false);
if (isIE) {
window.removeEventListener('hashchange', this.stateChangeHandler, false);
}
};
- // go() - Find the first <app-route> that matches the current URL and change the active route
- router.go = function() {
- var urlPath = this.parseUrlPath(window.location.href);
+ // go(path, options) Navigate to the path
+ //
+ // options = {
+ // replace: true
+ // }
+ AppRouter.go = function(path, options) {
+ if (this.getAttribute('mode') !== 'pushstate') {
+ // mode == auto or hash
+ path = '#' + path;
+ }
+ if (options && options.replace === true) {
+ window.history.replaceState(null, null, path);
+ } else {
+ window.history.pushState(null, null, path);
+ }
+
+ // dispatch a popstate event
+ try {
+ var popstateEvent = new PopStateEvent('popstate', {
+ bubbles: false,
+ cancelable: false,
+ state: {}
+ });
+
+ if ('dispatchEvent_' in window) {
+ // FireFox with polyfill
+ window.dispatchEvent_(popstateEvent);
+ } else {
+ // normal
+ window.dispatchEvent(popstateEvent);
+ }
+ } catch(error) {
+ // Internet Exploder
+ var fallbackEvent = document.createEvent('CustomEvent');
+ fallbackEvent.initCustomEvent('popstate', false, false, { state: {} });
+ window.dispatchEvent(fallbackEvent);
+ }
+ };
+
+ // fire(type, detail, node) - Fire a new CustomEvent(type, detail) on the node
+ //
+ // listen with document.querySelector('app-router').addEventListener(type, function(event) {
+ // event.detail, event.preventDefault()
+ // })
+ function fire(type, detail, node) {
+ // create a CustomEvent the old way for IE9/10 support
+ var event = document.createEvent('CustomEvent');
+
+ // initCustomEvent(type, bubbles, cancelable, detail)
+ event.initCustomEvent(type, false, true, detail);
+
+ // returns false when event.preventDefault() is called, true otherwise
+ return node.dispatchEvent(event);
+ }
+
+ // Find the first <app-route> that matches the current URL and change the active route
+ function stateChange(router) {
+ var url = utilities.parseUrl(window.location.href, router.getAttribute('mode'));
+
+ // don't load a new route if only the hash fragment changed
+ if (url.hash !== previousUrl.hash && url.path === previousUrl.path && url.search === previousUrl.search && url.isHashPath === previousUrl.isHashPath) {
+ scrollToHash(url.hash);
+ return;
+ }
+ previousUrl = url;
+
+ // fire a state-change event on the app-router and return early if the user called event.preventDefault()
var eventDetail = {
- path: urlPath
+ path: url.path
};
- if (!fire('state-change', eventDetail, this)) {
+ if (!fire('state-change', eventDetail, router)) {
return;
}
- var routes = this.querySelectorAll('app-route');
- for (var i = 0; i < routes.length; i++) {
- if (this.testRoute(routes[i].getAttribute('path'), urlPath, this.getAttribute('trailingSlash'), routes[i].hasAttribute('regex'))) {
- this.activateRoute(routes[i], urlPath);
+
+ // find the first matching route
+ var route = router.firstElementChild;
+ while (route) {
+ if (route.tagName === 'APP-ROUTE' && utilities.testRoute(route.getAttribute('path'), url.path, router.getAttribute('trailingSlash'), route.hasAttribute('regex'))) {
+ activateRoute(router, route, url);
return;
}
+ route = route.nextSibling;
+ }
+
+ fire('not-found', eventDetail, router);
+ }
+
+ // Activate the route
+ function activateRoute(router, route, url) {
+ if (route.hasAttribute('redirect')) {
+ router.go(route.getAttribute('redirect'), {replace: true});
+ return;
}
- fire('not-found', eventDetail, this);
- };
- // activateRoute(route, urlPath) - Activate the route
- router.activateRoute = function(route, urlPath) {
var eventDetail = {
- path: urlPath,
+ path: url.path,
route: route,
- oldRoute: this.activeRoute
+ oldRoute: router.activeRoute
};
- if (!fire('activate-route-start', eventDetail, this)) {
+ if (!fire('activate-route-start', eventDetail, router)) {
return;
}
if (!fire('activate-route-start', eventDetail, route)) {
return;
}
-
- this.activeRoute.removeAttribute('active');
- route.setAttribute('active', 'active');
- this.activeRoute = route;
- var importUri = route.getAttribute('import');
- var routePath = route.getAttribute('path');
- var isRegExp = route.hasAttribute('regex');
- var elementName = route.getAttribute('element');
- var isTemplate = route.hasAttribute('template');
- var isElement = !isTemplate;
+ // update the references to the activeRoute and previousRoute. if you switch between routes quickly you may go to a
+ // new route before the previous route's transition animation has completed. if that's the case we need to remove
+ // the previous route's content before we replace the reference to the previous route.
+ if (router.previousRoute && router.previousRoute.transitionAnimationInProgress) {
+ transitionAnimationEnd(router.previousRoute);
+ }
+ if (router.activeRoute) {
+ router.activeRoute.removeAttribute('active');
+ }
+ router.previousRoute = router.activeRoute;
+ router.activeRoute = route;
+ router.activeRoute.setAttribute('active', 'active');
- // import custom element
- if (isElement && importUri) {
- this.importAndActivateCustomElement(importUri, elementName, routePath, urlPath, isRegExp, eventDetail);
+ // import custom element or template
+ if (route.hasAttribute('import')) {
+ importAndActivate(router, route.getAttribute('import'), route, url, eventDetail);
}
// pre-loaded custom element
- else if (isElement && !importUri && elementName) {
- this.activateCustomElement(elementName, routePath, urlPath, isRegExp, eventDetail);
+ else if (route.hasAttribute('element')) {
+ activateCustomElement(router, route.getAttribute('element'), route, url, eventDetail);
}
- // import template
- else if (isTemplate && importUri) {
- this.importAndActivateTemplate(importUri, route, eventDetail);
+ // inline template
+ else if (route.firstElementChild && route.firstElementChild.tagName === 'TEMPLATE') {
+ activateTemplate(router, route.firstElementChild, route, url, eventDetail);
}
- // pre-loaded template
- else if (isTemplate && !importUri) {
- this.activateTemplate(route, eventDetail);
+ }
+
+ // Import and activate a custom element or template
+ function importAndActivate(router, importUri, route, url, eventDetail) {
+ var importLink;
+ function importLoadedCallback() {
+ activateImport(router, importLink, importUri, route, url, eventDetail);
}
- };
- // importAndActivateCustomElement(importUri, elementName, routePath, urlPath, isRegExp, eventDetail) - Import the custom element then replace the active route
- // with a new instance of the custom element
- router.importAndActivateCustomElement = function(importUri, elementName, routePath, urlPath, isRegExp, eventDetail) {
if (!importedURIs.hasOwnProperty(importUri)) {
+ // hasn't been imported yet
importedURIs[importUri] = true;
- var elementLink = document.createElement('link');
- elementLink.setAttribute('rel', 'import');
- elementLink.setAttribute('href', importUri);
- document.head.appendChild(elementLink);
+ importLink = document.createElement('link');
+ importLink.setAttribute('rel', 'import');
+ importLink.setAttribute('href', importUri);
+ importLink.addEventListener('load', importLoadedCallback);
+ document.head.appendChild(importLink);
+ } else {
+ // previously imported. this is an async operation and may not be complete yet.
+ importLink = document.querySelector('link[href="' + importUri + '"]');
+ if (importLink.import) {
+ // import complete
+ importLoadedCallback();
+ } else {
+ // wait for `onload`
+ importLink.addEventListener('load', importLoadedCallback);
+ }
}
- this.activateCustomElement(elementName || importUri.split('/').slice(-1)[0].replace('.html', ''), routePath, urlPath, isRegExp, eventDetail);
- };
+ }
- // activateCustomElement(elementName, routePath, urlPath, isRegExp, eventDetail) - Replace the active route with a new instance of the custom element
- router.activateCustomElement = function(elementName, routePath, urlPath, isRegExp, eventDetail) {
- var resourceEl = document.createElement(elementName);
- var routeArgs = this.routeArguments(routePath, urlPath, window.location.href, isRegExp);
- for (var arg in routeArgs) {
- if (routeArgs.hasOwnProperty(arg)) {
- resourceEl[arg] = routeArgs[arg];
+ // Activate the imported custom element or template
+ function activateImport(router, importLink, importUri, route, url, eventDetail) {
+ // make sure the user didn't navigate to a different route while it loaded
+ if (route.hasAttribute('active')) {
+ if (route.hasAttribute('template')) {
+ // template
+ activateTemplate(router, importLink.import.querySelector('template'), route, url, eventDetail);
+ } else {
+ // custom element
+ activateCustomElement(router, route.getAttribute('element') || importUri.split('/').slice(-1)[0].replace('.html', ''), route, url, eventDetail);
}
}
- this.activeElement(resourceEl, eventDetail);
- };
+ }
- // importAndActivateTemplate(importUri, route, eventDetail) - Import the template then replace the active route with a clone of the template's content
- router.importAndActivateTemplate = function(importUri, route, eventDetail) {
- if (importedURIs.hasOwnProperty(importUri)) {
- // previously imported. this is an async operation and may not be complete yet.
- var previousLink = document.querySelector('link[href="' + importUri + '"]');
- if (previousLink.import) {
- // the import is complete
- this.activeElement(document.importNode(previousLink.import.querySelector('template').content, true), eventDetail);
- } else {
- // wait for `onload`
- previousLink.onload = function() {
- if (route.hasAttribute('active')) {
- this.activeElement(document.importNode(previousLink.import.querySelector('template').content, true), eventDetail);
- }
- }.bind(this);
+ // Data bind the custom element then activate it
+ function activateCustomElement(router, elementName, route, url, eventDetail) {
+ var customElement = document.createElement(elementName);
+ var model = createModel(router, route, url, eventDetail);
+ for (var property in model) {
+ if (model.hasOwnProperty(property)) {
+ customElement[property] = model[property];
}
+ }
+ activateElement(router, customElement, url, eventDetail);
+ }
+
+ // Create an instance of the template
+ function activateTemplate(router, template, route, url, eventDetail) {
+ var templateInstance;
+ if ('createInstance' in template) {
+ // template.createInstance(model) is a Polymer method that binds a model to a template and also fixes
+ // https://github.com/erikringsmuth/app-router/issues/19
+ var model = createModel(router, route, url, eventDetail);
+ templateInstance = template.createInstance(model);
} else {
- // template hasn't been loaded yet
- importedURIs[importUri] = true;
- var templateLink = document.createElement('link');
- templateLink.setAttribute('rel', 'import');
- templateLink.setAttribute('href', importUri);
- templateLink.onload = function() {
- if (route.hasAttribute('active')) {
- this.activeElement(document.importNode(templateLink.import.querySelector('template').content, true), eventDetail);
- }
- }.bind(this);
- document.head.appendChild(templateLink);
+ templateInstance = document.importNode(template.content, true);
}
- };
+ activateElement(router, templateInstance, url, eventDetail);
+ }
- // activateTemplate(route, eventDetail) - Replace the active route with a clone of the template's content
- router.activateTemplate = function(route, eventDetail) {
- var clone = document.importNode(route.querySelector('template').content, true);
- this.activeElement(clone, eventDetail);
- };
+ // Create the route's model
+ function createModel(router, route, url, eventDetail) {
+ var model = utilities.routeArguments(route.getAttribute('path'), url.path, url.search, route.hasAttribute('regex'), router.getAttribute('typecast') === 'auto');
+ if (route.hasAttribute('bindRouter') || router.hasAttribute('bindRouter')) {
+ model.router = router;
+ }
+ eventDetail.model = model;
+ fire('before-data-binding', eventDetail, router);
+ fire('before-data-binding', eventDetail, eventDetail.route);
+ return eventDetail.model;
+ }
+
+ // Replace the active route's content with the new element
+ function activateElement(router, element, url, eventDetail) {
+ // core-animated-pages temporarily needs the old and new route in the DOM at the same time to animate the transition,
+ // otherwise we can remove the old route's content right away.
+ // UNLESS
+ // if the route we're navigating to matches the same app-route (ex: path="/article/:id" navigating from /article/0 to
+ // /article/1), then we have to simply replace the route's content instead of animating a transition.
+ if (!router.hasAttribute('core-animated-pages') || eventDetail.route === eventDetail.oldRoute) {
+ removeRouteContent(router.previousRoute);
+ }
+
+ // add the new content
+ router.activeRoute.appendChild(element);
+
+ // animate the transition if core-animated-pages are being used
+ if (router.hasAttribute('core-animated-pages')) {
+ router.coreAnimatedPages.selected = router.activeRoute.getAttribute('path');
+
+ // we already wired up transitionAnimationEnd() in init()
- // activeElement(element, eventDetail) - Replace the active route's content with the new element
- router.activeElement = function(element, eventDetail) {
- while (this.activeRouteContent.firstChild) {
- this.activeRouteContent.removeChild(this.activeRouteContent.firstChild);
+ // use to check if the previous route has finished animating before being removed
+ if (router.previousRoute) {
+ router.previousRoute.transitionAnimationInProgress = true;
+ }
+ }
+
+ // scroll to the URL hash if it's present
+ if (url.hash && !router.hasAttribute('core-animated-pages')) {
+ scrollToHash(url.hash);
}
- this.activeRouteContent.appendChild(element);
- fire('activate-route-end', eventDetail, this);
+
+ fire('activate-route-end', eventDetail, router);
fire('activate-route-end', eventDetail, eventDetail.route);
- };
+ }
- // urlPath(url) - Parses the url to get the path
+ // Call when the previousRoute has finished the transition animation out
+ function transitionAnimationEnd(previousRoute) {
+ if (previousRoute) {
+ previousRoute.transitionAnimationInProgress = false;
+ removeRouteContent(previousRoute);
+ }
+ }
+
+ // Remove the route's content (but not the <template> if it exists)
+ function removeRouteContent(route) {
+ if (route) {
+ var node = route.firstChild;
+ while (node) {
+ var nodeToRemove = node;
+ node = node.nextSibling;
+ if (nodeToRemove.tagName !== 'TEMPLATE') {
+ route.removeChild(nodeToRemove);
+ }
+ }
+ }
+ }
+
+ // scroll to the element with id="hash" or name="hash"
+ function scrollToHash(hash) {
+ if (!hash) return;
+
+ // wait for the browser's scrolling to finish before we scroll to the hash
+ // ex: http://example.com/#/page1#middle
+ // the browser will scroll to an element with id or name `/page1#middle` when the page finishes loading. if it doesn't exist
+ // it will scroll to the top of the page. let the browser finish the current event loop and scroll to the top of the page
+ // before we scroll to the element with id or name `middle`.
+ setTimeout(function() {
+ var hashElement = document.querySelector('html /deep/ ' + hash) || document.querySelector('html /deep/ [name="' + hash.substring(1) + '"]');
+ if (hashElement && hashElement.scrollIntoView) {
+ hashElement.scrollIntoView(true);
+ }
+ }, 0);
+ }
+
+ // parseUrl(location, mode) - Augment the native URL() constructor to get info about hash paths
//
- // This will return the hash path if it exists or return the real path if no hash path exists.
+ // Example parseUrl('http://domain.com/other/path?queryParam3=false#/example/path?queryParam1=true&queryParam2=example%20string#middle', 'auto')
//
- // Example URL = 'http://domain.com/other/path?queryParam3=false#/example/path?queryParam1=true&queryParam2=example%20string'
- // path = '/example/path'
+ // returns {
+ // path: '/example/path',
+ // hash: '#middle'
+ // search: '?queryParam1=true&queryParam2=example%20string',
+ // isHashPath: true
+ // }
//
- // Note: The URL must contain the protocol like 'http(s)://'
- router.parseUrlPath = function(url) {
- // The relative URI is everything after the third slash including the third slash
- // Example relativeUri = '/other/path?queryParam3=false#/example/path?queryParam1=true&queryParam2=example%20string'
- var splitUrl = url.split('/');
- var relativeUri = '/' + splitUrl.splice(3, splitUrl.length - 3).join('/');
-
- // The path is everything in the relative URI up to the first ? or #
- // Example path = '/other/path'
- var path = relativeUri.split(/[\?#]/)[0];
-
- // The hash is everything from the first # up to the the search starting with ? if it exists
- // Example hash = '#/example/path'
- var hashIndex = relativeUri.indexOf('#');
- if (hashIndex !== -1) {
- var hash = relativeUri.substring(hashIndex).split('?')[0];
- if (hash.substring(0, 2) === '#/') {
- // Hash path
- path = hash.substring(1);
- } else if (hash.substring(0, 3) === '#!/') {
- // Hashbang path
- path = hash.substring(2);
+ // Note: The location must be a fully qualified URL with a protocol like 'http(s)://'
+ utilities.parseUrl = function(location, mode) {
+ var url = {
+ isHashPath: mode === 'hash'
+ };
+
+ if (typeof URL === 'function') {
+ // browsers that support `new URL()`
+ var nativeUrl = new URL(location);
+ url.path = nativeUrl.pathname;
+ url.hash = nativeUrl.hash;
+ url.search = nativeUrl.search;
+ } else {
+ // IE
+ var anchor = document.createElement('a');
+ anchor.href = location;
+ url.path = anchor.pathname;
+ if (url.path.charAt(0) !== '/') {
+ url.path = '/' + url.path;
}
+ url.hash = anchor.hash;
+ url.search = anchor.search;
}
- return path;
+ if (mode !== 'pushstate') {
+ // auto or hash
+
+ // check for a hash path
+ if (url.hash.substring(0, 2) === '#/') {
+ // hash path
+ url.isHashPath = true;
+ url.path = url.hash.substring(1);
+ } else if (url.hash.substring(0, 3) === '#!/') {
+ // hashbang path
+ url.isHashPath = true;
+ url.path = url.hash.substring(2);
+ } else if (url.isHashPath) {
+ // still use the hash if mode="hash"
+ if (url.hash.length === 0) {
+ url.path = '/';
+ } else {
+ url.path = url.hash.substring(1);
+ }
+ }
+
+ if (url.isHashPath) {
+ url.hash = '';
+
+ // hash paths might have an additional hash in the hash path for scrolling to a specific part of the page #/hash/path#elementId
+ var secondHashIndex = url.path.indexOf('#');
+ if (secondHashIndex !== -1) {
+ url.hash = url.path.substring(secondHashIndex);
+ url.path = url.path.substring(0, secondHashIndex);
+ }
+
+ // hash paths get the search from the hash if it exists
+ var searchIndex = url.path.indexOf('?');
+ if (searchIndex !== -1) {
+ url.search = url.path.substring(searchIndex);
+ url.path = url.path.substring(0, searchIndex);
+ }
+ }
+ }
+
+ return url;
};
- // router.testRoute(routePath, urlPath, trailingSlashOption, isRegExp) - Test if the route's path matches the URL's path
+ // testRoute(routePath, urlPath, trailingSlashOption, isRegExp) - Test if the route's path matches the URL's path
//
- // Example routePath: '/example/*'
- // Example urlPath = '/example/path'
- router.testRoute = function(routePath, urlPath, trailingSlashOption, isRegExp) {
- // This algorithm tries to fail or succeed as quickly as possible for the most common cases.
+ // Example routePath: '/user/:userId/**'
+ // Example urlPath = '/user/123/bio'
+ utilities.testRoute = function(routePath, urlPath, trailingSlashOption, isRegExp) {
+ // try to fail or succeed as quickly as possible for the most common cases
// handle trailing slashes (options: strict (default), ignore)
if (trailingSlashOption === 'ignore') {
@@ -261,113 +486,85 @@
}
}
+ // test regular expressions
if (isRegExp) {
- // parse HTML attribute path="/^\/\w+\/\d+$/i" to a regular expression `new RegExp('^\/\w+\/\d+$', 'i')`
- // note that 'i' is the only valid option. global 'g', multiline 'm', and sticky 'y' won't be valid matchers for a path.
- if (routePath.charAt(0) !== '/') {
- // must start with a slash
- return false;
- }
- routePath = routePath.slice(1);
- var options = '';
- if (routePath.slice(-1) === '/') {
- routePath = routePath.slice(0, -1);
- }
- else if (routePath.slice(-2) === '/i') {
- routePath = routePath.slice(0, -2);
- options = 'i';
- }
- else {
- // must end with a slash followed by zero or more options
- return false;
- }
- return new RegExp(routePath, options).test(urlPath);
+ return utilities.testRegExString(routePath, urlPath);
}
- // If the urlPath is an exact match or '*' then the route is a match
+ // if the urlPath is an exact match or '*' then the route is a match
if (routePath === urlPath || routePath === '*') {
return true;
}
- // Look for wildcards
- if (routePath.indexOf('*') === -1 && routePath.indexOf(':') === -1) {
- // No wildcards and we already made sure it wasn't an exact match so the test fails
- return false;
+ // relative routes a/b/c are the same as routes that start with a globstar /**/a/b/c
+ if (routePath.charAt(0) !== '/') {
+ routePath = '/**/' + routePath;
}
- // Example urlPathSegments = ['', example', 'path']
- var urlPathSegments = urlPath.split('/');
+ // recursively test if the segments match (start at 1 because 0 is always an empty string)
+ return segmentsMatch(routePath.split('/'), 1, urlPath.split('/'), 1)
+ };
- // Example routePathSegments = ['', 'example', '*']
- var routePathSegments = routePath.split('/');
+ // segmentsMatch(routeSegments, routeIndex, urlSegments, urlIndex, pathVariables)
+ // recursively test the route segments against the url segments in place (without creating copies of the arrays
+ // for each recursive call)
+ //
+ // example routeSegments ['', 'user', ':userId', '**']
+ // example urlSegments ['', 'user', '123', 'bio']
+ function segmentsMatch(routeSegments, routeIndex, urlSegments, urlIndex, pathVariables) {
+ var routeSegment = routeSegments[routeIndex];
+ var urlSegment = urlSegments[urlIndex];
+
+ // if we're at the last route segment and it is a globstar, it will match the rest of the url
+ if (routeSegment === '**' && routeIndex === routeSegments.length - 1) {
+ return true;
+ }
- // There must be the same number of path segments or it isn't a match
- if (urlPathSegments.length !== routePathSegments.length) {
- return false;
+ // we hit the end of the route segments or the url segments
+ if (typeof routeSegment === 'undefined' || typeof urlSegment === 'undefined') {
+ // return true if we hit the end of both at the same time meaning everything else matched, else return false
+ return routeSegment === urlSegment;
}
- // Check equality of each path segment
- for (var i = 0; i < routePathSegments.length; i++) {
- // The path segments must be equal, be a wildcard segment '*', or be a path parameter like ':id'
- var routeSegment = routePathSegments[i];
- if (routeSegment !== urlPathSegments[i] && routeSegment !== '*' && routeSegment.charAt(0) !== ':') {
- // The path segment wasn't the same string and it wasn't a wildcard or parameter
- return false;
+ // if the current segments match, recursively test the remaining segments
+ if (routeSegment === urlSegment || routeSegment === '*' || routeSegment.charAt(0) === ':') {
+ // store the path variable if we have a pathVariables object
+ if (routeSegment.charAt(0) === ':' && typeof pathVariables !== 'undefined') {
+ pathVariables[routeSegment.substring(1)] = urlSegments[urlIndex];
}
+ return segmentsMatch(routeSegments, routeIndex + 1, urlSegments, urlIndex + 1, pathVariables);
}
- // Nothing failed. The route matches the URL.
- return true;
- };
+ // globstars can match zero to many URL segments
+ if (routeSegment === '**') {
+ // test if the remaining route segments match any combination of the remaining url segments
+ for (var i = urlIndex; i < urlSegments.length; i++) {
+ if (segmentsMatch(routeSegments, routeIndex + 1, urlSegments, i, pathVariables)) {
+ return true;
+ }
+ }
+ }
- // router.routeArguments(routePath, urlPath, url, isRegExp) - Gets the path variables and query parameter values from the URL
- router.routeArguments = function routeArguments(routePath, urlPath, url, isRegExp) {
- var args = {};
+ // all tests failed, the route segments do not match the url segments
+ return false;
+ }
- // Example urlPathSegments = ['', example', 'path']
- var urlPathSegments = urlPath.split('/');
+ // routeArguments(routePath, urlPath, search, isRegExp) - Gets the path variables and query parameter values from the URL
+ utilities.routeArguments = function(routePath, urlPath, search, isRegExp, typecast) {
+ var args = {};
+ // regular expressions can't have path variables
if (!isRegExp) {
- // Example routePathSegments = ['', 'example', '*']
- var routePathSegments = routePath.split('/');
-
- // Get path variables
+ // relative routes a/b/c are the same as routes that start with a globstar /**/a/b/c
+ if (routePath.charAt(0) !== '/') {
+ routePath = '/**/' + routePath;
+ }
+
+ // get path variables
// urlPath '/customer/123'
// routePath '/customer/:id'
// parses id = '123'
- for (var index = 0; index < routePathSegments.length; index++) {
- var routeSegment = routePathSegments[index];
- if (routeSegment.charAt(0) === ':') {
- args[routeSegment.substring(1)] = urlPathSegments[index];
- }
- }
- }
-
- // Get the query parameter values
- // The search is the query parameters including the leading '?'
- var searchIndex = url.indexOf('?');
- var search = '';
- if (searchIndex !== -1) {
- search = url.substring(searchIndex);
- var hashIndex = search.indexOf('#');
- if (hashIndex !== -1) {
- search = search.substring(0, hashIndex);
- }
- }
- // If it's a hash URL we need to get the search from the hash
- var hashPathIndex = url.indexOf('#/');
- var hashBangPathIndex = url.indexOf('#!/');
- if (hashPathIndex !== -1 || hashBangPathIndex !== -1) {
- var hash = '';
- if (hashPathIndex !== -1) {
- hash = url.substring(hashPathIndex);
- } else {
- hash = url.substring(hashBangPathIndex);
- }
- searchIndex = hash.indexOf('?');
- if (searchIndex !== -1) {
- search = hash.substring(searchIndex);
- }
+ segmentsMatch(routePath.split('/'), 1, urlPath.split('/'), 1, args);
}
var queryParameters = search.substring(1).split('&');
@@ -381,26 +578,62 @@
args[queryParameterParts[0]] = queryParameterParts.splice(1, queryParameterParts.length - 1).join('=');
}
- // Parse the arguments into unescaped strings, numbers, or booleans
- for (var arg in args) {
- var value = args[arg];
- if (value === 'true') {
- args[arg] = true;
- } else if (value === 'false') {
- args[arg] = false;
- } else if (!isNaN(value) && value !== '') {
- // numeric
- args[arg] = +value;
- } else {
- // string
- args[arg] = decodeURIComponent(value);
+ if (typecast) {
+ // parse the arguments into unescaped strings, numbers, or booleans
+ for (var arg in args) {
+ args[arg] = utilities.typecast(args[arg]);
}
}
return args;
};
+ // typecast(value) - Typecast the string value to an unescaped string, number, or boolean
+ utilities.typecast = function(value) {
+ // bool
+ if (value === 'true') {
+ return true;
+ }
+ if (value === 'false') {
+ return false;
+ }
+
+ // number
+ if (!isNaN(value) && value !== '' && value.charAt(0) !== '0') {
+ return +value;
+ }
+
+ // string
+ return decodeURIComponent(value);
+ };
+
+ // testRegExString(pattern, value) - Parse HTML attribute path="/^\/\w+\/\d+$/i" to a regular
+ // expression `new RegExp('^\/\w+\/\d+$', 'i')` and test against it.
+ //
+ // note that 'i' is the only valid option. global 'g', multiline 'm', and sticky 'y' won't be valid matchers for a path.
+ utilities.testRegExString = function(pattern, value) {
+ if (pattern.charAt(0) !== '/') {
+ // must start with a slash
+ return false;
+ }
+ pattern = pattern.slice(1);
+ var options = '';
+ if (pattern.slice(-1) === '/') {
+ pattern = pattern.slice(0, -1);
+ }
+ else if (pattern.slice(-2) === '/i') {
+ pattern = pattern.slice(0, -2);
+ options = 'i';
+ }
+ else {
+ // must end with a slash followed by zero or more options
+ return false;
+ }
+ return new RegExp(pattern, options).test(value);
+ };
+
document.registerElement('app-router', {
- prototype: router
+ prototype: AppRouter
});
+
})(window, document);
« no previous file with comments | « polymer_0.5.4/bower_components/app-router/src/app-router.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698