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