Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Use a different/better history manager, fixes #40.

  • Loading branch information...
commit bf8d2f4ab791eae640f1f41b84002b396bdeee7a 1 parent 5f87e2a
Brian Donovan eventualbuddha authored
63 public/javascripts/history.adapter.jquery.js
View
@@ -0,0 +1,63 @@
+// History.js jQuery Adapter
+// New-BSD License, Copyright 2011 Benjamin Arthur Lupton <contact@balupton.com>
+
+(function($,window,undefined){
+
+ // --------------------------------------------------------------------------
+ // Initialise
+
+ // History Object
+ window.History = window.History||{};
+
+ // Localise Globals
+ var
+ History = window.History||{},
+ history = window.history;
+
+ // Check Existence of Adapter
+ if ( typeof History.Adapter !== 'undefined' ) {
+ throw new Error('History.js Adapter has already been emulated...');
+ }
+
+ // Add the Adapter
+ History.Adapter = {
+
+ /**
+ * History.Adapter.bind(el,event,callback)
+ * @param {element} el
+ * @param {string} event
+ * @param {Function} callback
+ * @return {element}
+ */
+ bind: function(el,event,callback){
+ return $(el).bind(event,callback);
+ },
+
+ /**
+ * History.Adapter.trigger(el,event)
+ * @param {element} el
+ * @param {string} event
+ * @return {element}
+ */
+ trigger: function(el,event){
+ return $(el).trigger(event);
+ },
+
+ /**
+ * History.Adapter.trigger(el,event,data)
+ * @param {Function} callback
+ * @return {true}
+ */
+ onDomLoad: function(callback) {
+ jQuery(callback);
+ }
+
+ };
+
+ // Check Load Status
+ if ( typeof History.init !== 'undefined' ) {
+ // History.js loaded faster than the Adapter, Fire init
+ History.init();
+ }
+
+})(jQuery,window);
1,593 public/javascripts/history.js
View
@@ -0,0 +1,1593 @@
+// History.js
+// New-BSD License, Copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
+
+(function(window,undefined){
+
+ // --------------------------------------------------------------------------
+ // Initialise
+
+ // History Object
+ window.History = window.History||{};
+
+ // Localise Globals
+ var
+ document = window.document, // Make sure we are using the correct document
+ _History = {}, // Private History Object
+ History = window.History, // Public History Object
+ history = window.history; // Old History Object
+
+ // Check Existence of History.js
+ if ( typeof History.emulated !== 'undefined' ) {
+ throw new Error('History.js has already been emulated...');
+ }
+
+ // Initialise
+ History.init = function(){
+
+ // ----------------------------------------------------------------------
+ // Debug Helpers
+
+ /**
+ * History.options
+ * Configurable options
+ */
+ History.options = {
+ /**
+ * History.options.hashChangeCheckerDelay
+ * How long should the interval be before hashchange checks
+ */
+ hashChangeCheckerDelay: 100
+ };
+
+ // ----------------------------------------------------------------------
+ // Debug Helpers
+
+ /**
+ * History.debug(message,...)
+ * Logs the passed arguments if debug enabled
+ */
+ History.debug = function(){
+ if ( (History.debug.enable||false) ) {
+ History.log.apply(History,arguments);
+ }
+ };
+ History.debug.enable = false;
+
+ /**
+ * History.log(message,...)
+ * Logs the passed arguments
+ */
+ History.log = function(){
+ // Prepare
+ var consoleExists = typeof console !== 'undefined';
+
+ // Write to Console
+ if ( consoleExists ) {
+ console.log.apply(console,[arguments]);
+ }
+
+ // Write to log
+ var message = "\n"+arguments[0]+"\n";
+ for ( var i=1,n=arguments.length; i<n; ++i ) {
+ message += "\n"+arguments[i]+"\n";
+ }
+ var textarea = document.getElementById('log');
+ if ( textarea ) {
+ textarea.value += message+"\n-----\n";
+ } else if ( !consoleExists ) {
+ alert(message);
+ }
+
+ // Return true
+ return true;
+ }
+
+ // ----------------------------------------------------------------------
+ // Emulated Status
+
+ /**
+ * _History.getInternetExplorerMajorVersion()
+ * Get's the major version of Internet Explorer
+ * @return {integer}
+ * @license Public Domain
+ * @author Benjamin Lupton <contact@balupton.com>
+ * @author James Padolsey <https://gist.github.com/527683>
+ */
+ _History.getInternetExplorerMajorVersion = function(){
+ return _History.getInternetExplorerMajorVersion.cached =
+ (typeof _History.getInternetExplorerMajorVersion.cached !== 'undefined')
+ ? _History.getInternetExplorerMajorVersion.cached
+ : (function(){
+ var undef,
+ v = 3,
+ div = document.createElement('div'),
+ all = div.getElementsByTagName('i');
+ while (
+ div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
+ all[0]
+ );
+ return v > 4 ? v : undef;
+ })()
+ ;
+ };
+
+ /**
+ * _History.isInternetExplorer()
+ * Are we using Internet Explorer?
+ * @return {boolean}
+ * @license Public Domain
+ * @author Benjamin Lupton <contact@balupton.com>
+ */
+ _History.isInternetExplorer = function(){
+ return _History.isInternetExplorer.cached =
+ (typeof _History.isInternetExplorer.cached !== 'undefined')
+ ? _History.isInternetExplorer.cached
+ : (_History.getInternetExplorerMajorVersion() !== 0)
+ ;
+ };
+
+ /**
+ * History.emulated
+ * Which features require emulating?
+ */
+ History.emulated = {
+ pushState: !Boolean(window.history && window.history.pushState && window.history.replaceState),
+ hashChange: Boolean(
+ !('onhashchange' in window || 'onhashchange' in document)
+ ||
+ (_History.isInternetExplorer() && _History.getInternetExplorerMajorVersion() < 8)
+ )
+ };
+
+ /**
+ * _History.isEmptyObject(obj)
+ * Checks to see if the Object is Empty
+ * @param {Object} obj
+ * @return {boolean}
+ */
+ _History.isEmptyObject = function(obj) {
+ for ( var key in obj ) {
+ if ( !this.hasOwnProperty(key) ) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * _History.cloneObject(obj)
+ * Clones a object
+ * @param {Object} obj
+ * @return {Object}
+ */
+ _History.cloneObject = function(obj) {
+ var hash,newObj;
+ if ( obj ) {
+ hash = JSON.stringify(obj);
+ newObj = JSON.parse(hash);
+ }
+ else {
+ newObj = {};
+ }
+ return newObj;
+ };
+
+
+ // ----------------------------------------------------------------------
+ // Hash Helpers
+
+ /**
+ * History.setHash(hash)
+ * Sets the document hash
+ * @param {string} hash
+ * @return {string}
+ */
+ History.setHash = function(hash){
+ // Prepare
+ var adjustedHash = _History.escapeHash(hash);
+
+ // Log hash
+ History.debug('History.setHash',this,arguments,'hash:',hash,'adjustedHash:',adjustedHash,'oldHash:',document.location.hash);
+
+ // Apply hash
+ document.location.hash = adjustedHash;
+
+ // Return hash
+ return hash;
+ };
+
+ /**
+ * History.getHash()
+ * Gets the current document hash
+ * @return {string}
+ */
+ History.getHash = function(){
+ var hash = _History.unescapeHash(document.location.hash);
+ return hash;
+ };
+
+ /**
+ * _History.escape()
+ * Normalise and Escape a Hash
+ * @return {string}
+ */
+ _History.escapeHash = function(hash){
+ var result = _History.normalizeHash(hash);
+
+ // Escape hash
+ if ( /[^a-zA-Z0-9\/\-\_\%\.]/.test(result) ) {
+ result = escape(result);
+ }
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * _History.unescapeHash()
+ * Normalise and Unescape a Hash
+ * @return {string}
+ */
+ _History.unescapeHash = function(hash){
+ var result = _History.normalizeHash(hash);
+
+ // Unescape hash
+ if ( /[\%]/.test(result) ) {
+ result = unescape(result);
+ }
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * _History.normalizeHash()
+ * Normalise a hash across browsers
+ * @return {string}
+ */
+ _History.normalizeHash = function(hash){
+ var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.extractHashFromUrl(url)
+ * Extracts the Hash from a URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.extractHashFromUrl = function(url){
+ // Extract the hash
+ var hash = String(url)
+ .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
+ ;
+
+ // Unescape hash
+ hash = _History.unescapeHash(hash);
+
+ // Return hash
+ return hash;
+ };
+
+ /**
+ * History.isTraditionalAnchor(url)
+ * Checks to see if the url is a traditional anchor
+ * @param {string} url
+ * @return {boolean}
+ */
+ History.isTraditionalAnchor = function(url){
+ var
+ hash = History.extractHashFromUrl(url),
+ el = document.getElementById(hash),
+ exists = typeof el !== 'undefined';
+
+ return exists;
+ }
+
+ // ----------------------------------------------------------------------
+ // State Object Helpers
+
+ /**
+ * History.contractUrl(url)
+ * Ensures that we have a relative URL and not a absolute URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.contractUrl = function(url){
+ // Prepare
+ url = History.expandUrl(url);
+
+ // Prepare for Base Domain
+ var baseDomain = document.location.protocol+'//'+(document.location.hostname||document.location.host);
+ if ( document.location.port||false ) {
+ baseDomain += ':'+document.location.port;
+ }
+ baseDomain += '/';
+
+ // Adjust for Base Domain
+ url = url.replace(baseDomain,'/');
+
+ // Return url
+ return url;
+ };
+
+ /**
+ * History.expandUrl(url)
+ * Ensures that we have an absolute URL and not a relative URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.expandUrl = function(url){
+ // Prepare
+ url = url||'';
+
+ // Test for Full URL
+ if ( /[a-z]+\:\/\//.test(url) ) {
+ // We have a Full URL
+ }
+
+ // Relative URL
+ else {
+ // Test for Base Page
+ if ( url.length === 0 || url.substring(0,1) === '?' ) {
+ // Fetch Base Page
+ var basePage = document.location.href.replace(/[#\?].*/,'');
+
+ // Adjust Page
+ url = basePage+url;
+ }
+
+ // No Base Page
+ else {
+
+ // Prepare for Base Element
+ var
+ baseElements = document.getElementsByTagName('base'),
+ baseElement = null,
+ baseHref = '';
+
+ // Test for Base Element
+ if ( baseElements.length === 1 ) {
+ // Prepare for Base Element
+ baseElement = baseElements[0];
+ baseHref = baseElement.href;
+ if ( baseHref[baseHref.length-1] !== '/' ) baseHref += '/';
+
+ // Adjust for Base Element
+ url = baseHref+url.replace(/^\//,'');
+ }
+
+ // No Base Element
+ else {
+ // Test for Base URL
+ if ( url.substring(0,1) === '.' ) {
+ // Prepare for Base URL
+ var baseUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,'');
+ if ( baseUrl[baseUrl.length-1] !== '/' ) baseUrl += '/';
+
+ // Adjust for Base URL
+ url = baseUrl + url;
+ }
+
+ // No Base URL
+ else {
+ // Prepare for Base Domain
+ var baseDomain = document.location.protocol+'//'+(document.location.hostname||document.location.host);
+ if ( document.location.port||false ) {
+ baseDomain += ':'+document.location.port;
+ }
+ baseDomain += '/';
+
+ // Adjust for Base Domain
+ url = baseDomain+url.replace(/^\//,'');
+ }
+ }
+ }
+ }
+
+ // Return url
+ return url;
+ };
+
+ /**
+ * History.expandState(State)
+ * Expands a State Object
+ * @param {object} State
+ * @return {object}
+ */
+ History.expandState = function(oldState){
+ oldState = oldState||{};
+ var newState = {
+ 'data': oldState.data||{},
+ 'url': History.expandUrl(oldState.url||''),
+ 'title': oldState.title||''
+ };
+ newState.data.title = newState.data.title||newState.title;
+ newState.data.url = newState.data.url||newState.url;
+ return newState;
+ };
+
+ /**
+ * History.createStateObject(data,title,url)
+ * Creates a object based on the data, title and url state params
+ * @param {object} data
+ * @param {string} title
+ * @param {string} url
+ * @return {object}
+ */
+ History.createStateObject = function(data,title,url){
+ // Hashify
+ var State = {
+ "data": data,
+ "title": title,
+ "url": url
+ };
+
+ // Expand the State
+ State = History.expandState(State);
+
+ // Return object
+ return State;
+ };
+
+ /**
+ * History.expandHash(hash)
+ * Expands a Hash into a StateHash if applicable
+ * @param {string} hash
+ * @return {Object|null} State
+ */
+ History.expandHash = function(hash){
+ // Prepare
+ var State = null;
+
+ // JSON
+ try {
+ State = JSON.parse(hash);
+ }
+ catch ( Exception ) {
+ var
+ parts = /(.*)\/uid=([0-9]+)$/.exec(hash),
+ url = parts ? (parts[1]||hash) : hash,
+ uid = parts ? String(parts[2]||'') : '';
+
+ if ( uid ) {
+ State = _History.getStateByUid(uid)||null;
+ }
+
+ if ( !State && /\//.test(hash) ) {
+ // Is a URL
+ var expandedUrl = History.expandUrl(hash);
+ State = History.createStateObject(null,null,expandedUrl);
+ }
+ else {
+ // Non State Hash
+ // do nothing
+ }
+ }
+
+ // Expand
+ State = State ? History.expandState(State) : null;
+
+ // Return State
+ return State;
+ };
+
+ /**
+ * History.contractState(State)
+ * Creates a Hash for the State Object
+ * @param {object} passedState
+ * @return {string} hash
+ */
+ History.contractState = function(passedState){
+ // Check
+ if ( !passedState ) {
+ return null;
+ }
+
+ // Prepare
+ var
+ hash = null,
+ State = _History.cloneObject(passedState);
+
+ // Ensure State
+ if ( State ) {
+ // Clean
+ State.data = State.data||{};
+ delete State.data.title;
+ delete State.data.url;
+
+ // Handle
+ if ( _History.isEmptyObject(State) && !State.title ) {
+ hash = History.contractUrl(State.url);
+ }
+ else {
+ // Serialised Hash
+ hash = JSON.stringify(State);
+
+ // Has it been associated with a UID?
+ var uid;
+ if ( typeof _History.hashesToUids[hash] !== 'undefined' ) {
+ uid = _History.hashesToUids[hash];
+ }
+ else {
+ while ( true ) {
+ uid = String(Math.floor(Math.random()*1000));
+ if ( typeof _History.uidsToStates[uid] === 'undefined' ) {
+ break;
+ }
+ }
+ }
+
+ // Associate UID with Hash
+ _History.hashesToUids[hash] = uid;
+ _History.uidsToStates[uid] = State;
+
+ // Simplified Hash
+ hash = History.contractUrl(State.url)+'/uid='+uid;
+ }
+ }
+
+ // Return hash
+ return hash;
+ };
+
+ /**
+ * _History.uidsToStates
+ * UIDs to States
+ */
+ _History.uidsToStates = {};
+
+ /**
+ * _History.hashesToUids
+ * Serialised States to UIDs
+ */
+ _History.hashesToUids = {};
+
+ /**
+ * _History.getStateByUid(uid)
+ * Get a state by it's UID
+ * @param {string} uid
+ */
+ _History.getStateByUid = function(uid){
+ uid = String(uid);
+ var State = _History.uidsToStates[uid]||undefined;
+ return State;
+ };
+
+
+ // ----------------------------------------------------------------------
+ // State Logging
+
+ /**
+ * _History.statesByUrl
+ * Store the states indexed by their URLs
+ */
+ _History.statesByUrl = {};
+
+ /**
+ * _History.duplicateStateUrls
+ * Which urls have duplicate states (indexed by url)
+ */
+ _History.duplicateStateUrls = {};
+
+ /**
+ * _History.statesByHash
+ * Store the states indexed by their Hashes
+ */
+ _History.statesByHash = {};
+
+ /**
+ * _History.savedStates
+ * Store the states in an array
+ */
+ _History.savedStates = [];
+
+ /**
+ * History.getState()
+ * Get an object containing the data, title and url of the current state
+ * @return {Object} State
+ */
+ History.getState = function(){
+ return _History.getStateByIndex();
+ };
+
+ /**
+ * History.getStateHash()
+ * Get the hash of the current state
+ * @return {string} hash
+ */
+ History.getStateHash = function(){
+ return History.contractState(History.getState());
+ };
+
+ /**
+ * _History.getStateByUrl
+ * Get a state by it's url
+ * @param {string} stateUrl
+ */
+ _History.getStateByUrl = function(stateUrl){
+ var State = _History.statesByUrl[stateUrl]||undefined;
+ return State;
+ };
+
+ /**
+ * _History.getStateByHash
+ * Get a state by it's hash
+ * @param {string} stateHash
+ */
+ _History.getStateByHash = function(stateHash){
+ var State = _History.statesByHash[stateHash]||undefined;
+ return State;
+ };
+
+ /**
+ * _History.storeState
+ * Store a State
+ * @param {object} State
+ * @return {boolean} true
+ */
+ _History.storeState = function(newState){
+ // Prepare
+ var
+ newStateHash = History.contractState(newState),
+ oldState = _History.getStateByUrl(newState.url);
+
+ // Check for Conflict
+ if ( typeof oldState !== 'undefined' ) {
+ // Compare Hashes
+ var oldStateHash = History.contractState(oldState);
+ if ( oldStateHash !== newStateHash ) {
+ // We have a conflict
+ _History.duplicateStateUrls[newState.url] = true;
+ }
+ }
+
+ // Store the State
+ _History.statesByUrl[newState.url] = _History.statesByHash[newStateHash] = newState;
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * _History.isLastState(newState)
+ * Tests to see if the state is the last state
+ * @param {Object} newState
+ * @return {boolean} isLast
+ */
+ _History.isLastState = function(newState){
+ // Prepare
+ var
+ newStateHash = History.contractState(newState),
+ oldStateHash = History.getStateHash();
+
+ // Check
+ var isLast = _History.savedStates.length && newStateHash === oldStateHash;
+
+ // Return isLast
+ return isLast;
+ };
+
+ /**
+ * _History.saveState
+ * Push a State
+ * @param {Object} newState
+ * @return {boolean} changed
+ */
+ _History.saveState = function(newState){
+ // Check Hash
+ if ( _History.isLastState(newState) ) {
+ return false;
+ }
+
+ // Push the State
+ _History.savedStates.push(newState);
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * _History.getStateByIndex()
+ * Gets a state by the index
+ * @param {integer} index
+ * @return {Object}
+ */
+ _History.getStateByIndex = function(index){
+ // Prepare
+ var State = null;
+
+ // Handle
+ if ( typeof index === 'undefined' ) {
+ // Get the last inserted
+ State = _History.savedStates[_History.savedStates.length-1];
+ }
+ else if ( index < 0 ) {
+ // Get from the end
+ State = _History.savedStates[_History.savedStates.length+index];
+ }
+ else {
+ // Get from the beginning
+ State = _History.savedStates[index];
+ }
+
+ // Return State
+ return State;
+ };
+
+ /**
+ * _History.stateUrlExists
+ * Checks if the State Url Exists
+ * @param {string} stateUrl
+ * @return {boolean} exists
+ */
+ _History.stateUrlExists = function(stateUrl){
+ // Prepare
+ var exists = typeof _History.statesByUrl[stateUrl] !== 'undefined';
+
+ // Return exists
+ return exists;
+ };
+
+ /**
+ * _History.urlDuplicateExists
+ * Check if the url has multiple states associated to it
+ * @param {string} stateUrl
+ * @return {boolean} exists
+ */
+ _History.urlDuplicateExists = function(stateUrl){
+ var exists = typeof _History.duplicateStateUrls[stateUrl] !== 'undefined';
+ return exists;
+ };
+
+ // ----------------------------------------------------------------------
+ // Hash Logging
+
+ /**
+ * _History.savedHashes
+ * Store the hashes in an array
+ */
+ _History.savedHashes = [];
+
+ /**
+ * _History.isLastHash(newHash)
+ * Checks if the hash is the last hash
+ * @param {string} newHash
+ * @return {boolean} true
+ */
+ _History.isLastHash = function(newHash){
+ // Prepare
+ var oldHash = _History.getHashByIndex();
+
+ // Check
+ var isLast = newHash === oldHash;
+
+ // Return isLast
+ return isLast;
+ };
+
+ /**
+ * _History.saveHash(newHash)
+ * Push a Hash
+ * @param {string} newHash
+ * @return {boolean} true
+ */
+ _History.saveHash = function(newHash){
+ // Check Hash
+ if ( _History.isLastHash(newHash) ) {
+ return false;
+ }
+
+ // Push the Hash
+ _History.savedHashes.push(newHash);
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * _History.getHashByIndex()
+ * Gets a hash by the index
+ * @param {integer} index
+ * @return {string}
+ */
+ _History.getHashByIndex = function(index){
+ // Prepare
+ var hash = null;
+
+ // Handle
+ if ( typeof index === 'undefined' ) {
+ // Get the last inserted
+ hash = _History.savedHashes[_History.savedHashes.length-1];
+ }
+ else if ( index < 0 ) {
+ // Get from the end
+ hash = _History.savedHashes[_History.savedHashes.length+index];
+ }
+ else {
+ // Get from the beginning
+ hash = _History.savedHashes[index];
+ }
+
+ // Return hash
+ return hash;
+ };
+
+ /**
+ * _History.stateHashExists
+ * Checks if the State Hash Exists
+ * @param {string} stateHash
+ * @return {boolean} exists
+ */
+ _History.stateHashExists = function(stateHash){
+ // Prepare
+ var exists = typeof _History.statesByHash[stateHash] !== 'undefined';
+
+ // Return exists
+ return exists;
+ };
+
+
+ // ----------------------------------------------------------------------
+ // Discarded States
+
+ /**
+ * _History.discardedHashes
+ * A hashed array of discarded hashes
+ */
+ _History.discardedHashes = {};
+
+ /**
+ * _History.discardedStates
+ * A hashed array of discarded states
+ */
+ _History.discardedStates = {};
+
+ /**
+ * _History.discardState(State)
+ * Discards the state by ignoring it through History
+ * @param {object} State
+ * @return {true}
+ */
+ _History.discardState = function(discardedState,forwardState,backState){
+ History.debug('History.discardState',this,arguments);
+ // Prepare
+ var discardedStateHash = History.contractState(discardedState);
+
+ // Create Discard Object
+ var discardObject = {
+ 'discardedState': discardedState,
+ 'backState': backState,
+ 'forwardState': forwardState
+ };
+
+ // Add to DiscardedStates
+ _History.discardedStates[discardedStateHash] = discardObject;
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * _History.discardHash(hash)
+ * Discards the hash by ignoring it through History
+ * @param {string} hash
+ * @return {true}
+ */
+ _History.discardHash = function(discardedHash,forwardState,backState){
+ History.debug('History.discardState',this,arguments);
+ // Create Discard Object
+ var discardObject = {
+ 'discardedHash': discardedHash,
+ 'backState': backState,
+ 'forwardState': forwardState
+ };
+
+ // Add to discardedHash
+ _History.discardedHashes[discardedHash] = discardObject;
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * _History.discardState(State)
+ * Checks to see if the state is discarded
+ * @param {object} State
+ * @return {bool}
+ */
+ _History.discardedState = function(State){
+ // Prepare
+ var StateHash = History.contractState(State);
+
+ // Check
+ var discarded = _History.discardedStates[StateHash]||false;
+
+ // Return true
+ return discarded;
+ };
+
+ /**
+ * _History.discardedHash(hash)
+ * Checks to see if the state is discarded
+ * @param {string} State
+ * @return {bool}
+ */
+ _History.discardedHash = function(hash){
+ // Check
+ var discarded = _History.discardedHashes[hash]||false;
+
+ // Return true
+ return discarded;
+ };
+
+ /**
+ * _History.recycleState(State)
+ * Allows a discarded state to be used again
+ * @param {object} data
+ * @param {string} title
+ * @param {string} url
+ * @return {true}
+ */
+ _History.recycleState = function(State){
+ History.debug('History.recycleState',this,arguments);
+ // Prepare
+ var StateHash = History.contractState(State);
+
+ // Remove from DiscardedStates
+ if ( _History.discardedState(State) ) {
+ delete _History.discardedStates[StateHash];
+ }
+
+ // Return true
+ return true;
+ };
+
+
+ // ----------------------------------------------------------------------
+ // HTML4 HashChange Support
+
+ if ( History.emulated.hashChange ) {
+ /*
+ * We must emulate the HTML4 HashChange Support by manually checking for hash changes
+ */
+
+ (function(){
+ // Define our Checker Function
+ _History.checkerFunction = null;
+
+ // Handle depending on the browser
+ if ( _History.isInternetExplorer() ) {
+ // IE6 and IE7
+ // We need to use an iframe to emulate the back and forward buttons
+
+ // Create iFrame
+ var
+ iframeId = 'historyjs-iframe',
+ iframe = document.createElement('iframe');
+
+ // Adjust iFarme
+ iframe.setAttribute('id', iframeId);
+ iframe.style.display = 'none';
+
+ // Append iFrame
+ document.body.appendChild(iframe);
+
+ // Create initial history entry
+ iframe.contentWindow.document.open();
+ iframe.contentWindow.document.close();
+
+ // Define some variables that will help in our checker function
+ var
+ lastDocumentHash = null,
+ lastIframeHash = null,
+ checkerRunning = false;
+
+ // Define the checker function
+ _History.checkerFunction = function(){
+ // Check Running
+ if ( checkerRunning ) {
+ History.debug('hashchange.checker: checker is running');
+ return false;
+ }
+
+ // Update Running
+ checkerRunning = true;
+
+ // Fetch
+ var
+ documentHash = History.getHash(),
+ iframeHash = _History.unescapeHash(iframe.contentWindow.document.location.hash);
+
+ // The Document Hash has changed (application caused)
+ if ( documentHash !== lastDocumentHash ) {
+ // Equalise
+ lastDocumentHash = documentHash;
+
+ // Create a history entry in the iframe
+ if ( iframeHash !== documentHash ) {
+ History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
+
+ // Equalise
+ lastIframeHash = iframeHash = documentHash;
+
+ // Create History Entry
+ iframe.contentWindow.document.open();
+ iframe.contentWindow.document.close();
+
+ // Update the iframe's hash
+ iframe.contentWindow.document.location.hash = _History.escapeHash(documentHash);
+ }
+
+ // Trigger Hashchange Event
+ History.Adapter.trigger(window,'hashchange');
+ }
+
+ // The iFrame Hash has changed (back button caused)
+ else if ( iframeHash !== lastIframeHash ) {
+ History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
+
+ // Equalise
+ lastIframeHash = iframeHash;
+
+ // Update the Hash
+ History.setHash(iframeHash);
+ }
+
+ // Reset Running
+ checkerRunning = false;
+
+ // Return true
+ return true;
+ };
+ }
+ else {
+ // We are not IE
+ // Firefox 1 or 2, Opera
+
+ // Define some variables that will help in our checker function
+ var
+ lastDocumentHash = null;
+
+ // Define the checker function
+ _History.checkerFunction = function(){
+ // Prepare
+ var documentHash = History.getHash();
+
+ // The Document Hash has changed (application caused)
+ if ( documentHash !== lastDocumentHash ) {
+ // Equalise
+ lastDocumentHash = documentHash;
+
+ // Trigger Hashchange Event
+ History.Adapter.trigger(window,'hashchange');
+ }
+
+ // Return true
+ return true;
+ };
+ }
+
+ // Apply the checker function
+ setInterval(_History.checkerFunction, History.options.hashChangeCheckerDelay);
+
+ // Return true
+ return true;
+
+ })(); // closure
+
+ }
+
+ // ----------------------------------------------------------------------
+ // HTML5 State Support
+
+ if ( History.emulated.pushState ) {
+ /*
+ * We must emulate the HTML5 State Management by using HTML4 HashChange
+ */
+
+ /**
+ * _History.onHashChange(event)
+ * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
+ */
+ _History.onHashChange = function(event){
+ History.debug('_History.onHashChange',this,arguments);
+ // Prepare
+ var
+ currentUrl = (event && event.newURL) || document.location.href;
+ currentHash = unescape(History.extractHashFromUrl(currentUrl)),
+ currentState = null,
+ currentStateHash = null,
+ currentStateHashExits = null;
+
+ // Check if we are the same state
+ if ( _History.isLastHash(currentHash) ) {
+ // There has been no change (just the page's hash has finally propagated)
+ History.debug('_History.onHashChange: no change');
+ return false;
+ }
+
+ // Store our location for use in detecting back/forward direction
+ _History.saveHash(currentHash);
+
+ // Expand Hash
+ currentState = History.expandHash(currentHash);
+ if ( !currentState ) {
+ // Traditional Anchor Hash
+ History.debug('_History.onHashChange: traditional anchor');
+ History.Adapter.trigger('anchorchange');
+ return false;
+ }
+
+ // Check if we are the same state
+ if ( _History.isLastState(currentState) ) {
+ // There has been no change (just the page's hash has finally propagated)
+ History.debug('_History.onHashChange: no change');
+ return false;
+ }
+
+ // Create the state Hash
+ currentStateHash = History.contractState(currentState);
+
+ // Log
+ History.debug('_History.onHashChange: ',
+ 'currentStateHash',
+ currentStateHash,
+ 'Hash -1',
+ _History.getHashByIndex(-1),
+ 'Hash -2',
+ _History.getHashByIndex(-2),
+ 'Hash -3',
+ _History.getHashByIndex(-3),
+ 'Hash -4',
+ _History.getHashByIndex(-4),
+ 'Hash -5',
+ _History.getHashByIndex(-5),
+ 'Hash -6',
+ _History.getHashByIndex(-6),
+ 'Hash -7',
+ _History.getHashByIndex(-7)
+ );
+
+ // Check if we are DiscardedState
+ var discardObject = _History.discardedState(currentState);
+ if ( discardObject ) {
+ History.debug('forwardState:',History.contractState(discardObject.forwardState),'backState:',History.contractState(discardObject.backState));
+ // Ignore this state as it has been discarded and go back to the state before it
+ if ( _History.getHashByIndex(-2) === History.contractState(discardObject.forwardState) ) {
+ // We are going backwards
+ History.debug('_History.onHashChange: go backwards');
+ History.back();
+ } else {
+ // We are going forwards
+ History.debug('_History.onHashChange: go forwards');
+ History.forward();
+ }
+ return false;
+ }
+
+ // Push the new HTML5 State
+ History.debug('_History.onHashChange: success hashchange');
+ History.pushState(currentState.data,currentState.title,currentState.url);
+
+ // Return true
+ return true;
+ };
+ History.Adapter.bind(window,'hashchange',_History.onHashChange);
+
+ /**
+ * History.pushState(data,title,url)
+ * Add a new State to the history object, become it, and trigger onpopstate
+ * We have to trigger for HTML4 compatibility
+ * @param {object} data
+ * @param {string} title
+ * @param {string} url
+ * @return {true}
+ */
+ History.pushState = function(data,title,url){
+ History.debug('History.pushState',this,arguments);
+
+ // Check the State
+ if ( History.extractHashFromUrl(url) ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Fetch the State Object
+ var
+ newState = History.createStateObject(data,title,url),
+ newStateHash = History.contractState(newState),
+ oldState = History.getState(),
+ oldStateHash = History.getStateHash(),
+ html4Hash = unescape(History.getHash());
+
+ // Store the newState
+ _History.storeState(newState);
+
+ // Recycle the State
+ _History.recycleState(newState);
+
+ // Force update of the title
+ if ( newState.title ) {
+ document.title = newState.title
+ }
+
+ History.debug(
+ 'History.pushState: details',
+ 'newStateHash:', newStateHash,
+ 'oldStateHash:', oldStateHash,
+ 'html4Hash:', html4Hash
+ );
+
+ // Check if we are the same State
+ if ( newStateHash === oldStateHash ) {
+ History.debug('History.pushState: no change', newStateHash);
+ return false;
+ }
+
+ // Update HTML4 Hash
+ if ( newStateHash !== html4Hash ) {
+ History.debug('History.pushState: update hash', newStateHash);
+ History.setHash(newStateHash);
+ return false;
+ }
+
+ // Update HTML5 State
+ _History.saveState(newState);
+
+ // Fire HTML5 Event
+ History.debug('History.pushState: trigger popstate');
+ History.Adapter.trigger(window,'statechange');
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * History.replaceState(data,title,url)
+ * Replace the State and trigger onpopstate
+ * We have to trigger for HTML4 compatibility
+ * @param {object} data
+ * @param {string} title
+ * @param {string} url
+ * @return {true}
+ */
+ History.replaceState = function(data,title,url){
+ History.debug('History.replaceState',this,arguments);
+ // Check the State
+ if ( History.extractHashFromUrl(url) ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Fetch the State Objects
+ var
+ newState = History.createStateObject(data,title,url),
+ oldState = History.getState(),
+ previousState = _History.getStateByIndex(-2)
+
+ // Discard Old State
+ _History.discardState(oldState,newState,previousState);
+
+ // Alias to PushState
+ History.pushState(newState.data,newState.title,newState.url);
+
+ // Return true
+ return true;
+ };
+
+ /**
+ * Ensure initial state is handled correctly
+ **/
+ if ( !document.location.hash || document.location.hash === '#' ) {
+ History.Adapter.onDomLoad(function(){
+ History.debug('hash1');
+ var currentState = History.createStateObject({},'',document.location.href);
+ History.pushState(currentState.data,currentState.title,currentState.url);
+ });
+ } else if ( !History.emulated.hashChange ) {
+ History.debug('hash2');
+ History.Adapter.onDomLoad(function(){
+ _History.onHashChange();
+ });
+ }
+
+ }
+ else {
+
+ /**
+ * _History.onPopState(event,extra)
+ * Refresh the Current State
+ */
+ _History.onPopState = function(event){
+ History.debug('_History.onPopState',this,arguments);
+
+ // Check for a Hash, and handle apporiatly
+ var currentHash = unescape(History.getHash());
+ if ( currentHash ) {
+ // Expand Hash
+ var currentState = History.expandHash(currentHash);
+ if ( currentState ) {
+ // We were able to parse it, it must be a State!
+ // Let's forward to replaceState
+ History.debug('_History.onPopState: state anchor', currentHash, currentState);
+ History.replaceState(currentState.data, currentState.tite, currentState.url);
+ }
+ else {
+ // Traditional Anchor
+ History.debug('_History.onPopState: traditional anchor', currentHash);
+ History.Adapter.trigger(window,'anchorchange');
+ }
+
+ // We don't care for hashes
+ return false;
+ }
+
+ // Prepare
+ var
+ currentStateHashExits = null,
+ stateData = {},
+ stateTitle = null,
+ stateUrl = null,
+ newState = null;
+
+ // Prepare
+ event = event||{};
+ if ( typeof event.state === 'undefined' ) {
+ // jQuery
+ if ( typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.state !== 'undefined' ) {
+ event.state = event.originalEvent.state;
+ }
+ // MooTools
+ else if ( typeof event.event !== 'undefined' && typeof event.event.state !== 'undefined' ) {
+ event.state = event.event.state;
+ }
+ }
+
+ // Fetch Data
+ if ( event.state === null ) {
+ // Vanilla: State has no data (new state, not pushed)
+ stateData = event.state;
+ }
+ else if ( typeof event.state !== 'undefined' ) {
+ // Vanilla: Back/forward button was used
+
+ // Using Chrome Fix
+ var
+ newStateUrl = History.expandUrl(document.location.href),
+ oldState = _History.getStateByUrl(newStateUrl),
+ duplicateExists = _History.urlDuplicateExists(newStateUrl);
+
+ // Does oldState Exist?
+ if ( typeof oldState !== 'undefined' && !duplicateExists ) {
+ stateData = oldState.data;
+ }
+ else {
+ stateData = event.state;
+ }
+
+ // Use the way that should work
+ // stateData = event.state;
+ }
+ else {
+ // Vanilla: A new state was pushed, and popstate was called manually
+
+ // Get State object from the last state
+ var
+ newStateUrl = History.expandUrl(document.location.href),
+ oldState = _History.getStateByUrl(newStateUrl);
+
+ // Check if the URLs match
+ if ( newStateUrl == oldState.url ) {
+ stateData = oldState.data;
+ }
+ else {
+ throw new Error('Unknown state');
+ }
+ }
+
+ // Resolve newState
+ stateData = (typeof stateData !== 'object' || stateData === null) ? {} : stateData;
+ stateTitle = stateData.title||'',
+ stateUrl = stateData.url||document.location.href,
+ newState = History.createStateObject(stateData,stateTitle,stateUrl);
+
+ // Check if we are the same state
+ if ( _History.isLastState(newState) ) {
+ // There has been no change (just the page's hash has finally propagated)
+ History.debug('_History.onPopState: no change', newState, _History.savedStates);
+ return false;
+ }
+
+ // Log
+ History.debug(
+ '_History.onPopState',
+ 'newState:', newState,
+ 'oldState:', _History.getStateByUrl(History.expandUrl(document.location.href)),
+ 'duplicateExists:', _History.urlDuplicateExists(History.expandUrl(document.location.href))
+ );
+
+ // Store the State
+ _History.storeState(newState);
+ _History.saveState(newState);
+
+ // Force update of the title
+ if ( newState.title ) {
+ document.title = newState.title
+ }
+
+ // Fire Our Event
+ History.Adapter.trigger(window,'statechange');
+
+ // Return true
+ return true;
+ };
+ History.Adapter.bind(window,'popstate',_History.onPopState);
+
+ /**
+ * History.pushState(data,title,url)
+ * Add a new State to the history object, become it, and trigger onpopstate
+ * We have to trigger for HTML4 compatibility
+ * @param {object} data
+ * @param {string} title
+ * @param {string} url
+ * @return {true}
+ */
+ History.pushState = function(data,title,url){
+ // Check the State
+ if ( History.extractHashFromUrl(url) ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Create the newState
+ var newState = History.createStateObject(data,title,url);
+
+ // Store the newState
+ _History.storeState(newState);
+
+ // Push the newState
+ history.pushState(newState.data,newState.title,newState.url);
+
+ // Fire HTML5 Event
+ History.Adapter.trigger(window,'popstate');
+
+ // Return true
+ return true;
+ }
+
+ /**
+ * History.replaceState(data,title,url)
+ * Replace the State and trigger onpopstate
+ * We have to trigger for HTML4 compatibility
+ * @param {object} data
+ * @param {string} title
+ * @param {string} url
+ * @return {true}
+ */
+ History.replaceState = function(data,title,url){
+ // Check the State
+ if ( History.extractHashFromUrl(url) ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Create the newState
+ var newState = History.createStateObject(data,title,url);
+
+ // Store the newState
+ _History.storeState(newState);
+
+ // Push the newState
+ history.replaceState(newState.data,newState.title,newState.url);
+
+ // Fire HTML5 Event
+ History.Adapter.trigger(window,'popstate');
+
+ // Return true
+ return true;
+ }
+
+ }
+
+ // ----------------------------------------------------------------------
+ // HTML4 State Aliases
+ // We do not support go, as we cannot guarantee correct positioning due to discards
+
+ History.back = function(){
+ History.debug('History.back: called');
+
+ // Fix a bug in IE6
+ if ( History.emulated.hashChange && _History.isInternetExplorer() ) {
+ // Prepare
+ var currentHash = History.getHash();
+
+ // Apply Check
+ setTimeout(function(){
+ var newHash = History.getHash();
+ if ( newHash === currentHash ) {
+ // No change occurred, try again
+ History.debug('History.back: trying again');
+ return History.back();
+ }
+ return true;
+ },History.options.hashChangeCheckerDelay*2);
+ }
+
+ // Go back
+ return history.go(-1);
+ };
+
+ History.forward = function(){
+ History.debug('History.forward: called');
+
+ // Fix a bug in IE6
+ if ( History.emulated.hashChange && _History.isInternetExplorer() ) {
+ // Prepare
+ var currentHash = History.getHash();
+
+ // Apply Check
+ setTimeout(function(){
+ var newHash = History.getHash();
+ if ( newHash === currentHash ) {
+ // No change occurred, try again
+ History.debug('History.forward: trying again');
+ return History.forward();
+ }
+ return true;
+ },History.options.hashChangeCheckerDelay*2);
+ }
+
+ // Go forward
+ return history.go(1);
+ };
+
+ History.go = function(index){
+ History.debug('History.go: called with index ['+index+']');
+
+ // Handle
+ if ( index > 0 ) {
+ // Forward
+ for ( var i=1; i<=index; ++i ) {
+ var timeout = History.options.hashChangeCheckerDelay*20*i;
+ setTimeout(
+ function(){
+ History.debug('History.go: heading forward');
+ History.forward();
+ },
+ timeout
+ );
+ }
+ }
+ else if ( index < 0 ) {
+ // Backward
+ for ( var i=-1; i>=index; --i ) {
+ var timeout = History.options.hashChangeCheckerDelay*20*(i*-1);
+ setTimeout(
+ function(){
+ History.debug('History.go: heading back');
+ History.back();
+ },
+ timeout
+ );
+ }
+ }
+ else {
+ throw new Error('History.go: History.go requires a positive or negative integer passed.');
+ }
+
+ // Return true
+ return true;
+ };
+
+
+ History.debug('History.emulated: hashchange['+History.emulated.hashChange+'] pushstate['+History.emulated.pushState+']');
+
+ }; // init
+
+ // Check Load Status
+ if ( typeof History.Adapter !== 'undefined' ) {
+ // Adapter loaded faster than History.js, fire init.
+ History.init();
+ }
+
+})(window);
764 public/javascripts/jquery/address.js
View
@@ -1,764 +0,0 @@
-/*
- * jQuery Address Plugin v1.3.1
- * http://www.asual.com/jquery/address/
- *
- * Copyright (c) 2009-2010 Rostislav Hristov
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Date: 2010-11-29 11:54:20 +0200 (Mon, 29 Nov 2010)
- */
-(function ($) {
-
- $.address = (function () {
-
- var _trigger = function(name) {
- $($.address).trigger(
- $.extend($.Event(name),
- (function() {
- var parameters = {},
- parameterNames = $.address.parameterNames();
- for (var i = 0, l = parameterNames.length; i < l; i++) {
- parameters[parameterNames[i]] = $.address.parameter(parameterNames[i]);
- }
- return {
- value: $.address.value(),
- path: $.address.path(),
- pathNames: $.address.pathNames(),
- parameterNames: parameterNames,
- parameters: parameters,
- queryString: $.address.queryString()
- };
- }).call($.address)
- )
- );
- },
- _bind = function(value, data, fn) {
- $($.address).bind(value, data, fn);
- return $.address;
- },
- _supportsState = function() {
- return (_h.pushState && _opts.state !== UNDEFINED);
- },
- _hrefState = function() {
- return ('/' + _l.pathname.replace(new RegExp(_opts.state), '') +
- _l.search + (_hrefHash() ? '#' + _hrefHash() : '')).replace(_re, '/');
- },
- _hrefHash = function() {
- var index = _l.href.indexOf('#');
- return index != -1 ? _crawl(_l.href.substr(index + 1), FALSE) : '';
- },
- _href = function() {
- return _supportsState() ? _hrefState() : _hrefHash();
- },
- _window = function() {
- try {
- return top.document !== UNDEFINED ? top : window;
- } catch (e) {
- return window;
- }
- },
- _js = function() {
- return 'javascript';
- },
- _strict = function(value) {
- value = value.toString();
- return (_opts.strict && value.substr(0, 1) != '/' ? '/' : '') + value;
- },
- _crawl = function(value, direction) {
- if (_opts.crawlable && direction) {
- return (value != '' ? '!' : '') + value;
- }
- return value.replace(/^\!/, '');
- },
- _cssint = function(el, value) {
- return parseInt(el.css(value), 10);
- },
- _search = function(el) {
- var url, s;
- for (var i = 0, l = el.childNodes.length; i < l; i++) {
- if (el.childNodes[i].src) {
- url = String(el.childNodes[i].src);
- }
- s = _search(el.childNodes[i]);
- if (s) {
- url = s;
- }
- }
- return url;
- },
- _listen = function() {
- if (!_silent) {
- var hash = _href(),
- diff = _value != hash;
- if (_webkit && _version < 523) {
- if (_length != _h.length) {
- _length = _h.length;
- if (_stack[_length - 1] !== UNDEFINED) {
- _value = _stack[_length - 1];
- }
- _update(FALSE);
- }
- } else if (diff) {
- if (_msie && _version < 7) {
- _l.reload();
- } else {
- if (_msie && _version < 8 && _opts.history) {
- _st(_html, 50);
- }
- _value = hash;
- _update(FALSE);
- }
- }
- }
- },
- _update = function(internal) {
- _trigger(CHANGE);
- _trigger(internal ? INTERNAL_CHANGE : EXTERNAL_CHANGE);
- _st(_track, 10);
- },
- _track = function() {
- if (_opts.tracker !== 'null' && _opts.tracker !== null) {
- var fn = $.isFunction(_opts.tracker) ? _opts.tracker : _t[_opts.tracker],
- value = (_l.pathname + _l.search +
- ($.address && !_supportsState() ? $.address.value() : ''))
- .replace(/\/\//, '/').replace(/^\/$/, '');
- if ($.isFunction(fn)) {
- fn(value);
- } else if ($.isFunction(_t.urchinTracker)) {
- _t.urchinTracker(value);
- } else if (_t.pageTracker !== UNDEFINED && $.isFunction(_t.pageTracker._trackPageview)) {
- _t.pageTracker._trackPageview(value);
- } else if (_t._gaq !== UNDEFINED && $.isFunction(_t._gaq.push)) {
- _t._gaq.push(['_trackPageview', value]);
- }
- }
- },
- _html = function() {
- var src = _js() + ':' + FALSE + ';document.open();document.writeln(\'<html><head><title>' +
- _d.title.replace('\'', '\\\'') + '</title><script>var ' + ID + ' = "' + encodeURIComponent(_href()) +
- (_d.domain != _l.host ? '";document.domain="' + _d.domain : '') +
- '";</' + 'script></head></html>\');document.close();';
- if (_version < 7) {
- _frame.src = src;
- } else {
- _frame.contentWindow.location.replace(src);
- }
- },
- _options = function() {
- if (_url && _qi != -1) {
- var param, params = _url.substr(_qi + 1).split('&');
- for (i = 0; i < params.length; i++) {
- param = params[i].split('=');
- if (/^(autoUpdate|crawlable|history|strict|wrap)$/.test(param[0])) {
- _opts[param[0]] = (isNaN(param[1]) ? /^(true|yes)$/i.test(param[1]) : (parseInt(param[1], 10) !== 0));
- }
- if (/^(state|tracker)$/.test(param[0])) {
- _opts[param[0]] = param[1];
- }
- }
- _url = null;
- }
- _value = _href();
- },
- _load = function() {
- if (!_loaded) {
- _loaded = TRUE;
- _options();
- var complete = function() {
- _enable.call(this);
- _unescape.call(this);
- },
- body = $('body').ajaxComplete(complete);
- complete();
- if (_opts.wrap) {
- var wrap = $('body > *')
- .wrapAll('<div style="padding:' +
- (_cssint(body, 'marginTop') + _cssint(body, 'paddingTop')) + 'px ' +
- (_cssint(body, 'marginRight') + _cssint(body, 'paddingRight')) + 'px ' +
- (_cssint(body, 'marginBottom') + _cssint(body, 'paddingBottom')) + 'px ' +
- (_cssint(body, 'marginLeft') + _cssint(body, 'paddingLeft')) + 'px;" />')
- .parent()
- .wrap('<div id="' + ID + '" style="height:100%;overflow:auto;position:relative;' +
- (_webkit ? (window.statusbar.visible && !/chrome/i.test(_agent) ? '' : 'resize:both;') : '') + '" />');
- $('html, body')
- .css({
- height: '100%',
- margin: 0,
- padding: 0,
- overflow: 'hidden'
- });
- if (_webkit) {
- $('<style type="text/css" />')
- .appendTo('head')
- .text('#' + ID + '::-webkit-resizer { background-color: #fff; }');
- }
- }
- if (_msie && _version < 8) {
- var frameset = _d.getElementsByTagName('frameset')[0];
- _frame = _d.createElement((frameset ? '' : 'i') + 'frame');
- if (frameset) {
- frameset.insertAdjacentElement('beforeEnd', _frame);
- frameset[frameset.cols ? 'cols' : 'rows'] += ',0';
- _frame.noResize = TRUE;
- _frame.frameBorder = _frame.frameSpacing = 0;
- } else {
- _frame.style.display = 'none';
- _frame.style.width = _frame.style.height = 0;
- _frame.tabIndex = -1;
- _d.body.insertAdjacentElement('afterBegin', _frame);
- }
- _st(function() {
- $(_frame).bind('load', function() {
- var win = _frame.contentWindow;
- _value = win[ID] !== UNDEFINED ? win[ID] : '';
- if (_value != _href()) {
- _update(FALSE);
- _l.hash = _crawl(_value, TRUE);
- }
- });
- if (_frame.contentWindow[ID] === UNDEFINED) {
- _html();
- }
- }, 50);
- } else if (_webkit) {
- if (_version < 418) {
- $(_d.body).append('<form id="' + ID + '" style="position:absolute;top:-9999px;" method="get"></form>');
- _form = _d.getElementById(ID);
- }
- if (_l[ID] === UNDEFINED) {
- _l[ID] = {};
- }
- if (_l[ID][_l.pathname] !== UNDEFINED) {
- _stack = _l[ID][_l.pathname].split(',');
- }
- }
-
- _st(function() {
- _trigger('init');
- _update(FALSE);
- }, 1);
-
- if (!_supportsState()) {
- if ((_msie && _version > 7) || (!_msie && ('on' + HASH_CHANGE) in _t)) {
- if (_t.addEventListener) {
- _t.addEventListener(HASH_CHANGE, _listen, FALSE);
- } else if (_t.attachEvent) {
- _t.attachEvent('on' + HASH_CHANGE, _listen);
- }
- } else {
- _si(_listen, 50);
- }
- }
- }
- },
- _enable = function() {
- var el,
- elements = $('a'),
- length = elements.size(),
- delay = 1,
- index = -1;
- _st(function() {
- if (++index != length) {
- el = $(elements.get(index));
- if (el.is('[rel*=address:]')) {
- el.address();
- }
- _st(arguments.callee, delay);
- }
- }, delay);
- },
- _popstate = function() {
- if (_value != _href()) {
- _value = _href();
- _update(FALSE);
- }
- },
- _unload = function() {
- if (_t.removeEventListener) {
- _t.removeEventListener(HASH_CHANGE, _listen, FALSE);
- } else if (_t.detachEvent) {
- _t.detachEvent('on' + HASH_CHANGE, _listen);
- }
- },
- _unescape = function() {
- if (_opts.crawlable) {
- var base = _l.pathname.replace(/\/$/, ''),
- fragment = '_escaped_fragment_';
- if ($('body').html().indexOf(fragment) != -1) {
- $('a[href]:not([href^=http]), , a[href*=' + document.domain + ']').each(function() {
- var href = $(this).attr('href').replace(/^http:/, '').replace(new RegExp(base + '/?$'), '');
- if (href == '' || href.indexOf(fragment) != -1) {
- $(this).attr('href', '#' + $.address.decode(href.replace(new RegExp('/(.*)\\?' + fragment + '=(.*)$'), '!$2')));
- }
- });
- }
- }
- },
- _encode = function(value) {
- return _ec(_dc(value)).replace(/%20/g, '+');
- },
- _path = function(value) {
- return value.split('#')[0].split('?')[0];
- },
- _pathNames = function(value) {
- var path = _path(value),
- names = path.replace(_re, '/').split('/');
- if (path.substr(0, 1) == '/' || path.length === 0) {
- names.splice(0, 1);
- }
- if (path.substr(path.length - 1, 1) == '/') {
- names.splice(names.length - 1, 1);
- }
- return names;
- },
- _queryString = function(value) {
- var arr = value.split('?');
- return arr.slice(1, arr.length).join('?').split('#')[0];
- },
- _parameter = function(name, value) {
- value = _queryString(value);
- if (value) {
- params = value.split('&');
- var r = [];
- for (i = 0; i < params.length; i++) {
- var p = params[i].split('=');
- if (p[0] == name || $.address.decode(p[0]) == name) {
- r.push(p.slice(1).join('='));
- }
- }
- if (r.length !== 0) {
- return r.length != 1 ? r : r[0];
- }
- }
- },
- _parameterNames = function(value) {
- var qs = _queryString(value),
- names = [];
- if (qs && qs.indexOf('=') != -1) {
- var params = qs.split('&');
- for (var i = 0; i < params.length; i++) {
- var name = params[i].split('=')[0];
- if ($.inArray(name, names) == -1) {
- names.push(name);
- }
- }
- }
- return names;
- },
- _hash = function(value) {
- var arr = value.split('#');
- return arr.slice(1, arr.length).join('#');
- },
- UNDEFINED,
- ID = 'jQueryAddress',
- STRING = 'string',
- HASH_CHANGE = 'hashchange',
- INIT = 'init',
- CHANGE = 'change',
- INTERNAL_CHANGE = 'internalChange',
- EXTERNAL_CHANGE = 'externalChange',
- TRUE = true,
- FALSE = false,
- _opts = {
- autoUpdate: TRUE,
- crawlable: FALSE,
- history: TRUE,
- strict: TRUE,
- wrap: FALSE,
- state: ''
- },
- _browser = $.browser,
- _version = parseFloat($.browser.version),
- _mozilla = _browser.mozilla,
- _msie = _browser.msie,
- _opera = _browser.opera,
- _webkit = _browser.webkit || _browser.safari,
- _supported = FALSE,
- _t = _window(),
- _d = _t.document,
- _h = _t.history,
- _l = _t.location,
- _si = setInterval,
- _st = setTimeout,
- _ec = encodeURIComponent,
- _dc = decodeURIComponent,
- _re = /\/{2,9}/g,
- _agent = navigator.userAgent,
- _frame,
- _form,
- _url = _search(document),
- _qi = _url ? _url.indexOf('?') : -1,
- _title = _d.title,
- _length = _h.length,
- _silent = FALSE,
- _loaded = FALSE,
- _justset = TRUE,
- _juststart = TRUE,
- _updating = FALSE,
- _stack = [],
- _listeners = {},
- _value = _href();
-
- if (_msie) {
- _version = parseFloat(_agent.substr(_agent.indexOf('MSIE') + 4));
- if (_d.documentMode && _d.documentMode != _version) {
- _version = _d.documentMode != 8 ? 7 : 8;
- }
- $(document).bind('propertychange', function() {
- if (_d.title != _title && _d.title.indexOf('#' + _href()) != -1) {
- _d.title = _title;
- }
- });
- }
-
- _supported =
- (_mozilla && _version >= 1) ||
- (_msie && _version >= 6) ||
- (_opera && _version >= 9.5) ||
- (_webkit && _version >= 312);
-
- if (_supported) {
- for (var i = 1; i < _length; i++) {
- _stack.push('');
- }
- _stack.push(_value);
- if (_opera) {
- history.navigationMode = 'compatible';
- }
- if (document.readyState == 'complete') {
- var interval = setInterval(function() {
- if ($.address) {
- _load();
- clearInterval(interval);
- }
- }, 50);
- } else {
- _options();
- $(_load);
- }
- var hrefState = _hrefState();
- if (_opts.state !== UNDEFINED) {
- if (_h.pushState) {
- if (hrefState.substr(0, 3) == '/#/') {
- _l.replace(_opts.state.replace(/^\/$/, '') + hrefState.substr(2));
- }
- } else if (hrefState != '/' && hrefState.replace(/^\/#/, '') != _hrefHash()) {
- _l.replace(_opts.state.replace(/^\/$/, '') + '/#' + hrefState);
- }
- }
- $(window).bind('popstate', _popstate).bind('unload', _unload);
- } else if ((!_supported && _hrefHash() != '') ||
- (_webkit && _version < 418 && _hrefHash() != '' && _l.search != '')) {
- _l.replace(_l.href.substr(0, _l.href.indexOf('#')));
- } else {
- _track();
- }
-
- return {
- bind: function(type, data, fn) {
- return _bind(type, data, fn);
- },
- init: function(fn) {
- return _bind(INIT, fn);
- },
- change: function(fn) {
- return _bind(CHANGE, fn);
- },
- internalChange: function(fn) {
- return _bind(INTERNAL_CHANGE, fn);
- },
- externalChange: function(fn) {
- return _bind(EXTERNAL_CHANGE, fn);
- },
- baseURL: function() {
- var url = _l.href;
- if (url.indexOf('#') != -1) {
- url = url.substr(0, url.indexOf('#'));
- }
- if (/\/$/.test(url)) {
- url = url.substr(0, url.length - 1);
- }
- return url;
- },
- autoUpdate: function(value) {
- if (value !== UNDEFINED) {
- _opts.autoUpdate = value;
- return this;
- }
- return _opts.autoUpdate;
- },
- crawlable: function(value) {
- if (value !== UNDEFINED) {
- _opts.crawlable = value;
- return this;
- }
- return _opts.crawlable;
- },
- history: function(value) {
- if (value !== UNDEFINED) {
- _opts.history = value;
- return this;
- }
- return _opts.history;
- },
- state: function(value) {
- if (value !== UNDEFINED) {
- _opts.state = value;
- return this;
- }
- return _opts.state;
- },
- strict: function(value) {
- if (value !== UNDEFINED) {
- _opts.strict = value;
- return this;
- }
- return _opts.strict;
- },
- tracker: function(value) {
- if (value !== UNDEFINED) {
- _opts.tracker = value;
- return this;
- }
- return _opts.tracker;
- },
- wrap: function(value) {
- if (value !== UNDEFINED) {
- _opts.wrap = value;
- return this;
- }
- return _opts.wrap;
- },
- update: function() {
- _updating = TRUE;
- this.value(_value);
- _updating = FALSE;
- return this;
- },
- encode: function(value) {
- var pathNames = _pathNames(value),
- parameterNames = _parameterNames(value),
- queryString = _queryString(value),
- hash = _hash(value),
- first = value.substr(0, 1),
- last = value.substr(value.length - 1),
- encoded = '';
- $.each(pathNames, function(i, v) {
- encoded += '/' + _encode(v);
- });
- if (queryString !== '') {
- encoded += '?';
- if (parameterNames.length === 0) {
- encoded += queryString;
- } else {
- $.each(parameterNames, function(i, v) {
- var pv = _parameter(v, value);
- if (typeof pv !== STRING) {
- $.each(pv, function(ni, nv) {
- encoded += _encode(v) + '=' + _encode(nv) + '&';
- });
- } else {
- encoded += _encode(v) + '=' + _encode(pv) + '&';
- }
- });
- encoded = encoded.substr(0, encoded.length - 1);
- }
- }
- if (hash !== '') {
- encoded += '#' + _encode(hash);
- }
- if (first != '/' && encoded.substr(0, 1) == '/') {
- encoded = encoded.substr(1);
- }
- if (first == '/' && encoded.substr(0, 1) != '/') {
- encoded = '/' + encoded;
- }
- if (/#|&|\?/.test(last)) {
- encoded += last;
- }
- return encoded;
- },
- decode: function(value) {
- if (value !== UNDEFINED) {
- var result = [],
- replace = function(value) {
- return _dc(value.toString().replace(/\+/g, '%20'));
- };
- if (typeof value == 'object' && value.length !== UNDEFINED) {
- for (var i = 0, l = value.length; i < l; i++) {
- result[i] = replace(value[i]);
- }
- return result;
- } else {
- return replace(value);
- }
- }
- },
- title: function(value) {
- if (value !== UNDEFINED) {
- _st(function() {
- _title = _d.title = value;
- if (_juststart && _frame && _frame.contentWindow && _frame.contentWindow.document) {
- _frame.contentWindow.document.title = value;
- _juststart = FALSE;
- }
- if (!_justset && _mozilla) {
- _l.replace(_l.href.indexOf('#') != -1 ? _l.href : _l.href + '#');
- }
- _justset = FALSE;
- }, 50);
- return this;
- }
- return _d.title;
- },
- value: function(value) {
- if (value !== UNDEFINED) {
- value = this.encode(_strict(value));
- if (value == '/') {
- value = '';
- }
- if (_value == value && !_updating) {
- return;
- }
- _justset = TRUE;
- _value = value;
- if (_opts.autoUpdate || _updating) {
- _update(TRUE);
- if (_supportsState()) {
- _h[_opts.history ? 'pushState' : 'replaceState']({}, '',
- _opts.state.replace(/\/$/, '') + (_value == '' ? '/' : _value));
- } else {
- _silent = TRUE;
- _stack[_h.length] = _value;
- if (_webkit) {
- if (_opts.history) {
- _l[ID][_l.pathname] = _stack.toString();
- _length = _h.length + 1;
- if (_version < 418) {
- if (_l.search == '') {
- _form.action = '#' + _crawl(_value, TRUE);
- _form.submit();
- }
- } else if (_version < 523 || _value == '') {
- var evt = _d.createEvent('MouseEvents');
- evt.initEvent('click', TRUE, TRUE);
- var anchor = _d.createElement('a');
- anchor.href = '#' + _crawl(_value, TRUE);
- anchor.dispatchEvent(evt);
- } else {
- _l.hash = '#' + _crawl(_value, TRUE);
- }
- } else {
- _l.replace('#' + _crawl(_value, TRUE));
- }
- } else if (_value != _href()) {
- if (_opts.history) {
- _l.hash = '#' + _crawl(_value, TRUE);
- } else {
- _l.replace('#' + _crawl(_value, TRUE));
- }
- }
- if ((_msie && _version < 8) && _opts.history) {
- _st(_html, 50);
- }
- if (_webkit) {
- _st(function(){ _silent = FALSE; }, 1);
- } else {
- _silent = FALSE;
- }
- }
- }
- return this;
- }
- if (!_supported) {
- return null;
- }
- return this.decode(_strict(_value));
- },
- path: function(value) {
- if (value !== UNDEFINED) {
- var qs = _queryString(_strict(_value)),
- hash = _hash(_strict(_value));
- this.value(value + (qs ? '?' + qs : '') + (hash ? '#' + hash : ''));
- return this;
- }
- return this.decode(_path(_strict(_value)));
- },
- pathNames: function() {
- return this.decode(_pathNames(_strict(_value)));
- },
- queryString: function(value) {
- if (value !== UNDEFINED) {
- var hash = _hash(_strict(_value));
- this.value(this.path() + (value ? '?' + value : '') + (hash ? '#' + hash : ''));
- return this;
- }
- return this.decode(_queryString(_strict(_value)));
- },
- parameter: function(name, value, append) {
- var i, params;
- if (value !== UNDEFINED) {
- var names = this.parameterNames();
- params = [];
- value = value ? _ec(value) : '';
- for (i = 0; i < names.length; i++) {
- var n = names[i],
- v = this.parameter(n);
- if (typeof v == STRING) {
- v = [v];
- }
- if (n == name) {
- v = (value === null || value === '') ? [] :
- (append ? v.concat([value]) : [value]);
- }
- for (var j = 0; j < v.length; j++) {
- params.push(n + '=' + _encode(v[j]));
- }
- }
- if ($.inArray(name, names) == -1 && value !== null && value !== '') {
- params.push(name + '=' + _encode(value));
- }
- this.queryString(params.join('&'));
- return this;
- }
- return this.decode(_parameter(name, _strict(_value)));
- },
- parameterNames: function() {
- return this.decode(_parameterNames(_strict(_value)));
- },
- hash: function(value) {
- if (value !== UNDEFINED) {
- this.value(_strict(_value).split('#')[0] + (value ? '#' + value : ''));
- return this;
- }
- return this.decode(_hash(_strict(_value)));
- }
- };
- })();
-
- $.fn.address = function(fn) {
- if (!$(this).attr('address')) {
- var f = function(e) {
- if ($(this).is('a')) {
- var value = fn ? fn.call(this) :
- /address:/.test($(this).attr('rel')) ? $(this).attr('rel').split('address:')[1].split(' ')[0] :
- $.address.state() !== undefined && $.address.state() != '/' ?
- $(this).attr('href').replace(new RegExp('^(.*' + $.address.state() + '|\\.)'), '') :
- $(this).attr('href').replace(/^(#\!?|\.)/, '');
- $.address.value(value);
- e.preventDefault();
- }
- };
- $(this).click(f).live('click', f).submit(function(e) {
- if ($(this).is('form')) {
- var value = fn ? fn.call(this) : $(this).attr('action') + '?' + $.address.decode($(this).serialize());
- $.address.value(value);
- e.preventDefault();
- }
- }).attr('address', true);
- }
- return this;
- };
-
-}(jQuery));
480 public/javascripts/json2.js
View
@@ -0,0 +1,480 @@
+/*
+ http://www.JSON.org/json2.js
+ 2011-01-18
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, strict: false, regexp: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+var JSON;
+if (!JSON) {
+ JSON = {};
+}
+
+(function () {
+ "use strict";
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+