var VnObject = require ('./object'); var VnDate = require ('./date'); var Lot = require ('./lot'); var Value = require ('./value'); /** * Class to handle the URL. */ module.exports = new Class ({ Extends: VnObject ,Implements: Lot ,Properties: { window: { type: Window ,set: function (x) { this._window = x; x.addEventListener ('hashchange', this._hashChangedHandler); this._hashChanged (); } ,get: function () { return this._window; } }, params: { type: Object ,set: function (x) { this.setAll (x); } ,get: function () { return this._hashMap; } } } ,initialize: function (props) { this._hash = null; this._hashMap = null; this._window = null; this._hashChangedHandler = this._hashChanged.bind (this); this.parent (props); } ,get: function (key) { return this._hashMap[key]; } ,set: function (key, value) { var object = {}; object[key] = value; this.assign (object); } /** * Sets the hash part of the URL, respecting the current hash variables. * * @param {Object} object A key-value object */ ,assign: function (object) { var newObject = {}; for (var key in this._hashMap) newObject[key] = this._hashMap[key]; for (var key in object) newObject[key] = object[key]; this.setAll (newObject); } /** * Sets the hash part of the URL. * * @param {Object} object A key-value object */ ,setAll: function (object) { if (object) for (var key in object) if (object[key] === null || object[key] === undefined) delete object[key]; var newHash = this.make (object); if (!object) object = {}; if (!Value.equals (this._hashMap, object)) { this._hashMap = object; this._hash = newHash; this._blockChanged = true; location.hash = newHash; this._blockChanged = false; this.changed (); } } /** * Creates a URL with the given hash data. * * @param {Object} object A key-value object * @param {boolean} add %true to combine with the current params, %false otherwise * @return {String} The URL */ ,make: function (object, add) { var hash = '#!'; if (add && object) for (var key in this._hashMap) if (!object[key]) object[key] = this._hashMap[key]; for (var key in object) { if (hash.length > 2) hash += '&'; hash += encodeURIComponent (key) +'='+ this.renderValue (object[key]); } return hash; } ,_hashChanged: function () { var newHash = location.hash; if (this._blockChanged || this._hash == newHash) return; var newMap = hashMap = {}; var kvPairs = newHash.substr(2).split ('&'); for (var i = 0; i < kvPairs.length; i++) { var kvPair = kvPairs[i].split ('=', 2); if (kvPair[0]) newMap[decodeURIComponent (kvPair[0])] = this.parseValue (kvPair[1]); } if (!Value.equals (this._hashMap, newMap)) { this._hashMap = newMap; this._hash = newHash; this.changed (); } } ,renderValue: function (v) { switch (typeof v) { case 'number': return '(Number)'+ v; case 'boolean': return '(Boolean)'+ (v ? 'true' : 'false'); case 'object': if (v instanceof Date) return '(Date)'+ VnDate.strftime (v, '%Y-%m-%d'); else return '(Object)'+ JSON.stringify (v) } switch (v.charAt (0)) { case '(': case '\\': return '\\'+ v; } return v; } ,parseValue: function (v) { if (v == null) return v; v = decodeURIComponent (v); if (v === '') return v; var typeStr; switch (v.charAt(0)) { case '(': var index = v.indexOf (')'); typeStr = v.substr (1, index - 1); v = v.substr (index + 1); break; case '\\': v = v.substr (1); break; } if (v === '') return null; if (!typeStr) return v; switch (typeStr) { case 'Boolean': return (/^(true|1)$/i).test (v); case 'Number': return 0 + new Number (v); case 'Date': var date = new Date (v); date.setHours (0, 0, 0, 0); return date; case 'Object': return JSON.parse (v); default: return v; } } ,_destroy: function () { this._window.removeEventListener ('hashchange', this._hashChangedHandler); this._window = null; this.parent (); } });