| /** |
| * |
| * jsGET |
| * |
| * jsGET is a http GET-Variables clone for javascript, using the hash part of the URL (index.html#...). |
| * You can set and get variables, and run a listener to hash changes, e.g. when the the history back button gets pressed. |
| * This allows you to create a usable history navigation in your ajax application. It should work with all A-grade browsers. |
| * |
| * @author Fabian Vogelsteller <fabian@feindura.org> |
| * @copyright Fabian Vogelsteller |
| * @license http://www.gnu.org/licenses GNU General Public License version 3 |
| * |
| * @version 0.2 |
| * |
| * ### Properties |
| * - vars: (object) the hash variables object loaded by get(), set(), remove(), or clear() or load() plus various indicators and trackers: |
| * - vars.current: (object) the current variables. |
| * - vars.old: (object) the old variables, before they where changed with set(), remove(), clear() or the browser history back button. *WARNING*: this is one is only valid while the listener is invoked. |
| * - vars.changed: (object) the variables which have changed since the last call of get(), set(), remove(), clear(), load() or the browser history back button. *WARNING*: this is one is updated by setChangedVars() depending on 'vars.old' and usually is only valid while the listener is invoked. |
| * - vars.change_count: (integer) a number of variables changed/added/removed since the last time the listener was invoked. *WARNING*: this is one is updated by setChangedVars() depending on 'vars.old' and usually is only valid while the listener is invoked. |
| * - vars.last_hash_loaded: (string, internal use only) the raw hash string which was just processed; in the listener, this equals the current hash. |
| * - vars.last_hash_saved: (string, internal use only) the hash section string which was the last one generated by set(), clear() or remove(). |
| * - vars.foreign_hash_change: (boolean) TRUE when the hash was changed from outside our control, e.g. when the user hit the 'back/history' button in the browser, after the previous invocation of the listener. |
| * - vars.hash_changed: (boolean) TRUE when the hash has changed after the previous invocation of the listener. |
| * |
| * ### Methods |
| * - load(): loads the current hash variables into the vars.current property as JSON object. Return the updated set of key/value pairs. |
| * - clear(): clears the hash part of the URL. (because it's not completely possible, it sets it to "#_") |
| * - get(get): (string) try to get a hash variable with the given name. |
| * - set(set): (string,object) sets the given parameters to the hash variables. If it's a string it should have the following format: "key=value". Return the updated set of key/value pairs. |
| * - remove(remove): (string,array) the variable name(s) which should be removed from the hash variables. Return the old set of key/value pairs. |
| * - addListener(listener,callAlways,bind): (listener: function, callAlways: boolean, bind: object instance) creates a listener which calls the given function when a hash change occurs. The called function will get the vars property (vars.current,vars.old,vars.changed) and use the "bind" parameter as "this", when specified. |
| * The return of the addListener() method is a setInterval ID and must be passed to the removeListener() method to stop the listening. |
| * When callAlways is FALSE, it only calls when the browser history buttons are pressed and not when get(), set(), remove() or clear() is called. |
| * - removeListener(listenerID): (the setInterval Id received from a addListener() method) removes a listener set with the addListener() method. |
| * - setChangedVars(): (internal use) updates the vars.changed collection and vars.change_count value. |
| * |
| * ### ATTENTION! |
| * Everytime you call set(), remove() or clear() a new hash string will be set, |
| * that means you also create a new history step in the browser history! |
| * |
| * These are 'special' characters to jsGET and will therefor be encoded when they are part of a key or value: |
| * # & = |
| */ |
| |
| var jsGET = { |
| vars: { |
| old: {}, |
| current: {}, |
| changed: {}, |
| change_count: 0, |
| last_hash_loaded: '', |
| last_hash_saved: window.location.hash, |
| foreign_hash_change: false, |
| hash_changed: false |
| }, |
| load: function() { |
| // only load hash variables when anything changed in the hash since last time we loaded them: |
| var i; |
| var new_hash = window.location.hash; |
| |
| if (this.vars.last_hash_loaded !== new_hash) { |
| // detect whether the hash was changed outside our control, e.g. when user pushed BACK/HISTORY button in browser: |
| if (new_hash !== this.vars.last_hash_saved) { |
| this.vars.foreign_hash_change = true; |
| } |
| this.vars.hash_changed = true; |
| |
| var hashVars = new_hash.split('#'); |
| this.vars.current = {}; |
| if (typeof hashVars[1] !== 'undefined' && hashVars[1] && hashVars[1] !== '_') { |
| hashVars = hashVars[1].split('&'); |
| for(i = 0; i < hashVars.length; i++) { |
| var hashVar = hashVars[i].split('='); |
| this.vars.current[this.decode(hashVar[0])] = (typeof hashVar[1] !== 'undefined' ? this.decode(hashVar[1]) : ''); |
| } |
| } |
| this.vars.last_hash_loaded = new_hash; |
| } |
| return this.vars.current; |
| }, |
| // encode special characters in the input string; use encodeURIComponent() to encode as that one is fast and ensures proper Unicode handling as well: bonus! |
| encode: function(s) { |
| s = encodeURIComponent(s); |
| // BUT! browsers take things like '%26' in the URL anywhere and translate it to '&' before we get our hands on the fragment part, so we need to prevent the browsers from doing this: |
| s = s.replace(/%/g, '$'); // we can do this safely as encodeURIComponent() will have encoded any '$' in the original string! |
| return s; |
| }, |
| decode: function(s) { |
| s = s.replace(/\$/g, '%'); |
| s = decodeURIComponent(s); |
| return s; |
| }, |
| clear: function() { |
| this.vars.last_hash_saved = window.location.hash = "#_"; |
| //window.location.href = window.location.href.replace( /#.*$/, ""); |
| return false; |
| }, |
| get: function(key) { |
| this.load(); |
| return (this.vars.current.hasOwnProperty(key) ? this.vars.current[key] : null); |
| }, |
| set: function(set) { |
| var key; |
| |
| //if (typeof console !== 'undefined' && console.log) console.log('savedHistory'); |
| this.load(); |
| |
| if (typeof set !== 'object') { |
| var setSplit = set.split('='); |
| set = {}; |
| // be aware that the _value_ of the key, value pair can have an embedded '=' (or more) itself: |
| key = setSplit.shift(); |
| var value = setSplit.join('='); |
| set[key] = value; |
| } |
| else { |
| // do not damage the set passed in as a parameter |
| set = this.helpers.cloneObject(set); |
| } |
| |
| // var |
| var hashString = ''; |
| var sep = '#'; |
| |
| // check for change in existing vars |
| for(key in this.vars.current) { |
| if (this.vars.current.hasOwnProperty(key)) { |
| if (set.hasOwnProperty(key)) { |
| hashString += sep+this.encode(key)+'='+this.encode(set[key]); |
| delete set[key]; |
| } |
| else { |
| hashString += sep+this.encode(key)+'='+this.encode(this.vars.current[key]); |
| } |
| sep = '&'; |
| } |
| } |
| |
| // add new vars |
| for(key in set) { |
| if (set.hasOwnProperty(key)) { |
| hashString += sep+this.encode(key)+'='+this.encode(set[key]); |
| sep = '&'; |
| } |
| } |
| this.vars.last_hash_saved = window.location.hash = hashString; |
| return this.load(); |
| }, |
| remove: function(remove) { |
| var removes; |
| var i; |
| var key; |
| |
| this.load(); |
| |
| if (typeof remove !== 'object') { |
| removes = [remove]; |
| } else { |
| removes = remove; |
| } |
| |
| // var |
| var hashString = ''; |
| var sep = '#'; |
| |
| for (i = 0; i < removes.length; i++) { |
| if (this.vars.current.hasOwnProperty(removes[i])) { |
| delete this.vars.current[removes[i]]; |
| } |
| } |
| |
| // create new hash string |
| for(key in this.vars.current) { |
| if (this.vars.current.hasOwnProperty(key)) { |
| hashString += sep+this.encode(key)+'='+this.encode(this.vars.current[key]); |
| sep = '&'; |
| } |
| } |
| this.vars.last_hash_saved = window.location.hash = hashString; |
| |
| this.load(); |
| // a bit odd: this one returns the OLD set, while set() returns the UPDATED set... |
| return this.vars.current; |
| }, |
| setChangedVars: function() { |
| var change_count; |
| var key; |
| var oldVars = this.helpers.cloneObject(this.vars.old); |
| this.vars.changed = this.helpers.cloneObject(this.vars.current); |
| |
| // check for changed vars |
| change_count = 0; |
| for (key in this.vars.changed) { |
| if (this.vars.changed.hasOwnProperty(key)) { |
| if (oldVars.hasOwnProperty(key)) { |
| if (oldVars[key] === this.vars.changed[key]) { |
| change_count--; // faster?/simpler than multiple 'else' branches just to track change_count |
| delete this.vars.changed[key]; |
| } |
| delete oldVars[key]; |
| } |
| change_count++; |
| } |
| } |
| // merge the rest of this.vars.old with the changedVars |
| for (key in oldVars) { |
| if (oldVars.hasOwnProperty(key) /* && !this.vars.changed.hasOwnProperty(key) */ ) { |
| this.vars.changed[key] = oldVars[key]; |
| change_count++; |
| } |
| } |
| this.vars.change_count = change_count; |
| }, |
| addListener: function(listener, callAlways, bind, freq) { // use the returned interval ID for removeListener |
| |
| this.load(); |
| this.vars.hash_changed = false; |
| this.vars.foreign_hash_change = false; |
| this.vars.old = this.helpers.cloneObject(this.vars.current); |
| |
| this.pollHash = function() { |
| var key; |
| |
| this.load(); // side effect: an immediate check (one more) to see whether the hash has changed by us or others |
| |
| // and make sure listener always fires whn callAlways==FALSE and user is going back&forth in the browser history (one or more history entries may match this.vars.last_hash_saved !) |
| this.vars.last_hash_saved = window.location.hash; |
| |
| if (this.vars.hash_changed) { |
| this.setChangedVars(); |
| if (callAlways || this.vars.foreign_hash_change) { |
| // var |
| /* |
| if (typeof console !== 'undefined' && console.log) console.log('-----'); |
| if (typeof console !== 'undefined' && console.log) console.log(this.vars.old); |
| if (typeof console !== 'undefined' && console.log) console.log(this.vars.changed); |
| */ |
| // call the given listener function |
| if (typeof listener === 'function') { |
| listener.apply(bind, [this.vars]); |
| } |
| |
| // only reset the 'old' array, i.e. effect the '.changed' set, when the listener was actually (to be) invoked. |
| // |
| // also reset the 'changed' markers so changes applied inside the listener don't 'recursively' trigger the listener: |
| this.load(); |
| this.vars.hash_changed = false; |
| this.vars.foreign_hash_change = false; |
| |
| /* |
| if (typeof console !== 'undefined' && console.log) console.log('-----'); |
| if (typeof console !== 'undefined' && console.log) console.log(this.vars.current); |
| if (typeof console !== 'undefined' && console.log) console.log(this.vars.old); |
| if (typeof console !== 'undefined' && console.log) console.log(this.vars.changed); |
| */ |
| this.vars.old = new this.vars.current.constructor(); |
| for (key in this.vars.current) { |
| if (this.vars.current.hasOwnProperty(key)) { |
| this.vars.old[key] = this.vars.current[key]; |
| } |
| } |
| } |
| } |
| }; |
| |
| var self = this; |
| return setInterval(function() { |
| self.pollHash(); |
| }, (freq || 500)); |
| }, |
| removeListener: function(listenerID) { // use the interval ID returned by addListener |
| delete this.pollHash; |
| return clearInterval(listenerID); |
| }, |
| helpers: |
| { |
| // eqv. of mootools Object.clone(): |
| cloneObject: function(obj) { |
| var key; |
| var rv = new obj.constructor(); |
| for (key in obj) { |
| if (obj.hasOwnProperty(key)) { |
| rv[key] = obj[key]; |
| } |
| } |
| return rv; |
| } |
| } |
| }; |
| |
| |
| /* settings for jsLint: undef: true, browser: true, indent: 4 */ |
| |