X-Git-Url: https://git.openstreetmap.org/rails.git/blobdiff_plain/c9600ea323b52aab7d317e6ea5440ac2fdb185b4..78f8e2a4503f95e23c8312193aecfb20455fa01e:/app/assets/javascripts/router.js?ds=sidebyside diff --git a/app/assets/javascripts/router.js b/app/assets/javascripts/router.js index 33182c292..56616c269 100644 --- a/app/assets/javascripts/router.js +++ b/app/assets/javascripts/router.js @@ -1,3 +1,50 @@ +/* + OSM.Router implements pushState-based navigation for the main page and + other pages that use a sidebar+map based layout (export, search results, + history, and browse pages). + + For browsers without pushState, it falls back to full page loads, which all + of the above pages support. + + The router is initialized with a set of routes: a mapping of URL path templates + to route controller objects. Path templates can contain placeholders + (`/note/:id`) and optional segments (`/:type/:id(/history)`). + + Route controller objects can define four methods that are called at defined + times during routing: + + * The `load` method is called by the router when a path which matches the + route's path template is loaded via a normal full page load. It is passed + as arguments the URL path plus any matching arguments for placeholders + in the path template. + + * The `pushstate` method is called when a page which matches the route's path + template is loaded via pushState. It is passed the same arguments as `load`. + + * The `popstate` method is called when returning to a previously + pushState-loaded page via popstate (i.e. browser back/forward buttons). + + * The `unload` method is called on the exiting route controller when navigating + via pushState or popstate to another route. + + Note that while `load` is not called by the router for pushState-based loads, + it's frequently useful for route controllers to call it manually inside their + definition of the `pushstate` and `popstate` methods. + + An instance of OSM.Router is assigned to `OSM.router`. To navigate to a new page + via pushState (with automatic full-page load fallback), call `OSM.router.route`: + + OSM.router.route('/way/1234'); + + If `route` is passed a path that matches one of the path templates, it performs + the appropriate actions and returns true. Otherwise it returns false. + + OSM.Router also handles updating the hash portion of the URL containing transient + map state such as the position and zoom level. Some route controllers may wish to + temporarily suppress updating the hash (for example, to omit the hash on pages + such as `/way/1234` unless the map is moved). This can be done by calling + `OSM.router.moveListenerOff` and `OSM.router.moveListenerOn`. + */ OSM.Router = function(map, rts) { var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; var optionalParam = /\((.*?)\)/g; @@ -28,7 +75,7 @@ OSM.Router = function(map, rts) { }); } - (controller[action] || $.noop).apply(controller, params); + return (controller[action] || $.noop).apply(controller, params); }; return route; @@ -44,23 +91,13 @@ OSM.Router = function(map, rts) { } }; - var currentPath = window.location.pathname + window.location.search, + var currentPath = window.location.pathname.replace(/(.)\/$/, '$1') + window.location.search, currentRoute = routes.recognize(currentPath), currentHash = location.hash || OSM.formatHash(map); - currentRoute.run('load', currentPath); - - var router, stateChange; + var router = {}; if (window.history && window.history.pushState) { - stateChange = function(state, hash) { - window.history.replaceState(state, document.title, hash); - }; - - // Set a non-null initial state, so that the e.originalEvent.state - // check below works correctly when going back to the initial page. - stateChange(OSM.parseHash(currentHash), currentPath + currentHash); - $(window).on('popstate', function(e) { if (!e.originalEvent.state) return; // Is it a real popstate event or just a hash change? var path = window.location.pathname + window.location.search; @@ -76,7 +113,7 @@ OSM.Router = function(map, rts) { } }); - router = function (url) { + router.route = function (url) { var path = url.replace(/#.*/, ''), route = routes.recognize(path); if (!route) return false; @@ -86,22 +123,30 @@ OSM.Router = function(map, rts) { currentRoute = route; currentRoute.run('pushstate', currentPath); return true; - } - } else { - stateChange = function(state, hash) { - window.location.replace(hash); }; - router = function (url) { + router.stateChange = function(state) { + if (state.center) { + window.history.replaceState(state, document.title, OSM.formatHash(state)); + } else { + window.history.replaceState(state, document.title, window.location); + } + }; + } else { + router.route = function (url) { window.location.assign(url); - } + }; + + router.stateChange = function(state) { + if (state.center) window.location.replace(OSM.formatHash(state)); + }; } router.updateHash = function() { var hash = OSM.formatHash(map); if (hash === currentHash) return; currentHash = hash; - stateChange(OSM.parseHash(hash), hash); + router.stateChange(OSM.parseHash(hash)); }; router.hashUpdated = function() { @@ -112,7 +157,24 @@ OSM.Router = function(map, rts) { if (!state) return; map.setView(state.center, state.zoom); map.updateLayers(state.layers); - stateChange(state, hash); + router.stateChange(state, hash); + }; + + router.moveListenerOn = function() { + map.on('moveend', router.updateHash); + }; + + router.moveListenerOff = function() { + map.off('moveend', router.updateHash); + }; + + router.load = function() { + if (currentRoute) { + var loadState = currentRoute.run('load', currentPath); + router.stateChange(loadState || {}); + } else { + throw "Unable to match route for: " + currentPath; + } }; map.on('moveend baselayerchange overlaylayerchange', router.updateHash);