1 /** 2 * Olives http://flams.github.com/olives 3 * The MIT License (MIT) 4 * Copyright (c) 2012-2014 Olivier Scherrer <pode.fr@gmail.com> - Olivier Wietrich <olivier.wietrich@gmail.com> 5 */ 6 "use strict"; 7 8 var Router = require("emily").Router, 9 Tools = require("emily").Tools; 10 11 /** 12 * @class 13 * A locationRouter is a router which navigates to the route defined in the URL and updates this URL 14 * while navigating. It's a subtype of Emily's Router 15 */ 16 function LocationRouterConstructor() { 17 18 /** 19 * The handle on the watch 20 * @private 21 */ 22 var _watchHandle, 23 24 /** 25 * The default route to navigate to when nothing is supplied in the url 26 * @private 27 */ 28 _defaultRoute = "", 29 30 /** 31 * The last route that was navigated to 32 * @private 33 */ 34 _lastRoute = window.location.hash; 35 36 /** 37 * Navigates to the current hash or to the default route if none is supplied in the url 38 * @private 39 */ 40 /*jshint validthis:true*/ 41 function doNavigate() { 42 if (window.location.hash) { 43 var parsedHash = this.parse(window.location.hash); 44 this.navigate.apply(this, parsedHash); 45 } else { 46 this.navigate(_defaultRoute); 47 } 48 } 49 50 /** 51 * Set the default route to navigate to when nothing is defined in the url 52 * @param {String} defaultRoute the defaultRoute to navigate to 53 * @returns {Boolean} true if it's not an empty string 54 */ 55 this.setDefaultRoute = function setDefaultRoute(defaultRoute) { 56 if (defaultRoute && typeof defaultRoute == "string") { 57 _defaultRoute = defaultRoute; 58 return true; 59 } else { 60 return false; 61 } 62 }; 63 64 /** 65 * Get the currently set default route 66 * @returns {String} the default route 67 */ 68 this.getDefaultRoute = function getDefaultRoute() { 69 return _defaultRoute; 70 }; 71 72 /** 73 * The function that parses the url to determine the route to navigate to. 74 * It has a default behavior explained below, but can be overriden as long as 75 * it has the same contract. 76 * @param {String} hash the hash coming from window.location.has 77 * @returns {Array} has to return an array with the list of arguments to call 78 * navigate with. The first item of the array must be the name of the route. 79 * 80 * Example: #album/holiday/2013 81 * will navigate to the route "album" and give two arguments "holiday" and "2013" 82 */ 83 this.parse = function parse(hash) { 84 return hash.split("#").pop().split("/"); 85 }; 86 87 /** 88 * The function that converts, or serialises the route and its arguments to a valid URL. 89 * It has a default behavior below, but can be overriden as long as it has the same contract. 90 * @param {Array} args the list of arguments to serialize 91 * @returns {String} the serialized arguments to add to the url hashmark 92 * 93 * Example: 94 * ["album", "holiday", "2013"]; 95 * will give "album/holiday/2013" 96 * 97 */ 98 this.toUrl = function toUrl(args) { 99 return args.join("/"); 100 }; 101 102 /** 103 * When all the routes and handlers have been defined, start the location router 104 * so it parses the URL and navigates to the corresponding route. 105 * It will also start listening to route changes and hashmark changes to navigate. 106 * While navigating, the hashmark itself will also change to reflect the current route state 107 */ 108 this.start = function start(defaultRoute) { 109 this.setDefaultRoute(defaultRoute); 110 doNavigate.call(this); 111 this.bindOnHashChange(); 112 this.bindOnRouteChange(); 113 }; 114 115 /** 116 * Remove the events handler for cleaning. 117 */ 118 this.destroy = function destroy() { 119 this.unwatch(_watchHandle); 120 window.removeEventListener("hashchange", this.boundOnHashChange, true); 121 }; 122 123 /** 124 * Parse the hash and navigate to the corresponding url 125 * @private 126 */ 127 this.onHashChange = function onHashChange() { 128 if (window.location.hash != _lastRoute) { 129 doNavigate.call(this); 130 } 131 }; 132 133 /** 134 * The bound version of onHashChange for add/removeEventListener 135 * @private 136 */ 137 this.boundOnHashChange = this.onHashChange.bind(this); 138 139 /** 140 * Add an event listener to hashchange to navigate to the corresponding route 141 * when it changes 142 * @private 143 */ 144 this.bindOnHashChange = function bindOnHashChange() { 145 window.addEventListener("hashchange", this.boundOnHashChange, true); 146 }; 147 148 /** 149 * Watch route change events from the router to update the location 150 * @private 151 */ 152 this.bindOnRouteChange = function bindOnRouteChange() { 153 _watchHandle = this.watch(this.onRouteChange, this); 154 }; 155 156 /** 157 * The handler for when the route changes 158 * It updates the location 159 * @private 160 */ 161 this.onRouteChange = function onRouteChange() { 162 window.location.hash = this.toUrl(Tools.toArray(arguments)); 163 _lastRoute = window.location.hash; 164 }; 165 166 this.getLastRoute = function getLastRoute() { 167 return _lastRoute; 168 }; 169 170 } 171 172 module.exports = function LocationRouterFactory() { 173 LocationRouterConstructor.prototype = new Router(); 174 return new LocationRouterConstructor(); 175 }; 176