OLD | NEW |
(Empty) | |
| 1 <link rel="import" href="../polymer/polymer.html"> |
| 2 |
| 3 <!-- |
| 4 |
| 5 The `iron-page-url` element manages binding to and from the current URL. |
| 6 |
| 7 iron-page-url is the first, and lowest level element in the Polymer team's |
| 8 routing system. This is a beta release of iron-page-url as we continue work |
| 9 on higher level elements, and as such iron-page-url may undergo breaking |
| 10 changes. |
| 11 |
| 12 #### Properties |
| 13 |
| 14 When the URL is: `/search?query=583#details` iron-page-url's properties will be: |
| 15 |
| 16 - path: `'/search'` |
| 17 - query: `'query=583'` |
| 18 - hash: `'details'` |
| 19 |
| 20 These bindings are bidirectional. Modifying them will in turn modify the URL. |
| 21 |
| 22 iron-page-url is only active while it is attached to the document. |
| 23 |
| 24 #### Links |
| 25 |
| 26 While iron-page-url is active in the document it will intercept clicks on links |
| 27 within your site, updating the URL pushing the updated URL out through the |
| 28 databinding system. iron-page-url only intercepts clicks with the intent to |
| 29 open in the same window, so middle mouse clicks and ctrl/cmd clicks work fine. |
| 30 |
| 31 You can customize this behavior with the `urlSpaceRegex`. |
| 32 |
| 33 #### Dwell Time |
| 34 |
| 35 iron-page-url protects against accidental history spamming by only adding |
| 36 entries to the user's history if the URL stays unchanged for `dwellTime` |
| 37 milliseconds. |
| 38 |
| 39 @demo demo/index.html |
| 40 |
| 41 --> |
| 42 <script> |
| 43 'use strict'; |
| 44 |
| 45 Polymer({ |
| 46 is: 'iron-page-url', |
| 47 properties: { |
| 48 /** |
| 49 * The pathname component of the URL. |
| 50 */ |
| 51 path: { |
| 52 type: String, |
| 53 notify: true, |
| 54 value: function() { |
| 55 return window.location.pathname; |
| 56 } |
| 57 }, |
| 58 /** |
| 59 * The query string portion of the URL. |
| 60 */ |
| 61 query: { |
| 62 type: String, |
| 63 notify: true, |
| 64 value: function() { |
| 65 return window.location.search.slice(1); |
| 66 } |
| 67 }, |
| 68 /** |
| 69 * The hash component of the URL. |
| 70 */ |
| 71 hash: { |
| 72 type: String, |
| 73 notify: true, |
| 74 value: function() { |
| 75 return window.location.hash.slice(1); |
| 76 } |
| 77 }, |
| 78 /** |
| 79 * If the user was on a URL for less than `dwellTime` milliseconds, it |
| 80 * won't be added to the browser's history, but instead will be replaced |
| 81 * by the next entry. |
| 82 * |
| 83 * This is to prevent large numbers of entries from clogging up the user's |
| 84 * browser history. Disable by setting to a negative number. |
| 85 */ |
| 86 dwellTime: { |
| 87 type: Number, |
| 88 value: 2000 |
| 89 }, |
| 90 |
| 91 /** |
| 92 * A regexp that defines the set of URLs that should be considered part |
| 93 * of this web app. |
| 94 * |
| 95 * Clicking on a link that matches this regex won't result in a full page |
| 96 * navigation, but will instead just update the URL state in place. |
| 97 * |
| 98 * This regexp is given everything after the origin in an absolute |
| 99 * URL. So to match just URLs that start with /search/ do: |
| 100 * url-space-regex="^/search/" |
| 101 * |
| 102 * @type {string|RegExp} |
| 103 */ |
| 104 urlSpaceRegex: { |
| 105 type: String, |
| 106 value: '' |
| 107 }, |
| 108 |
| 109 /** |
| 110 * urlSpaceRegex, but coerced into a regexp. |
| 111 * |
| 112 * @type {RegExp} |
| 113 */ |
| 114 _urlFilterRegExp: { |
| 115 compute: '_makeRegExp(urlFilterExp)' |
| 116 }, |
| 117 |
| 118 _lastChangedAtAt: { |
| 119 type: Number, |
| 120 value: -Infinity |
| 121 }, |
| 122 |
| 123 _initialized: { |
| 124 type: Boolean, |
| 125 value: false |
| 126 } |
| 127 }, |
| 128 hostAttributes: { |
| 129 hidden: true |
| 130 }, |
| 131 observers: [ |
| 132 '_updateUrl(path, query, hash)' |
| 133 ], |
| 134 attached: function() { |
| 135 this.listen(window, 'hashchange', '_hashChanged'); |
| 136 this.listen(window, 'location-changed', '_urlChanged'); |
| 137 this.listen(window, 'popstate', '_urlChanged'); |
| 138 this.listen(document.body, 'click', '_globalOnClick'); |
| 139 |
| 140 this._urlChanged(); |
| 141 this._initialized = true; |
| 142 }, |
| 143 detached: function() { |
| 144 this.unlisten(window, 'hashchange', '_hashChanged'); |
| 145 this.unlisten(window, 'location-changed', '_urlChanged'); |
| 146 this.unlisten(window, 'popstate', '_urlChanged'); |
| 147 this.unlisten(document.body, 'click', '_globalOnClick'); |
| 148 this._initialized = false; |
| 149 }, |
| 150 /** |
| 151 * @return {number} the number of milliseconds since some point in the |
| 152 * past. Only useful for comparing against other results from this |
| 153 * function. |
| 154 */ |
| 155 _now: function() { |
| 156 if (window.performance && window.performance.now) { |
| 157 return window.performance.now(); |
| 158 } |
| 159 return +new Date(); |
| 160 }, |
| 161 _hashChanged: function() { |
| 162 this.hash = window.location.hash.substring(1); |
| 163 }, |
| 164 _urlChanged: function() { |
| 165 // We want to extract all info out of the updated URL before we |
| 166 // try to write anything back into it. |
| 167 // |
| 168 // i.e. without _dontUpdateUrl we'd overwrite the new path with the old |
| 169 // one when we set this.hash. Likewise for query. |
| 170 this._dontUpdateUrl = true; |
| 171 this._hashChanged(); |
| 172 this.path = window.location.pathname; |
| 173 this.query = window.location.search.substring(1); |
| 174 this._dontUpdateUrl = false; |
| 175 this._updateUrl(); |
| 176 }, |
| 177 _getUrl: function() { |
| 178 var url = this.path; |
| 179 var query = this.query; |
| 180 if (query) { |
| 181 url += '?' + query; |
| 182 } |
| 183 if (this.hash) { |
| 184 url += '#' + this.hash; |
| 185 } |
| 186 return url; |
| 187 }, |
| 188 _updateUrl: function() { |
| 189 if (this._dontUpdateUrl || !this._initialized) { |
| 190 return; |
| 191 } |
| 192 var newUrl = this._getUrl(); |
| 193 var currentUrl = ( |
| 194 window.location.pathname + |
| 195 window.location.search + |
| 196 window.location.hash |
| 197 ); |
| 198 if (newUrl == currentUrl) { |
| 199 // nothing to do, the URL didn't change |
| 200 return; |
| 201 } |
| 202 |
| 203 var shouldReplace = |
| 204 this._lastChangedAt + this.dwellTime > this._now(); |
| 205 this._lastChangedAt = this._now(); |
| 206 if (shouldReplace) { |
| 207 window.history.replaceState({}, '', newUrl); |
| 208 } else { |
| 209 window.history.pushState({}, '', newUrl); |
| 210 } |
| 211 }, |
| 212 /** |
| 213 * A necessary evil so that links work as expected. Does its best to |
| 214 * bail out early if possible. |
| 215 * |
| 216 * @param {MouseEvent} event . |
| 217 */ |
| 218 _globalOnClick: function(event) { |
| 219 var href = this._getSameOriginLinkHref(event); |
| 220 if (!href) { |
| 221 return; |
| 222 } |
| 223 |
| 224 window.history.pushState({}, '', href); |
| 225 this.fire('location-changed', {}, {node: window}); |
| 226 event.preventDefault(); |
| 227 }, |
| 228 /** |
| 229 * Returns the absolute URL of the link (if any) that this click event |
| 230 * is clicking on, if we can and should override the resulting full |
| 231 * page navigation. Returns null otherwise. |
| 232 * |
| 233 * This method is separated away from _globalOnClick for testability, |
| 234 * as we can't test that a clicked link should have resulted in navigating |
| 235 * away from the test page. |
| 236 * |
| 237 * @param {MouseEvent} event . |
| 238 * @return {string?} . |
| 239 */ |
| 240 _getSameOriginLinkHref: function(event) { |
| 241 // We only care about left-clicks. |
| 242 if (event.button !== 0) { |
| 243 return null; |
| 244 } |
| 245 // We don't want modified clicks, where the intent is to open the page |
| 246 // in a new tab. |
| 247 if (event.metaKey || event.ctrlKey) { |
| 248 return null; |
| 249 } |
| 250 var eventPath = Polymer.dom(event).path; |
| 251 var href = null; |
| 252 for (var i = 0; i < eventPath.length; i++) { |
| 253 var element = eventPath[i]; |
| 254 if (element.tagName === 'A' && element.href) { |
| 255 href = element.href; |
| 256 break; |
| 257 } |
| 258 } |
| 259 // If there's no link there's nothing to do. |
| 260 if (!href) { |
| 261 return null; |
| 262 } |
| 263 |
| 264 // It only makes sense for us to intercept same-origin navigations. |
| 265 // pushState/replaceState don't work with cross-origin links. |
| 266 var url = new URL(href, document.baseURI); |
| 267 if (url.origin !== window.location.origin) { |
| 268 return null; |
| 269 } |
| 270 var normalizedHref = url.pathname + url.search + url.hash; |
| 271 |
| 272 // If we've been configured not to handle this url... don't handle it! |
| 273 if (this._urlFilterRegExp && |
| 274 !this._urlFilterRegExp.test(normalizedHref)) { |
| 275 return null; |
| 276 } |
| 277 |
| 278 return normalizedHref; |
| 279 }, |
| 280 _makeRegExp: function(urlFilterExp) { |
| 281 return RegExp(urlFilterExp); |
| 282 } |
| 283 }); |
| 284 </script> |
OLD | NEW |