Skip to content

Instantly share code, notes, and snippets.

@rgrove
Created August 11, 2010 16:44
Show Gist options
  • Save rgrove/519292 to your computer and use it in GitHub Desktop.
Save rgrove/519292 to your computer and use it in GitHub Desktop.
History Lite for YUI 2.
/**
* Lightweight history utility for YUI 2. Similar in purpose to the YUI Browser
* History Manager, but with a more flexible API, no initialization requirement,
* no IE6/7 support, and a much smaller footprint.
*
* @module history-lite
* @requires YAHOO, Event
* @class HistoryLite
* @static
*/
(function () {
var d = document,
w = window,
Y = YAHOO,
yut = Y.util,
yue = yut.Event,
ua = Y.env.ua,
encode = encodeURIComponent,
lastHash,
pollInterval,
self;
// -- Private Methods ------------------------------------------------------
/**
* Creates a hash string from the specified object of name/value parameter
* pairs.
*
* @method createHash
* @param {Object} params name/value parameter pairs
* @return {String} hash string
* @private
*/
function createHash(params) {
var hash = [],
name, value;
for (name in params) {
if (params.hasOwnProperty(name)) {
value = params[name];
if (Y.lang.isValue(value)) {
hash.push(encode(name) + '=' + encode(value));
}
}
}
return hash.join('&');
}
/**
* Wrapper around <code>decodeURIComponent()</code> that also converts +
* chars into spaces.
*
* @method decode
* @param {String} string string to decode
* @return {String} decoded string
* @private
*/
function decode(string) {
return decodeURIComponent(string.replace(/\+/g, ' '));
}
/**
* Gets the current URL hash.
*
* @method getHash
* @return {String}
* @private
*/
var getHash;
if (Y.env.ua.gecko) {
// We branch at runtime for Gecko since window.location.hash in Gecko
// returns a decoded string, and we want all encoding untouched.
getHash = function () {
var matches = /#.*$/.exec(w.location.href);
return matches && matches[0] ? matches[0] : '';
};
} else {
getHash = function () {
return w.location.hash;
};
}
/**
* Sets the browser's location hash to the specified string.
*
* @method setHash
* @param {String} hash
* @private
*/
function setHash(hash) {
w.location.hash = hash;
}
/**
* Begins polling to check for location hash changes.
*
* @method startPolling
* @private
*/
function startPolling() {
lastHash = getHash();
// IE8 supports the hashchange event, but only in IE8 Standards
// Mode. However, IE8 in IE7 compatibility mode still defines the
// event (but never fires it), so we can't just sniff for the event. We
// also can't just sniff for IE8, since other browsers will eventually
// support this event as well. Thanks Microsoft!
if ('onhashchange' in w && (!d.documentMode || d.documentMode > 7)) {
yue.on(w, 'hashchange', function () {
handleHashChange(getHash());
});
} else {
pollInterval = pollInterval || setInterval(function () {
var hash = getHash();
if (hash !== lastHash) {
handleHashChange(hash);
}
}, 50);
}
}
// -- Private Event Handlers -----------------------------------------------
/**
* Handles changes to the location hash and fires the onChange event if
* necessary.
*
* @method handleHashChange
* @param {String} newHash new hash value
* @private
*/
function handleHashChange(newHash) {
var changedParams = {},
lastParsed = self.parseQuery(lastHash),
newParsed = self.parseQuery(newHash),
removedParams = {},
isChanged, name;
// Figure out what changed.
for (name in newParsed) {
if (newParsed.hasOwnProperty(name) &&
lastParsed[name] !== newParsed[name]) {
changedParams[name] = newParsed[name];
isChanged = true;
}
}
// Figure out what was removed.
for (name in lastParsed) {
if (lastParsed.hasOwnProperty(name) &&
!newParsed.hasOwnProperty(name)) {
removedParams[name] = lastParsed[name];
isChanged = true;
}
}
if (isChanged) {
lastHash = newHash;
self.onChange.fire(changedParams, removedParams);
}
}
Y.HistoryLite = self = {
// -- Public Events ----------------------------------------------------
/**
* Fired when the history state changes.
*
* @event onChange
* @param {Object} changedParams name/value pairs of history parameters
* that have been added or changed
* @param {Object} removedParams name/value pairs of history parameters
* that have been removed (values are the old values)
*/
onChange: new yut.CustomEvent('browserHistoryChange'),
// -- Public Methods ---------------------------------------------------
/**
* Adds a history entry with changes to the specified parameters. Any
* parameters with a <code>null</code> or <code>undefined</code> value
* will be removed from the new history entry.
*
* @method add
* @param {String|Object} params query string, hash string, or object
* containing name/value parameter pairs
* @param {Boolean} quiet if <em>true</em>, a history change event will
* not be fired for this change
*/
add: function (params, quiet) {
var newHash = createHash(Y.lang.merge(self.parseQuery(getHash()),
Y.lang.isString(params) ? self.parseQuery(params) : params));
if (quiet) {
lastHash = newHash;
}
setHash(newHash);
},
/**
* Gets the current value of the specified history parameter, or an
* object of name/value pairs for all current values if no parameter
* name is specified.
*
* @method get
* @param {String} name (optional) parameter name
* @return {Object|mixed}
*/
get: function (name) {
var params = self.parseQuery(getHash());
return name ? params[name] : params;
},
/**
* Parses a query string or hash string into an object of name/value
* parameter pairs.
*
* @method parseQuery
* @param {String} query query string or hash string
* @return {Object}
*/
parseQuery: function (query) {
var matches = query.match(/([^\?#&]+)=([^&]+)/g) || [],
params = {},
i, len, param;
for (i = 0, len = matches.length; i < len; ++i) {
param = matches[i].split('=');
params[decode(param[0])] = decode(param[1]);
}
return params;
}
};
startPolling();
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment