aboutsummaryrefslogtreecommitdiff
path: root/catapult/third_party/polymer/components/app-route/app-route.html
diff options
context:
space:
mode:
Diffstat (limited to 'catapult/third_party/polymer/components/app-route/app-route.html')
-rw-r--r--catapult/third_party/polymer/components/app-route/app-route.html421
1 files changed, 421 insertions, 0 deletions
diff --git a/catapult/third_party/polymer/components/app-route/app-route.html b/catapult/third_party/polymer/components/app-route/app-route.html
new file mode 100644
index 00000000..7ed66b46
--- /dev/null
+++ b/catapult/third_party/polymer/components/app-route/app-route.html
@@ -0,0 +1,421 @@
+<!--
+@license
+Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+Code distributed by Google as part of the polymer project is also
+subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+-->
+
+<link rel="import" href="../polymer/polymer.html">
+
+<!--
+`app-route` is an element that enables declarative, self-describing routing
+for a web app.
+
+> *n.b. app-route is still in beta. We expect it will need some changes. We're counting on your feedback!*
+
+In its typical usage, a `app-route` element consumes an object that describes
+some state about the current route, via the `route` property. It then parses
+that state using the `pattern` property, and produces two artifacts: some `data`
+related to the `route`, and a `tail` that contains the rest of the `route` that
+did not match.
+
+Here is a basic example, when used with `app-location`:
+
+ <app-location route="{{route}}"></app-location>
+ <app-route
+ route="{{route}}"
+ pattern="/:page"
+ data="{{data}}"
+ tail="{{tail}}">
+ </app-route>
+
+In the above example, the `app-location` produces a `route` value. Then, the
+`route.path` property is matched by comparing it to the `pattern` property. If
+the `pattern` property matches `route.path`, the `app-route` will set or update
+its `data` property with an object whose properties correspond to the parameters
+in `pattern`. So, in the above example, if `route.path` was `'/about'`, the value
+of `data` would be `{"page": "about"}`.
+
+The `tail` property represents the remaining part of the route state after the
+`pattern` has been applied to a matching `route`.
+
+Here is another example, where `tail` is used:
+
+ <app-location route="{{route}}"></app-location>
+ <app-route
+ route="{{route}}"
+ pattern="/:page"
+ data="{{routeData}}"
+ tail="{{subroute}}">
+ </app-route>
+ <app-route
+ route="{{subroute}}"
+ pattern="/:id"
+ data="{{subrouteData}}">
+ </app-route>
+
+In the above example, there are two `app-route` elements. The first
+`app-route` consumes a `route`. When the `route` is matched, the first
+`app-route` also produces `routeData` from its `data`, and `subroute` from
+its `tail`. The second `app-route` consumes the `subroute`, and when it
+matches, it produces an object called `subrouteData` from its `data`.
+
+So, when `route.path` is `'/about'`, the `routeData` object will look like
+this: `{ page: 'about' }`
+
+And `subrouteData` will be null. However, if `route.path` changes to
+`'/article/123'`, the `routeData` object will look like this:
+`{ page: 'article' }`
+
+And the `subrouteData` will look like this: `{ id: '123' }`
+
+`app-route` is responsive to bi-directional changes to the `data` objects
+they produce. So, if `routeData.page` changed from `'article'` to `'about'`,
+the `app-route` will update `route.path`. This in-turn will update the
+`app-location`, and cause the global location bar to change its value.
+
+@element app-route
+@demo demo/index.html
+@demo demo/data-loading-demo.html
+@demo demo/simple-demo.html
+-->
+
+<script>
+ (function() {
+ 'use strict';
+
+ Polymer({
+ is: 'app-route',
+
+ properties: {
+ /**
+ * The URL component managed by this element.
+ */
+ route: {
+ type: Object,
+ notify: true
+ },
+
+ /**
+ * The pattern of slash-separated segments to match `route.path` against.
+ *
+ * For example the pattern "/foo" will match "/foo" or "/foo/bar"
+ * but not "/foobar".
+ *
+ * Path segments like `/:named` are mapped to properties on the `data` object.
+ */
+ pattern: {
+ type: String
+ },
+
+ /**
+ * The parameterized values that are extracted from the route as
+ * described by `pattern`.
+ */
+ data: {
+ type: Object,
+ value: function() {return {};},
+ notify: true
+ },
+
+ /**
+ * @type {?Object}
+ */
+ queryParams: {
+ type: Object,
+ value: function() {
+ return {};
+ },
+ notify: true
+ },
+
+ /**
+ * The part of `route.path` NOT consumed by `pattern`.
+ */
+ tail: {
+ type: Object,
+ value: function() {return {path: null, prefix: null, __queryParams: null};},
+ notify: true
+ },
+
+ /**
+ * Whether the current route is active. True if `route.path` matches the
+ * `pattern`, false otherwise.
+ */
+ active: {
+ type: Boolean,
+ notify: true,
+ readOnly: true
+ },
+
+ _queryParamsUpdating: {
+ type: Boolean,
+ value: false
+ },
+ /**
+ * @type {?string}
+ */
+ _matched: {
+ type: String,
+ value: ''
+ }
+ },
+
+ observers: [
+ '__tryToMatch(route.path, pattern)',
+ '__updatePathOnDataChange(data.*)',
+ '__tailPathChanged(tail.path)',
+ '__routeQueryParamsChanged(route.__queryParams)',
+ '__tailQueryParamsChanged(tail.__queryParams)',
+ '__queryParamsChanged(queryParams.*)'
+ ],
+
+ created: function() {
+ this.linkPaths('route.__queryParams', 'tail.__queryParams');
+ this.linkPaths('tail.__queryParams', 'route.__queryParams');
+ },
+
+ /**
+ * Deal with the query params object being assigned to wholesale.
+ * @export
+ */
+ __routeQueryParamsChanged: function(queryParams) {
+ if (queryParams && this.tail) {
+ this.set('tail.__queryParams', queryParams);
+
+ if (!this.active || this._queryParamsUpdating) {
+ return;
+ }
+
+ // Copy queryParams and track whether there are any differences compared
+ // to the existing query params.
+ var copyOfQueryParams = {};
+ var anythingChanged = false;
+ for (var key in queryParams) {
+ copyOfQueryParams[key] = queryParams[key];
+ if (anythingChanged ||
+ !this.queryParams ||
+ queryParams[key] !== this.queryParams[key]) {
+ anythingChanged = true;
+ }
+ }
+ // Need to check whether any keys were deleted
+ for (var key in this.queryParams) {
+ if (anythingChanged || !(key in queryParams)) {
+ anythingChanged = true;
+ break;
+ }
+ }
+
+ if (!anythingChanged) {
+ return;
+ }
+ this._queryParamsUpdating = true;
+ this.set('queryParams', copyOfQueryParams);
+ this._queryParamsUpdating = false;
+ }
+ },
+
+ /**
+ * @export
+ */
+ __tailQueryParamsChanged: function(queryParams) {
+ if (queryParams && this.route) {
+ this.set('route.__queryParams', queryParams);
+ }
+ },
+
+ /**
+ * @export
+ */
+ __queryParamsChanged: function(changes) {
+ if (!this.active || this._queryParamsUpdating) {
+ return;
+ }
+
+ this.set('route.__' + changes.path, changes.value);
+ },
+
+ __resetProperties: function() {
+ this._setActive(false);
+ this._matched = null;
+ //this.tail = { path: null, prefix: null, queryParams: null };
+ //this.data = {};
+ },
+
+ /**
+ * @export
+ */
+ __tryToMatch: function() {
+ if (!this.route) {
+ return;
+ }
+ var path = this.route.path;
+ var pattern = this.pattern;
+ if (!pattern) {
+ return;
+ }
+
+ if (!path) {
+ this.__resetProperties();
+ return;
+ }
+
+ var remainingPieces = path.split('/');
+ var patternPieces = pattern.split('/');
+
+ var matched = [];
+ var namedMatches = {};
+
+ for (var i=0; i < patternPieces.length; i++) {
+ var patternPiece = patternPieces[i];
+ if (!patternPiece && patternPiece !== '') {
+ break;
+ }
+ var pathPiece = remainingPieces.shift();
+
+ // We don't match this path.
+ if (!pathPiece && pathPiece !== '') {
+ this.__resetProperties();
+ return;
+ }
+ matched.push(pathPiece);
+
+ if (patternPiece.charAt(0) == ':') {
+ namedMatches[patternPiece.slice(1)] = pathPiece;
+ } else if (patternPiece !== pathPiece) {
+ this.__resetProperties();
+ return;
+ }
+ }
+
+ this._matched = matched.join('/');
+
+ // Properties that must be updated atomically.
+ var propertyUpdates = {};
+
+ //this.active
+ if (!this.active) {
+ propertyUpdates.active = true;
+ }
+
+ // this.tail
+ var tailPrefix = this.route.prefix + this._matched;
+ var tailPath = remainingPieces.join('/');
+ if (remainingPieces.length > 0) {
+ tailPath = '/' + tailPath;
+ }
+ if (!this.tail ||
+ this.tail.prefix !== tailPrefix ||
+ this.tail.path !== tailPath) {
+ propertyUpdates.tail = {
+ prefix: tailPrefix,
+ path: tailPath,
+ __queryParams: this.route.__queryParams
+ };
+ }
+
+ // this.data
+ propertyUpdates.data = namedMatches;
+ this._dataInUrl = {};
+ for (var key in namedMatches) {
+ this._dataInUrl[key] = namedMatches[key];
+ }
+
+ this.__setMulti(propertyUpdates);
+ },
+
+ /**
+ * @export
+ */
+ __tailPathChanged: function(path) {
+ if (!this.active) {
+ return;
+ }
+ var tailPath = path;
+ var newPath = this._matched;
+ if (tailPath) {
+ if (tailPath.charAt(0) !== '/') {
+ tailPath = '/' + tailPath;
+ }
+ newPath += tailPath;
+ }
+ this.set('route.path', newPath);
+ },
+
+ /**
+ * @export
+ */
+ __updatePathOnDataChange: function() {
+ if (!this.route || !this.active) {
+ return;
+ }
+ var newPath = this.__getLink({});
+ var oldPath = this.__getLink(this._dataInUrl);
+ if (newPath === oldPath) {
+ return;
+ }
+ this.set('route.path', newPath);
+ },
+
+ __getLink: function(overrideValues) {
+ var values = {tail: null};
+ for (var key in this.data) {
+ values[key] = this.data[key];
+ }
+ for (var key in overrideValues) {
+ values[key] = overrideValues[key];
+ }
+ var patternPieces = this.pattern.split('/');
+ var interp = patternPieces.map(function(value) {
+ if (value[0] == ':') {
+ value = values[value.slice(1)];
+ }
+ return value;
+ }, this);
+ if (values.tail && values.tail.path) {
+ if (interp.length > 0 && values.tail.path.charAt(0) === '/') {
+ interp.push(values.tail.path.slice(1));
+ } else {
+ interp.push(values.tail.path);
+ }
+ }
+ return interp.join('/');
+ },
+
+ __setMulti: function(setObj) {
+ // HACK(rictic): skirting around 1.0's lack of a setMulti by poking at
+ // internal data structures. I would not advise that you copy this
+ // example.
+ //
+ // In the future this will be a feature of Polymer itself.
+ // See: https://github.com/Polymer/polymer/issues/3640
+ //
+ // Hacking around with private methods like this is juggling footguns,
+ // and is likely to have unexpected and unsupported rough edges.
+ //
+ // Be ye so warned.
+ for (var property in setObj) {
+ this._propertySetter(property, setObj[property]);
+ }
+ //notify in a specific order
+ if (setObj.data !== undefined) {
+ this._pathEffector('data', this.data);
+ this._notifyChange('data');
+ }
+ if (setObj.active !== undefined) {
+ this._pathEffector('active', this.active);
+ this._notifyChange('active');
+ }
+ if (setObj.tail !== undefined) {
+ this._pathEffector('tail', this.tail);
+ this._notifyChange('tail');
+ }
+
+ }
+ });
+ })();
+</script>