diff --git a/scripts/bundled/html4+html5/dojo.history.js b/scripts/bundled/html4+html5/dojo.history.js
new file mode 100644
index 00000000..fe8241e2
--- /dev/null
+++ b/scripts/bundled/html4+html5/dojo.history.js
@@ -0,0 +1,3125 @@
+/*
+ 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 ' '),
+ 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.
+
+if (!window.JSON) {
+ window.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 JSON = window.JSON,
+ 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;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' : gap ?
+ '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' : gap ?
+ '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
+ '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
+/**
+ * History.js Dojo Adapter
+ * @author Lakin Wecker
+ * @copyright 2012-2012 Lakin Wecker
+ * @license New BSD License
+ */
+
+require(["dojo/on", "dojo/ready", "dojo/_base/lang"], function(on,ready, lang) {
+ "use strict";
+
+ // Localise Globals
+ var
+ History = window.History = window.History||{};
+
+ // Check Existence
+ if ( typeof History.Adapter !== 'undefined' ) {
+ throw new Error('History.js Adapter has already been loaded...');
+ }
+
+ // Add the Adapter
+ History.Adapter = {
+ /**
+ * History.Adapter.bind(el,event,callback)
+ * @param {Element|string} el
+ * @param {string} event - custom and standard events
+ * @param {function} callback
+ * @return {void}
+ */
+ bind: function(el,event,callback){
+ on(el,event,callback);
+ },
+
+ /**
+ * History.Adapter.trigger(el,event)
+ * @param {Element|string} el
+ * @param {string} event - custom and standard events
+ * @param {Object=} extra - a object of extra event data (optional)
+ * @return {void}
+ */
+ trigger: function(el,event,extra){
+ extra = extra || {};
+ lang.mixin(extra, {
+ bubbles: true,
+ cancelable: true
+ });
+
+ on.emit(el,event,extra);
+ },
+
+ /**
+ * History.Adapter.extractEventData(key,event,extra)
+ * @param {string} key - key for the event data to extract
+ * @param {string} event - custom and standard events
+ * @param {Object=} extra - a object of extra event data (optional)
+ * @return {mixed}
+ */
+ extractEventData: function(key,event,extra){
+ // dojo Native then dojo Custom
+ var result = (event && event[key]) || (extra && extra[key]) || undefined;
+
+ // Return
+ return result;
+ },
+
+ /**
+ * History.Adapter.onDomLoad(callback)
+ * @param {function} callback
+ * @return {void}
+ */
+ onDomLoad: function(callback) {
+ ready(callback);
+ }
+ };
+
+ // Try and Initialise History
+ if ( typeof History.init !== 'undefined' ) {
+ History.init();
+ }
+
+});
+
+/**
+ * History.js HTML4 Support
+ * Depends on the HTML5 Support
+ * @author Benjamin Arthur Lupton
+ * @copyright 2010-2011 Benjamin Arthur Lupton
+ * @license New BSD License
+ */
+
+(function(window,undefined){
+ "use strict";
+
+ // ========================================================================
+ // Initialise
+
+ // Localise Globals
+ var
+ document = window.document, // Make sure we are using the correct document
+ setTimeout = window.setTimeout||setTimeout,
+ clearTimeout = window.clearTimeout||clearTimeout,
+ setInterval = window.setInterval||setInterval,
+ History = window.History = window.History||{}; // Public History Object
+
+ // Check Existence
+ if ( typeof History.initHtml4 !== 'undefined' ) {
+ throw new Error('History.js HTML4 Support has already been loaded...');
+ }
+
+
+ // ========================================================================
+ // Initialise HTML4 Support
+
+ // Initialise HTML4 Support
+ History.initHtml4 = function(){
+ // Initialise
+ if ( typeof History.initHtml4.initialized !== 'undefined' ) {
+ // Already Loaded
+ return false;
+ }
+ else {
+ History.initHtml4.initialized = true;
+ }
+
+
+ // ====================================================================
+ // Properties
+
+ /**
+ * History.enabled
+ * Is History enabled?
+ */
+ History.enabled = true;
+
+
+ // ====================================================================
+ // Hash Storage
+
+ /**
+ * 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(),
+ isLast;
+
+ // Check
+ 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;
+ };
+
+
+ // ====================================================================
+ // 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', arguments);
+ // Prepare
+ var discardedStateHash = History.getHashByState(discardedState),
+ discardObject;
+
+ // Create Discard Object
+ 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', 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.getHashByState(State),
+ discarded;
+
+ // Check
+ 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', arguments);
+ // Prepare
+ var StateHash = History.getHashByState(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
+ */
+
+ /**
+ * History.hashChangeInit()
+ * Init the HashChange Emulation
+ */
+ History.hashChangeInit = function(){
+ // Define our Checker Function
+ History.checkerFunction = null;
+
+ // Define some variables that will help in our checker function
+ var lastDocumentHash = '',
+ iframeId, iframe,
+ lastIframeHash, checkerRunning;
+
+ // 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
+ 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
+ lastIframeHash = '';
+ checkerRunning = false;
+
+ // Define the checker function
+ History.checkerFunction = function(){
+ // Check Running
+ if ( checkerRunning ) {
+ 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,false);
+ }
+
+ // Reset Running
+ checkerRunning = false;
+
+ // Return true
+ return true;
+ };
+ }
+ else {
+ // We are not IE
+ // Firefox 1 or 2, Opera
+
+ // 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
+ History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
+
+ // Done
+ return true;
+ }; // History.hashChangeInit
+
+ // Bind hashChangeInit
+ History.Adapter.onDomLoad(History.hashChangeInit);
+
+ } // History.emulated.hashChange
+
+
+ // ====================================================================
+ // HTML5 State Support
+
+ // Non-Native pushState Implementation
+ 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', arguments);
+
+ // Prepare
+ var currentUrl = ((event && event.newURL) || document.location.href),
+ currentHash = History.getHashByUrl(currentUrl),
+ currentState = null,
+ currentStateHash = null,
+ currentStateHashExits = null,
+ discardObject;
+
+ // 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');
+ History.busy(false);
+ return false;
+ }
+
+ // Reset the double check
+ History.doubleCheckComplete();
+
+ // Store our location for use in detecting back/forward direction
+ History.saveHash(currentHash);
+
+ // Expand Hash
+ if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
+ //History.debug('History.onHashChange: traditional anchor', currentHash);
+ // Traditional Anchor Hash
+ History.Adapter.trigger(window,'anchorchange');
+ History.busy(false);
+ return false;
+ }
+
+ // Create State
+ currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true);
+
+ // Check if we are the same state
+ if ( History.isLastSavedState(currentState) ) {
+ //History.debug('History.onHashChange: no change');
+ // There has been no change (just the page's hash has finally propagated)
+ History.busy(false);
+ return false;
+ }
+
+ // Create the state Hash
+ currentStateHash = History.getHashByState(currentState);
+
+ // Check if we are DiscardedState
+ discardObject = History.discardedState(currentState);
+ if ( discardObject ) {
+ // Ignore this state as it has been discarded and go back to the state before it
+ if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
+ // We are going backwards
+ //History.debug('History.onHashChange: go backwards');
+ History.back(false);
+ } else {
+ // We are going forwards
+ //History.debug('History.onHashChange: go forwards');
+ History.forward(false);
+ }
+ return false;
+ }
+
+ // Push the new HTML5 State
+ //History.debug('History.onHashChange: success hashchange');
+ History.pushState(currentState.data,currentState.title,currentState.url,false);
+
+ // End onHashChange closure
+ 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,queue){
+ //History.debug('History.pushState: called', arguments);
+
+ // Check the State
+ if ( History.getHashByUrl(url) ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.pushState: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.pushState,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy
+ History.busy(true);
+
+ // Fetch the State Object
+ var newState = History.createStateObject(data,title,url),
+ newStateHash = History.getHashByState(newState),
+ oldState = History.getState(false),
+ oldStateHash = History.getHashByState(oldState),
+ html4Hash = History.getHash();
+
+ // Store the newState
+ History.storeState(newState);
+ History.expectedStateId = newState.id;
+
+ // Recycle the State
+ History.recycleState(newState);
+
+ // Force update of the title
+ History.setTitle(newState);
+
+ // Check if we are the same State
+ if ( newStateHash === oldStateHash ) {
+ //History.debug('History.pushState: no change', newStateHash);
+ History.busy(false);
+ return false;
+ }
+
+ // Update HTML4 Hash
+ if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) {
+ //History.debug('History.pushState: update hash', newStateHash, html4Hash);
+ History.setHash(newStateHash,false);
+ return false;
+ }
+
+ // Update HTML5 State
+ History.saveState(newState);
+
+ // Fire HTML5 Event
+ //History.debug('History.pushState: trigger popstate');
+ History.Adapter.trigger(window,'statechange');
+ History.busy(false);
+
+ // End pushState closure
+ 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,queue){
+ //History.debug('History.replaceState: called', arguments);
+
+ // Check the State
+ if ( History.getHashByUrl(url) ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.replaceState: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.replaceState,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy
+ History.busy(true);
+
+ // Fetch the State Objects
+ var newState = History.createStateObject(data,title,url),
+ oldState = History.getState(false),
+ previousState = History.getStateByIndex(-2);
+
+ // Discard Old State
+ History.discardState(oldState,newState,previousState);
+
+ // Alias to PushState
+ History.pushState(newState.data,newState.title,newState.url,false);
+
+ // End replaceState closure
+ return true;
+ };
+
+ } // History.emulated.pushState
+
+
+
+ // ====================================================================
+ // Initialise
+
+ // Non-Native pushState Implementation
+ if ( History.emulated.pushState ) {
+ /**
+ * Ensure initial state is handled correctly
+ */
+ if ( History.getHash() && !History.emulated.hashChange ) {
+ History.Adapter.onDomLoad(function(){
+ History.Adapter.trigger(window,'hashchange');
+ });
+ }
+
+ } // History.emulated.pushState
+
+ }; // History.initHtml4
+
+ // Try and Initialise History
+ if ( typeof History.init !== 'undefined' ) {
+ History.init();
+ }
+
+})(window);
+/**
+ * History.js Core
+ * @author Benjamin Arthur Lupton
+ * @copyright 2010-2011 Benjamin Arthur Lupton
+ * @license New BSD License
+ */
+
+(function(window,undefined){
+ "use strict";
+
+ // ========================================================================
+ // Initialise
+
+ // Localise Globals
+ var
+ console = window.console||undefined, // Prevent a JSLint complain
+ document = window.document, // Make sure we are using the correct document
+ navigator = window.navigator, // Make sure we are using the correct navigator
+ sessionStorage = window.sessionStorage||false, // sessionStorage
+ setTimeout = window.setTimeout,
+ clearTimeout = window.clearTimeout,
+ setInterval = window.setInterval,
+ clearInterval = window.clearInterval,
+ JSON = window.JSON,
+ alert = window.alert,
+ History = window.History = window.History||{}, // Public History Object
+ history = window.history; // Old History Object
+
+ // MooTools Compatibility
+ JSON.stringify = JSON.stringify||JSON.encode;
+ JSON.parse = JSON.parse||JSON.decode;
+
+ // Check Existence
+ if ( typeof History.init !== 'undefined' ) {
+ throw new Error('History.js Core has already been loaded...');
+ }
+
+ // Initialise History
+ History.init = function(){
+ // Check Load Status of Adapter
+ if ( typeof History.Adapter === 'undefined' ) {
+ return false;
+ }
+
+ // Check Load Status of Core
+ if ( typeof History.initCore !== 'undefined' ) {
+ History.initCore();
+ }
+
+ // Check Load Status of HTML4 Support
+ if ( typeof History.initHtml4 !== 'undefined' ) {
+ History.initHtml4();
+ }
+
+ // Return true
+ return true;
+ };
+
+
+ // ========================================================================
+ // Initialise Core
+
+ // Initialise Core
+ History.initCore = function(){
+ // Initialise
+ if ( typeof History.initCore.initialized !== 'undefined' ) {
+ // Already Loaded
+ return false;
+ }
+ else {
+ History.initCore.initialized = true;
+ }
+
+
+ // ====================================================================
+ // Options
+
+ /**
+ * History.options
+ * Configurable options
+ */
+ History.options = History.options||{};
+
+ /**
+ * History.options.hashChangeInterval
+ * How long should the interval be before hashchange checks
+ */
+ History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
+
+ /**
+ * History.options.safariPollInterval
+ * How long should the interval be before safari poll checks
+ */
+ History.options.safariPollInterval = History.options.safariPollInterval || 500;
+
+ /**
+ * History.options.doubleCheckInterval
+ * How long should the interval be before we perform a double check
+ */
+ History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
+
+ /**
+ * History.options.storeInterval
+ * How long should we wait between store calls
+ */
+ History.options.storeInterval = History.options.storeInterval || 1000;
+
+ /**
+ * History.options.busyDelay
+ * How long should we wait between busy events
+ */
+ History.options.busyDelay = History.options.busyDelay || 250;
+
+ /**
+ * History.options.debug
+ * If true will enable debug messages to be logged
+ */
+ History.options.debug = History.options.debug || false;
+
+ /**
+ * History.options.initialTitle
+ * What is the title of the initial state
+ */
+ History.options.initialTitle = History.options.initialTitle || document.title;
+
+
+ // ====================================================================
+ // Interval record
+
+ /**
+ * History.intervalList
+ * List of intervals set, to be cleared when document is unloaded.
+ */
+ History.intervalList = [];
+
+ /**
+ * History.clearAllIntervals
+ * Clears all setInterval instances.
+ */
+ History.clearAllIntervals = function(){
+ var i, il = History.intervalList;
+ if (typeof il !== "undefined" && il !== null) {
+ for (i = 0; i < il.length; i++) {
+ clearInterval(il[i]);
+ }
+ History.intervalList = null;
+ }
+ };
+
+
+ // ====================================================================
+ // Debug
+
+ /**
+ * History.debug(message,...)
+ * Logs the passed arguments if debug enabled
+ */
+ History.debug = function(){
+ if ( (History.options.debug||false) ) {
+ History.log.apply(History,arguments);
+ }
+ };
+
+ /**
+ * History.log(message,...)
+ * Logs the passed arguments
+ */
+ History.log = function(){
+ // Prepare
+ var
+ consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
+ textarea = document.getElementById('log'),
+ message,
+ i,n,
+ args,arg
+ ;
+
+ // Write to Console
+ if ( consoleExists ) {
+ args = Array.prototype.slice.call(arguments);
+ message = args.shift();
+ if ( typeof console.debug !== 'undefined' ) {
+ console.debug.apply(console,[message,args]);
+ }
+ else {
+ console.log.apply(console,[message,args]);
+ }
+ }
+ else {
+ message = ("\n"+arguments[0]+"\n");
+ }
+
+ // Write to log
+ for ( i=1,n=arguments.length; i
+ * @author James Padolsey
+ */
+ History.getInternetExplorerMajorVersion = function(){
+ var result = History.getInternetExplorerMajorVersion.cached =
+ (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
+ ? History.getInternetExplorerMajorVersion.cached
+ : (function(){
+ var v = 3,
+ div = document.createElement('div'),
+ all = div.getElementsByTagName('i');
+ while ( (div.innerHTML = '') && all[0] ) {}
+ return (v > 4) ? v : false;
+ })()
+ ;
+ return result;
+ };
+
+ /**
+ * History.isInternetExplorer()
+ * Are we using Internet Explorer?
+ * @return {boolean}
+ * @license Public Domain
+ * @author Benjamin Arthur Lupton
+ */
+ History.isInternetExplorer = function(){
+ var result =
+ History.isInternetExplorer.cached =
+ (typeof History.isInternetExplorer.cached !== 'undefined')
+ ? History.isInternetExplorer.cached
+ : Boolean(History.getInternetExplorerMajorVersion())
+ ;
+ return result;
+ };
+
+ /**
+ * History.emulated
+ * Which features require emulating?
+ */
+ History.emulated = {
+ pushState: !Boolean(
+ window.history && window.history.pushState && window.history.replaceState
+ && !(
+ (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
+ || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
+ )
+ ),
+ hashChange: Boolean(
+ !(('onhashchange' in window) || ('onhashchange' in document))
+ ||
+ (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
+ )
+ };
+
+ /**
+ * History.enabled
+ * Is History enabled?
+ */
+ History.enabled = !History.emulated.pushState;
+
+ /**
+ * History.bugs
+ * Which bugs are present
+ */
+ History.bugs = {
+ /**
+ * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
+ * https://bugs.webkit.org/show_bug.cgi?id=56249
+ */
+ setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
+
+ /**
+ * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
+ * https://bugs.webkit.org/show_bug.cgi?id=42940
+ */
+ safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
+
+ /**
+ * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
+ */
+ ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
+
+ /**
+ * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
+ */
+ hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
+ };
+
+ /**
+ * History.isEmptyObject(obj)
+ * Checks to see if the Object is Empty
+ * @param {Object} obj
+ * @return {boolean}
+ */
+ History.isEmptyObject = function(obj) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * History.cloneObject(obj)
+ * Clones a object and eliminate all references to the original contexts
+ * @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;
+ };
+
+
+ // ====================================================================
+ // URL Helpers
+
+ /**
+ * History.getRootUrl()
+ * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
+ * @return {String} rootUrl
+ */
+ History.getRootUrl = function(){
+ // Create
+ var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
+ if ( document.location.port||false ) {
+ rootUrl += ':'+document.location.port;
+ }
+ rootUrl += '/';
+
+ // Return
+ return rootUrl;
+ };
+
+ /**
+ * History.getBaseHref()
+ * Fetches the `href` attribute of the `` element if it exists
+ * @return {String} baseHref
+ */
+ History.getBaseHref = function(){
+ // Create
+ 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.replace(/[^\/]+$/,'');
+ }
+
+ // Adjust trailing slash
+ baseHref = baseHref.replace(/\/+$/,'');
+ if ( baseHref ) baseHref += '/';
+
+ // Return
+ return baseHref;
+ };
+
+ /**
+ * History.getBaseUrl()
+ * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
+ * @return {String} baseUrl
+ */
+ History.getBaseUrl = function(){
+ // Create
+ var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
+
+ // Return
+ return baseUrl;
+ };
+
+ /**
+ * History.getPageUrl()
+ * Fetches the URL of the current page
+ * @return {String} pageUrl
+ */
+ History.getPageUrl = function(){
+ // Fetch
+ var
+ State = History.getState(false,false),
+ stateUrl = (State||{}).url||document.location.href,
+ pageUrl;
+
+ // Create
+ pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
+ return (/\./).test(part) ? part : part+'/';
+ });
+
+ // Return
+ return pageUrl;
+ };
+
+ /**
+ * History.getBasePageUrl()
+ * Fetches the Url of the directory of the current page
+ * @return {String} basePageUrl
+ */
+ History.getBasePageUrl = function(){
+ // Create
+ var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
+ return (/[^\/]$/).test(part) ? '' : part;
+ }).replace(/\/+$/,'')+'/';
+
+ // Return
+ return basePageUrl;
+ };
+
+ /**
+ * History.getFullUrl(url)
+ * Ensures that we have an absolute URL and not a relative URL
+ * @param {string} url
+ * @param {Boolean} allowBaseHref
+ * @return {string} fullUrl
+ */
+ History.getFullUrl = function(url,allowBaseHref){
+ // Prepare
+ var fullUrl = url, firstChar = url.substring(0,1);
+ allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
+
+ // Check
+ if ( /[a-z]+\:\/\//.test(url) ) {
+ // Full URL
+ }
+ else if ( firstChar === '/' ) {
+ // Root URL
+ fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
+ }
+ else if ( firstChar === '#' ) {
+ // Anchor URL
+ fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
+ }
+ else if ( firstChar === '?' ) {
+ // Query URL
+ fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
+ }
+ else {
+ // Relative URL
+ if ( allowBaseHref ) {
+ fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
+ } else {
+ fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
+ }
+ // We have an if condition above as we do not want hashes
+ // which are relative to the baseHref in our URLs
+ // as if the baseHref changes, then all our bookmarks
+ // would now point to different locations
+ // whereas the basePageUrl will always stay the same
+ }
+
+ // Return
+ return fullUrl.replace(/\#$/,'');
+ };
+
+ /**
+ * History.getShortUrl(url)
+ * Ensures that we have a relative URL and not a absolute URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.getShortUrl = function(url){
+ // Prepare
+ var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
+
+ // Trim baseUrl
+ if ( History.emulated.pushState ) {
+ // We are in a if statement as when pushState is not emulated
+ // The actual url these short urls are relative to can change
+ // So within the same session, we the url may end up somewhere different
+ shortUrl = shortUrl.replace(baseUrl,'');
+ }
+
+ // Trim rootUrl
+ shortUrl = shortUrl.replace(rootUrl,'/');
+
+ // Ensure we can still detect it as a state
+ if ( History.isTraditionalAnchor(shortUrl) ) {
+ shortUrl = './'+shortUrl;
+ }
+
+ // Clean It
+ shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
+
+ // Return
+ return shortUrl;
+ };
+
+
+ // ====================================================================
+ // State Storage
+
+ /**
+ * History.store
+ * The store for all session specific data
+ */
+ History.store = {};
+
+ /**
+ * History.idToState
+ * 1-1: State ID to State Object
+ */
+ History.idToState = History.idToState||{};
+
+ /**
+ * History.stateToId
+ * 1-1: State String to State ID
+ */
+ History.stateToId = History.stateToId||{};
+
+ /**
+ * History.urlToId
+ * 1-1: State URL to State ID
+ */
+ History.urlToId = History.urlToId||{};
+
+ /**
+ * History.storedStates
+ * Store the states in an array
+ */
+ History.storedStates = History.storedStates||[];
+
+ /**
+ * History.savedStates
+ * Saved the states in an array
+ */
+ History.savedStates = History.savedStates||[];
+
+ /**
+ * History.noramlizeStore()
+ * Noramlize the store by adding necessary values
+ */
+ History.normalizeStore = function(){
+ History.store.idToState = History.store.idToState||{};
+ History.store.urlToId = History.store.urlToId||{};
+ History.store.stateToId = History.store.stateToId||{};
+ };
+
+ /**
+ * History.getState()
+ * Get an object containing the data, title and url of the current state
+ * @param {Boolean} friendly
+ * @param {Boolean} create
+ * @return {Object} State
+ */
+ History.getState = function(friendly,create){
+ // Prepare
+ if ( typeof friendly === 'undefined' ) { friendly = true; }
+ if ( typeof create === 'undefined' ) { create = true; }
+
+ // Fetch
+ var State = History.getLastSavedState();
+
+ // Create
+ if ( !State && create ) {
+ State = History.createStateObject();
+ }
+
+ // Adjust
+ if ( friendly ) {
+ State = History.cloneObject(State);
+ State.url = State.cleanUrl||State.url;
+ }
+
+ // Return
+ return State;
+ };
+
+ /**
+ * History.getIdByState(State)
+ * Gets a ID for a State
+ * @param {State} newState
+ * @return {String} id
+ */
+ History.getIdByState = function(newState){
+
+ // Fetch ID
+ var id = History.extractId(newState.url),
+ str;
+
+ if ( !id ) {
+ // Find ID via State String
+ str = History.getStateString(newState);
+ if ( typeof History.stateToId[str] !== 'undefined' ) {
+ id = History.stateToId[str];
+ }
+ else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
+ id = History.store.stateToId[str];
+ }
+ else {
+ // Generate a new ID
+ while ( true ) {
+ id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
+ if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
+ break;
+ }
+ }
+
+ // Apply the new State to the ID
+ History.stateToId[str] = id;
+ History.idToState[id] = newState;
+ }
+ }
+
+ // Return ID
+ return id;
+ };
+
+ /**
+ * History.normalizeState(State)
+ * Expands a State Object
+ * @param {object} State
+ * @return {object}
+ */
+ History.normalizeState = function(oldState){
+ // Variables
+ var newState, dataNotEmpty;
+
+ // Prepare
+ if ( !oldState || (typeof oldState !== 'object') ) {
+ oldState = {};
+ }
+
+ // Check
+ if ( typeof oldState.normalized !== 'undefined' ) {
+ return oldState;
+ }
+
+ // Adjust
+ if ( !oldState.data || (typeof oldState.data !== 'object') ) {
+ oldState.data = {};
+ }
+
+ // ----------------------------------------------------------------
+
+ // Create
+ newState = {};
+ newState.normalized = true;
+ newState.title = oldState.title||'';
+ newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
+ newState.hash = History.getShortUrl(newState.url);
+ newState.data = History.cloneObject(oldState.data);
+
+ // Fetch ID
+ newState.id = History.getIdByState(newState);
+
+ // ----------------------------------------------------------------
+
+ // Clean the URL
+ newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
+ newState.url = newState.cleanUrl;
+
+ // Check to see if we have more than just a url
+ dataNotEmpty = !History.isEmptyObject(newState.data);
+
+ // Apply
+ if ( newState.title || dataNotEmpty ) {
+ // Add ID to Hash
+ newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
+ if ( !/\?/.test(newState.hash) ) {
+ newState.hash += '?';
+ }
+ newState.hash += '&_suid='+newState.id;
+ }
+
+ // Create the Hashed URL
+ newState.hashedUrl = History.getFullUrl(newState.hash);
+
+ // ----------------------------------------------------------------
+
+ // Update the URL if we have a duplicate
+ if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
+ newState.url = newState.hashedUrl;
+ }
+
+ // ----------------------------------------------------------------
+
+ // Return
+ 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.normalizeState(State);
+
+ // Return object
+ return State;
+ };
+
+ /**
+ * History.getStateById(id)
+ * Get a state by it's UID
+ * @param {String} id
+ */
+ History.getStateById = function(id){
+ // Prepare
+ id = String(id);
+
+ // Retrieve
+ var State = History.idToState[id] || History.store.idToState[id] || undefined;
+
+ // Return State
+ return State;
+ };
+
+ /**
+ * Get a State's String
+ * @param {State} passedState
+ */
+ History.getStateString = function(passedState){
+ // Prepare
+ var State, cleanedState, str;
+
+ // Fetch
+ State = History.normalizeState(passedState);
+
+ // Clean
+ cleanedState = {
+ data: State.data,
+ title: passedState.title,
+ url: passedState.url
+ };
+
+ // Fetch
+ str = JSON.stringify(cleanedState);
+
+ // Return
+ return str;
+ };
+
+ /**
+ * Get a State's ID
+ * @param {State} passedState
+ * @return {String} id
+ */
+ History.getStateId = function(passedState){
+ // Prepare
+ var State, id;
+
+ // Fetch
+ State = History.normalizeState(passedState);
+
+ // Fetch
+ id = State.id;
+
+ // Return
+ return id;
+ };
+
+ /**
+ * History.getHashByState(State)
+ * Creates a Hash for the State Object
+ * @param {State} passedState
+ * @return {String} hash
+ */
+ History.getHashByState = function(passedState){
+ // Prepare
+ var State, hash;
+
+ // Fetch
+ State = History.normalizeState(passedState);
+
+ // Hash
+ hash = State.hash;
+
+ // Return
+ return hash;
+ };
+
+ /**
+ * History.extractId(url_or_hash)
+ * Get a State ID by it's URL or Hash
+ * @param {string} url_or_hash
+ * @return {string} id
+ */
+ History.extractId = function ( url_or_hash ) {
+ // Prepare
+ var id,parts,url;
+
+ // Extract
+ parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
+ url = parts ? (parts[1]||url_or_hash) : url_or_hash;
+ id = parts ? String(parts[2]||'') : '';
+
+ // Return
+ return id||false;
+ };
+
+ /**
+ * History.isTraditionalAnchor
+ * Checks to see if the url is a traditional anchor or not
+ * @param {String} url_or_hash
+ * @return {Boolean}
+ */
+ History.isTraditionalAnchor = function(url_or_hash){
+ // Check
+ var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
+
+ // Return
+ return isTraditional;
+ };
+
+ /**
+ * History.extractState
+ * Get a State by it's URL or Hash
+ * @param {String} url_or_hash
+ * @return {State|null}
+ */
+ History.extractState = function(url_or_hash,create){
+ // Prepare
+ var State = null, id, url;
+ create = create||false;
+
+ // Fetch SUID
+ id = History.extractId(url_or_hash);
+ if ( id ) {
+ State = History.getStateById(id);
+ }
+
+ // Fetch SUID returned no State
+ if ( !State ) {
+ // Fetch URL
+ url = History.getFullUrl(url_or_hash);
+
+ // Check URL
+ id = History.getIdByUrl(url)||false;
+ if ( id ) {
+ State = History.getStateById(id);
+ }
+
+ // Create State
+ if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
+ State = History.createStateObject(null,null,url);
+ }
+ }
+
+ // Return
+ return State;
+ };
+
+ /**
+ * History.getIdByUrl()
+ * Get a State ID by a State URL
+ */
+ History.getIdByUrl = function(url){
+ // Fetch
+ var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
+
+ // Return
+ return id;
+ };
+
+ /**
+ * History.getLastSavedState()
+ * Get an object containing the data, title and url of the current state
+ * @return {Object} State
+ */
+ History.getLastSavedState = function(){
+ return History.savedStates[History.savedStates.length-1]||undefined;
+ };
+
+ /**
+ * History.getLastStoredState()
+ * Get an object containing the data, title and url of the current state
+ * @return {Object} State
+ */
+ History.getLastStoredState = function(){
+ return History.storedStates[History.storedStates.length-1]||undefined;
+ };
+
+ /**
+ * History.hasUrlDuplicate
+ * Checks if a Url will have a url conflict
+ * @param {Object} newState
+ * @return {Boolean} hasDuplicate
+ */
+ History.hasUrlDuplicate = function(newState) {
+ // Prepare
+ var hasDuplicate = false,
+ oldState;
+
+ // Fetch
+ oldState = History.extractState(newState.url);
+
+ // Check
+ hasDuplicate = oldState && oldState.id !== newState.id;
+
+ // Return
+ return hasDuplicate;
+ };
+
+ /**
+ * History.storeState
+ * Store a State
+ * @param {Object} newState
+ * @return {Object} newState
+ */
+ History.storeState = function(newState){
+ // Store the State
+ History.urlToId[newState.url] = newState.id;
+
+ // Push the State
+ History.storedStates.push(History.cloneObject(newState));
+
+ // Return newState
+ return newState;
+ };
+
+ /**
+ * History.isLastSavedState(newState)
+ * Tests to see if the state is the last state
+ * @param {Object} newState
+ * @return {boolean} isLast
+ */
+ History.isLastSavedState = function(newState){
+ // Prepare
+ var isLast = false,
+ newId, oldState, oldId;
+
+ // Check
+ if ( History.savedStates.length ) {
+ newId = newState.id;
+ oldState = History.getLastSavedState();
+ oldId = oldState.id;
+
+ // Check
+ isLast = (newId === oldId);
+ }
+
+ // Return
+ return isLast;
+ };
+
+ /**
+ * History.saveState
+ * Push a State
+ * @param {Object} newState
+ * @return {boolean} changed
+ */
+ History.saveState = function(newState){
+ // Check Hash
+ if ( History.isLastSavedState(newState) ) {
+ return false;
+ }
+
+ // Push the State
+ History.savedStates.push(History.cloneObject(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;
+ };
+
+
+ // ====================================================================
+ // Hash Helpers
+
+ /**
+ * History.getHash()
+ * Gets the current document hash
+ * @return {string}
+ */
+ History.getHash = function(){
+ var hash = History.unescapeHash(document.location.hash);
+ return hash;
+ };
+
+ /**
+ * History.unescapeString()
+ * Unescape a string
+ * @param {String} str
+ * @return {string}
+ */
+ History.unescapeString = function(str){
+ // Prepare
+ var result = str,
+ tmp;
+
+ // Unescape hash
+ while ( true ) {
+ tmp = window.unescape(result);
+ if ( tmp === result ) {
+ break;
+ }
+ result = tmp;
+ }
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.unescapeHash()
+ * normalize and Unescape a Hash
+ * @param {String} hash
+ * @return {string}
+ */
+ History.unescapeHash = function(hash){
+ // Prepare
+ var result = History.normalizeHash(hash);
+
+ // Unescape hash
+ result = History.unescapeString(result);
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.normalizeHash()
+ * normalize a hash across browsers
+ * @return {string}
+ */
+ History.normalizeHash = function(hash){
+ // Prepare
+ var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.setHash(hash)
+ * Sets the document hash
+ * @param {string} hash
+ * @return {History}
+ */
+ History.setHash = function(hash,queue){
+ // Prepare
+ var adjustedHash, State, pageUrl;
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.setHash: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.setHash,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Log
+ //History.debug('History.setHash: called',hash);
+
+ // Prepare
+ adjustedHash = History.escapeHash(hash);
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Check if hash is a state
+ State = History.extractState(hash,true);
+ if ( State && !History.emulated.pushState ) {
+ // Hash is a state so skip the setHash
+ //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
+
+ // PushState
+ History.pushState(State.data,State.title,State.url,false);
+ }
+ else if ( document.location.hash !== adjustedHash ) {
+ // Hash is a proper hash, so apply it
+
+ // Handle browser bugs
+ if ( History.bugs.setHash ) {
+ // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
+
+ // Fetch the base page
+ pageUrl = History.getPageUrl();
+
+ // Safari hash apply
+ History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
+ }
+ else {
+ // Normal hash apply
+ document.location.hash = adjustedHash;
+ }
+ }
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.escape()
+ * normalize and Escape a Hash
+ * @return {string}
+ */
+ History.escapeHash = function(hash){
+ // Prepare
+ var result = History.normalizeHash(hash);
+
+ // Escape hash
+ result = window.escape(result);
+
+ // IE6 Escape Bug
+ if ( !History.bugs.hashEscape ) {
+ // Restore common parts
+ result = result
+ .replace(/\%21/g,'!')
+ .replace(/\%26/g,'&')
+ .replace(/\%3D/g,'=')
+ .replace(/\%3F/g,'?');
+ }
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.getHashByUrl(url)
+ * Extracts the Hash from a URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.getHashByUrl = function(url){
+ // Extract the hash
+ var hash = String(url)
+ .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
+ ;
+
+ // Unescape hash
+ hash = History.unescapeHash(hash);
+
+ // Return hash
+ return hash;
+ };
+
+ /**
+ * History.setTitle(title)
+ * Applies the title to the document
+ * @param {State} newState
+ * @return {Boolean}
+ */
+ History.setTitle = function(newState){
+ // Prepare
+ var title = newState.title,
+ firstState;
+
+ // Initial
+ if ( !title ) {
+ firstState = History.getStateByIndex(0);
+ if ( firstState && firstState.url === newState.url ) {
+ title = firstState.title||History.options.initialTitle;
+ }
+ }
+
+ // Apply
+ try {
+ document.getElementsByTagName('title')[0].innerHTML = title.replace('<','<').replace('>','>').replace(' & ',' & ');
+ }
+ catch ( Exception ) { }
+ document.title = title;
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // Queueing
+
+ /**
+ * History.queues
+ * The list of queues to use
+ * First In, First Out
+ */
+ History.queues = [];
+
+ /**
+ * History.busy(value)
+ * @param {boolean} value [optional]
+ * @return {boolean} busy
+ */
+ History.busy = function(value){
+ // Apply
+ if ( typeof value !== 'undefined' ) {
+ //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
+ History.busy.flag = value;
+ }
+ // Default
+ else if ( typeof History.busy.flag === 'undefined' ) {
+ History.busy.flag = false;
+ }
+
+ // Queue
+ if ( !History.busy.flag ) {
+ // Execute the next item in the queue
+ clearTimeout(History.busy.timeout);
+ var fireNext = function(){
+ var i, queue, item;
+ if ( History.busy.flag ) return;
+ for ( i=History.queues.length-1; i >= 0; --i ) {
+ queue = History.queues[i];
+ if ( queue.length === 0 ) continue;
+ item = queue.shift();
+ History.fireQueueItem(item);
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
+ }
+ };
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
+ }
+
+ // Return
+ return History.busy.flag;
+ };
+
+ /**
+ * History.busy.flag
+ */
+ History.busy.flag = false;
+
+ /**
+ * History.fireQueueItem(item)
+ * Fire a Queue Item
+ * @param {Object} item
+ * @return {Mixed} result
+ */
+ History.fireQueueItem = function(item){
+ return item.callback.apply(item.scope||History,item.args||[]);
+ };
+
+ /**
+ * History.pushQueue(callback,args)
+ * Add an item to the queue
+ * @param {Object} item [scope,callback,args,queue]
+ */
+ History.pushQueue = function(item){
+ // Prepare the queue
+ History.queues[item.queue||0] = History.queues[item.queue||0]||[];
+
+ // Add to the queue
+ History.queues[item.queue||0].push(item);
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.queue (item,queue), (func,queue), (func), (item)
+ * Either firs the item now if not busy, or adds it to the queue
+ */
+ History.queue = function(item,queue){
+ // Prepare
+ if ( typeof item === 'function' ) {
+ item = {
+ callback: item
+ };
+ }
+ if ( typeof queue !== 'undefined' ) {
+ item.queue = queue;
+ }
+
+ // Handle
+ if ( History.busy() ) {
+ History.pushQueue(item);
+ } else {
+ History.fireQueueItem(item);
+ }
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.clearQueue()
+ * Clears the Queue
+ */
+ History.clearQueue = function(){
+ History.busy.flag = false;
+ History.queues = [];
+ return History;
+ };
+
+
+ // ====================================================================
+ // IE Bug Fix
+
+ /**
+ * History.stateChanged
+ * States whether or not the state has changed since the last double check was initialised
+ */
+ History.stateChanged = false;
+
+ /**
+ * History.doubleChecker
+ * Contains the timeout used for the double checks
+ */
+ History.doubleChecker = false;
+
+ /**
+ * History.doubleCheckComplete()
+ * Complete a double check
+ * @return {History}
+ */
+ History.doubleCheckComplete = function(){
+ // Update
+ History.stateChanged = true;
+
+ // Clear
+ History.doubleCheckClear();
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.doubleCheckClear()
+ * Clear a double check
+ * @return {History}
+ */
+ History.doubleCheckClear = function(){
+ // Clear
+ if ( History.doubleChecker ) {
+ clearTimeout(History.doubleChecker);
+ History.doubleChecker = false;
+ }
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.doubleCheck()
+ * Create a double check
+ * @return {History}
+ */
+ History.doubleCheck = function(tryAgain){
+ // Reset
+ History.stateChanged = false;
+ History.doubleCheckClear();
+
+ // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
+ // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
+ if ( History.bugs.ieDoubleCheck ) {
+ // Apply Check
+ History.doubleChecker = setTimeout(
+ function(){
+ History.doubleCheckClear();
+ if ( !History.stateChanged ) {
+ //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
+ // Re-Attempt
+ tryAgain();
+ }
+ return true;
+ },
+ History.options.doubleCheckInterval
+ );
+ }
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // Safari Bug Fix
+
+ /**
+ * History.safariStatePoll()
+ * Poll the current state
+ * @return {History}
+ */
+ History.safariStatePoll = function(){
+ // Poll the URL
+
+ // Get the Last State which has the new URL
+ var
+ urlState = History.extractState(document.location.href),
+ newState;
+
+ // Check for a difference
+ if ( !History.isLastSavedState(urlState) ) {
+ newState = urlState;
+ }
+ else {
+ return;
+ }
+
+ // Check if we have a state with that url
+ // If not create it
+ if ( !newState ) {
+ //History.debug('History.safariStatePoll: new');
+ newState = History.createStateObject();
+ }
+
+ // Apply the New State
+ //History.debug('History.safariStatePoll: trigger');
+ History.Adapter.trigger(window,'popstate');
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // State Aliases
+
+ /**
+ * History.back(queue)
+ * Send the browser history back one item
+ * @param {Integer} queue [optional]
+ */
+ History.back = function(queue){
+ //History.debug('History.back: called', arguments);
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.back: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.back,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Fix certain browser bugs that prevent the state from changing
+ History.doubleCheck(function(){
+ History.back(false);
+ });
+
+ // Go back
+ history.go(-1);
+
+ // End back closure
+ return true;
+ };
+
+ /**
+ * History.forward(queue)
+ * Send the browser history forward one item
+ * @param {Integer} queue [optional]
+ */
+ History.forward = function(queue){
+ //History.debug('History.forward: called', arguments);
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.forward: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.forward,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Fix certain browser bugs that prevent the state from changing
+ History.doubleCheck(function(){
+ History.forward(false);
+ });
+
+ // Go forward
+ history.go(1);
+
+ // End forward closure
+ return true;
+ };
+
+ /**
+ * History.go(index,queue)
+ * Send the browser history back or forward index times
+ * @param {Integer} queue [optional]
+ */
+ History.go = function(index,queue){
+ //History.debug('History.go: called', arguments);
+
+ // Prepare
+ var i;
+
+ // Handle
+ if ( index > 0 ) {
+ // Forward
+ for ( i=1; i<=index; ++i ) {
+ History.forward(queue);
+ }
+ }
+ else if ( index < 0 ) {
+ // Backward
+ for ( i=-1; i>=index; --i ) {
+ History.back(queue);
+ }
+ }
+ else {
+ throw new Error('History.go: History.go requires a positive or negative integer passed.');
+ }
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // HTML5 State Support
+
+ // Non-Native pushState Implementation
+ if ( History.emulated.pushState ) {
+ /*
+ * Provide Skeleton for HTML4 Browsers
+ */
+
+ // Prepare
+ var emptyFunction = function(){};
+ History.pushState = History.pushState||emptyFunction;
+ History.replaceState = History.replaceState||emptyFunction;
+ } // History.emulated.pushState
+
+ // Native pushState Implementation
+ else {
+ /*
+ * Use native HTML5 History API Implementation
+ */
+
+ /**
+ * History.onPopState(event,extra)
+ * Refresh the Current State
+ */
+ History.onPopState = function(event,extra){
+ // Prepare
+ var stateId = false, newState = false, currentHash, currentState;
+
+ // Reset the double check
+ History.doubleCheckComplete();
+
+ // Check for a Hash, and handle apporiatly
+ currentHash = History.getHash();
+ if ( currentHash ) {
+ // Expand Hash
+ currentState = History.extractState(currentHash||document.location.href,true);
+ 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.title, currentState.url, false);
+ }
+ else {
+ // Traditional Anchor
+ //History.debug('History.onPopState: traditional anchor', currentHash);
+ History.Adapter.trigger(window,'anchorchange');
+ History.busy(false);
+ }
+
+ // We don't care for hashes
+ History.expectedStateId = false;
+ return false;
+ }
+
+ // Ensure
+ stateId = History.Adapter.extractEventData('state',event,extra) || false;
+
+ // Fetch State
+ if ( stateId ) {
+ // Vanilla: Back/forward button was used
+ newState = History.getStateById(stateId);
+ }
+ else if ( History.expectedStateId ) {
+ // Vanilla: A new state was pushed, and popstate was called manually
+ newState = History.getStateById(History.expectedStateId);
+ }
+ else {
+ // Initial State
+ newState = History.extractState(document.location.href);
+ }
+
+ // The State did not exist in our store
+ if ( !newState ) {
+ // Regenerate the State
+ newState = History.createStateObject(null,null,document.location.href);
+ }
+
+ // Clean
+ History.expectedStateId = false;
+
+ // Check if we are the same state
+ if ( History.isLastSavedState(newState) ) {
+ // There has been no change (just the page's hash has finally propagated)
+ //History.debug('History.onPopState: no change', newState, History.savedStates);
+ History.busy(false);
+ return false;
+ }
+
+ // Store the State
+ History.storeState(newState);
+ History.saveState(newState);
+
+ // Force update of the title
+ History.setTitle(newState);
+
+ // Fire Our Event
+ History.Adapter.trigger(window,'statechange');
+ History.busy(false);
+
+ // 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,queue){
+ //History.debug('History.pushState: called', arguments);
+
+ // Check the State
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.pushState: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.pushState,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Create the newState
+ var newState = History.createStateObject(data,title,url);
+
+ // Check it
+ if ( History.isLastSavedState(newState) ) {
+ // Won't be a change
+ History.busy(false);
+ }
+ else {
+ // Store the newState
+ History.storeState(newState);
+ History.expectedStateId = newState.id;
+
+ // Push the newState
+ history.pushState(newState.id,newState.title,newState.url);
+
+ // Fire HTML5 Event
+ History.Adapter.trigger(window,'popstate');
+ }
+
+ // End pushState closure
+ 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,queue){
+ //History.debug('History.replaceState: called', arguments);
+
+ // Check the State
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.replaceState: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.replaceState,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Create the newState
+ var newState = History.createStateObject(data,title,url);
+
+ // Check it
+ if ( History.isLastSavedState(newState) ) {
+ // Won't be a change
+ History.busy(false);
+ }
+ else {
+ // Store the newState
+ History.storeState(newState);
+ History.expectedStateId = newState.id;
+
+ // Push the newState
+ history.replaceState(newState.id,newState.title,newState.url);
+
+ // Fire HTML5 Event
+ History.Adapter.trigger(window,'popstate');
+ }
+
+ // End replaceState closure
+ return true;
+ };
+
+ } // !History.emulated.pushState
+
+
+ // ====================================================================
+ // Initialise
+
+ /**
+ * Load the Store
+ */
+ if ( sessionStorage ) {
+ // Fetch
+ try {
+ History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
+ }
+ catch ( err ) {
+ History.store = {};
+ }
+
+ // Normalize
+ History.normalizeStore();
+ }
+ else {
+ // Default Load
+ History.store = {};
+ History.normalizeStore();
+ }
+
+ /**
+ * Clear Intervals on exit to prevent memory leaks
+ */
+ History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
+ History.Adapter.bind(window,"unload",History.clearAllIntervals);
+
+ /**
+ * Create the initial State
+ */
+ History.saveState(History.storeState(History.extractState(document.location.href,true)));
+
+ /**
+ * Bind for Saving Store
+ */
+ if ( sessionStorage ) {
+ // When the page is closed
+ History.onUnload = function(){
+ // Prepare
+ var currentStore, item;
+
+ // Fetch
+ try {
+ currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
+ }
+ catch ( err ) {
+ currentStore = {};
+ }
+
+ // Ensure
+ currentStore.idToState = currentStore.idToState || {};
+ currentStore.urlToId = currentStore.urlToId || {};
+ currentStore.stateToId = currentStore.stateToId || {};
+
+ // Sync
+ for ( item in History.idToState ) {
+ if ( !History.idToState.hasOwnProperty(item) ) {
+ continue;
+ }
+ currentStore.idToState[item] = History.idToState[item];
+ }
+ for ( item in History.urlToId ) {
+ if ( !History.urlToId.hasOwnProperty(item) ) {
+ continue;
+ }
+ currentStore.urlToId[item] = History.urlToId[item];
+ }
+ for ( item in History.stateToId ) {
+ if ( !History.stateToId.hasOwnProperty(item) ) {
+ continue;
+ }
+ currentStore.stateToId[item] = History.stateToId[item];
+ }
+
+ // Update
+ History.store = currentStore;
+ History.normalizeStore();
+
+ // Store
+ sessionStorage.setItem('History.store',JSON.stringify(currentStore));
+ };
+
+ // For Internet Explorer
+ History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
+
+ // For Other Browsers
+ History.Adapter.bind(window,'beforeunload',History.onUnload);
+ History.Adapter.bind(window,'unload',History.onUnload);
+
+ // Both are enabled for consistency
+ }
+
+ // Non-Native pushState Implementation
+ if ( !History.emulated.pushState ) {
+ // Be aware, the following is only for native pushState implementations
+ // If you are wanting to include something for all browsers
+ // Then include it above this if block
+
+ /**
+ * Setup Safari Fix
+ */
+ if ( History.bugs.safariPoll ) {
+ History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
+ }
+
+ /**
+ * Ensure Cross Browser Compatibility
+ */
+ if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
+ /**
+ * Fix Safari HashChange Issue
+ */
+
+ // Setup Alias
+ History.Adapter.bind(window,'hashchange',function(){
+ History.Adapter.trigger(window,'popstate');
+ });
+
+ // Initialise Alias
+ if ( History.getHash() ) {
+ History.Adapter.onDomLoad(function(){
+ History.Adapter.trigger(window,'hashchange');
+ });
+ }
+ }
+
+ } // !History.emulated.pushState
+
+
+ }; // History.initCore
+
+ // Try and Initialise History
+ History.init();
+
+})(window);
diff --git a/scripts/bundled/html5/dojo.history.js b/scripts/bundled/html5/dojo.history.js
new file mode 100644
index 00000000..96171a1c
--- /dev/null
+++ b/scripts/bundled/html5/dojo.history.js
@@ -0,0 +1,2504 @@
+/*
+ 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 ' '),
+ 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.
+
+if (!window.JSON) {
+ window.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 JSON = window.JSON,
+ 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;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' : gap ?
+ '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' : gap ?
+ '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
+ '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
+/**
+ * History.js Dojo Adapter
+ * @author Lakin Wecker
+ * @copyright 2012-2012 Lakin Wecker
+ * @license New BSD License
+ */
+
+require(["dojo/on", "dojo/ready", "dojo/_base/lang"], function(on,ready, lang) {
+ "use strict";
+
+ // Localise Globals
+ var
+ History = window.History = window.History||{};
+
+ // Check Existence
+ if ( typeof History.Adapter !== 'undefined' ) {
+ throw new Error('History.js Adapter has already been loaded...');
+ }
+
+ // Add the Adapter
+ History.Adapter = {
+ /**
+ * History.Adapter.bind(el,event,callback)
+ * @param {Element|string} el
+ * @param {string} event - custom and standard events
+ * @param {function} callback
+ * @return {void}
+ */
+ bind: function(el,event,callback){
+ on(el,event,callback);
+ },
+
+ /**
+ * History.Adapter.trigger(el,event)
+ * @param {Element|string} el
+ * @param {string} event - custom and standard events
+ * @param {Object=} extra - a object of extra event data (optional)
+ * @return {void}
+ */
+ trigger: function(el,event,extra){
+ extra = extra || {};
+ lang.mixin(extra, {
+ bubbles: true,
+ cancelable: true
+ });
+
+ on.emit(el,event,extra);
+ },
+
+ /**
+ * History.Adapter.extractEventData(key,event,extra)
+ * @param {string} key - key for the event data to extract
+ * @param {string} event - custom and standard events
+ * @param {Object=} extra - a object of extra event data (optional)
+ * @return {mixed}
+ */
+ extractEventData: function(key,event,extra){
+ // dojo Native then dojo Custom
+ var result = (event && event[key]) || (extra && extra[key]) || undefined;
+
+ // Return
+ return result;
+ },
+
+ /**
+ * History.Adapter.onDomLoad(callback)
+ * @param {function} callback
+ * @return {void}
+ */
+ onDomLoad: function(callback) {
+ ready(callback);
+ }
+ };
+
+ // Try and Initialise History
+ if ( typeof History.init !== 'undefined' ) {
+ History.init();
+ }
+
+});
+
+/**
+ * History.js Core
+ * @author Benjamin Arthur Lupton
+ * @copyright 2010-2011 Benjamin Arthur Lupton
+ * @license New BSD License
+ */
+
+(function(window,undefined){
+ "use strict";
+
+ // ========================================================================
+ // Initialise
+
+ // Localise Globals
+ var
+ console = window.console||undefined, // Prevent a JSLint complain
+ document = window.document, // Make sure we are using the correct document
+ navigator = window.navigator, // Make sure we are using the correct navigator
+ sessionStorage = window.sessionStorage||false, // sessionStorage
+ setTimeout = window.setTimeout,
+ clearTimeout = window.clearTimeout,
+ setInterval = window.setInterval,
+ clearInterval = window.clearInterval,
+ JSON = window.JSON,
+ alert = window.alert,
+ History = window.History = window.History||{}, // Public History Object
+ history = window.history; // Old History Object
+
+ // MooTools Compatibility
+ JSON.stringify = JSON.stringify||JSON.encode;
+ JSON.parse = JSON.parse||JSON.decode;
+
+ // Check Existence
+ if ( typeof History.init !== 'undefined' ) {
+ throw new Error('History.js Core has already been loaded...');
+ }
+
+ // Initialise History
+ History.init = function(){
+ // Check Load Status of Adapter
+ if ( typeof History.Adapter === 'undefined' ) {
+ return false;
+ }
+
+ // Check Load Status of Core
+ if ( typeof History.initCore !== 'undefined' ) {
+ History.initCore();
+ }
+
+ // Check Load Status of HTML4 Support
+ if ( typeof History.initHtml4 !== 'undefined' ) {
+ History.initHtml4();
+ }
+
+ // Return true
+ return true;
+ };
+
+
+ // ========================================================================
+ // Initialise Core
+
+ // Initialise Core
+ History.initCore = function(){
+ // Initialise
+ if ( typeof History.initCore.initialized !== 'undefined' ) {
+ // Already Loaded
+ return false;
+ }
+ else {
+ History.initCore.initialized = true;
+ }
+
+
+ // ====================================================================
+ // Options
+
+ /**
+ * History.options
+ * Configurable options
+ */
+ History.options = History.options||{};
+
+ /**
+ * History.options.hashChangeInterval
+ * How long should the interval be before hashchange checks
+ */
+ History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
+
+ /**
+ * History.options.safariPollInterval
+ * How long should the interval be before safari poll checks
+ */
+ History.options.safariPollInterval = History.options.safariPollInterval || 500;
+
+ /**
+ * History.options.doubleCheckInterval
+ * How long should the interval be before we perform a double check
+ */
+ History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
+
+ /**
+ * History.options.storeInterval
+ * How long should we wait between store calls
+ */
+ History.options.storeInterval = History.options.storeInterval || 1000;
+
+ /**
+ * History.options.busyDelay
+ * How long should we wait between busy events
+ */
+ History.options.busyDelay = History.options.busyDelay || 250;
+
+ /**
+ * History.options.debug
+ * If true will enable debug messages to be logged
+ */
+ History.options.debug = History.options.debug || false;
+
+ /**
+ * History.options.initialTitle
+ * What is the title of the initial state
+ */
+ History.options.initialTitle = History.options.initialTitle || document.title;
+
+
+ // ====================================================================
+ // Interval record
+
+ /**
+ * History.intervalList
+ * List of intervals set, to be cleared when document is unloaded.
+ */
+ History.intervalList = [];
+
+ /**
+ * History.clearAllIntervals
+ * Clears all setInterval instances.
+ */
+ History.clearAllIntervals = function(){
+ var i, il = History.intervalList;
+ if (typeof il !== "undefined" && il !== null) {
+ for (i = 0; i < il.length; i++) {
+ clearInterval(il[i]);
+ }
+ History.intervalList = null;
+ }
+ };
+
+
+ // ====================================================================
+ // Debug
+
+ /**
+ * History.debug(message,...)
+ * Logs the passed arguments if debug enabled
+ */
+ History.debug = function(){
+ if ( (History.options.debug||false) ) {
+ History.log.apply(History,arguments);
+ }
+ };
+
+ /**
+ * History.log(message,...)
+ * Logs the passed arguments
+ */
+ History.log = function(){
+ // Prepare
+ var
+ consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
+ textarea = document.getElementById('log'),
+ message,
+ i,n,
+ args,arg
+ ;
+
+ // Write to Console
+ if ( consoleExists ) {
+ args = Array.prototype.slice.call(arguments);
+ message = args.shift();
+ if ( typeof console.debug !== 'undefined' ) {
+ console.debug.apply(console,[message,args]);
+ }
+ else {
+ console.log.apply(console,[message,args]);
+ }
+ }
+ else {
+ message = ("\n"+arguments[0]+"\n");
+ }
+
+ // Write to log
+ for ( i=1,n=arguments.length; i
+ * @author James Padolsey
+ */
+ History.getInternetExplorerMajorVersion = function(){
+ var result = History.getInternetExplorerMajorVersion.cached =
+ (typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
+ ? History.getInternetExplorerMajorVersion.cached
+ : (function(){
+ var v = 3,
+ div = document.createElement('div'),
+ all = div.getElementsByTagName('i');
+ while ( (div.innerHTML = '') && all[0] ) {}
+ return (v > 4) ? v : false;
+ })()
+ ;
+ return result;
+ };
+
+ /**
+ * History.isInternetExplorer()
+ * Are we using Internet Explorer?
+ * @return {boolean}
+ * @license Public Domain
+ * @author Benjamin Arthur Lupton
+ */
+ History.isInternetExplorer = function(){
+ var result =
+ History.isInternetExplorer.cached =
+ (typeof History.isInternetExplorer.cached !== 'undefined')
+ ? History.isInternetExplorer.cached
+ : Boolean(History.getInternetExplorerMajorVersion())
+ ;
+ return result;
+ };
+
+ /**
+ * History.emulated
+ * Which features require emulating?
+ */
+ History.emulated = {
+ pushState: !Boolean(
+ window.history && window.history.pushState && window.history.replaceState
+ && !(
+ (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
+ || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
+ )
+ ),
+ hashChange: Boolean(
+ !(('onhashchange' in window) || ('onhashchange' in document))
+ ||
+ (History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
+ )
+ };
+
+ /**
+ * History.enabled
+ * Is History enabled?
+ */
+ History.enabled = !History.emulated.pushState;
+
+ /**
+ * History.bugs
+ * Which bugs are present
+ */
+ History.bugs = {
+ /**
+ * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
+ * https://bugs.webkit.org/show_bug.cgi?id=56249
+ */
+ setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
+
+ /**
+ * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
+ * https://bugs.webkit.org/show_bug.cgi?id=42940
+ */
+ safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
+
+ /**
+ * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
+ */
+ ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
+
+ /**
+ * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
+ */
+ hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
+ };
+
+ /**
+ * History.isEmptyObject(obj)
+ * Checks to see if the Object is Empty
+ * @param {Object} obj
+ * @return {boolean}
+ */
+ History.isEmptyObject = function(obj) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ };
+
+ /**
+ * History.cloneObject(obj)
+ * Clones a object and eliminate all references to the original contexts
+ * @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;
+ };
+
+
+ // ====================================================================
+ // URL Helpers
+
+ /**
+ * History.getRootUrl()
+ * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
+ * @return {String} rootUrl
+ */
+ History.getRootUrl = function(){
+ // Create
+ var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
+ if ( document.location.port||false ) {
+ rootUrl += ':'+document.location.port;
+ }
+ rootUrl += '/';
+
+ // Return
+ return rootUrl;
+ };
+
+ /**
+ * History.getBaseHref()
+ * Fetches the `href` attribute of the `` element if it exists
+ * @return {String} baseHref
+ */
+ History.getBaseHref = function(){
+ // Create
+ 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.replace(/[^\/]+$/,'');
+ }
+
+ // Adjust trailing slash
+ baseHref = baseHref.replace(/\/+$/,'');
+ if ( baseHref ) baseHref += '/';
+
+ // Return
+ return baseHref;
+ };
+
+ /**
+ * History.getBaseUrl()
+ * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
+ * @return {String} baseUrl
+ */
+ History.getBaseUrl = function(){
+ // Create
+ var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
+
+ // Return
+ return baseUrl;
+ };
+
+ /**
+ * History.getPageUrl()
+ * Fetches the URL of the current page
+ * @return {String} pageUrl
+ */
+ History.getPageUrl = function(){
+ // Fetch
+ var
+ State = History.getState(false,false),
+ stateUrl = (State||{}).url||document.location.href,
+ pageUrl;
+
+ // Create
+ pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
+ return (/\./).test(part) ? part : part+'/';
+ });
+
+ // Return
+ return pageUrl;
+ };
+
+ /**
+ * History.getBasePageUrl()
+ * Fetches the Url of the directory of the current page
+ * @return {String} basePageUrl
+ */
+ History.getBasePageUrl = function(){
+ // Create
+ var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
+ return (/[^\/]$/).test(part) ? '' : part;
+ }).replace(/\/+$/,'')+'/';
+
+ // Return
+ return basePageUrl;
+ };
+
+ /**
+ * History.getFullUrl(url)
+ * Ensures that we have an absolute URL and not a relative URL
+ * @param {string} url
+ * @param {Boolean} allowBaseHref
+ * @return {string} fullUrl
+ */
+ History.getFullUrl = function(url,allowBaseHref){
+ // Prepare
+ var fullUrl = url, firstChar = url.substring(0,1);
+ allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
+
+ // Check
+ if ( /[a-z]+\:\/\//.test(url) ) {
+ // Full URL
+ }
+ else if ( firstChar === '/' ) {
+ // Root URL
+ fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
+ }
+ else if ( firstChar === '#' ) {
+ // Anchor URL
+ fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
+ }
+ else if ( firstChar === '?' ) {
+ // Query URL
+ fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
+ }
+ else {
+ // Relative URL
+ if ( allowBaseHref ) {
+ fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
+ } else {
+ fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
+ }
+ // We have an if condition above as we do not want hashes
+ // which are relative to the baseHref in our URLs
+ // as if the baseHref changes, then all our bookmarks
+ // would now point to different locations
+ // whereas the basePageUrl will always stay the same
+ }
+
+ // Return
+ return fullUrl.replace(/\#$/,'');
+ };
+
+ /**
+ * History.getShortUrl(url)
+ * Ensures that we have a relative URL and not a absolute URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.getShortUrl = function(url){
+ // Prepare
+ var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
+
+ // Trim baseUrl
+ if ( History.emulated.pushState ) {
+ // We are in a if statement as when pushState is not emulated
+ // The actual url these short urls are relative to can change
+ // So within the same session, we the url may end up somewhere different
+ shortUrl = shortUrl.replace(baseUrl,'');
+ }
+
+ // Trim rootUrl
+ shortUrl = shortUrl.replace(rootUrl,'/');
+
+ // Ensure we can still detect it as a state
+ if ( History.isTraditionalAnchor(shortUrl) ) {
+ shortUrl = './'+shortUrl;
+ }
+
+ // Clean It
+ shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
+
+ // Return
+ return shortUrl;
+ };
+
+
+ // ====================================================================
+ // State Storage
+
+ /**
+ * History.store
+ * The store for all session specific data
+ */
+ History.store = {};
+
+ /**
+ * History.idToState
+ * 1-1: State ID to State Object
+ */
+ History.idToState = History.idToState||{};
+
+ /**
+ * History.stateToId
+ * 1-1: State String to State ID
+ */
+ History.stateToId = History.stateToId||{};
+
+ /**
+ * History.urlToId
+ * 1-1: State URL to State ID
+ */
+ History.urlToId = History.urlToId||{};
+
+ /**
+ * History.storedStates
+ * Store the states in an array
+ */
+ History.storedStates = History.storedStates||[];
+
+ /**
+ * History.savedStates
+ * Saved the states in an array
+ */
+ History.savedStates = History.savedStates||[];
+
+ /**
+ * History.noramlizeStore()
+ * Noramlize the store by adding necessary values
+ */
+ History.normalizeStore = function(){
+ History.store.idToState = History.store.idToState||{};
+ History.store.urlToId = History.store.urlToId||{};
+ History.store.stateToId = History.store.stateToId||{};
+ };
+
+ /**
+ * History.getState()
+ * Get an object containing the data, title and url of the current state
+ * @param {Boolean} friendly
+ * @param {Boolean} create
+ * @return {Object} State
+ */
+ History.getState = function(friendly,create){
+ // Prepare
+ if ( typeof friendly === 'undefined' ) { friendly = true; }
+ if ( typeof create === 'undefined' ) { create = true; }
+
+ // Fetch
+ var State = History.getLastSavedState();
+
+ // Create
+ if ( !State && create ) {
+ State = History.createStateObject();
+ }
+
+ // Adjust
+ if ( friendly ) {
+ State = History.cloneObject(State);
+ State.url = State.cleanUrl||State.url;
+ }
+
+ // Return
+ return State;
+ };
+
+ /**
+ * History.getIdByState(State)
+ * Gets a ID for a State
+ * @param {State} newState
+ * @return {String} id
+ */
+ History.getIdByState = function(newState){
+
+ // Fetch ID
+ var id = History.extractId(newState.url),
+ str;
+
+ if ( !id ) {
+ // Find ID via State String
+ str = History.getStateString(newState);
+ if ( typeof History.stateToId[str] !== 'undefined' ) {
+ id = History.stateToId[str];
+ }
+ else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
+ id = History.store.stateToId[str];
+ }
+ else {
+ // Generate a new ID
+ while ( true ) {
+ id = (new Date()).getTime() + String(Math.random()).replace(/\D/g,'');
+ if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
+ break;
+ }
+ }
+
+ // Apply the new State to the ID
+ History.stateToId[str] = id;
+ History.idToState[id] = newState;
+ }
+ }
+
+ // Return ID
+ return id;
+ };
+
+ /**
+ * History.normalizeState(State)
+ * Expands a State Object
+ * @param {object} State
+ * @return {object}
+ */
+ History.normalizeState = function(oldState){
+ // Variables
+ var newState, dataNotEmpty;
+
+ // Prepare
+ if ( !oldState || (typeof oldState !== 'object') ) {
+ oldState = {};
+ }
+
+ // Check
+ if ( typeof oldState.normalized !== 'undefined' ) {
+ return oldState;
+ }
+
+ // Adjust
+ if ( !oldState.data || (typeof oldState.data !== 'object') ) {
+ oldState.data = {};
+ }
+
+ // ----------------------------------------------------------------
+
+ // Create
+ newState = {};
+ newState.normalized = true;
+ newState.title = oldState.title||'';
+ newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
+ newState.hash = History.getShortUrl(newState.url);
+ newState.data = History.cloneObject(oldState.data);
+
+ // Fetch ID
+ newState.id = History.getIdByState(newState);
+
+ // ----------------------------------------------------------------
+
+ // Clean the URL
+ newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
+ newState.url = newState.cleanUrl;
+
+ // Check to see if we have more than just a url
+ dataNotEmpty = !History.isEmptyObject(newState.data);
+
+ // Apply
+ if ( newState.title || dataNotEmpty ) {
+ // Add ID to Hash
+ newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
+ if ( !/\?/.test(newState.hash) ) {
+ newState.hash += '?';
+ }
+ newState.hash += '&_suid='+newState.id;
+ }
+
+ // Create the Hashed URL
+ newState.hashedUrl = History.getFullUrl(newState.hash);
+
+ // ----------------------------------------------------------------
+
+ // Update the URL if we have a duplicate
+ if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
+ newState.url = newState.hashedUrl;
+ }
+
+ // ----------------------------------------------------------------
+
+ // Return
+ 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.normalizeState(State);
+
+ // Return object
+ return State;
+ };
+
+ /**
+ * History.getStateById(id)
+ * Get a state by it's UID
+ * @param {String} id
+ */
+ History.getStateById = function(id){
+ // Prepare
+ id = String(id);
+
+ // Retrieve
+ var State = History.idToState[id] || History.store.idToState[id] || undefined;
+
+ // Return State
+ return State;
+ };
+
+ /**
+ * Get a State's String
+ * @param {State} passedState
+ */
+ History.getStateString = function(passedState){
+ // Prepare
+ var State, cleanedState, str;
+
+ // Fetch
+ State = History.normalizeState(passedState);
+
+ // Clean
+ cleanedState = {
+ data: State.data,
+ title: passedState.title,
+ url: passedState.url
+ };
+
+ // Fetch
+ str = JSON.stringify(cleanedState);
+
+ // Return
+ return str;
+ };
+
+ /**
+ * Get a State's ID
+ * @param {State} passedState
+ * @return {String} id
+ */
+ History.getStateId = function(passedState){
+ // Prepare
+ var State, id;
+
+ // Fetch
+ State = History.normalizeState(passedState);
+
+ // Fetch
+ id = State.id;
+
+ // Return
+ return id;
+ };
+
+ /**
+ * History.getHashByState(State)
+ * Creates a Hash for the State Object
+ * @param {State} passedState
+ * @return {String} hash
+ */
+ History.getHashByState = function(passedState){
+ // Prepare
+ var State, hash;
+
+ // Fetch
+ State = History.normalizeState(passedState);
+
+ // Hash
+ hash = State.hash;
+
+ // Return
+ return hash;
+ };
+
+ /**
+ * History.extractId(url_or_hash)
+ * Get a State ID by it's URL or Hash
+ * @param {string} url_or_hash
+ * @return {string} id
+ */
+ History.extractId = function ( url_or_hash ) {
+ // Prepare
+ var id,parts,url;
+
+ // Extract
+ parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
+ url = parts ? (parts[1]||url_or_hash) : url_or_hash;
+ id = parts ? String(parts[2]||'') : '';
+
+ // Return
+ return id||false;
+ };
+
+ /**
+ * History.isTraditionalAnchor
+ * Checks to see if the url is a traditional anchor or not
+ * @param {String} url_or_hash
+ * @return {Boolean}
+ */
+ History.isTraditionalAnchor = function(url_or_hash){
+ // Check
+ var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
+
+ // Return
+ return isTraditional;
+ };
+
+ /**
+ * History.extractState
+ * Get a State by it's URL or Hash
+ * @param {String} url_or_hash
+ * @return {State|null}
+ */
+ History.extractState = function(url_or_hash,create){
+ // Prepare
+ var State = null, id, url;
+ create = create||false;
+
+ // Fetch SUID
+ id = History.extractId(url_or_hash);
+ if ( id ) {
+ State = History.getStateById(id);
+ }
+
+ // Fetch SUID returned no State
+ if ( !State ) {
+ // Fetch URL
+ url = History.getFullUrl(url_or_hash);
+
+ // Check URL
+ id = History.getIdByUrl(url)||false;
+ if ( id ) {
+ State = History.getStateById(id);
+ }
+
+ // Create State
+ if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
+ State = History.createStateObject(null,null,url);
+ }
+ }
+
+ // Return
+ return State;
+ };
+
+ /**
+ * History.getIdByUrl()
+ * Get a State ID by a State URL
+ */
+ History.getIdByUrl = function(url){
+ // Fetch
+ var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
+
+ // Return
+ return id;
+ };
+
+ /**
+ * History.getLastSavedState()
+ * Get an object containing the data, title and url of the current state
+ * @return {Object} State
+ */
+ History.getLastSavedState = function(){
+ return History.savedStates[History.savedStates.length-1]||undefined;
+ };
+
+ /**
+ * History.getLastStoredState()
+ * Get an object containing the data, title and url of the current state
+ * @return {Object} State
+ */
+ History.getLastStoredState = function(){
+ return History.storedStates[History.storedStates.length-1]||undefined;
+ };
+
+ /**
+ * History.hasUrlDuplicate
+ * Checks if a Url will have a url conflict
+ * @param {Object} newState
+ * @return {Boolean} hasDuplicate
+ */
+ History.hasUrlDuplicate = function(newState) {
+ // Prepare
+ var hasDuplicate = false,
+ oldState;
+
+ // Fetch
+ oldState = History.extractState(newState.url);
+
+ // Check
+ hasDuplicate = oldState && oldState.id !== newState.id;
+
+ // Return
+ return hasDuplicate;
+ };
+
+ /**
+ * History.storeState
+ * Store a State
+ * @param {Object} newState
+ * @return {Object} newState
+ */
+ History.storeState = function(newState){
+ // Store the State
+ History.urlToId[newState.url] = newState.id;
+
+ // Push the State
+ History.storedStates.push(History.cloneObject(newState));
+
+ // Return newState
+ return newState;
+ };
+
+ /**
+ * History.isLastSavedState(newState)
+ * Tests to see if the state is the last state
+ * @param {Object} newState
+ * @return {boolean} isLast
+ */
+ History.isLastSavedState = function(newState){
+ // Prepare
+ var isLast = false,
+ newId, oldState, oldId;
+
+ // Check
+ if ( History.savedStates.length ) {
+ newId = newState.id;
+ oldState = History.getLastSavedState();
+ oldId = oldState.id;
+
+ // Check
+ isLast = (newId === oldId);
+ }
+
+ // Return
+ return isLast;
+ };
+
+ /**
+ * History.saveState
+ * Push a State
+ * @param {Object} newState
+ * @return {boolean} changed
+ */
+ History.saveState = function(newState){
+ // Check Hash
+ if ( History.isLastSavedState(newState) ) {
+ return false;
+ }
+
+ // Push the State
+ History.savedStates.push(History.cloneObject(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;
+ };
+
+
+ // ====================================================================
+ // Hash Helpers
+
+ /**
+ * History.getHash()
+ * Gets the current document hash
+ * @return {string}
+ */
+ History.getHash = function(){
+ var hash = History.unescapeHash(document.location.hash);
+ return hash;
+ };
+
+ /**
+ * History.unescapeString()
+ * Unescape a string
+ * @param {String} str
+ * @return {string}
+ */
+ History.unescapeString = function(str){
+ // Prepare
+ var result = str,
+ tmp;
+
+ // Unescape hash
+ while ( true ) {
+ tmp = window.unescape(result);
+ if ( tmp === result ) {
+ break;
+ }
+ result = tmp;
+ }
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.unescapeHash()
+ * normalize and Unescape a Hash
+ * @param {String} hash
+ * @return {string}
+ */
+ History.unescapeHash = function(hash){
+ // Prepare
+ var result = History.normalizeHash(hash);
+
+ // Unescape hash
+ result = History.unescapeString(result);
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.normalizeHash()
+ * normalize a hash across browsers
+ * @return {string}
+ */
+ History.normalizeHash = function(hash){
+ // Prepare
+ var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.setHash(hash)
+ * Sets the document hash
+ * @param {string} hash
+ * @return {History}
+ */
+ History.setHash = function(hash,queue){
+ // Prepare
+ var adjustedHash, State, pageUrl;
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.setHash: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.setHash,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Log
+ //History.debug('History.setHash: called',hash);
+
+ // Prepare
+ adjustedHash = History.escapeHash(hash);
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Check if hash is a state
+ State = History.extractState(hash,true);
+ if ( State && !History.emulated.pushState ) {
+ // Hash is a state so skip the setHash
+ //History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
+
+ // PushState
+ History.pushState(State.data,State.title,State.url,false);
+ }
+ else if ( document.location.hash !== adjustedHash ) {
+ // Hash is a proper hash, so apply it
+
+ // Handle browser bugs
+ if ( History.bugs.setHash ) {
+ // Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
+
+ // Fetch the base page
+ pageUrl = History.getPageUrl();
+
+ // Safari hash apply
+ History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
+ }
+ else {
+ // Normal hash apply
+ document.location.hash = adjustedHash;
+ }
+ }
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.escape()
+ * normalize and Escape a Hash
+ * @return {string}
+ */
+ History.escapeHash = function(hash){
+ // Prepare
+ var result = History.normalizeHash(hash);
+
+ // Escape hash
+ result = window.escape(result);
+
+ // IE6 Escape Bug
+ if ( !History.bugs.hashEscape ) {
+ // Restore common parts
+ result = result
+ .replace(/\%21/g,'!')
+ .replace(/\%26/g,'&')
+ .replace(/\%3D/g,'=')
+ .replace(/\%3F/g,'?');
+ }
+
+ // Return result
+ return result;
+ };
+
+ /**
+ * History.getHashByUrl(url)
+ * Extracts the Hash from a URL
+ * @param {string} url
+ * @return {string} url
+ */
+ History.getHashByUrl = function(url){
+ // Extract the hash
+ var hash = String(url)
+ .replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
+ ;
+
+ // Unescape hash
+ hash = History.unescapeHash(hash);
+
+ // Return hash
+ return hash;
+ };
+
+ /**
+ * History.setTitle(title)
+ * Applies the title to the document
+ * @param {State} newState
+ * @return {Boolean}
+ */
+ History.setTitle = function(newState){
+ // Prepare
+ var title = newState.title,
+ firstState;
+
+ // Initial
+ if ( !title ) {
+ firstState = History.getStateByIndex(0);
+ if ( firstState && firstState.url === newState.url ) {
+ title = firstState.title||History.options.initialTitle;
+ }
+ }
+
+ // Apply
+ try {
+ document.getElementsByTagName('title')[0].innerHTML = title.replace('<','<').replace('>','>').replace(' & ',' & ');
+ }
+ catch ( Exception ) { }
+ document.title = title;
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // Queueing
+
+ /**
+ * History.queues
+ * The list of queues to use
+ * First In, First Out
+ */
+ History.queues = [];
+
+ /**
+ * History.busy(value)
+ * @param {boolean} value [optional]
+ * @return {boolean} busy
+ */
+ History.busy = function(value){
+ // Apply
+ if ( typeof value !== 'undefined' ) {
+ //History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
+ History.busy.flag = value;
+ }
+ // Default
+ else if ( typeof History.busy.flag === 'undefined' ) {
+ History.busy.flag = false;
+ }
+
+ // Queue
+ if ( !History.busy.flag ) {
+ // Execute the next item in the queue
+ clearTimeout(History.busy.timeout);
+ var fireNext = function(){
+ var i, queue, item;
+ if ( History.busy.flag ) return;
+ for ( i=History.queues.length-1; i >= 0; --i ) {
+ queue = History.queues[i];
+ if ( queue.length === 0 ) continue;
+ item = queue.shift();
+ History.fireQueueItem(item);
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
+ }
+ };
+ History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
+ }
+
+ // Return
+ return History.busy.flag;
+ };
+
+ /**
+ * History.busy.flag
+ */
+ History.busy.flag = false;
+
+ /**
+ * History.fireQueueItem(item)
+ * Fire a Queue Item
+ * @param {Object} item
+ * @return {Mixed} result
+ */
+ History.fireQueueItem = function(item){
+ return item.callback.apply(item.scope||History,item.args||[]);
+ };
+
+ /**
+ * History.pushQueue(callback,args)
+ * Add an item to the queue
+ * @param {Object} item [scope,callback,args,queue]
+ */
+ History.pushQueue = function(item){
+ // Prepare the queue
+ History.queues[item.queue||0] = History.queues[item.queue||0]||[];
+
+ // Add to the queue
+ History.queues[item.queue||0].push(item);
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.queue (item,queue), (func,queue), (func), (item)
+ * Either firs the item now if not busy, or adds it to the queue
+ */
+ History.queue = function(item,queue){
+ // Prepare
+ if ( typeof item === 'function' ) {
+ item = {
+ callback: item
+ };
+ }
+ if ( typeof queue !== 'undefined' ) {
+ item.queue = queue;
+ }
+
+ // Handle
+ if ( History.busy() ) {
+ History.pushQueue(item);
+ } else {
+ History.fireQueueItem(item);
+ }
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.clearQueue()
+ * Clears the Queue
+ */
+ History.clearQueue = function(){
+ History.busy.flag = false;
+ History.queues = [];
+ return History;
+ };
+
+
+ // ====================================================================
+ // IE Bug Fix
+
+ /**
+ * History.stateChanged
+ * States whether or not the state has changed since the last double check was initialised
+ */
+ History.stateChanged = false;
+
+ /**
+ * History.doubleChecker
+ * Contains the timeout used for the double checks
+ */
+ History.doubleChecker = false;
+
+ /**
+ * History.doubleCheckComplete()
+ * Complete a double check
+ * @return {History}
+ */
+ History.doubleCheckComplete = function(){
+ // Update
+ History.stateChanged = true;
+
+ // Clear
+ History.doubleCheckClear();
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.doubleCheckClear()
+ * Clear a double check
+ * @return {History}
+ */
+ History.doubleCheckClear = function(){
+ // Clear
+ if ( History.doubleChecker ) {
+ clearTimeout(History.doubleChecker);
+ History.doubleChecker = false;
+ }
+
+ // Chain
+ return History;
+ };
+
+ /**
+ * History.doubleCheck()
+ * Create a double check
+ * @return {History}
+ */
+ History.doubleCheck = function(tryAgain){
+ // Reset
+ History.stateChanged = false;
+ History.doubleCheckClear();
+
+ // Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
+ // Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
+ if ( History.bugs.ieDoubleCheck ) {
+ // Apply Check
+ History.doubleChecker = setTimeout(
+ function(){
+ History.doubleCheckClear();
+ if ( !History.stateChanged ) {
+ //History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
+ // Re-Attempt
+ tryAgain();
+ }
+ return true;
+ },
+ History.options.doubleCheckInterval
+ );
+ }
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // Safari Bug Fix
+
+ /**
+ * History.safariStatePoll()
+ * Poll the current state
+ * @return {History}
+ */
+ History.safariStatePoll = function(){
+ // Poll the URL
+
+ // Get the Last State which has the new URL
+ var
+ urlState = History.extractState(document.location.href),
+ newState;
+
+ // Check for a difference
+ if ( !History.isLastSavedState(urlState) ) {
+ newState = urlState;
+ }
+ else {
+ return;
+ }
+
+ // Check if we have a state with that url
+ // If not create it
+ if ( !newState ) {
+ //History.debug('History.safariStatePoll: new');
+ newState = History.createStateObject();
+ }
+
+ // Apply the New State
+ //History.debug('History.safariStatePoll: trigger');
+ History.Adapter.trigger(window,'popstate');
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // State Aliases
+
+ /**
+ * History.back(queue)
+ * Send the browser history back one item
+ * @param {Integer} queue [optional]
+ */
+ History.back = function(queue){
+ //History.debug('History.back: called', arguments);
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.back: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.back,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Fix certain browser bugs that prevent the state from changing
+ History.doubleCheck(function(){
+ History.back(false);
+ });
+
+ // Go back
+ history.go(-1);
+
+ // End back closure
+ return true;
+ };
+
+ /**
+ * History.forward(queue)
+ * Send the browser history forward one item
+ * @param {Integer} queue [optional]
+ */
+ History.forward = function(queue){
+ //History.debug('History.forward: called', arguments);
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.forward: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.forward,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Fix certain browser bugs that prevent the state from changing
+ History.doubleCheck(function(){
+ History.forward(false);
+ });
+
+ // Go forward
+ history.go(1);
+
+ // End forward closure
+ return true;
+ };
+
+ /**
+ * History.go(index,queue)
+ * Send the browser history back or forward index times
+ * @param {Integer} queue [optional]
+ */
+ History.go = function(index,queue){
+ //History.debug('History.go: called', arguments);
+
+ // Prepare
+ var i;
+
+ // Handle
+ if ( index > 0 ) {
+ // Forward
+ for ( i=1; i<=index; ++i ) {
+ History.forward(queue);
+ }
+ }
+ else if ( index < 0 ) {
+ // Backward
+ for ( i=-1; i>=index; --i ) {
+ History.back(queue);
+ }
+ }
+ else {
+ throw new Error('History.go: History.go requires a positive or negative integer passed.');
+ }
+
+ // Chain
+ return History;
+ };
+
+
+ // ====================================================================
+ // HTML5 State Support
+
+ // Non-Native pushState Implementation
+ if ( History.emulated.pushState ) {
+ /*
+ * Provide Skeleton for HTML4 Browsers
+ */
+
+ // Prepare
+ var emptyFunction = function(){};
+ History.pushState = History.pushState||emptyFunction;
+ History.replaceState = History.replaceState||emptyFunction;
+ } // History.emulated.pushState
+
+ // Native pushState Implementation
+ else {
+ /*
+ * Use native HTML5 History API Implementation
+ */
+
+ /**
+ * History.onPopState(event,extra)
+ * Refresh the Current State
+ */
+ History.onPopState = function(event,extra){
+ // Prepare
+ var stateId = false, newState = false, currentHash, currentState;
+
+ // Reset the double check
+ History.doubleCheckComplete();
+
+ // Check for a Hash, and handle apporiatly
+ currentHash = History.getHash();
+ if ( currentHash ) {
+ // Expand Hash
+ currentState = History.extractState(currentHash||document.location.href,true);
+ 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.title, currentState.url, false);
+ }
+ else {
+ // Traditional Anchor
+ //History.debug('History.onPopState: traditional anchor', currentHash);
+ History.Adapter.trigger(window,'anchorchange');
+ History.busy(false);
+ }
+
+ // We don't care for hashes
+ History.expectedStateId = false;
+ return false;
+ }
+
+ // Ensure
+ stateId = History.Adapter.extractEventData('state',event,extra) || false;
+
+ // Fetch State
+ if ( stateId ) {
+ // Vanilla: Back/forward button was used
+ newState = History.getStateById(stateId);
+ }
+ else if ( History.expectedStateId ) {
+ // Vanilla: A new state was pushed, and popstate was called manually
+ newState = History.getStateById(History.expectedStateId);
+ }
+ else {
+ // Initial State
+ newState = History.extractState(document.location.href);
+ }
+
+ // The State did not exist in our store
+ if ( !newState ) {
+ // Regenerate the State
+ newState = History.createStateObject(null,null,document.location.href);
+ }
+
+ // Clean
+ History.expectedStateId = false;
+
+ // Check if we are the same state
+ if ( History.isLastSavedState(newState) ) {
+ // There has been no change (just the page's hash has finally propagated)
+ //History.debug('History.onPopState: no change', newState, History.savedStates);
+ History.busy(false);
+ return false;
+ }
+
+ // Store the State
+ History.storeState(newState);
+ History.saveState(newState);
+
+ // Force update of the title
+ History.setTitle(newState);
+
+ // Fire Our Event
+ History.Adapter.trigger(window,'statechange');
+ History.busy(false);
+
+ // 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,queue){
+ //History.debug('History.pushState: called', arguments);
+
+ // Check the State
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.pushState: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.pushState,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Create the newState
+ var newState = History.createStateObject(data,title,url);
+
+ // Check it
+ if ( History.isLastSavedState(newState) ) {
+ // Won't be a change
+ History.busy(false);
+ }
+ else {
+ // Store the newState
+ History.storeState(newState);
+ History.expectedStateId = newState.id;
+
+ // Push the newState
+ history.pushState(newState.id,newState.title,newState.url);
+
+ // Fire HTML5 Event
+ History.Adapter.trigger(window,'popstate');
+ }
+
+ // End pushState closure
+ 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,queue){
+ //History.debug('History.replaceState: called', arguments);
+
+ // Check the State
+ if ( History.getHashByUrl(url) && History.emulated.pushState ) {
+ throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
+ }
+
+ // Handle Queueing
+ if ( queue !== false && History.busy() ) {
+ // Wait + Push to Queue
+ //History.debug('History.replaceState: we must wait', arguments);
+ History.pushQueue({
+ scope: History,
+ callback: History.replaceState,
+ args: arguments,
+ queue: queue
+ });
+ return false;
+ }
+
+ // Make Busy + Continue
+ History.busy(true);
+
+ // Create the newState
+ var newState = History.createStateObject(data,title,url);
+
+ // Check it
+ if ( History.isLastSavedState(newState) ) {
+ // Won't be a change
+ History.busy(false);
+ }
+ else {
+ // Store the newState
+ History.storeState(newState);
+ History.expectedStateId = newState.id;
+
+ // Push the newState
+ history.replaceState(newState.id,newState.title,newState.url);
+
+ // Fire HTML5 Event
+ History.Adapter.trigger(window,'popstate');
+ }
+
+ // End replaceState closure
+ return true;
+ };
+
+ } // !History.emulated.pushState
+
+
+ // ====================================================================
+ // Initialise
+
+ /**
+ * Load the Store
+ */
+ if ( sessionStorage ) {
+ // Fetch
+ try {
+ History.store = JSON.parse(sessionStorage.getItem('History.store'))||{};
+ }
+ catch ( err ) {
+ History.store = {};
+ }
+
+ // Normalize
+ History.normalizeStore();
+ }
+ else {
+ // Default Load
+ History.store = {};
+ History.normalizeStore();
+ }
+
+ /**
+ * Clear Intervals on exit to prevent memory leaks
+ */
+ History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
+ History.Adapter.bind(window,"unload",History.clearAllIntervals);
+
+ /**
+ * Create the initial State
+ */
+ History.saveState(History.storeState(History.extractState(document.location.href,true)));
+
+ /**
+ * Bind for Saving Store
+ */
+ if ( sessionStorage ) {
+ // When the page is closed
+ History.onUnload = function(){
+ // Prepare
+ var currentStore, item;
+
+ // Fetch
+ try {
+ currentStore = JSON.parse(sessionStorage.getItem('History.store'))||{};
+ }
+ catch ( err ) {
+ currentStore = {};
+ }
+
+ // Ensure
+ currentStore.idToState = currentStore.idToState || {};
+ currentStore.urlToId = currentStore.urlToId || {};
+ currentStore.stateToId = currentStore.stateToId || {};
+
+ // Sync
+ for ( item in History.idToState ) {
+ if ( !History.idToState.hasOwnProperty(item) ) {
+ continue;
+ }
+ currentStore.idToState[item] = History.idToState[item];
+ }
+ for ( item in History.urlToId ) {
+ if ( !History.urlToId.hasOwnProperty(item) ) {
+ continue;
+ }
+ currentStore.urlToId[item] = History.urlToId[item];
+ }
+ for ( item in History.stateToId ) {
+ if ( !History.stateToId.hasOwnProperty(item) ) {
+ continue;
+ }
+ currentStore.stateToId[item] = History.stateToId[item];
+ }
+
+ // Update
+ History.store = currentStore;
+ History.normalizeStore();
+
+ // Store
+ sessionStorage.setItem('History.store',JSON.stringify(currentStore));
+ };
+
+ // For Internet Explorer
+ History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
+
+ // For Other Browsers
+ History.Adapter.bind(window,'beforeunload',History.onUnload);
+ History.Adapter.bind(window,'unload',History.onUnload);
+
+ // Both are enabled for consistency
+ }
+
+ // Non-Native pushState Implementation
+ if ( !History.emulated.pushState ) {
+ // Be aware, the following is only for native pushState implementations
+ // If you are wanting to include something for all browsers
+ // Then include it above this if block
+
+ /**
+ * Setup Safari Fix
+ */
+ if ( History.bugs.safariPoll ) {
+ History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
+ }
+
+ /**
+ * Ensure Cross Browser Compatibility
+ */
+ if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
+ /**
+ * Fix Safari HashChange Issue
+ */
+
+ // Setup Alias
+ History.Adapter.bind(window,'hashchange',function(){
+ History.Adapter.trigger(window,'popstate');
+ });
+
+ // Initialise Alias
+ if ( History.getHash() ) {
+ History.Adapter.onDomLoad(function(){
+ History.Adapter.trigger(window,'hashchange');
+ });
+ }
+ }
+
+ } // !History.emulated.pushState
+
+
+ }; // History.initCore
+
+ // Try and Initialise History
+ History.init();
+
+})(window);
diff --git a/scripts/uncompressed/history.adapter.dojo.js b/scripts/uncompressed/history.adapter.dojo.js
new file mode 100644
index 00000000..9a7b5d45
--- /dev/null
+++ b/scripts/uncompressed/history.adapter.dojo.js
@@ -0,0 +1,81 @@
+/**
+ * History.js Dojo Adapter
+ * @author Lakin Wecker
+ * @copyright 2012-2012 Lakin Wecker
+ * @license New BSD License
+ */
+
+require(["dojo/on", "dojo/ready", "dojo/_base/lang"], function(on,ready, lang) {
+ "use strict";
+
+ // Localise Globals
+ var
+ History = window.History = window.History||{};
+
+ // Check Existence
+ if ( typeof History.Adapter !== 'undefined' ) {
+ throw new Error('History.js Adapter has already been loaded...');
+ }
+
+ // Add the Adapter
+ History.Adapter = {
+ /**
+ * History.Adapter.bind(el,event,callback)
+ * @param {Element|string} el
+ * @param {string} event - custom and standard events
+ * @param {function} callback
+ * @return {void}
+ */
+ bind: function(el,event,callback){
+ on(el,event,callback);
+ },
+
+ /**
+ * History.Adapter.trigger(el,event)
+ * @param {Element|string} el
+ * @param {string} event - custom and standard events
+ * @param {Object=} extra - a object of extra event data (optional)
+ * @return {void}
+ */
+ trigger: function(el,event,extra){
+ extra = extra || {};
+ lang.mixin(extra, {
+ bubbles: true,
+ cancelable: true
+ });
+
+ on.emit(el,event,extra);
+ },
+
+ /**
+ * History.Adapter.extractEventData(key,event,extra)
+ * @param {string} key - key for the event data to extract
+ * @param {string} event - custom and standard events
+ * @param {Object=} extra - a object of extra event data (optional)
+ * @return {mixed}
+ */
+ extractEventData: function(key,event,extra){
+ // dojo Native then dojo Custom
+ var result = (event && event[key]) || (extra && extra[key]) || undefined;
+
+ // Return
+ return result;
+ },
+
+ /**
+ * History.Adapter.onDomLoad(callback)
+ * @param {function} callback
+ * @return {void}
+ */
+ onDomLoad: function(callback) {
+ ready(callback);
+ }
+ };
+
+ // Try and Initialise History
+ if ( typeof History.init !== 'undefined' ) {
+ History.init();
+ }
+
+});
+
diff --git a/tests/html4+html5.dojo.html b/tests/html4+html5.dojo.html
new file mode 100644
index 00000000..11c8513b
--- /dev/null
+++ b/tests/html4+html5.dojo.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+ History.js HTML4+HTML5 Dojo Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test markup
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/html5.dojo.html b/tests/html5.dojo.html
new file mode 100644
index 00000000..66a7b373
--- /dev/null
+++ b/tests/html5.dojo.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+ History.js HTML5 Dojo Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test markup
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vendor/dojo.js b/vendor/dojo.js
new file mode 100644
index 00000000..9f5f7d5e
--- /dev/null
+++ b/vendor/dojo.js
@@ -0,0 +1,15 @@
+/*
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
+ Available via Academic Free License >= 2.1 OR the modified BSD license.
+ see: http://dojotoolkit.org/license for details
+*/
+
+/*
+ This is an optimized version of Dojo, built for deployment and not for
+ development. To get sources and documentation, please visit:
+
+ http://dojotoolkit.org
+*/
+
+//>>built
+(function(_1,_2){var _3=function(){},_4=function(it){for(var p in it){return 0;}return 1;},_5={}.toString,_6=function(it){return _5.call(it)=="[object Function]";},_7=function(it){return _5.call(it)=="[object String]";},_8=function(it){return _5.call(it)=="[object Array]";},_9=function(_a,_b){if(_a){for(var i=0;i<_a.length;){_b(_a[i++]);}}},_c=function(_d,_e){for(var p in _e){_d[p]=_e[p];}return _d;},_f=function(_10,_11){return _c(new Error(_10),{src:"dojoLoader",info:_11});},_12=1,uid=function(){return "_"+_12++;},req=function(_13,_14,_15){return _16(_13,_14,_15,0,req);},_17=this,doc=_17.document,_18=doc&&doc.createElement("DiV"),has=req.has=function(_19){return _6(_1a[_19])?(_1a[_19]=_1a[_19](_17,doc,_18)):_1a[_19];},_1a=has.cache=_2.hasCache;has.add=function(_1b,_1c,now,_1d){(_1a[_1b]===undefined||_1d)&&(_1a[_1b]=_1c);return now&&has(_1b);};false&&has.add("host-node",typeof process=="object"&&/node(\.exe)?$/.test(process.execPath));if(0){require("./_base/configNode.js").config(_2);_2.loaderPatch.nodeRequire=require;}false&&has.add("host-rhino",typeof load=="function"&&(typeof Packages=="function"||typeof Packages=="object"));if(0){for(var _1e=_1.baseUrl||".",arg,_1f=this.arguments,i=0;i<_1f.length;){arg=(_1f[i++]+"").split("=");if(arg[0]=="baseUrl"){_1e=arg[1];break;}}load(_1e+"/_base/configRhino.js");rhinoDojoConfig(_2,_1e,_1f);}for(var p in _1.has){has.add(p,_1.has[p],0,1);}var _20=1,_21=2,_22=3,_23=4,_24=5;if(0){_20="requested";_21="arrived";_22="not-a-module";_23="executing";_24="executed";}var _25=0,_26="sync",xd="xd",_27=[],_28=0,_29=_3,_2a=_3,_2b;if(1){req.isXdUrl=_3;req.initSyncLoader=function(_2c,_2d,_2e){if(!_28){_28=_2c;_29=_2d;_2a=_2e;}return {sync:_26,xd:xd,arrived:_21,nonmodule:_22,executing:_23,executed:_24,syncExecStack:_27,modules:_2f,execQ:_30,getModule:_31,injectModule:_32,setArrived:_33,signal:_34,finishExec:_35,execModule:_36,dojoRequirePlugin:_28,getLegacyMode:function(){return _25;},holdIdle:function(){_74++;},releaseIdle:function(){_37();}};};if(1){var _38=location.protocol,_39=location.host,_3a=!_39;req.isXdUrl=function(url){if(_3a||/^\./.test(url)){return false;}if(/^\/\//.test(url)){return true;}var _3b=url.match(/^([^\/\:]+\:)\/\/([^\/]+)/);return _3b&&(_3b[1]!=_38||_3b[2]!=_39);};true||has.add("dojo-xhr-factory",1);has.add("dojo-force-activex-xhr",1&&!doc.addEventListener&&window.location.protocol=="file:");has.add("native-xhr",typeof XMLHttpRequest!="undefined");if(has("native-xhr")&&!has("dojo-force-activex-xhr")){_2b=function(){return new XMLHttpRequest();};}else{for(var _3c=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],_3d,i=0;i<3;){try{_3d=_3c[i++];if(new ActiveXObject(_3d)){break;}}catch(e){}}_2b=function(){return new ActiveXObject(_3d);};}req.getXhr=_2b;has.add("dojo-gettext-api",1);req.getText=function(url,_3e,_3f){var xhr=_2b();xhr.open("GET",_40(url),false);xhr.send(null);if(xhr.status==200||(!location.host&&!xhr.status)){if(_3f){_3f(xhr.responseText,_3e);}}else{throw _f("xhrFailed",xhr.status);}return xhr.responseText;};}}else{req.async=1;}var _41=new Function("__text","return eval(__text);");req.eval=function(_42,_43){return _41(_42+"\r\n////@ sourceURL="+_43);};var _44={},_45="error",_34=req.signal=function(_46,_47){var _48=_44[_46];_9(_48&&_48.slice(0),function(_49){_49.apply(null,_8(_47)?_47:[_47]);});},on=req.on=function(_4a,_4b){var _4c=_44[_4a]||(_44[_4a]=[]);_4c.push(_4b);return {remove:function(){for(var i=0;i<_4c.length;i++){if(_4c[i]===_4b){_4c.splice(i,1);return;}}}};};var _4d=[],_4e={},_4f=[],_50={},_51={},_52=[],_2f={},_53="",_54={},_55={},_56={};if(1){var _57=function(_58){for(var p in _55){var _59=p.match(/^url\:(.+)/);if(_59){_54[_5a(_59[1],_58)]=_55[p];}else{if(p!="*noref"){_54[_5b(p,_58).mid]=_55[p];}}}_55={};},_5c=function(map,_5d,_5e){_5d.splice(0,_5d.length);var p,i,_5f,_60=0;for(p in map){_5d.push([p,map[p]]);if(map[p]==_5e){_60=p;}}_5d.sort(function(lhs,rhs){return rhs[0].length-lhs[0].length;});for(i=0;i<_5d.length;){_5f=_5d[i++];_5f[2]=new RegExp("^"+_5f[0].replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,function(c){return "\\"+c;})+"(/|$)");_5f[3]=_5f[0].length+1;}return _60;},_61=function(_62,_63){var _64=_62.name;if(!_64){_64=_62;_62={name:_64};}_62=_c({main:"main",mapProg:[]},_62);_62.location=(_63||"")+(_62.location?_62.location:_64);_62.reverseName=_5c(_62.packageMap,_62.mapProg,_64);if(!_62.main.indexOf("./")){_62.main=_62.main.substring(2);}_c(_4e,_62.paths);_50[_64]=_62;_51[_64]=_64;},_65=function(_66,_67){for(var p in _66){if(p=="waitSeconds"){req.waitms=(_66[p]||0)*1000;}if(p=="cacheBust"){_53=_66[p]?(_7(_66[p])?_66[p]:(new Date()).getTime()+""):"";}if(p=="baseUrl"||p=="combo"){req[p]=_66[p];}if(1&&p=="async"){var _68=_66[p];req.legacyMode=_25=(_7(_68)&&/sync|legacyAsync/.test(_68)?_68:(!_68?"sync":false));req.async=!_25;}if(_66[p]!==_1a){req.rawConfig[p]=_66[p];p!="has"&&has.add("config-"+p,_66[p],0,_67);}}if(!req.baseUrl){req.baseUrl="./";}if(!/\/$/.test(req.baseUrl)){req.baseUrl+="/";}for(p in _66.has){has.add(p,_66.has[p],0,_67);}_9(_66.packages,_61);for(_1e in _66.packagePaths){_9(_66.packagePaths[_1e],function(_69){_61(_69,_1e+"/");});}_5c(_c(_4e,_66.paths),_4f);_9(_66.aliases,function(_6a){if(_7(_6a[0])){_6a[0]=new RegExp("^"+_6a[0].replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,function(c){return "\\"+c;})+"$");}_4d.push(_6a);});_5c(_c(_51,_66.packageMap),_52);if(_66.cache){_57();_55=_66.cache;if(_66.cache["*noref"]){_57();}}_34("config",[_66,req.rawConfig]);};if(has("dojo-cdn")||1){for(var _6b,src,_6c,_6d=doc.getElementsByTagName("script"),i=0;i<_6d.length&&!_6c;i++){if((src=_6d[i].getAttribute("src"))&&(_6c=src.match(/(.*)\/?dojo\.js(\W|$)/i))){_1.baseUrl=_6b=_1.baseUrl||_2.baseUrl||_6c[1];src=(_6d[i].getAttribute("data-dojo-config")||_6d[i].getAttribute("djConfig"));if(src){_56=req.eval("({ "+src+" })","data-dojo-config");}if(0){var _6e=_6d[i].getAttribute("data-main");if(_6e){_56.deps=_56.deps||[_6e];}}}}}if(0){try{if(window.parent!=window&&window.parent.require){var doh=window.parent.require("doh");doh&&_c(_56,doh.testConfig);}}catch(e){}}req.rawConfig={};_65(_2,1);_65(_1,1);_65(_56,1);if(has("dojo-cdn")){_50.dojo.location=_6b;_50.dijit.location=_6b+"../dijit/";_50.dojox.location=_6b+"../dojox/";}}else{_4e=_2.paths;_4f=_2.pathsMapProg;_50=_2.packs;_4d=_2.aliases;_51=_2.packageMap;_52=_2.packageMapProg;_2f=_2.modules;_54=_2.cache;_53=_2.cacheBust;req.rawConfig=_2;}if(0){req.combo=req.combo||{add:_3};var _6f=0,_70=[],_71=null;}var _72=function(_73){_74++;_9(_73.deps,_32);if(0&&_6f&&!_71){_71=setTimeout(function(){_6f=0;_71=null;req.combo.done(function(_75,url){var _76=function(){_77(0,_75);_78();};_70.push(_75);_79=_75;req.injectUrl(url,_76,_75);_79=0;},req);},0);}_37();},_16=function(a1,a2,a3,_7a,_7b){var _7c,_7d;if(_7(a1)){_7c=_31(a1,_7a,true);if(_7c&&_7c.executed){return _7c.result;}throw _f("undefinedModule",a1);}if(!_8(a1)){_65(a1);a1=a2;a2=a3;}if(_8(a1)){if(!a1.length){a2&&a2();}else{_7d="require*"+uid();for(var mid,_7e=[],i=0;i_a6){_a7=_6(_a8[1])?mid.replace(_a8[0],_a8[1]):_a8[1];}});if(_a7){return _95(_a7,0,_97,_98,_99,_9a,_9b,_9c);}_a2=_98[mid];if(_a2){return _9c?_7f(_a2.pid,_a2.mid,_a2.pack,_a2.url,_a5):_98[mid];}}_a0=_8c(mid,_9b);if(_a0){url=_a0[1]+mid.substring(_a0[3]-1);}else{if(pid){url=_9d.location+"/"+_9e;}else{if(has("config-tlmSiblingOfDojo")){url="../"+mid;}else{url=mid;}}}if(!(/(^\/)|(\:)/.test(url))){url=_99+url;}url+=".js";return _7f(pid,mid,_9d,_8e(url),_a5);},_5b=function(mid,_aa){return _95(mid,_aa,_50,_2f,req.baseUrl,_52,_4f);},_ab=function(_ac,_ad,_ae){return _ac.normalize?_ac.normalize(_ad,function(mid){return _af(mid,_ae);}):_af(_ad,_ae);},_b0=0,_31=function(mid,_b1,_b2){var _b3,_b4,_b5,_b6;_b3=mid.match(/^(.+?)\!(.*)$/);if(_b3){_b4=_31(_b3[1],_b1,_b2);if(1&&_25==_26&&!_b4.executed){_32(_b4);if(_b4.injected===_21&&!_b4.executed){_74++;_36(_b4);_37();}if(_b4.executed){_b7(_b4);}else{_30.unshift(_b4);}}if(_b4.executed===_24&&!_b4.load){_b7(_b4);}if(_b4.load){_b5=_ab(_b4,_b3[2],_b1);mid=(_b4.mid+"!"+(_b4.dynamic?++_b0+"!":"")+_b5);}else{_b5=_b3[2];mid=_b4.mid+"!"+(++_b0)+"!waitingForPlugin";}_b6={plugin:_b4,mid:mid,req:_81(_b1),prid:_b5};}else{_b6=_5b(mid,_b1);}return _2f[_b6.mid]||(!_b2&&(_2f[_b6.mid]=_b6));},_af=req.toAbsMid=function(mid,_b8){return _5b(mid,_b8).mid;},_5a=req.toUrl=function(_b9,_ba){var _bb=_b9.match(/(.+)(\.[^\/\.]+?)$/),_bc=(_bb&&_bb[1])||_b9,ext=(_bb&&_bb[2])||"",_bd=_5b(_bc,_ba),url=_bd.url;url=typeof _bd.pid=="string"?url.substring(0,url.length-3):url;return _40(url+ext);},_be={injected:_21,executed:_24,def:_22,result:_22},_bf=function(mid){return _2f[mid]=_c({mid:mid},_be);},_c0=_bf("require"),_c1=_bf("exports"),_c2=_bf("module"),_c3=function(_c4,_c5){req.trace("loader-run-factory",[_c4.mid]);var _c6=_c4.def,_c7;1&&_27.unshift(_c4);if(has("config-dojo-loader-catches")){try{_c7=_6(_c6)?_c6.apply(null,_c5):_c6;}catch(e){_34(_45,_c4.result=_f("factoryThrew",[_c4,e]));}}else{_c7=_6(_c6)?_c6.apply(null,_c5):_c6;}_c4.result=_c7===undefined&&_c4.cjs?_c4.cjs.exports:_c7;1&&_27.shift(_c4);},_c8={},_c9=0,_b7=function(_ca){var _cb=_ca.result;_ca.dynamic=_cb.dynamic;_ca.normalize=_cb.normalize;_ca.load=_cb.load;return _ca;},_cc=function(_cd){var map={};_9(_cd.loadQ,function(_ce){var _cf=_ce.mid,_d0=_ab(_cd,_ce.prid,_ce.req.module),mid=_cd.dynamic?_ce.mid.replace(/waitingForPlugin$/,_d0):(_cd.mid+"!"+_d0),_d1=_c(_c({},_ce),{mid:mid,prid:_d0,injected:0});if(!_2f[mid]){_e2(_2f[mid]=_d1);}map[_ce.mid]=_2f[mid];_33(_ce);delete _2f[_ce.mid];});_cd.loadQ=0;var _d2=function(_d3){for(var _d4,_d5=_d3.deps||[],i=0;i<_d5.length;i++){_d4=map[_d5[i].mid];if(_d4){_d5[i]=_d4;}}};for(var p in _2f){_d2(_2f[p]);}_9(_30,_d2);},_35=function(_d6){req.trace("loader-finish-exec",[_d6.mid]);_d6.executed=_24;_d6.defOrder=_c9++;1&&_9(_d6.provides,function(cb){cb();});if(_d6.loadQ){_b7(_d6);_cc(_d6);}for(i=0;i<_30.length;){if(_30[i]===_d6){_30.splice(i,1);}else{i++;}}},_d7=[],_36=function(_d8,_d9){if(_d8.executed===_23){req.trace("loader-circular-dependency",[_d7.concat(mid).join("->")]);return (!_d8.def||_d9)?_c8:(_d8.cjs&&_d8.cjs.exports);}if(!_d8.executed){if(!_d8.def){return _c8;}var mid=_d8.mid,_da=_d8.deps||[],arg,_db,_dc=[],i=0;if(0){_d7.push(mid);req.trace("loader-exec-module",["exec",_d7.length,mid]);}_d8.executed=_23;while(i<_da.length){arg=_da[i++];_db=((arg===_c0)?_81(_d8):((arg===_c1)?_d8.cjs.exports:((arg===_c2)?_d8.cjs:_36(arg,_d9))));if(_db===_c8){_d8.executed=0;req.trace("loader-exec-module",["abort",mid]);0&&_d7.pop();return _c8;}_dc.push(_db);}_c3(_d8,_dc);_35(_d8);}0&&_d7.pop();return _d8.result;},_74=0,_78=function(){if(_74){return;}_74++;_29();for(var _dd,_de,i=0;i<_30.length;){_dd=_c9;_de=_30[i];_36(_de);if(_dd!=_c9){_29();i=0;}else{i++;}}_37();},_37=function(){_74--;if(_8b()){_34("idle",[]);}};if(0){req.undef=function(_df,_e0){var _e1=_31(_df,_e0);_33(_e1);delete _2f[_e1.mid];};}if(1){if(has("dojo-loader-eval-hint-url")===undefined){has.add("dojo-loader-eval-hint-url",1);}var _40=function(url){url+="";return url+(_53?((/\?/.test(url)?"&":"?")+_53):"");},_e2=function(_e3){var _e4=_e3.plugin;if(_e4.executed===_24&&!_e4.load){_b7(_e4);}var _e5=function(def){_e3.result=def;_33(_e3);_35(_e3);_78();};_87(_e3);if(_e4.load){_e4.load(_e3.prid,_e3.req,_e5);}else{if(_e4.loadQ){_e4.loadQ.push(_e3);}else{_30.unshift(_e4);_32(_e4);if(_e4.load){_e4.load(_e3.prid,_e3.req,_e5);}else{_e4.loadQ=[_e3];}}}},_e6=0,_79=0,_e7=0,_e8=function(_e9,_ea){_e7=1;if(has("config-dojo-loader-catches")){try{if(_e9===_e6){_e6.call(null);}else{req.eval(_e9,has("dojo-loader-eval-hint-url")?_ea.url:_ea.mid);}}catch(e){_34(_45,_f("evalModuleThrew",_ea));}}else{if(_e9===_e6){_e6.call(null);}else{req.eval(_e9,has("dojo-loader-eval-hint-url")?_ea.url:_ea.mid);}}_e7=0;},_32=function(_eb){var mid=_eb.mid,url=_eb.url;if(_eb.executed||_eb.injected||_86[mid]||(_eb.url&&((_eb.pack&&_86[_eb.url]===_eb.pack)||_86[_eb.url]==1))){return;}if(0){var _ec=0;if(_eb.plugin&&_eb.plugin.isCombo){req.combo.add(_eb.plugin.mid,_eb.prid,0,req);_ec=1;}else{if(!_eb.plugin){_ec=req.combo.add(0,_eb.mid,_eb.url,req);}}if(_ec){_87(_eb);_6f=1;return;}}if(_eb.plugin){_e2(_eb);return;}_87(_eb);var _ed=function(){_77(_eb);if(_eb.injected!==_21){_33(_eb);_c(_eb,_be);}if(1&&_25){!_27.length&&_78();}else{_78();}};_e6=_54[mid]||_54[_eb.cacheId];if(_e6){req.trace("loader-inject",["cache",_eb.mid,url]);_e8(_e6,_eb);_ed();return;}if(1&&_25){if(_eb.isXd){_25==_26&&(_25=xd);}else{if(_eb.isAmd&&_25!=_26){}else{var _ee=function(_ef){if(_25==_26){_27.unshift(_eb);_e8(_ef,_eb);_27.shift();_77(_eb);if(!_eb.cjs){_33(_eb);_35(_eb);}if(_eb.finish){var _f0=mid+"*finish",_f1=_eb.finish;delete _eb.finish;def(_f0,["dojo",("dojo/require!"+_f1.join(",")).replace(/\./g,"/")],function(_f2){_9(_f1,function(mid){_f2.require(mid);});});_30.unshift(_31(_f0));}_ed();}else{_ef=_2a(_eb,_ef);if(_ef){_e8(_ef,_eb);_ed();}else{_79=_eb;req.injectUrl(_40(url),_ed,_eb);_79=0;}}};req.trace("loader-inject",["xhr",_eb.mid,url,_25!=_26]);if(has("config-dojo-loader-catches")){try{req.getText(url,_25!=_26,_ee);}catch(e){_34(_45,_f("xhrInjectFailed",[_eb,e]));}}else{req.getText(url,_25!=_26,_ee);}return;}}}req.trace("loader-inject",["script",_eb.mid,url]);_79=_eb;req.injectUrl(_40(url),_ed,_eb);_79=0;},_f3=function(_f4,_f5,def){req.trace("loader-define-module",[_f4.mid,_f5]);if(0&&_f4.plugin&&_f4.plugin.isCombo){_f4.result=_6(def)?def():def;_33(_f4);_35(_f4);return _f4;}var mid=_f4.mid;if(_f4.injected===_21){_34(_45,_f("multipleDefine",_f4));return _f4;}_c(_f4,{deps:_f5,def:def,cjs:{id:_f4.mid,uri:_f4.url,exports:(_f4.result={}),setExports:function(_f6){_f4.cjs.exports=_f6;}}});for(var i=0;i<_f5.length;i++){_f5[i]=_31(_f5[i],_f4);}if(1&&_25&&!_86[mid]){_72(_f4);_30.push(_f4);_78();}_33(_f4);if(!_6(def)&&!_f5.length){_f4.result=def;_35(_f4);}return _f4;},_77=function(_f7,_f8){_57(_f7);var _f9=[],_fa,_fb;while(_85.length){_fb=_85.shift();_f8&&(_fb[0]=_f8.shift());_fa=_fb[0]&&_31(_fb[0])||_f7;_f9.push(_f3(_fa,_fb[1],_fb[2]));}_9(_f9,_72);};}var _fc=0,_8a=_3,_fd=_3;if(1){_8a=function(){_fc&&clearTimeout(_fc);_fc=0;},_fd=function(){_8a();req.waitms&&(_fc=setTimeout(function(){_8a();_34(_45,_f("timeout",_86));},req.waitms));};}if(1){has.add("ie-event-behavior",doc.attachEvent&&(typeof opera==="undefined"||opera.toString()!="[object Opera]"));}if(1&&(1||1)){var _fe=function(_ff,_100,_101,_102){if(!has("ie-event-behavior")){_ff.addEventListener(_100,_102,false);return function(){_ff.removeEventListener(_100,_102,false);};}else{_ff.attachEvent(_101,_102);return function(){_ff.detachEvent(_101,_102);};}},_103=_fe(window,"load","onload",function(){req.pageLoaded=1;doc.readyState!="complete"&&(doc.readyState="complete");_103();});if(1){var _104=doc.getElementsByTagName("script")[0],_105=_104.parentNode;req.injectUrl=function(url,_106,_107){_fd();var node=_107.node=doc.createElement("script"),_108=function(e){e=e||window.event;var node=e.target||e.srcElement;if(e.type==="load"||/complete|loaded/.test(node.readyState)){_109();_106&&_106();}},_109=_fe(node,"load","onreadystatechange",_108);node.type="text/javascript";node.charset="utf-8";node.src=url;_105.insertBefore(node,_104);return node;};}}if(1){req.log=function(){try{for(var i=0;i0){_124._delayTimer=setTimeout(_125,de);return _124;}_125();return _124;},_play:function(_126){var _127=this;if(_127._delayTimer){_127._clearTimer();}_127._startTime=new Date().valueOf();if(_127._paused){_127._startTime-=_127.duration*_127._percent;}_127._active=true;_127._paused=false;var _128=_127.curve.getValue(_127._getStep());if(!_127._percent){if(!_127._startRepeatCount){_127._startRepeatCount=_127.repeat;}_127._fire("onBegin",[_128]);}_127._fire("onPlay",[_128]);_127._cycle();return _127;},pause:function(){var _129=this;if(_129._delayTimer){_129._clearTimer();}_129._stopTimer();if(!_129._active){return _129;}_129._paused=true;_129._fire("onPause",[_129.curve.getValue(_129._getStep())]);return _129;},gotoPercent:function(_12a,_12b){var _12c=this;_12c._stopTimer();_12c._active=_12c._paused=true;_12c._percent=_12a;if(_12b){_12c.play();}return _12c;},stop:function(_12d){var _12e=this;if(_12e._delayTimer){_12e._clearTimer();}if(!_12e._timer){return _12e;}_12e._stopTimer();if(_12d){_12e._percent=1;}_12e._fire("onStop",[_12e.curve.getValue(_12e._getStep())]);_12e._active=_12e._paused=false;return _12e;},status:function(){if(this._active){return this._paused?"paused":"playing";}return "stopped";},_cycle:function(){var _12f=this;if(_12f._active){var curr=new Date().valueOf();var step=(curr-_12f._startTime)/(_12f.duration);if(step>=1){step=1;}_12f._percent=step;if(_12f.easing){step=_12f.easing(step);}_12f._fire("onAnimate",[_12f.curve.getValue(step)]);if(_12f._percent<1){_12f._startTimer();}else{_12f._active=false;if(_12f.repeat>0){_12f.repeat--;_12f.play(null,true);}else{if(_12f.repeat==-1){_12f.play(null,true);}else{if(_12f._startRepeatCount){_12f.repeat=_12f._startRepeatCount;_12f._startRepeatCount=0;}}}_12f._percent=0;_12f._fire("onEnd",[_12f.node]);!_12f.repeat&&_12f._stopTimer();}}return _12f;},_clearTimer:function(){clearTimeout(this._delayTimer);delete this._delayTimer;}});var ctr=0,_130=null,_131={run:function(){}};lang.extend(dojo.Animation,{_startTimer:function(){if(!this._timer){this._timer=_11c.connect(_131,"run",this,"_cycle");ctr++;}if(!_130){_130=setInterval(lang.hitch(_131,"run"),this.rate);}},_stopTimer:function(){if(this._timer){_11c.disconnect(this._timer);this._timer=null;ctr--;}if(ctr<=0){clearInterval(_130);_130=null;ctr=0;}}});var _132=has("ie")?function(node){var ns=node.style;if(!ns.width.length&&_11d.get(node,"width")=="auto"){ns.width="auto";}}:function(){};dojo._fade=function(args){args.node=dom.byId(args.node);var _133=_11e({properties:{}},args),_134=(_133.properties.opacity={});_134.start=!("start" in _133)?function(){return +_11d.get(_133.node,"opacity")||0;}:_133.start;_134.end=_133.end;var anim=dojo.animateProperty(_133);_11c.connect(anim,"beforeBegin",lang.partial(_132,_133.node));return anim;};dojo.fadeIn=function(args){return dojo._fade(_11e({end:1},args));};dojo.fadeOut=function(args){return dojo._fade(_11e({end:0},args));};dojo._defaultEasing=function(n){return 0.5+((Math.sin((n+1.5)*Math.PI))/2);};var _135=function(_136){this._properties=_136;for(var p in _136){var prop=_136[p];if(prop.start instanceof _11b){prop.tempColor=new _11b();}}};_135.prototype.getValue=function(r){var ret={};for(var p in this._properties){var prop=this._properties[p],_137=prop.start;if(_137 instanceof _11b){ret[p]=_11b.blendColors(_137,prop.end,r,prop.tempColor).toCss();}else{if(!lang.isArray(_137)){ret[p]=((prop.end-_137)*r)+_137+(p!="opacity"?prop.units||"px":0);}}}return ret;};dojo.animateProperty=function(args){var n=args.node=dom.byId(args.node);if(!args.easing){args.easing=dojo._defaultEasing;}var anim=new dojo.Animation(args);_11c.connect(anim,"beforeBegin",anim,function(){var pm={};for(var p in this.properties){if(p=="width"||p=="height"){this.node.display="block";}var prop=this.properties[p];if(lang.isFunction(prop)){prop=prop(n);}prop=pm[p]=_11e({},(lang.isObject(prop)?prop:{end:prop}));if(lang.isFunction(prop.start)){prop.start=prop.start(n);}if(lang.isFunction(prop.end)){prop.end=prop.end(n);}var _138=(p.toLowerCase().indexOf("color")>=0);function _139(node,p){var v={height:node.offsetHeight,width:node.offsetWidth}[p];if(v!==undefined){return v;}v=_11d.get(node,p);return (p=="opacity")?+v:(_138?v:parseFloat(v));};if(!("end" in prop)){prop.end=_139(n,p);}else{if(!("start" in prop)){prop.start=_139(n,p);}}if(_138){prop.start=new _11b(prop.start);prop.end=new _11b(prop.end);}else{prop.start=(p=="opacity")?+prop.start:parseFloat(prop.start);}}this.curve=new _135(pm);});_11c.connect(anim,"onAnimate",lang.hitch(_11d,"set",anim.node));return anim;};dojo.anim=function(node,_13a,_13b,_13c,_13d,_13e){return dojo.animateProperty({node:node,duration:_13b||dojo.Animation.prototype.duration,properties:_13a,easing:_13c,onEnd:_13d}).play(_13e||0);};return {_Line:dojo._Line,Animation:dojo.Animation,_fade:dojo._fade,fadeIn:dojo.fadeIn,fadeOut:dojo.fadeOut,_defaultEasing:dojo._defaultEasing,animateProperty:dojo.animateProperty,anim:dojo.anim};});},"dojo/dom-form":function(){define("dojo/dom-form",["./_base/lang","./dom","./io-query","./json"],function(lang,dom,ioq,json){function _13f(obj,name,_140){if(_140===null){return;}var val=obj[name];if(typeof val=="string"){obj[name]=[val,_140];}else{if(lang.isArray(val)){val.push(_140);}else{obj[name]=_140;}}};var _141="file|submit|image|reset|button";var form={fieldToObject:function fieldToObject(_142){var ret=null;_142=dom.byId(_142);if(_142){var _143=_142.name,type=(_142.type||"").toLowerCase();if(_143&&type&&!_142.disabled){if(type=="radio"||type=="checkbox"){if(_142.checked){ret=_142.value;}}else{if(_142.multiple){ret=[];var _144=[_142.firstChild];while(_144.length){for(var node=_144.pop();node;node=node.nextSibling){if(node.nodeType==1&&node.tagName.toLowerCase()=="option"){if(node.selected){ret.push(node.value);}}else{if(node.nextSibling){_144.push(node.nextSibling);}if(node.firstChild){_144.push(node.firstChild);}break;}}}}else{ret=_142.value;}}}}return ret;},toObject:function formToObject(_145){var ret={},_146=dom.byId(_145).elements;for(var i=0,l=_146.length;i=0;i--){var node=(_1ee?this._cloneNode(ary[i]):ary[i]);if(ary._runParse&&dojo.parser&&dojo.parser.parse){if(!_1f0){_1f0=_1ef.ownerDocument.createElement("div");}_1f0.appendChild(node);dojo.parser.parse(_1f0);node=_1f0.firstChild;while(_1f0.firstChild){_1f0.removeChild(_1f0.firstChild);}}if(i==_1f1-1){_1de.place(node,_1ef,_1ed);}else{_1ef.parentNode.insertBefore(node,_1ef);}_1ef=node;}},attr:awc(_1e5(_1e0),_1e2),style:awc(_1e5(_1e1),_1e2),addClass:aafe(_1dd.add),removeClass:aafe(_1dd.remove),replaceClass:aafe(_1dd.replace),toggleClass:aafe(_1dd.toggle),empty:aafe(_1de.empty),removeAttr:aafe(_1e0.remove),position:aam(_1df.position),marginBox:aam(_1df.getMarginBox),place:function(_1f2,_1f3){var item=_1db(_1f2)[0];return this.forEach(function(node){_1de.place(node,item,_1f3);});},orphan:function(_1f4){return (_1f4?_1db._filterResult(this,_1f4):this).forEach(_1e3);},adopt:function(_1f5,_1f6){return _1db(_1f5).place(this[0],_1f6)._stash(this);},query:function(_1f7){if(!_1f7){return this;}var ret=new _1e4;this.map(function(node){_1db(_1f7,node).forEach(function(_1f8){if(_1f8!==undefined){ret.push(_1f8);}});});return ret._stash(this);},filter:function(_1f9){var a=arguments,_1fa=this,_1fb=0;if(typeof _1f9=="string"){_1fa=_1db._filterResult(this,a[0]);if(a.length==1){return _1fa._stash(this);}_1fb=1;}return this._wrap(_1dc.filter(_1fa,a[_1fb],a[_1fb+1]),this);},addContent:function(_1fc,_1fd){_1fc=this._normalize(_1fc,this[0]);for(var i=0,node;(node=this[i]);i++){this._place(_1fc,node,_1fd,i>0);}return this;}});return _1e4;});},"dojo/query":function(){define(["./_base/kernel","./has","./dom","./on","./_base/array","./_base/lang","./selector/_loader","./selector/_loader!default"],function(dojo,has,dom,on,_1fe,lang,_1ff,_200){"use strict";has.add("array-extensible",function(){return lang.delegate([],{length:1}).length==1&&!has("bug-for-in-skips-shadowed");});var ap=Array.prototype,aps=ap.slice,apc=ap.concat,_201=_1fe.forEach;var tnl=function(a,_202,_203){var _204=new (_203||this._NodeListCtor||nl)(a);return _202?_204._stash(_202):_204;};var _205=function(f,a,o){a=[0].concat(aps.call(a,0));o=o||dojo.global;return function(node){a[0]=node;return f.apply(o,a);};};var _206=function(f,o){return function(){this.forEach(_205(f,arguments,o));return this;};};var _207=function(f,o){return function(){return this.map(_205(f,arguments,o));};};var _208=function(f,o){return function(){return this.filter(_205(f,arguments,o));};};var _209=function(f,g,o){return function(){var a=arguments,body=_205(f,a,o);if(g.call(o||dojo.global,a)){return this.map(body);}this.forEach(body);return this;};};var _20a=function(_20b){var _20c=this instanceof nl&&has("array-extensible");if(typeof _20b=="number"){_20b=Array(_20b);}var _20d=(_20b&&"length" in _20b)?_20b:arguments;if(_20c||!_20d.sort){var _20e=_20c?this:[],l=_20e.length=_20d.length;for(var i=0;i0;};_21f.filter=_21d.filter||function(_223,_224,root){return _21f(_224,root).filter(function(node){return _1fe.indexOf(_223,node)>-1;});};if(typeof _21d!="function"){var _225=_21d.search;_21d=function(_226,root){return _225(root||document,_226);};}return _21f;};var _219=_21c(_200,_20a);dojo.query=_21c(_200,function(_227){return _20a(_227);});_219.load=function(id,_228,_229,_22a){_1ff.load(id,_228,function(_22b){_229(_21c(_22b,_20a));});};dojo._filterQueryResult=_219._filterResult=function(_22c,_22d,root){return new _20a(_219.filter(_22c,_22d,root));};dojo.NodeList=_219.NodeList=_20a;return _219;});},"dojo/has":function(){define(["require"],function(_22e){var has=_22e.has||function(){};if(!1){var _22f=typeof window!="undefined"&&typeof location!="undefined"&&typeof document!="undefined"&&window.location==location&&window.document==document,_230=this,doc=_22f&&document,_231=doc&&doc.createElement("DiV"),_232={};has=function(name){return typeof _232[name]=="function"?(_232[name]=_232[name](_230,doc,_231)):_232[name];};has.cache=_232;has.add=function(name,test,now,_233){(typeof _232[name]=="undefined"||_233)&&(_232[name]=test);return now&&has(name);};true||has.add("host-browser",_22f);true||has.add("dom",_22f);true||has.add("dojo-dom-ready-api",1);true||has.add("dojo-sniff",1);}if(1){var _234=navigator.userAgent;has.add("dom-addeventlistener",!!document.addEventListener);has.add("touch","ontouchstart" in document);has.add("device-width",screen.availWidth||innerWidth);has.add("agent-ios",!!_234.match(/iPhone|iP[ao]d/));has.add("agent-android",_234.indexOf("android")>1);}has.clearElement=function(_235){_235.innerHTML="";return _235;};has.normalize=function(id,_236){var _237=id.match(/[\?:]|[^:\?]*/g),i=0,get=function(skip){var term=_237[i++];if(term==":"){return 0;}else{if(_237[i++]=="?"){if(!skip&&has(term)){return get();}else{get(true);return get(skip);}}return term||0;}};id=get();return id&&_236(id);};has.load=function(id,_238,_239){if(id){_238([id],_239);}else{_239();}};return has;});},"dojo/_base/loader":function(){define(["./kernel","../has","require","module","./json","./lang","./array"],function(dojo,has,_23a,_23b,json,lang,_23c){if(!1){console.error("cannot load the Dojo v1.x loader with a foreign loader");return 0;}var _23d=function(id){return {src:_23b.id,id:id};},_23e=function(name){return name.replace(/\./g,"/");},_23f=/\/\/>>built/,_240=[],_241=[],_242=function(mid,_243,_244){_240.push(_244);_23c.forEach(mid.split(","),function(mid){var _245=_246(mid,_243.module);_241.push(_245);_247(_245);});_248();},_249,_24a=function(m){if(_249[m.mid]||/loadInit\!/.test(m.mid)){return true;}_249[m.mid]=1;if(m.injected!==_24b&&!m.executed){return false;}for(var deps=m.deps||[],i=0;i=0;--j){_2ad=lin[j].prototype;if(!_2ad.hasOwnProperty("declaredClass")){_2ad.declaredClass="uniqName_"+(_2a4++);}name=_2ad.declaredClass;if(!_2ab.hasOwnProperty(name)){_2ab[name]={count:0,refs:[],cls:lin[j]};++_2ac;}rec=_2ab[name];if(top&&top!==rec){rec.refs.push(top);++top.count;}top=rec;}++top.count;_2aa[0].refs.push(top);}while(_2aa.length){top=_2aa.pop();_2a9.push(top.cls);--_2ac;while(refs=top.refs,refs.length==1){top=refs[0];if(!top||--top.count){top=0;break;}_2a9.push(top.cls);--_2ac;}if(top){for(i=0,l=refs.length;i=0;--i){f=_2c5[i];m=f._meta;f=m?m.ctor:f;if(f){f.apply(this,_2c7?_2c7[i]:a);}}f=this.postscript;if(f){f.apply(this,args);}};};function _2c9(ctor,_2ca){return function(){var a=arguments,t=a,a0=a[0],f;if(!(this instanceof a.callee)){return _2c8(a);}if(_2ca){if(a0){f=a0.preamble;if(f){t=f.apply(this,t)||t;}}f=this.preamble;if(f){f.apply(this,t);}}if(ctor){ctor.apply(this,a);}f=this.postscript;if(f){f.apply(this,a);}};};function _2cb(_2cc){return function(){var a=arguments,i=0,f,m;if(!(this instanceof a.callee)){return _2c8(a);}for(;f=_2cc[i];++i){m=f._meta;f=m?m.ctor:f;if(f){f.apply(this,a);break;}}f=this.postscript;if(f){f.apply(this,a);}};};function _2cd(name,_2ce,_2cf){return function(){var b,m,f,i=0,step=1;if(_2cf){i=_2ce.length-1;step=-1;}for(;b=_2ce[i];i+=step){m=b._meta;f=(m?m.hidden:b.prototype)[name];if(f){f.apply(this,arguments);}}};};function _2d0(ctor){xtor.prototype=ctor.prototype;var t=new xtor;xtor.prototype=null;return t;};function _2c8(args){var ctor=args.callee,t=_2d0(ctor);ctor.apply(t,args);return t;};function _2c3(_2d1,_2d2,_2d3){if(typeof _2d1!="string"){_2d3=_2d2;_2d2=_2d1;_2d1="";}_2d3=_2d3||{};var _2d4,i,t,ctor,name,_2d5,_2d6,_2d7=1,_2d8=_2d2;if(opts.call(_2d2)=="[object Array]"){_2d5=_2a6(_2d2,_2d1);t=_2d5[0];_2d7=_2d5.length-t;_2d2=_2d5[_2d7];}else{_2d5=[0];if(_2d2){if(opts.call(_2d2)=="[object Function]"){t=_2d2._meta;_2d5=_2d5.concat(t?t.bases:_2d2);}else{err("base class is not a callable constructor.",_2d1);}}else{if(_2d2!==null){err("unknown base class. Did you use dojo.require to pull it in?",_2d1);}}}if(_2d2){for(i=_2d7-1;;--i){_2d4=_2d0(_2d2);if(!i){break;}t=_2d5[i];(t._meta?_2b9:mix)(_2d4,t.prototype);ctor=new Function;ctor.superclass=_2d2;ctor.prototype=_2d4;_2d2=_2d4.constructor=ctor;}}else{_2d4={};}_2c3.safeMixin(_2d4,_2d3);t=_2d3.constructor;if(t!==op.constructor){t.nom=_2a5;_2d4.constructor=t;}for(i=_2d7-1;i;--i){t=_2d5[i]._meta;if(t&&t.chains){_2d6=mix(_2d6||{},t.chains);}}if(_2d4["-chains-"]){_2d6=mix(_2d6||{},_2d4["-chains-"]);}t=!_2d6||!_2d6.hasOwnProperty(_2a5);_2d5[0]=ctor=(_2d6&&_2d6.constructor==="manual")?_2cb(_2d5):(_2d5.length==1?_2c9(_2d3.constructor,t):_2c4(_2d5,t));ctor._meta={bases:_2d5,hidden:_2d3,chains:_2d6,parents:_2d8,ctor:_2d3.constructor};ctor.superclass=_2d2&&_2d2.prototype;ctor.extend=_2c1;ctor.prototype=_2d4;_2d4.constructor=ctor;_2d4.getInherited=_2b4;_2d4.isInstanceOf=_2b7;_2d4.inherited=_2b6;_2d4.__inherited=_2ae;if(_2d1){_2d4.declaredClass=_2d1;lang.setObject(_2d1,ctor);}if(_2d6){for(name in _2d6){if(_2d4[name]&&typeof _2d6[name]=="string"&&name!=_2a5){t=_2d4[name]=_2cd(name,_2d5,_2d6[name]==="after");t.nom=name;}}}return ctor;};dojo.safeMixin=_2c3.safeMixin=_2bd;dojo.declare=_2c3;return _2c3;});},"dojo/dom":function(){define(["./_base/sniff","./_base/lang","./_base/window"],function(has,lang,win){try{document.execCommand("BackgroundImageCache",false,true);}catch(e){}var dom={};if(has("ie")){dom.byId=function(id,doc){if(typeof id!="string"){return id;}var _2d9=doc||win.doc,te=id&&_2d9.getElementById(id);if(te&&(te.attributes.id.value==id||te.id==id)){return te;}else{var eles=_2d9.all[id];if(!eles||eles.nodeName){eles=[eles];}var i=0;while((te=eles[i++])){if((te.attributes&&te.attributes.id&&te.attributes.id.value==id)||te.id==id){return te;}}}};}else{dom.byId=function(id,doc){return ((typeof id=="string")?(doc||win.doc).getElementById(id):id)||null;};}dom.isDescendant=function(node,_2da){try{node=dom.byId(node);_2da=dom.byId(_2da);while(node){if(node==_2da){return true;}node=node.parentNode;}}catch(e){}return false;};dom.setSelectable=function(node,_2db){node=dom.byId(node);if(has("mozilla")){node.style.MozUserSelect=_2db?"":"none";}else{if(has("khtml")||has("webkit")){node.style.KhtmlUserSelect=_2db?"auto":"none";}else{if(has("ie")){var v=(node.unselectable=_2db?"":"on"),cs=node.getElementsByTagName("*"),i=0,l=cs.length;for(;i=0){_2e2+=" * ";}else{_2e2+=" ";}var ts=function(s,e){return trim(_2e2.slice(s,e));};var _2e3=[];var _2e4=-1,_2e5=-1,_2e6=-1,_2e7=-1,_2e8=-1,inId=-1,_2e9=-1,lc="",cc="",_2ea;var x=0,ql=_2e2.length,_2eb=null,_2ec=null;var _2ed=function(){if(_2e9>=0){var tv=(_2e9==x)?null:ts(_2e9,x);_2eb[(_2de.indexOf(tv)<0)?"tag":"oper"]=tv;_2e9=-1;}};var _2ee=function(){if(inId>=0){_2eb.id=ts(inId,x).replace(/\\/g,"");inId=-1;}};var _2ef=function(){if(_2e8>=0){_2eb.classes.push(ts(_2e8+1,x).replace(/\\/g,""));_2e8=-1;}};var _2f0=function(){_2ee();_2ed();_2ef();};var _2f1=function(){_2f0();if(_2e7>=0){_2eb.pseudos.push({name:ts(_2e7+1,x)});}_2eb.loops=(_2eb.pseudos.length||_2eb.attrs.length||_2eb.classes.length);_2eb.oquery=_2eb.query=ts(_2ea,x);_2eb.otag=_2eb.tag=(_2eb["oper"])?null:(_2eb.tag||"*");if(_2eb.tag){_2eb.tag=_2eb.tag.toUpperCase();}if(_2e3.length&&(_2e3[_2e3.length-1].oper)){_2eb.infixOper=_2e3.pop();_2eb.query=_2eb.infixOper.query+" "+_2eb.query;}_2e3.push(_2eb);_2eb=null;};for(;lc=cc,cc=_2e2.charAt(x),x=0){if(cc=="]"){if(!_2ec.attr){_2ec.attr=ts(_2e4+1,x);}else{_2ec.matchFor=ts((_2e6||_2e4+1),x);}var cmf=_2ec.matchFor;if(cmf){if((cmf.charAt(0)=="\"")||(cmf.charAt(0)=="'")){_2ec.matchFor=cmf.slice(1,-1);}}_2eb.attrs.push(_2ec);_2ec=null;_2e4=_2e6=-1;}else{if(cc=="="){var _2f2=("|~^$*".indexOf(lc)>=0)?lc:"";_2ec.type=_2f2+cc;_2ec.attr=ts(_2e4+1,x-_2f2.length);_2e6=x+1;}}}else{if(_2e5>=0){if(cc==")"){if(_2e7>=0){_2ec.value=ts(_2e5+1,x);}_2e7=_2e5=-1;}}else{if(cc=="#"){_2f0();inId=x+1;}else{if(cc=="."){_2f0();_2e8=x;}else{if(cc==":"){_2f0();_2e7=x;}else{if(cc=="["){_2f0();_2e4=x;_2ec={};}else{if(cc=="("){if(_2e7>=0){_2ec={name:ts(_2e7+1,x),value:null};_2eb.pseudos.push(_2ec);}_2e5=x;}else{if((cc==" ")&&(lc!=cc)){_2f1();}}}}}}}}}return _2e3;};var _2f3=function(_2f4,_2f5){if(!_2f4){return _2f5;}if(!_2f5){return _2f4;}return function(){return _2f4.apply(window,arguments)&&_2f5.apply(window,arguments);};};var _2f6=function(i,arr){var r=arr||[];if(i){r.push(i);}return r;};var _2f7=function(n){return (1==n.nodeType);};var _2f8="";var _2f9=function(elem,attr){if(!elem){return _2f8;}if(attr=="class"){return elem.className||_2f8;}if(attr=="for"){return elem.htmlFor||_2f8;}if(attr=="style"){return elem.style.cssText||_2f8;}return (_2df?elem.getAttribute(attr):elem.getAttribute(attr,2))||_2f8;};var _2fa={"*=":function(attr,_2fb){return function(elem){return (_2f9(elem,attr).indexOf(_2fb)>=0);};},"^=":function(attr,_2fc){return function(elem){return (_2f9(elem,attr).indexOf(_2fc)==0);};},"$=":function(attr,_2fd){return function(elem){var ea=" "+_2f9(elem,attr);return (ea.lastIndexOf(_2fd)==(ea.length-_2fd.length));};},"~=":function(attr,_2fe){var tval=" "+_2fe+" ";return function(elem){var ea=" "+_2f9(elem,attr)+" ";return (ea.indexOf(tval)>=0);};},"|=":function(attr,_2ff){var _300=_2ff+"-";return function(elem){var ea=_2f9(elem,attr);return ((ea==_2ff)||(ea.indexOf(_300)==0));};},"=":function(attr,_301){return function(elem){return (_2f9(elem,attr)==_301);};}};var _302=(typeof _2dc().firstChild.nextElementSibling=="undefined");var _303=!_302?"nextElementSibling":"nextSibling";var _304=!_302?"previousElementSibling":"previousSibling";var _305=(_302?_2f7:_2e0);var _306=function(node){while(node=node[_304]){if(_305(node)){return false;}}return true;};var _307=function(node){while(node=node[_303]){if(_305(node)){return false;}}return true;};var _308=function(node){var root=node.parentNode;var i=0,tret=root.children||root.childNodes,ci=(node["_i"]||-1),cl=(root["_l"]||-1);if(!tret){return -1;}var l=tret.length;if(cl==l&&ci>=0&&cl>=0){return ci;}root["_l"]=l;ci=-1;for(var te=root["firstElementChild"]||root["firstChild"];te;te=te[_303]){if(_305(te)){te["_i"]=++i;if(node===te){ci=i;}}}return ci;};var _309=function(elem){return !((_308(elem))%2);};var _30a=function(elem){return ((_308(elem))%2);};var _30b={"checked":function(name,_30c){return function(elem){return !!("checked" in elem?elem.checked:elem.selected);};},"first-child":function(){return _306;},"last-child":function(){return _307;},"only-child":function(name,_30d){return function(node){return _306(node)&&_307(node);};},"empty":function(name,_30e){return function(elem){var cn=elem.childNodes;var cnl=elem.childNodes.length;for(var x=cnl-1;x>=0;x--){var nt=cn[x].nodeType;if((nt===1)||(nt==3)){return false;}}return true;};},"contains":function(name,_30f){var cz=_30f.charAt(0);if(cz=="\""||cz=="'"){_30f=_30f.slice(1,-1);}return function(elem){return (elem.innerHTML.indexOf(_30f)>=0);};},"not":function(name,_310){var p=_2e1(_310)[0];var _311={el:1};if(p.tag!="*"){_311.tag=1;}if(!p.classes.length){_311.classes=1;}var ntf=_312(p,_311);return function(elem){return (!ntf(elem));};},"nth-child":function(name,_313){var pi=parseInt;if(_313=="odd"){return _30a;}else{if(_313=="even"){return _309;}}if(_313.indexOf("n")!=-1){var _314=_313.split("n",2);var pred=_314[0]?((_314[0]=="-")?-1:pi(_314[0])):1;var idx=_314[1]?pi(_314[1]):0;var lb=0,ub=-1;if(pred>0){if(idx<0){idx=(idx%pred)&&(pred+(idx%pred));}else{if(idx>0){if(idx>=pred){lb=idx-idx%pred;}idx=idx%pred;}}}else{if(pred<0){pred*=-1;if(idx>0){ub=idx;idx=idx%pred;}}}if(pred>0){return function(elem){var i=_308(elem);return (i>=lb)&&(ub<0||i<=ub)&&((i%pred)==idx);};}else{_313=idx;}}var _315=pi(_313);return function(elem){return (_308(elem)==_315);};}};var _316=(dojo.isIE&&(dojo.isIE<9||dojo.isQuirks))?function(cond){var clc=cond.toLowerCase();if(clc=="class"){cond="className";}return function(elem){return (_2df?elem.getAttribute(cond):elem[cond]||elem[clc]);};}:function(cond){return function(elem){return (elem&&elem.getAttribute&&elem.hasAttribute(cond));};};var _312=function(_317,_318){if(!_317){return _2e0;}_318=_318||{};var ff=null;if(!("el" in _318)){ff=_2f3(ff,_2f7);}if(!("tag" in _318)){if(_317.tag!="*"){ff=_2f3(ff,function(elem){return (elem&&(elem.tagName==_317.getTag()));});}}if(!("classes" in _318)){each(_317.classes,function(_319,idx,arr){var re=new RegExp("(?:^|\\s)"+_319+"(?:\\s|$)");ff=_2f3(ff,function(elem){return re.test(elem.className);});ff.count=idx;});}if(!("pseudos" in _318)){each(_317.pseudos,function(_31a){var pn=_31a.name;if(_30b[pn]){ff=_2f3(ff,_30b[pn](pn,_31a.value));}});}if(!("attrs" in _318)){each(_317.attrs,function(attr){var _31b;var a=attr.attr;if(attr.type&&_2fa[attr.type]){_31b=_2fa[attr.type](a,attr.matchFor);}else{if(a.length){_31b=_316(a);}}if(_31b){ff=_2f3(ff,_31b);}});}if(!("id" in _318)){if(_317.id){ff=_2f3(ff,function(elem){return (!!elem&&(elem.id==_317.id));});}}if(!ff){if(!("default" in _318)){ff=_2e0;}}return ff;};var _31c=function(_31d){return function(node,ret,bag){while(node=node[_303]){if(_302&&(!_2f7(node))){continue;}if((!bag||_31e(node,bag))&&_31d(node)){ret.push(node);}break;}return ret;};};var _31f=function(_320){return function(root,ret,bag){var te=root[_303];while(te){if(_305(te)){if(bag&&!_31e(te,bag)){break;}if(_320(te)){ret.push(te);}}te=te[_303];}return ret;};};var _321=function(_322){_322=_322||_2e0;return function(root,ret,bag){var te,x=0,tret=root.children||root.childNodes;while(te=tret[x++]){if(_305(te)&&(!bag||_31e(te,bag))&&(_322(te,x))){ret.push(te);}}return ret;};};var _323=function(node,root){var pn=node.parentNode;while(pn){if(pn==root){break;}pn=pn.parentNode;}return !!pn;};var _324={};var _325=function(_326){var _327=_324[_326.query];if(_327){return _327;}var io=_326.infixOper;var oper=(io?io.oper:"");var _328=_312(_326,{el:1});var qt=_326.tag;var _329=("*"==qt);var ecs=_2dc()["getElementsByClassName"];if(!oper){if(_326.id){_328=(!_326.loops&&_329)?_2e0:_312(_326,{el:1,id:1});_327=function(root,arr){var te=dom.byId(_326.id,(root.ownerDocument||root));if(!te||!_328(te)){return;}if(9==root.nodeType){return _2f6(te,arr);}else{if(_323(te,root)){return _2f6(te,arr);}}};}else{if(ecs&&/\{\s*\[native code\]\s*\}/.test(String(ecs))&&_326.classes.length&&!_2dd){_328=_312(_326,{el:1,classes:1,id:1});var _32a=_326.classes.join(" ");_327=function(root,arr,bag){var ret=_2f6(0,arr),te,x=0;var tret=root.getElementsByClassName(_32a);while((te=tret[x++])){if(_328(te,root)&&_31e(te,bag)){ret.push(te);}}return ret;};}else{if(!_329&&!_326.loops){_327=function(root,arr,bag){var ret=_2f6(0,arr),te,x=0;var tret=root.getElementsByTagName(_326.getTag());while((te=tret[x++])){if(_31e(te,bag)){ret.push(te);}}return ret;};}else{_328=_312(_326,{el:1,tag:1,id:1});_327=function(root,arr,bag){var ret=_2f6(0,arr),te,x=0;var tret=root.getElementsByTagName(_326.getTag());while((te=tret[x++])){if(_328(te,root)&&_31e(te,bag)){ret.push(te);}}return ret;};}}}}else{var _32b={el:1};if(_329){_32b.tag=1;}_328=_312(_326,_32b);if("+"==oper){_327=_31c(_328);}else{if("~"==oper){_327=_31f(_328);}else{if(">"==oper){_327=_321(_328);}}}}return _324[_326.query]=_327;};var _32c=function(root,_32d){var _32e=_2f6(root),qp,x,te,qpl=_32d.length,bag,ret;for(var i=0;i0){bag={};ret.nozip=true;}var gef=_325(qp);for(var j=0;(te=_32e[j]);j++){gef(te,ret,bag);}if(!ret.length){break;}_32e=ret;}return ret;};var _32f={},_330={};var _331=function(_332){var _333=_2e1(trim(_332));if(_333.length==1){var tef=_325(_333[0]);return function(root){var r=tef(root,[]);if(r){r.nozip=true;}return r;};}return function(root){return _32c(root,_333);};};var nua=navigator.userAgent;var wk="WebKit/";var _334=(dojo.isWebKit&&(nua.indexOf(wk)>0)&&(parseFloat(nua.split(wk)[1])>528));var _335=dojo.isIE?"commentStrip":"nozip";var qsa="querySelectorAll";var _336=(!!_2dc()[qsa]&&(!dojo.isSafari||(dojo.isSafari>3.1)||_334));var _337=/n\+\d|([^ ])?([>~+])([^ =])?/g;var _338=function(_339,pre,ch,post){return ch?(pre?pre+" ":"")+ch+(post?" "+post:""):_339;};var _33a=function(_33b,_33c){_33b=_33b.replace(_337,_338);if(_336){var _33d=_330[_33b];if(_33d&&!_33c){return _33d;}}var _33e=_32f[_33b];if(_33e){return _33e;}var qcz=_33b.charAt(0);var _33f=(-1==_33b.indexOf(" "));if((_33b.indexOf("#")>=0)&&(_33f)){_33c=true;}var _340=(_336&&(!_33c)&&(_2de.indexOf(qcz)==-1)&&(!dojo.isIE||(_33b.indexOf(":")==-1))&&(!(_2dd&&(_33b.indexOf(".")>=0)))&&(_33b.indexOf(":contains")==-1)&&(_33b.indexOf(":checked")==-1)&&(_33b.indexOf("|=")==-1));if(_340){var tq=(_2de.indexOf(_33b.charAt(_33b.length-1))>=0)?(_33b+" *"):_33b;return _330[_33b]=function(root){try{if(!((9==root.nodeType)||_33f)){throw "";}var r=root[qsa](tq);r[_335]=true;return r;}catch(e){return _33a(_33b,true)(root);}};}else{var _341=_33b.split(/\s*,\s*/);return _32f[_33b]=((_341.length<2)?_331(_33b):function(root){var _342=0,ret=[],tp;while((tp=_341[_342++])){ret=ret.concat(_331(tp)(root));}return ret;});}};var _343=0;var _344=dojo.isIE?function(node){if(_2df){return (node.getAttribute("_uid")||node.setAttribute("_uid",++_343)||_343);}else{return node.uniqueID;}}:function(node){return (node._uid||(node._uid=++_343));};var _31e=function(node,bag){if(!bag){return 1;}var id=_344(node);if(!bag[id]){return bag[id]=1;}return 0;};var _345="_zipIdx";var _346=function(arr){if(arr&&arr.nozip){return arr;}var ret=[];if(!arr||!arr.length){return ret;}if(arr[0]){ret.push(arr[0]);}if(arr.length<2){return ret;}_343++;if(dojo.isIE&&_2df){var _347=_343+"";arr[0].setAttribute(_345,_347);for(var x=1,te;te=arr[x];x++){if(arr[x].getAttribute(_345)!=_347){ret.push(te);}te.setAttribute(_345,_347);}}else{if(dojo.isIE&&arr.commentStrip){try{for(var x=1,te;te=arr[x];x++){if(_2f7(te)){ret.push(te);}}}catch(e){}}else{if(arr[0]){arr[0][_345]=_343;}for(var x=1,te;te=arr[x];x++){if(arr[x][_345]!=_343){ret.push(te);}te[_345]=_343;}}}return ret;};var _348=function(_349,root){root=root||_2dc();var od=root.ownerDocument||root.documentElement;_2df=(root.contentType&&root.contentType=="application/xml")||(dojo.isOpera&&(root.doctype||od.toString()=="[object XMLDocument]"))||(!!od)&&(dojo.isIE?od.xml:(root.xmlVersion||od.xmlVersion));var r=_33a(_349)(root);if(r&&r.nozip){return r;}return _346(r);};_348.filter=function(_34a,_34b,root){var _34c=[],_34d=_2e1(_34b),_34e=(_34d.length==1&&!/[^\w#\.]/.test(_34b))?_312(_34d[0]):function(node){return dojo.query(_34b,root).indexOf(node)!=-1;};for(var x=0,te;te=_34a[x];x++){if(_34e(te)){_34c.push(te);}}return _34c;};return _348;});},"dojo/dom-style":function(){define(["./_base/sniff","./dom"],function(has,dom){var _34f,_350={};if(has("webkit")){_34f=function(node){var s;if(node.nodeType==1){var dv=node.ownerDocument.defaultView;s=dv.getComputedStyle(node,null);if(!s&&node.style){node.style.display="";s=dv.getComputedStyle(node,null);}}return s||{};};}else{if(has("ie")&&(has("ie")<9||has("quirks"))){_34f=function(node){return node.nodeType==1?node.currentStyle:{};};}else{_34f=function(node){return node.nodeType==1?node.ownerDocument.defaultView.getComputedStyle(node,null):{};};}}_350.getComputedStyle=_34f;var _351;if(!has("ie")){_351=function(_352,_353){return parseFloat(_353)||0;};}else{_351=function(_354,_355){if(!_355){return 0;}if(_355=="medium"){return 4;}if(_355.slice&&_355.slice(-2)=="px"){return parseFloat(_355);}var s=_354.style,rs=_354.runtimeStyle,cs=_354.currentStyle,_356=s.left,_357=rs.left;rs.left=cs.left;try{s.left=_355;_355=s.pixelLeft;}catch(e){_355=0;}s.left=_356;rs.left=_357;return _355;};}_350.toPixelValue=_351;var astr="DXImageTransform.Microsoft.Alpha";var af=function(n,f){try{return n.filters.item(astr);}catch(e){return f?{}:null;}};var _358=has("ie")<9||(has("ie")&&has("quirks"))?function(node){try{return af(node).Opacity/100;}catch(e){return 1;}}:function(node){return _34f(node).opacity;};var _359=has("ie")<9||(has("ie")&&has("quirks"))?function(node,_35a){var ov=_35a*100,_35b=_35a==1;node.style.zoom=_35b?"":1;if(!af(node)){if(_35b){return _35a;}node.style.filter+=" progid:"+astr+"(Opacity="+ov+")";}else{af(node,1).Opacity=ov;}af(node,1).Enabled=!_35b;if(node.tagName.toLowerCase()=="tr"){for(var td=node.firstChild;td;td=td.nextSibling){if(td.tagName.toLowerCase()=="td"){_359(td,_35a);}}}return _35a;}:function(node,_35c){return node.style.opacity=_35c;};var _35d={left:true,top:true};var _35e=/margin|padding|width|height|max|min|offset/;function _35f(node,type,_360){type=type.toLowerCase();if(has("ie")){if(_360=="auto"){if(type=="height"){return node.offsetHeight;}if(type=="width"){return node.offsetWidth;}}if(type=="fontweight"){switch(_360){case 700:return "bold";case 400:default:return "normal";}}}if(!(type in _35d)){_35d[type]=_35e.test(type);}return _35d[type]?_351(node,_360):_360;};var _361=has("ie")?"styleFloat":"cssFloat",_362={"cssFloat":_361,"styleFloat":_361,"float":_361};_350.get=function getStyle(node,name){var n=dom.byId(node),l=arguments.length,op=(name=="opacity");if(l==2&&op){return _358(n);}name=_362[name]||name;var s=_350.getComputedStyle(n);return (l==1)?s:_35f(n,name,s[name]||n.style[name]);};_350.set=function setStyle(node,name,_363){var n=dom.byId(node),l=arguments.length,op=(name=="opacity");name=_362[name]||name;if(l==3){return op?_359(n,_363):n.style[name]=_363;}for(var x in name){_350.set(node,x,name[x]);}return _350.getComputedStyle(n);};return _350;});},"dojo/dom-geometry":function(){define(["./_base/sniff","./_base/window","./dom","./dom-style"],function(has,win,dom,_364){var geom={};geom.boxModel="content-box";if(has("ie")){geom.boxModel=document.compatMode=="BackCompat"?"border-box":"content-box";}geom.getPadExtents=function getPadExtents(node,_365){node=dom.byId(node);var s=_365||_364.getComputedStyle(node),px=_364.toPixelValue,l=px(node,s.paddingLeft),t=px(node,s.paddingTop),r=px(node,s.paddingRight),b=px(node,s.paddingBottom);return {l:l,t:t,r:r,b:b,w:l+r,h:t+b};};var none="none";geom.getBorderExtents=function getBorderExtents(node,_366){node=dom.byId(node);var px=_364.toPixelValue,s=_366||_364.getComputedStyle(node),l=s.borderLeftStyle!=none?px(node,s.borderLeftWidth):0,t=s.borderTopStyle!=none?px(node,s.borderTopWidth):0,r=s.borderRightStyle!=none?px(node,s.borderRightWidth):0,b=s.borderBottomStyle!=none?px(node,s.borderBottomWidth):0;return {l:l,t:t,r:r,b:b,w:l+r,h:t+b};};geom.getPadBorderExtents=function getPadBorderExtents(node,_367){node=dom.byId(node);var s=_367||_364.getComputedStyle(node),p=geom.getPadExtents(node,s),b=geom.getBorderExtents(node,s);return {l:p.l+b.l,t:p.t+b.t,r:p.r+b.r,b:p.b+b.b,w:p.w+b.w,h:p.h+b.h};};geom.getMarginExtents=function getMarginExtents(node,_368){node=dom.byId(node);var s=_368||_364.getComputedStyle(node),px=_364.toPixelValue,l=px(node,s.marginLeft),t=px(node,s.marginTop),r=px(node,s.marginRight),b=px(node,s.marginBottom);if(has("webkit")&&(s.position!="absolute")){r=l;}return {l:l,t:t,r:r,b:b,w:l+r,h:t+b};};geom.getMarginBox=function getMarginBox(node,_369){node=dom.byId(node);var s=_369||_364.getComputedStyle(node),me=geom.getMarginExtents(node,s),l=node.offsetLeft-me.l,t=node.offsetTop-me.t,p=node.parentNode,px=_364.toPixelValue,pcs;if(has("mozilla")){var sl=parseFloat(s.left),st=parseFloat(s.top);if(!isNaN(sl)&&!isNaN(st)){l=sl,t=st;}else{if(p&&p.style){pcs=_364.getComputedStyle(p);if(pcs.overflow!="visible"){l+=pcs.borderLeftStyle!=none?px(node,pcs.borderLeftWidth):0;t+=pcs.borderTopStyle!=none?px(node,pcs.borderTopWidth):0;}}}}else{if(has("opera")||(has("ie")==8&&!has("quirks"))){if(p){pcs=_364.getComputedStyle(p);l-=pcs.borderLeftStyle!=none?px(node,pcs.borderLeftWidth):0;t-=pcs.borderTopStyle!=none?px(node,pcs.borderTopWidth):0;}}}return {l:l,t:t,w:node.offsetWidth+me.w,h:node.offsetHeight+me.h};};geom.getContentBox=function getContentBox(node,_36a){node=dom.byId(node);var s=_36a||_364.getComputedStyle(node),w=node.clientWidth,h,pe=geom.getPadExtents(node,s),be=geom.getBorderExtents(node,s);if(!w){w=node.offsetWidth;h=node.offsetHeight;}else{h=node.clientHeight;be.w=be.h=0;}if(has("opera")){pe.l+=be.l;pe.t+=be.t;}return {l:pe.l,t:pe.t,w:w-pe.w-be.w,h:h-pe.h-be.h};};function _36b(node,l,t,w,h,u){u=u||"px";var s=node.style;if(!isNaN(l)){s.left=l+u;}if(!isNaN(t)){s.top=t+u;}if(w>=0){s.width=w+u;}if(h>=0){s.height=h+u;}};function _36c(node){return node.tagName.toLowerCase()=="button"||node.tagName.toLowerCase()=="input"&&(node.getAttribute("type")||"").toLowerCase()=="button";};function _36d(node){return geom.boxModel=="border-box"||node.tagName.toLowerCase()=="table"||_36c(node);};geom.setContentSize=function setContentSize(node,box,_36e){node=dom.byId(node);var w=box.w,h=box.h;if(_36d(node)){var pb=geom.getPadBorderExtents(node,_36e);if(w>=0){w+=pb.w;}if(h>=0){h+=pb.h;}}_36b(node,NaN,NaN,w,h);};var _36f={l:0,t:0,w:0,h:0};geom.setMarginBox=function setMarginBox(node,box,_370){node=dom.byId(node);var s=_370||_364.getComputedStyle(node),w=box.w,h=box.h,pb=_36d(node)?_36f:geom.getPadBorderExtents(node,s),mb=geom.getMarginExtents(node,s);if(has("webkit")){if(_36c(node)){var ns=node.style;if(w>=0&&!ns.width){ns.width="4px";}if(h>=0&&!ns.height){ns.height="4px";}}}if(w>=0){w=Math.max(w-pb.w-mb.w,0);}if(h>=0){h=Math.max(h-pb.h-mb.h,0);}_36b(node,box.l,box.t,w,h);};geom.isBodyLtr=function isBodyLtr(){return (win.body().dir||win.doc.documentElement.dir||"ltr").toLowerCase()=="ltr";};geom.docScroll=function docScroll(){var node=win.doc.parentWindow||win.doc.defaultView;return "pageXOffset" in node?{x:node.pageXOffset,y:node.pageYOffset}:(node=has("quirks")?win.body():win.doc.documentElement,{x:geom.fixIeBiDiScrollLeft(node.scrollLeft||0),y:node.scrollTop||0});};geom.getIeDocumentElementOffset=function getIeDocumentElementOffset(){var de=win.doc.documentElement;if(has("ie")<8){var r=de.getBoundingClientRect(),l=r.left,t=r.top;if(has("ie")<7){l+=de.clientLeft;t+=de.clientTop;}return {x:l<0?0:l,y:t<0?0:t};}else{return {x:0,y:0};}};geom.fixIeBiDiScrollLeft=function fixIeBiDiScrollLeft(_371){var ie=has("ie");if(ie&&!geom.isBodyLtr()){var qk=has("quirks"),de=qk?win.body():win.doc.documentElement;if(ie==6&&!qk&&win.global.frameElement&&de.scrollHeight>de.clientHeight){_371+=de.clientLeft;}return (ie<8||qk)?(_371+de.clientWidth-de.scrollWidth):-_371;}return _371;};geom.position=function(node,_372){node=dom.byId(node);var db=win.body(),dh=db.parentNode,ret=node.getBoundingClientRect();ret={x:ret.left,y:ret.top,w:ret.right-ret.left,h:ret.bottom-ret.top};if(has("ie")){var _373=geom.getIeDocumentElementOffset();ret.x-=_373.x+(has("quirks")?db.clientLeft+db.offsetLeft:0);ret.y-=_373.y+(has("quirks")?db.clientTop+db.offsetTop:0);}else{if(has("ff")==3){var cs=_364.getComputedStyle(dh),px=_364.toPixelValue;ret.x-=px(dh,cs.marginLeft)+px(dh,cs.borderLeftWidth);ret.y-=px(dh,cs.marginTop)+px(dh,cs.borderTopWidth);}}if(_372){var _374=geom.docScroll();ret.x+=_374.x;ret.y+=_374.y;}return ret;};geom.getMarginSize=function getMarginSize(node,_375){node=dom.byId(node);var me=geom.getMarginExtents(node,_375||_364.getComputedStyle(node));var size=node.getBoundingClientRect();return {w:(size.right-size.left)+me.w,h:(size.bottom-size.top)+me.h};};geom.normalizeEvent=function(_376){if(!("layerX" in _376)){_376.layerX=_376.offsetX;_376.layerY=_376.offsetY;}if(!has("dom-addeventlistener")){var se=_376.target;var doc=(se&&se.ownerDocument)||document;var _377=has("quirks")?doc.body:doc.documentElement;var _378=geom.getIeDocumentElementOffset();_376.pageX=_376.clientX+geom.fixIeBiDiScrollLeft(_377.scrollLeft||0)-_378.x;_376.pageY=_376.clientY+(_377.scrollTop||0)-_378.y;}};return geom;});},"dojo/dom-prop":function(){define("dojo/dom-prop",["exports","./_base/kernel","./_base/sniff","./_base/lang","./dom","./dom-style","./dom-construct","./_base/connect"],function(_379,dojo,has,lang,dom,_37a,ctr,conn){var _37b={},_37c=0,_37d=dojo._scopeName+"attrid";var _37e={col:1,colgroup:1,table:1,tbody:1,tfoot:1,thead:1,tr:1,title:1};_379.names={"class":"className","for":"htmlFor",tabindex:"tabIndex",readonly:"readOnly",colspan:"colSpan",frameborder:"frameBorder",rowspan:"rowSpan",valuetype:"valueType"};_379.get=function getProp(node,name){node=dom.byId(node);var lc=name.toLowerCase(),_37f=_379.names[lc]||name;return node[_37f];};_379.set=function setProp(node,name,_380){node=dom.byId(node);var l=arguments.length;if(l==2&&typeof name!="string"){for(var x in name){_379.set(node,x,name[x]);}return node;}var lc=name.toLowerCase(),_381=_379.names[lc]||name;if(_381=="style"&&typeof _380!="string"){_37a.style(node,_380);return node;}if(_381=="innerHTML"){if(has("ie")&&node.tagName.toLowerCase() in _37e){ctr.empty(node);node.appendChild(ctr.toDom(_380,node.ownerDocument));}else{node[_381]=_380;}return node;}if(lang.isFunction(_380)){var _382=node[_37d];if(!_382){_382=_37c++;node[_37d]=_382;}if(!_37b[_382]){_37b[_382]={};}var h=_37b[_382][_381];if(h){conn.disconnect(h);}else{try{delete node[_381];}catch(e){}}if(_380){_37b[_382][_381]=conn.connect(node,_381,_380);}else{node[_381]=null;}return node;}node[_381]=_380;return node;};});},"dojo/dom-attr":function(){define(["exports","./_base/sniff","./_base/lang","./dom","./dom-style","./dom-prop"],function(_383,has,lang,dom,_384,prop){var _385={innerHTML:1,className:1,htmlFor:has("ie"),value:1},_386={classname:"class",htmlfor:"for",tabindex:"tabIndex",readonly:"readOnly"};function _387(node,name){var attr=node.getAttributeNode&&node.getAttributeNode(name);return attr&&attr.specified;};_383.has=function hasAttr(node,name){var lc=name.toLowerCase();return _385[prop.names[lc]||name]||_387(dom.byId(node),_386[lc]||name);};_383.get=function getAttr(node,name){node=dom.byId(node);var lc=name.toLowerCase(),_388=prop.names[lc]||name,_389=_385[_388];value=node[_388];if(_389&&typeof value!="undefined"){return value;}if(_388!="href"&&(typeof value=="boolean"||lang.isFunction(value))){return value;}var _38a=_386[lc]||name;return _387(node,_38a)?node.getAttribute(_38a):null;};_383.set=function setAttr(node,name,_38b){node=dom.byId(node);if(arguments.length==2){for(var x in name){_383.set(node,x,name[x]);}return node;}var lc=name.toLowerCase(),_38c=prop.names[lc]||name,_38d=_385[_38c];if(_38c=="style"&&typeof _38b!="string"){_384.set(node,_38b);return node;}if(_38d||typeof _38b=="boolean"||lang.isFunction(_38b)){return prop.set(node,name,_38b);}node.setAttribute(_386[lc]||name,_38b);return node;};_383.remove=function removeAttr(node,name){dom.byId(node).removeAttribute(_386[name.toLowerCase()]||name);};_383.getNodeProp=function getNodeProp(node,name){node=dom.byId(node);var lc=name.toLowerCase(),_38e=prop.names[lc]||name;if((_38e in node)&&_38e!="href"){return node[_38e];}var _38f=_386[lc]||name;return _387(node,_38f)?node.getAttribute(_38f):null;};});},"dojo/dom-construct":function(){define("dojo/dom-construct",["exports","./_base/kernel","./_base/sniff","./_base/window","./dom","./dom-attr","./on"],function(_390,dojo,has,win,dom,attr,on){var _391={option:["select"],tbody:["table"],thead:["table"],tfoot:["table"],tr:["table","tbody"],td:["table","tbody","tr"],th:["table","thead","tr"],legend:["fieldset"],caption:["table"],colgroup:["table"],col:["table","colgroup"],li:["ul"]},_392=/<\s*([\w\:]+)/,_393={},_394=0,_395="__"+dojo._scopeName+"ToDomId";for(var _396 in _391){if(_391.hasOwnProperty(_396)){var tw=_391[_396];tw.pre=_396=="option"?"