1 /** 2 * @author Gillis Haasnoot <gillis.haasnoot@gmail.com> 3 * @package Banana.Util 4 * @summary UrlManager 5 */ 6 7 /** @namespace Banana.Util.UrlManager */ 8 goog.provide('Banana.Util.UrlManager'); 9 10 /** 11 * Url Manager. It allows a user to manager url parameters in the achor part of the url. 12 * It provides the functionality to set url parameters, listen to changes in the url and 13 * mark and read history points 14 * 15 * @constructor 16 */ 17 Banana.Util.UrlManager = (function() 18 { 19 var initials = []; 20 var listeners = {}; 21 var paramHistory = {}; 22 var delim = "&"; 23 var members = {}; 24 var started = false; 25 var timeout; 26 var timeoutLength = 20; 27 var handler = null; 28 var urlCheckHandler = null; 29 var running = false; 30 31 /** 32 * @ignore 33 * @return object 34 */ 35 var getUrlObject = function() 36 { 37 var url = getBrowserUrl(); 38 var urlObject = {}; 39 var key; 40 var value; 41 var itemData; 42 43 url = url.split(delim); 44 45 for (var i = url.length-1; i>=0; i--) { 46 itemData = url[i].split("="); 47 key = itemData[0]; 48 urlObject[key] = itemData.slice(1).join("="); 49 } 50 return urlObject; 51 }; 52 53 54 /** 55 * sets the browser url. for ie we do this in a iframe 56 * @ignore 57 */ 58 var setBrowserUrl = function(newUrl) 59 { 60 if (navigator.appName == 'Microsoft Internet Explorer' && document.getElementById("URLFrame")) 61 { 62 document.frames["URLFrame"].location.replace(document.frames["URLFrame"].location.pathname + "?" + location.hash.slice(1)); 63 64 document.getElementById("URLFrame").setAttribute("src", document.frames["URLFrame"].location.pathname + "?" + newUrl); 65 location.hash = newUrl; 66 } 67 else 68 { 69 location.hash = newUrl; 70 71 } 72 }; 73 74 /** 75 * @return string url of the browser. for ie we use the iframe 76 * @ignore 77 */ 78 var getBrowserUrl = function() 79 { 80 if (navigator.appName == 'Microsoft Internet Explorer' && document.getElementById("URLFrame")) { 81 82 location.hash = document.frames["URLFrame"].location.search.slice(1); 83 return document.frames["URLFrame"].location.search.slice(1); 84 } 85 else 86 { 87 return location.hash.slice(1); 88 } 89 }; 90 91 92 /** 93 * updates url according to current registered modules 94 * @ignore 95 */ 96 var updateBrowserUrl = function() 97 { 98 var newUrl = ""; 99 100 //to ensure we have always sorted url params. 101 //we do this to prevent situations with 2 equal url params in 2 different sequences 102 members = Banana.Util.sortObjectKeys(members); 103 104 for (var key in members) 105 { 106 if (members[key].bhmValue) 107 { 108 newUrl = newUrl + (newUrl.length === 0 ? "" : delim); 109 newUrl = newUrl + key + "=" + members[key].bhmValue; 110 } 111 } 112 113 //dont register duplicated urls 114 if (newUrl ==getBrowserUrl()) return; 115 116 setBrowserUrl(newUrl); 117 }; 118 119 /** 120 * Starts checking the url for changes 121 * @ignore 122 */ 123 var startChecking = function() 124 { 125 stopHandler = false; 126 127 if (!running) 128 { 129 startHandler(); 130 } 131 }; 132 133 /** 134 * Starts the handler for url change detection. 135 * If a change in the achor part of the url is dedected we trigger a url.{key} event 136 * @ignore 137 */ 138 var startHandler = function() 139 { 140 running = true; 141 142 if (stopHandler) 143 { 144 clearTimeout(urlCheckHandler); 145 stopHandler = false; 146 return; 147 } 148 149 if (!handler) 150 { 151 /** 152 * @ignore 153 */ 154 handler = function() 155 { 156 var currentURL = getUrlObject(); 157 var currentURLVal; 158 for (var key in members) 159 { 160 currentURLVal = currentURL[key] || ""; 161 if (!members[key] || !currentURL[key]) continue; 162 163 if (members[key].bhmValue != currentURLVal) 164 { 165 members[key].bhmValue = currentURLVal; 166 jQuery(document).trigger("url." + key, [currentURLVal, key]); 167 } 168 } 169 170 startHandler(); 171 }; 172 } 173 174 urlCheckHandler = setTimeout(handler, 20); 175 }; 176 177 /** 178 * Stops checking 179 * @ignore 180 */ 181 var stopChecking = function() 182 { 183 stopHandler = true; 184 clearTimeout(urlCheckHandler); 185 urlCheckHandler = null; 186 running = false; 187 } 188 189 190 /** 191 * public properties 192 */ 193 return({ 194 /** 195 * registers module in the url. This doesn't mean that we see it in the url. 196 * A value needs to be set first. 197 * 198 * @param {String} name of the url param 199 */ 200 registerModule : function(name) 201 { 202 if (!members[name]) 203 { 204 members[name] = {}; 205 } 206 207 if (getUrlObject()[name]) 208 { 209 members[name].bhmValue = getUrlObject()[name]; //w 210 } 211 else 212 { 213 members[name].bhmValue = ""; 214 } 215 }, 216 217 /** 218 * Auto registers modules by looking at the current url 219 * All parameters in the achor part will be registered as a module 220 */ 221 autoRegisterModules : function() 222 { 223 params = getUrlObject(); 224 225 for(param in params) 226 { 227 if (param) 228 { 229 this.registerModule(param); 230 } 231 } 232 }, 233 234 /** 235 * Stop checking the url for changes 236 */ 237 stopChecking : function() 238 { 239 stopChecking(); 240 }, 241 242 /** 243 * Start checking the url for changes 244 */ 245 startChecking : function() 246 { 247 startChecking(); 248 }, 249 250 /** 251 * unregisters all modules. Change in url params pointing to removed modules 252 * will not result in a trigger change event 253 */ 254 removeModules : function() 255 { 256 for (var key in members) 257 { 258 stopChecking(); 259 this.unlistenModule(key); 260 } 261 members = {}; 262 }, 263 264 /** 265 * Clears the url by first removing all the modules (no change event can occur) 266 * and then remove all the external registered listeners. 267 */ 268 clearUrl : function() 269 { 270 this.removeModules(); 271 //remove rest of registered listeners 272 this.removeListeners(); 273 }, 274 275 /** 276 * removes module from url 277 * 278 * @param {String} name of the param which should be removed from the url 279 * @param {boolean} when true we only remove the param from the url. It stays registered. 280 */ 281 removeModule : function(name,hideOnly) 282 { 283 stopChecking(); 284 285 if (!hideOnly) 286 { 287 this.unlistenModule(name); 288 delete members[name]; 289 } 290 else 291 { 292 members[name]['bhmValue'] = ''; 293 } 294 295 updateBrowserUrl(); 296 for (var key in members) 297 { 298 return startChecking(); 299 } 300 }, 301 302 /** 303 * sets module value in the url. 304 * Calling this method will result in a visible url change. 305 * 306 * @param {String} name of the url parameter 307 * @param {String} value 308 * @param when true we dont update the url itself. so it wont trigger a change event 309 */ 310 setModule : function(name,value,dontUpdateUrl) 311 { 312 if (!name )return; 313 dontUpdateUrl = dontUpdateUrl || false; 314 315 if (!members[name]) return false; 316 317 stopChecking(); 318 319 members[name].bhmValue = value; 320 321 if (!dontUpdateUrl) 322 { 323 if (members[name] && this.getModule[name] != value) 324 { 325 updateBrowserUrl(); 326 jQuery(document).trigger("url." + name, [value, name]); 327 } 328 } 329 330 startChecking(); 331 }, 332 333 /** 334 * forces url to be updated 335 * Handy when you register multiple modules without updating url to prevent multiple 336 * browser history moments 337 */ 338 updateUrl : function() 339 { 340 updateBrowserUrl(); 341 }, 342 343 /** 344 * gets the value of a registered module. 345 * We always try to fetch the internally registered value first. 346 * @return {String} param name of in the url 347 */ 348 getModule : function(param) 349 { 350 //first we try to fetch a value from members array 351 //we could have set an url param without affecting the url itself. 352 //this is the array where the value should be in 353 if (members[param] && members[param].bhmValue) 354 { 355 return members[param].bhmValue; 356 } 357 //otherwise just fetch it from the url 358 else if (getUrlObject()[param]) 359 { 360 return getUrlObject()[param]; 361 } 362 363 return null; 364 }, 365 366 /** 367 * saves module history 368 */ 369 saveModuleHistory : function() 370 { 371 paramHistory[this.getModule('section')] = getUrlObject(); 372 }, 373 374 /** 375 * @return Object returns module history 376 */ 377 getModuleHistory : function(name) 378 { 379 return paramHistory[name]; 380 }, 381 382 /** 383 * gets current browser url. 384 * @return {String} 385 */ 386 getBrowserUrl : function() 387 { 388 return getBrowserUrl(); 389 }, 390 391 /** 392 * gets complete history 393 * @return {Array} 394 */ 395 getHistory : function() 396 { 397 return paramHistory; 398 }, 399 400 /** 401 * binds a function to a url change event. when 402 * 403 * @param {String} name of the url parameter to listen on 404 * @param {Function} fn callback function when change is detected 405 * @param {mixed} data optional 406 */ 407 listenModule : function(name, fn, data) 408 { 409 if (!data) {data = {}} 410 stopChecking(name); 411 412 jQuery(document).bind("url." + name,data, fn); 413 listeners[name] = true; 414 startChecking(name); 415 }, 416 417 /** 418 * unbinds all functions from change event 419 * 420 * @param {String} name of the url parameter 421 */ 422 unlistenModule : function(name) 423 { 424 //TODO: Could be done per function instead of removing all binds 425 jQuery(document).unbind("url." + name); 426 delete listeners[name]; 427 }, 428 429 /** 430 * Removes all listeners 431 */ 432 removeListeners : function() 433 { 434 for (l in listeners) 435 { 436 if (typeof(listeners[l]) == 'function') { continue; } 437 438 this.unlistenModule(l); 439 } 440 441 listeners = []; 442 }, 443 444 /** 445 * @return {Object} of url key values 446 */ 447 getURLObject : function() 448 { 449 return getUrlObject(); 450 }, 451 452 /** 453 * Helper to construct url string from complex object 454 * @param {String} section 455 * @param {Object} params 456 * 457 * @return {String} url 458 */ 459 createUrlString : function(section,params) 460 { 461 if (params) 462 { 463 //merge section into the params 464 params.section = section; 465 466 params = Banana.Util.sortObjectKeys(params); 467 468 var url = ""; 469 for(var j in params) 470 { 471 url +="&"+j+'='+params[j]; 472 } 473 474 //remove first & and return url 475 return url.substr(1,url.length); 476 } 477 else 478 { 479 return 'section='+section; 480 } 481 } 482 }); 483 }());