Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @typedef {{ | |
| 7 * dialog: (string|undefined), | |
| 8 * page: string, | |
| 9 * section: string, | |
| 10 * subpage: !Array<string>, | |
| 11 * }} | |
| 12 */ | |
| 13 var SettingsRoute; | |
| 14 | |
| 15 /** @typedef {SettingsRoute|{url: string}} */ | |
| 16 var CanonicalRoute; | |
| 17 | |
| 18 /** @typedef {SettingsRoute|{inHistory: boolean}} */ | |
| 19 var HistoricRoute; | |
| 20 | |
| 21 /** | |
| 22 * @fileoverview | 6 * @fileoverview |
| 23 * 'settings-router' is a simple router for settings. Its responsibilities: | 7 * 'settings-router' is a simple router for settings. Its responsibilities: |
| 24 * - Update the URL when the routing state changes. | 8 * - Update the URL when the routing state changes. |
| 25 * - Initialize the routing state with the initial URL. | 9 * - Initialize the routing state with the initial URL. |
| 26 * - Process and validate all routing state changes. | 10 * - Process and validate all routing state changes. |
| 27 * | 11 * |
| 28 * Example: | 12 * Example: |
| 29 * | 13 * |
| 30 * <settings-router current-route="{{currentRoute}}"> | 14 * <settings-router current-route="{{currentRoute}}"> |
| 31 * </settings-router> | 15 * </settings-router> |
| 32 */ | 16 */ |
| 33 Polymer({ | 17 Polymer({ |
| 34 is: 'settings-router', | 18 is: 'settings-router', |
| 35 | 19 |
| 36 properties: { | 20 properties: { |
| 37 /** | 21 /** |
| 38 * The current active route. This is reflected to the URL. Updates to this | 22 * The current active route. This may only be updated via the global |
| 39 * property should replace the whole object. | 23 * function settings.navigateTo. |
| 40 * | 24 * |
| 41 * currentRoute.page refers to top-level pages such as Basic and Advanced. | 25 * currentRoute.page refers to top-level pages such as Basic and Advanced. |
| 42 * | 26 * |
| 43 * currentRoute.section is only non-empty when the user is on a subpage. If | 27 * currentRoute.section is only non-empty when the user is on a subpage. If |
| 44 * the user is on Basic, for instance, this is an empty string. | 28 * the user is on Basic, for instance, this is an empty string. |
| 45 * | 29 * |
| 46 * currentRoute.subpage is an Array. The last element is the actual subpage | 30 * currentRoute.subpage is an Array. The last element is the actual subpage |
| 47 * the user is on. The previous elements are the ancestor subpages. This | 31 * the user is on. The previous elements are the ancestor subpages. This |
| 48 * enables support for multiple paths to the same subpage. This is used by | 32 * enables support for multiple paths to the same subpage. This is used by |
| 49 * both the Back button and the Breadcrumb to determine ancestor subpages. | 33 * both the Back button and the Breadcrumb to determine ancestor subpages. |
| 50 * @type {SettingsRoute} | 34 * @type {!settings.Route} |
| 51 */ | 35 */ |
| 52 currentRoute: { | 36 currentRoute: { |
| 53 notify: true, | 37 notify: true, |
| 54 observer: 'currentRouteChanged_', | |
| 55 type: Object, | 38 type: Object, |
| 56 value: function() { | 39 value: function() { |
| 57 var initialRoute = this.canonicalRoutes_[0]; | 40 return this.getRouteFor_(window.location.pathname); |
| 58 | |
| 59 // Take the current URL, find a matching pre-defined route, and | |
| 60 // initialize the currentRoute to that pre-defined route. | |
| 61 for (var i = 0; i < this.canonicalRoutes_.length; ++i) { | |
| 62 var canonicalRoute = this.canonicalRoutes_[i]; | |
| 63 if (canonicalRoute.url == window.location.pathname) { | |
| 64 initialRoute = canonicalRoute; | |
| 65 break; | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 return { | |
| 70 page: initialRoute.page, | |
| 71 section: initialRoute.section, | |
| 72 subpage: initialRoute.subpage, | |
| 73 dialog: initialRoute.dialog, | |
| 74 }; | |
| 75 }, | |
| 76 }, | |
| 77 | |
| 78 /** | |
| 79 * Page titles for the currently active route. Updated by the currentRoute | |
| 80 * property observer. | |
| 81 * @type {{pageTitle: string}} | |
| 82 */ | |
| 83 currentRouteTitles: { | |
| 84 notify: true, | |
| 85 type: Object, | |
| 86 value: function() { | |
| 87 return { | |
| 88 pageTitle: '', | |
| 89 }; | |
| 90 }, | 41 }, |
| 91 }, | 42 }, |
| 92 }, | 43 }, |
| 93 | 44 |
| 94 | |
| 95 /** | |
| 96 * @private {!Array<!CanonicalRoute>} | |
| 97 * The 'url' property is not accessible to other elements. | |
| 98 */ | |
| 99 canonicalRoutes_: Object.keys(settings.Route).map(function(key) { | |
| 100 return settings.Route[key]; | |
| 101 }), | |
| 102 | |
| 103 /** | 45 /** |
| 104 * Sets up a history popstate observer. | 46 * Sets up a history popstate observer. |
| 47 * @override | |
| 105 */ | 48 */ |
| 106 created: function() { | 49 created: function() { |
| 107 window.addEventListener('popstate', function(event) { | 50 window.addEventListener('popstate', function(event) { |
| 108 if (event.state && event.state.page) | 51 // On pop state, do not push the state onto the window.history again. |
| 109 this.currentRoute = event.state; | 52 this.currentRoute = this.getRouteFor_(window.location.pathname); |
| 110 }.bind(this)); | 53 }.bind(this)); |
| 54 | |
| 55 assert(!settings.navigateTo, | |
| 56 'settings.navigateTo already defined. There may only be one ' + | |
|
michaelpg
2016/07/20 19:48:23
using another namespace to enforce a singleton see
tommycli
2016/07/20 20:33:30
Well... the intent is to really prevent the global
| |
| 57 'instance of settings-router.'); | |
| 58 settings.navigateTo = this.navigateTo_.bind(this); | |
| 111 }, | 59 }, |
| 112 | 60 |
| 113 /** | 61 /** |
| 114 * Is called when another element modifies the route. This observer validates | 62 * Returns the matching canonical route, or the default route if none matches. |
| 115 * the route change against the pre-defined list of routes, and updates the | 63 * @param {string} path |
| 116 * URL appropriately. | 64 * @return {!settings.Route} |
| 117 * @param {!SettingsRoute} newRoute Where we're headed. | |
| 118 * @param {!SettingsRoute|undefined} oldRoute Where we've been. | |
| 119 * @private | 65 * @private |
| 120 */ | 66 */ |
| 121 currentRouteChanged_: function(newRoute, oldRoute) { | 67 getRouteFor_: function(path) { |
| 122 for (var i = 0; i < this.canonicalRoutes_.length; ++i) { | 68 var matchingKeys = Object.keys(settings.Route).filter(function(key) { |
|
michaelpg
2016/07/20 19:48:23
.find
tommycli
2016/07/20 20:33:30
Done.
| |
| 123 var canonicalRoute = this.canonicalRoutes_[i]; | 69 return settings.Route[key].url == path; |
| 124 if (canonicalRoute.page == newRoute.page && | 70 }); |
| 125 canonicalRoute.section == newRoute.section && | |
| 126 canonicalRoute.dialog == newRoute.dialog && | |
| 127 canonicalRoute.subpage.length == newRoute.subpage.length && | |
| 128 canonicalRoute.subpage.every(function(value, index) { | |
| 129 return value == newRoute.subpage[index]; | |
| 130 })) { | |
| 131 // Update the property containing the titles for the current route. | |
| 132 this.currentRouteTitles = { | |
| 133 pageTitle: loadTimeData.getString(canonicalRoute.page + 'PageTitle'), | |
| 134 }; | |
| 135 | 71 |
| 136 // If we are restoring a state from history, don't push it again. | 72 if (matchingKeys.length == 0) |
| 137 if (/** @type {HistoricRoute} */(newRoute).inHistory) | 73 return settings.Route.BASIC; |
| 138 return; | |
| 139 | 74 |
| 140 // Mark routes persisted in history as already stored in history. | 75 assert(matchingKeys.length == 1, 'Duplicate routes for: ' + path); |
|
michaelpg
2016/07/20 19:48:23
rely on testing for this instead
tommycli
2016/07/20 20:33:30
Done.
| |
| 141 var historicRoute = /** @type {HistoricRoute} */({ | 76 return settings.Route[matchingKeys[0]]; |
| 142 inHistory: true, | 77 }, |
| 143 page: newRoute.page, | |
| 144 section: newRoute.section, | |
| 145 subpage: newRoute.subpage, | |
| 146 dialog: newRoute.dialog, | |
| 147 }); | |
| 148 | 78 |
| 149 // Push the current route to the history state, so when the user | 79 /** |
| 150 // navigates with the browser back button, we can recall the route. | 80 * Navigates to a canonical route. |
| 151 if (oldRoute) { | 81 * @param {!settings.Route} route |
| 152 window.history.pushState(historicRoute, document.title, | 82 * @private |
| 153 canonicalRoute.url); | 83 */ |
| 154 } else { | 84 navigateTo_: function(route) { |
| 155 // For the very first route (oldRoute will be undefined), we replace | 85 window.history.pushState(undefined, document.title, route.url); |
| 156 // the existing state instead of pushing a new one. This is to allow | 86 this.currentRoute = route; |
| 157 // the user to use the browser back button to exit Settings entirely. | |
| 158 window.history.replaceState(historicRoute, document.title); | |
| 159 } | |
| 160 | |
| 161 return; | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 assertNotReached('Route not found: ' + JSON.stringify(newRoute)); | |
| 166 }, | 87 }, |
| 167 }); | 88 }); |
| OLD | NEW |