1 /** Manages controllers, providing a means for separating functionality into feature-centric modules. 2 * @constructor Creates a new RouteManager instance. 3 * @augments puredom.ControllerManager 4 * @param {Object} [options] Hashmap of options to be given to the instance. 5 * @param {Boolean} [options.allowTemplateFallback=false] If no URL templates match, attempt to load by name. 6 * @param {Boolean} [options.useBest=false] If no URL templates match, attempt to load by name. 7 * @param {Boolean} [options.allowPartialUrlFallback=false] Use the longest URL template match, even if it isn't a perfect match. 8 */ 9 puredom.RouteManager = function(options) { 10 var self = this; 11 options = options || {}; 12 puredom.ControllerManager.call(this); 13 14 this.allowTemplateFallback = options.allowTemplateFallback===true || options.useBest===true; 15 this.allowPartialUrlFallback = options.allowPartialUrlFallback===true; 16 17 /** @ignore */ 18 this._controllerUpdateState = function(options) { 19 self.doStateUpdate(self._routerState, options); 20 }; 21 }; 22 23 24 puredom.inherits(puredom.RouteManager, puredom.ControllerManager); 25 26 27 puredom.extend(puredom.RouteManager.prototype, /** @lends puredom.RouteManager# */ { 28 29 /** RouteManagers are singular by default, because a browser can only navigate to one URL at a time. */ 30 singular : true, 31 32 33 /** @public */ 34 rewrites : [ 35 /* 36 { 37 inbound : { 38 pattern : /^\/?urlToChange(?:\/(.*?))?$/gim, 39 replace : '/final/url/$1' 40 } 41 //outbound : //gim, 42 } 43 */ 44 ], 45 46 47 /** For use with StateManager */ 48 restoreState : function(state) { 49 if (this.initialized!==true) { 50 this._initState = state; 51 } 52 else { 53 if (state && state.current_url) { 54 this.route(state.current_url); 55 } 56 else { 57 this.routeDefault(); 58 } 59 } 60 }, 61 62 63 /** For use with StateManager */ 64 doStateUpdate : function(state, options) { 65 var controller, 66 templatedUrl; 67 this._routerState = state && puredom.extend({}, state); 68 if (state && state.current) { 69 controller = this.get(state.current); 70 delete state.current; 71 templatedUrl = this._templateUrl(controller.customUrl || controller.urlTemplate || controller.name, controller); 72 if (templatedUrl.substring(0,1)!=='/') { 73 templatedUrl = '/' + templatedUrl; 74 } 75 state.current_url = templatedUrl; 76 } 77 this.updateState(state, options); 78 }, 79 80 81 /** @override */ 82 register : function(name, controller) { 83 controller.updateState = controller.updateRouterState = this._controllerUpdateState; 84 return puredom.ControllerManager.prototype.register.call(this, name, controller); 85 }, 86 87 88 /** Attempt to route the given URL to a controller. */ 89 route : function(url) { 90 var list = this._controllers, 91 normUrl = url.replace(/^[#!\/]+/gm,'').replace(/#.+$/gm,''), 92 item, i, p, urlTemplate, matches, params, 93 partialMatchName; 94 95 for (i=0; i<list.length; i++) { 96 item = list[i]; 97 urlTemplate = item.customUrl || item.urlTemplate || item.name; 98 matches = {}; 99 if (this._checkUrlMatch(urlTemplate, url, matches)===true) { 100 params = {}; 101 for (p in matches) { 102 if ((p+'').substring(0,7)==='params.') { 103 params[p.substring(7)] = matches[p]; 104 } 105 } 106 return this.load(item.name, { 107 params : params 108 }); 109 } 110 else if (this.allowPartialUrlFallback===true && this._checkUrlMatch(urlTemplate, url, matches, {partial:true})===true) { 111 partialMatchName = item.name; 112 } 113 } 114 115 if (partialMatchName) { 116 return this.load(partialMatchName, { 117 params : {} 118 }); 119 } 120 121 if (this.allowTemplateFallback!==false && this.get(normUrl)) { 122 return this.load(normUrl, { 123 params : {} 124 }); 125 } 126 127 this.fireEvent('routingError', [{ 128 attemptedFallback : this.allowTemplateFallback!==false, 129 url : url, 130 type : 'RoutingError' 131 }]); 132 133 return false; 134 }, 135 136 137 /** Load a controller */ 138 /* 139 load : function(name, options) { 140 this.__super.prototype.load.apply(this, arguments); 141 this.doStateUpdate(); 142 return this; 143 }, 144 */ 145 146 147 /** Attempt to route the given URL to a controller. */ 148 routeDefault : function(url) { 149 return this.loadDefault(); 150 }, 151 152 153 /** Template a URL using values from the current controller. Non-matched fields are left unchanged. 154 * @private 155 * @param {String} tpl The URL template 156 * @param {Object} [controller=current] Explicit controller reference. 157 * @returns {String} templatedUrl 158 */ 159 _templateUrl : function(tpl, controller) { 160 controller = controller || this.current(); 161 return puredom.template(tpl, controller, false); 162 }, 163 164 165 /** Check if a URL template matches the given URL. 166 * @private 167 * @param {String} urlTemplate A URL template, as used in Controller.customUrl 168 * @param {String} url The URL to test 169 * @param {Object} matches Optional Object to populate with the matched URL segments 170 * @returns {Boolean} didMatch 171 */ 172 _checkUrlMatch : function(urlTemplate, url, matches, options) { 173 var templateSegments = this._getUrlSegments(urlTemplate), 174 urlSegments = this._getUrlSegments(url), 175 tplFieldReg = /^\{([^{}]+)\}$/gim, 176 isMatch = true, 177 i; 178 options = options || {}; 179 matches = matches || {}; 180 if (options.partial===true) { 181 for (i=templateSegments.length; i--; ) { 182 if (templateSegments[i].match(tplFieldReg)) { 183 templateSegments.splice(i, 1); 184 } 185 } 186 //console.log(urlSegments, templateSegments); 187 } 188 if (urlSegments.length===templateSegments.length) { 189 for (i=0; i<urlSegments.length; i++) { 190 if (urlSegments[i]===templateSegments[i] || templateSegments[i].match(tplFieldReg)) { 191 matches[templateSegments[i].replace(tplFieldReg,'$1')] = urlSegments[i]; 192 } 193 else { 194 isMatch = false; 195 break; 196 } 197 } 198 } 199 else { 200 isMatch = false; 201 } 202 return isMatch; 203 }, 204 205 206 /** Normalize a URL for comparison 207 * @private 208 */ 209 _getUrlSegments : function(url) { 210 var segs = (url+'').split('/'), 211 i; 212 for (i=segs.length; i--; ) { 213 if (segs[i].replace(/(\s|\/)/gm,'').length===0) { 214 segs.splice(i, 1); 215 } 216 } 217 return segs; 218 } 219 220 });