[Jifty-commit] r5145 - in jifty/branches/jquery: share/plugins/Jifty/Plugin/SinglePage share/plugins/Jifty/Plugin/SinglePage/web share/plugins/Jifty/Plugin/SinglePage/web/static share/plugins/Jifty/Plugin/SinglePage/web/static/js share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh
Jifty commits
jifty-commit at lists.jifty.org
Wed Feb 20 15:24:20 EST 2008
Author: hlb
Date: Wed Feb 20 15:24:13 2008
New Revision: 5145
Added:
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/LICENSE.txt
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/blank.html
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/rsh.js
jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/spa.js
Modified:
jifty/branches/jquery/lib/Jifty/Plugin/SinglePage.pm
Log:
SinglePage plugin: added history support for links.
We use the "Really Simple History"(http://code.google.com/p/reallysimplehistory/) javascript librar
y to make the magic rock.
The rsh.js is a modified version. The path of blank.html has been changed to fit Jifty's js cache mechanism. Please check http://code.google.com/p/reallysimplehistory/wiki/UsageInstructions to know more about RSH.
Modified: jifty/branches/jquery/lib/Jifty/Plugin/SinglePage.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/SinglePage.pm (original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/SinglePage.pm Wed Feb 20 15:24:13 2008
@@ -20,6 +20,11 @@
=cut
+Jifty->web->add_javascript(
+ 'singlepage/rsh/rsh.js',
+ 'singlepage/spa.js'
+);
+
sub init {
my $self = shift;
return if $self->_pre_init;
@@ -61,6 +66,7 @@
$self->_push_onclick($args, {
region => $self->region_name,
replace_with => $url,
+ beforeclick => qq{SPA.historyChange('$url', { 'continuation':{}, 'actions':{}, 'fragments':[{'mode':'Replace','args':@{[ Jifty::JSON::objToJson($args->{parameters})]},'region':'__page','path':'$url'}],'action_arguments':{}}, true);},
args => { %{$args->{parameters}}} });
}
elsif (exists $args->{submit} && !$args->{onclick}) {
Added: jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/LICENSE.txt
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/LICENSE.txt Wed Feb 20 15:24:13 2008
@@ -0,0 +1,15 @@
+Copyright (c) 2007 Brian Dillard and Brad Neuberg:
+Brian Dillard | Project Lead | bdillard at pathf.com | http://blogs.pathf.com/agileajax/
+Brad Neuberg | Original Project Creator | http://codinginparadise.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Added: jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/blank.html
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/blank.html Wed Feb 20 15:24:13 2008
@@ -0,0 +1,39 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<!--
+Copyright (c) 2007 Brian Dillard and Brad Neuberg:
+Brian Dillard | Project Lead | bdillard at pathf.com | http://blogs.pathf.com/agileajax/
+Brad Neuberg | Original Project Creator | http://codinginparadise.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script language="JavaScript">
+function pageLoaded() {
+ window.parent.dhtmlHistory.iframeLoaded(window.location);
+ document.getElementById("output").innerHTML = window.location;
+}
+</script>
+
+</head>
+
+<body onload="pageLoaded();" style="width:700px;padding:2px;margin:0;">
+
+ <p>blank.html - Needed for Internet Explorer's hidden iframe</p>
+ <p id="output"></p>
+
+</body>
+</html>
Added: jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/rsh.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/rsh/rsh.js Wed Feb 20 15:24:13 2008
@@ -0,0 +1,667 @@
+/*
+Copyright (c) 2007 Brian Dillard and Brad Neuberg:
+Brian Dillard | Project Lead | bdillard at pathf.com | http://blogs.pathf.com/agileajax/
+Brad Neuberg | Original Project Creator | http://codinginparadise.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/*
+ Modified version. The path of blank.html has been changed to fit Jifty's directory structure and cache mechanism.
+*/
+
+/*
+ dhtmlHistory: An object that provides history, history data, and bookmarking for DHTML and Ajax applications.
+
+ dependencies:
+ * the historyStorage object included in this file.
+
+*/
+window.dhtmlHistory = {
+
+ /*Public: User-agent booleans*/
+ isIE: false,
+ isOpera: false,
+ isSafari: false,
+ isKonquerer: false,
+ isGecko: false,
+ isSupported: false,
+
+ /*Public: Create the DHTML history infrastructure*/
+ create: function(options) {
+
+ /*
+ options - object to store initialization parameters
+ options.debugMode - boolean that causes hidden form fields to be shown for development purposes.
+ options.toJSON - function to override default JSON stringifier
+ options.fromJSON - function to override default JSON parser
+ */
+
+ var that = this;
+
+ /*set user-agent flags*/
+ var UA = navigator.userAgent.toLowerCase();
+ var platform = navigator.platform.toLowerCase();
+ var vendor = navigator.vendor || "";
+ if (vendor === "KDE") {
+ this.isKonqueror = true;
+ this.isSupported = false;
+ } else if (typeof window.opera !== "undefined") {
+ this.isOpera = true;
+ this.isSupported = true;
+ } else if (typeof document.all !== "undefined") {
+ this.isIE = true;
+ this.isSupported = true;
+ } else if (vendor.indexOf("Apple Computer, Inc.") > -1) {
+ this.isSafari = true;
+ this.isSupported = (platform.indexOf("mac") > -1);
+ } else if (UA.indexOf("gecko") != -1) {
+ this.isGecko = true;
+ this.isSupported = true;
+ }
+
+ /*Set up the historyStorage object; pass in init parameters*/
+ window.historyStorage.setup(options);
+
+ /*Execute browser-specific setup methods*/
+ if (this.isSafari) {
+ this.createSafari();
+ } else if (this.isOpera) {
+ this.createOpera();
+ }
+
+ /*Get our initial location*/
+ var initialHash = this.getCurrentLocation();
+
+ /*Save it as our current location*/
+ this.currentLocation = initialHash;
+
+ /*Now that we have a hash, create IE-specific code*/
+ if (this.isIE) {
+ this.createIE(initialHash);
+ }
+
+ /*Add an unload listener for the page; this is needed for FF 1.5+ because this browser caches all dynamic updates to the
+ page, which can break some of our logic related to testing whether this is the first instance a page has loaded or whether
+ it is being pulled from the cache*/
+
+ var unloadHandler = function() {
+ that.firstLoad = null;
+ };
+
+ this.addEventListener(window,'unload',unloadHandler);
+
+ /*Determine if this is our first page load; for IE, we do this in this.iframeLoaded(), which is fired on pageload. We do it
+ there because we have no historyStorage at this point, which only exists after the page is finished loading in IE*/
+ if (this.isIE) {
+ /*The iframe will get loaded on page load, and we want to ignore this fact*/
+ this.ignoreLocationChange = true;
+ } else {
+ if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) {
+ /*This is our first page load, so ignore the location change and add our special history entry*/
+ this.ignoreLocationChange = true;
+ this.firstLoad = true;
+ historyStorage.put(this.PAGELOADEDSTRING, true);
+ } else {
+ /*This isn't our first page load, so indicate that we want to pay attention to this location change*/
+ this.ignoreLocationChange = false;
+ /*For browsers other than IE, fire a history change event; on IE, the event will be thrown automatically when its
+ hidden iframe reloads on page load. Unfortunately, we don't have any listeners yet; indicate that we want to fire
+ an event when a listener is added.*/
+ this.fireOnNewListener = true;
+ }
+ }
+
+ /*Other browsers can use a location handler that checks at regular intervals as their primary mechanism; we use it for IE as
+ well to handle an important edge case; see checkLocation() for details*/
+ var locationHandler = function() {
+ that.checkLocation();
+ };
+ setInterval(locationHandler, 100);
+ },
+
+ /*Public: Initialize our DHTML history. You must call this after the page is finished loading.*/
+ initialize: function() {
+ /*IE needs to be explicitly initialized. IE doesn't autofill form data until the page is finished loading, so we have to wait*/
+ if (this.isIE) {
+ /*If this is the first time this page has loaded*/
+ if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) {
+ /*For IE, we do this in initialize(); for other browsers, we do it in create()*/
+ this.fireOnNewListener = false;
+ this.firstLoad = true;
+ historyStorage.put(this.PAGELOADEDSTRING, true);
+ }
+ /*Else if this is a fake onload event*/
+ else {
+ this.fireOnNewListener = true;
+ this.firstLoad = false;
+ }
+ }
+ },
+
+ /*Public: Adds a history change listener. Note that only one listener is supported at this time.*/
+ addListener: function(listener) {
+ this.listener = listener;
+ /*If the page was just loaded and we should not ignore it, fire an event to our new listener now*/
+ if (this.fireOnNewListener) {
+ this.fireHistoryEvent(this.currentLocation);
+ this.fireOnNewListener = false;
+ }
+ },
+
+ /*Public: Generic utility function for attaching events*/
+ addEventListener: function(o,e,l) {
+ if (o.addEventListener) {
+ o.addEventListener(e,l,false);
+ } else if (o.attachEvent) {
+ o.attachEvent('on'+e,function() {
+ l(window.event);
+ });
+ }
+ },
+
+ /*Public: Add a history point.*/
+ add: function(newLocation, historyData) {
+
+ if (this.isSafari) {
+
+ /*Remove any leading hash symbols on newLocation*/
+ newLocation = this.removeHash(newLocation);
+
+ /*Store the history data into history storage*/
+ historyStorage.put(newLocation, historyData);
+
+ /*Save this as our current location*/
+ this.currentLocation = newLocation;
+
+ /*Change the browser location*/
+ window.location.hash = newLocation;
+
+ /*Save this to the Safari form field*/
+ this.putSafariState(newLocation);
+
+ } else {
+
+ /*Most browsers require that we wait a certain amount of time before changing the location, such
+ as 200 MS; rather than forcing external callers to use window.setTimeout to account for this,
+ we internally handle it by putting requests in a queue.*/
+ var that = this;
+ var addImpl = function() {
+
+ /*Indicate that the current wait time is now less*/
+ if (that.currentWaitTime > 0) {
+ that.currentWaitTime = that.currentWaitTime - that.waitTime;
+ }
+
+ /*Remove any leading hash symbols on newLocation*/
+ newLocation = that.removeHash(newLocation);
+
+ /*IE has a strange bug; if the newLocation is the same as _any_ preexisting id in the
+ document, then the history action gets recorded twice; throw a programmer exception if
+ there is an element with this ID*/
+ if (document.getElementById(newLocation) && that.debugMode) {
+ var e = "Exception: History locations can not have the same value as _any_ IDs that might be in the document,"
+ + " due to a bug in IE; please ask the developer to choose a history location that does not match any HTML"
+ + " IDs in this document. The following ID is already taken and cannot be a location: " + newLocation;
+ throw new Error(e);
+ }
+
+ /*Store the history data into history storage*/
+ historyStorage.put(newLocation, historyData);
+
+ /*Indicate to the browser to ignore this upcomming location change since we're making it programmatically*/
+ that.ignoreLocationChange = true;
+
+ /*Indicate to IE that this is an atomic location change block*/
+ that.ieAtomicLocationChange = true;
+
+ /*Save this as our current location*/
+ that.currentLocation = newLocation;
+
+ /*Change the browser location*/
+ window.location.hash = newLocation;
+
+ /*Change the hidden iframe's location if on IE*/
+ if (that.isIE) {
+ that.iframe.src = "/static/js/singlepage/rsh/blank.html?" + newLocation;
+ }
+
+ /*End of atomic location change block for IE*/
+ that.ieAtomicLocationChange = false;
+ };
+
+ /*Now queue up this add request*/
+ window.setTimeout(addImpl, this.currentWaitTime);
+
+ /*Indicate that the next request will have to wait for awhile*/
+ this.currentWaitTime = this.currentWaitTime + this.waitTime;
+ }
+ },
+
+ /*Public*/
+ isFirstLoad: function() {
+ return this.firstLoad;
+ },
+
+ /*Public*/
+ getVersion: function() {
+ return "0.6";
+ },
+
+ /*Get browser's current hash location; for Safari, read value from a hidden form field*/
+
+ /*Public*/
+ getCurrentLocation: function() {
+ var r = (this.isSafari
+ ? this.getSafariState()
+ : this.getCurrentHash()
+ );
+ return r;
+ },
+
+ /*Public: Manually parse the current url for a hash; tip of the hat to YUI*/
+ getCurrentHash: function() {
+ var r = window.location.href;
+ var i = r.indexOf("#");
+ return (i >= 0
+ ? r.substr(i+1)
+ : ""
+ );
+ },
+
+ /*- - - - - - - - - - - -*/
+
+ /*Private: Constant for our own internal history event called when the page is loaded*/
+ PAGELOADEDSTRING: "DhtmlHistory_pageLoaded",
+
+ /*Private: Our history change listener.*/
+ listener: null,
+
+ /*Private: MS to wait between add requests - will be reset for certain browsers*/
+ waitTime: 200,
+
+ /*Private: MS before an add request can execute*/
+ currentWaitTime: 0,
+
+ /*Private: Our current hash location, without the "#" symbol.*/
+ currentLocation: null,
+
+ /*Private: Hidden iframe used to IE to detect history changes*/
+ iframe: null,
+
+ /*Private: Flags and DOM references used only by Safari*/
+ safariHistoryStartPoint: null,
+ safariStack: null,
+ safariLength: null,
+
+ /*Private: Flag used to keep checkLocation() from doing anything when it discovers location changes we've made ourselves
+ programmatically with the add() method. Basically, add() sets this to true. When checkLocation() discovers it's true,
+ it refrains from firing our listener, then resets the flag to false for next cycle. That way, our listener only gets fired on
+ history change events triggered by the user via back/forward buttons and manual hash changes. This flag also helps us set up
+ IE's special iframe-based method of handling history changes.*/
+ ignoreLocationChange: null,
+
+ /*Private: A flag that indicates that we should fire a history change event when we are ready, i.e. after we are initialized and
+ we have a history change listener. This is needed due to an edge case in browsers other than IE; if you leave a page entirely
+ then return, we must fire this as a history change event. Unfortunately, we have lost all references to listeners from earlier,
+ because JavaScript clears out.*/
+ fireOnNewListener: null,
+
+ /*Private: A variable that indicates whether this is the first time this page has been loaded. If you go to a web page, leave it
+ for another one, and then return, the page's onload listener fires again. We need a way to differentiate between the first page
+ load and subsequent ones. This variable works hand in hand with the pageLoaded variable we store into historyStorage.*/
+ firstLoad: null,
+
+ /*Private: A variable to handle an important edge case in IE. In IE, if a user manually types an address into their browser's
+ location bar, we must intercept this by calling checkLocation() at regular intervals. However, if we are programmatically
+ changing the location bar ourselves using the add() method, we need to ignore these changes in checkLocation(). Unfortunately,
+ these changes take several lines of code to complete, so for the duration of those lines of code, we set this variable to true.
+ That signals to checkLocation() to ignore the change-in-progress. Once we're done with our chunk of location-change code in
+ add(), we set this back to false. We'll do the same thing when capturing user-entered address changes in checkLocation itself.*/
+ ieAtomicLocationChange: null,
+
+ /*Private: Create IE-specific DOM nodes and overrides*/
+ createIE: function(initialHash) {
+ /*write out a hidden iframe for IE and set the amount of time to wait between add() requests*/
+ this.waitTime = 400;/*IE needs longer between history updates*/
+ var styles = (historyStorage.debugMode
+ ? 'width: 800px;height:80px;border:1px solid black;'
+ : historyStorage.hideStyles
+ );
+ var iframeID = "rshHistoryFrame";
+ var iframeHTML = '<iframe frameborder="0" id="' + iframeID + '" style="' + styles + '" src="/static/js/singlepage/rsh/blank.html?' + initialHash + '"></iframe>';
+ document.write(iframeHTML);
+ this.iframe = document.getElementById(iframeID);
+ },
+
+ /*Private: Create Opera-specific DOM nodes and overrides*/
+ createOpera: function() {
+ this.waitTime = 400;/*Opera needs longer between history updates*/
+ var imgHTML = '<img src="javascript:location.href=\'javascript:dhtmlHistory.checkLocation();\';" style="' + historyStorage.hideStyles + '" />';
+ document.write(imgHTML);
+ },
+
+ /*Private: Create Safari-specific DOM nodes and overrides*/
+ createSafari: function() {
+ var formID = "rshSafariForm";
+ var stackID = "rshSafariStack";
+ var lengthID = "rshSafariLength";
+ var formStyles = historyStorage.debugMode ? historyStorage.showStyles : historyStorage.hideStyles;
+ var inputStyles = (historyStorage.debugMode
+ ? 'width:800px;height:20px;border:1px solid black;margin:0;padding:0;'
+ : historyStorage.hideStyles
+ );
+ var safariHTML = '<form id="' + formID + '" style="' + formStyles + '">'
+ + '<input type="text" style="' + inputStyles + '" id="' + stackID + '" value="[]"/>'
+ + '<input type="text" style="' + inputStyles + '" id="' + lengthID + '" value=""/>'
+ + '</form>';
+ document.write(safariHTML);
+ this.safariStack = document.getElementById(stackID);
+ this.safariLength = document.getElementById(lengthID);
+ if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) {
+ this.safariHistoryStartPoint = history.length;
+ this.safariLength.value = this.safariHistoryStartPoint;
+ } else {
+ this.safariHistoryStartPoint = this.safariLength.value;
+ }
+ },
+
+ /*Private: Safari method to read the history stack from a hidden form field*/
+ getSafariStack: function() {
+ var r = this.safariStack.value;
+ return historyStorage.fromJSON(r);
+ },
+
+ /*Private: Safari method to read from the history stack*/
+ getSafariState: function() {
+ var stack = this.getSafariStack();
+ var state = stack[history.length - this.safariHistoryStartPoint - 1];
+ return state;
+ },
+ /*Private: Safari method to write the history stack to a hidden form field*/
+ putSafariState: function(newLocation) {
+ var stack = this.getSafariStack();
+ stack[history.length - this.safariHistoryStartPoint] = newLocation;
+ this.safariStack.value = historyStorage.toJSON(stack);
+ },
+
+ /*Private: Notify the listener of new history changes.*/
+ fireHistoryEvent: function(newHash) {
+ /*extract the value from our history storage for this hash*/
+ var historyData = historyStorage.get(newHash);
+ /*call our listener*/
+ this.listener.call(null, newHash, historyData);
+ },
+
+ /*Private: See if the browser has changed location. This is the primary history mechanism for Firefox. For IE, we use this to
+ handle an important edge case: if a user manually types in a new hash value into their IE location bar and press enter, we want to
+ to intercept this and notify any history listener.*/
+ checkLocation: function() {
+
+ /*Ignore any location changes that we made ourselves for browsers other than IE*/
+ if (!this.isIE && this.ignoreLocationChange) {
+ this.ignoreLocationChange = false;
+ return;
+ }
+
+ /*If we are dealing with IE and we are in the middle of making a location change from an iframe, ignore it*/
+ if (!this.isIE && this.ieAtomicLocationChange) {
+ return;
+ }
+
+ /*Get hash location*/
+ var hash = this.getCurrentLocation();
+
+ /*Do nothing if there's been no change*/
+ if (hash == this.currentLocation) {
+ return;
+ }
+
+ /*In IE, users manually entering locations into the browser; we do this by comparing the browser's location against the
+ iframe's location; if they differ, we are dealing with a manual event and need to place it inside our history, otherwise
+ we can return*/
+ this.ieAtomicLocationChange = true;
+
+ if (this.isIE && this.getIframeHash() != hash) {
+ this.iframe.src = "/static/js/singlepage/rsh/blank.html?" + hash;
+ }
+ else if (this.isIE) {
+ /*the iframe is unchanged*/
+ return;
+ }
+
+ /*Save this new location*/
+ this.currentLocation = hash;
+
+ this.ieAtomicLocationChange = false;
+
+ /*Notify listeners of the change*/
+ this.fireHistoryEvent(hash);
+ },
+
+ /*Private: Get the current location of IE's hidden iframe.*/
+ getIframeHash: function() {
+ var doc = this.iframe.contentWindow.document;
+ var hash = String(doc.location.search);
+ if (hash.length == 1 && hash.charAt(0) == "?") {
+ hash = "";
+ }
+ else if (hash.length >= 2 && hash.charAt(0) == "?") {
+ hash = hash.substring(1);
+ }
+ return hash;
+ },
+
+ /*Private: Remove any leading hash that might be on a location.*/
+ removeHash: function(hashValue) {
+ var r;
+ if (hashValue === null || hashValue === undefined) {
+ r = null;
+ }
+ else if (hashValue === "") {
+ r = "";
+ }
+ else if (hashValue.length == 1 && hashValue.charAt(0) == "#") {
+ r = "";
+ }
+ else if (hashValue.length > 1 && hashValue.charAt(0) == "#") {
+ r = hashValue.substring(1);
+ }
+ else {
+ r = hashValue;
+ }
+ return r;
+ },
+
+ /*Private: For IE, tell when the hidden iframe has finished loading.*/
+ iframeLoaded: function(newLocation) {
+ /*ignore any location changes that we made ourselves*/
+ if (this.ignoreLocationChange) {
+ this.ignoreLocationChange = false;
+ return;
+ }
+
+ /*Get the new location*/
+ var hash = String(newLocation.search);
+ if (hash.length == 1 && hash.charAt(0) == "?") {
+ hash = "";
+ }
+ else if (hash.length >= 2 && hash.charAt(0) == "?") {
+ hash = hash.substring(1);
+ }
+ /*Keep the browser location bar in sync with the iframe hash*/
+ window.location.hash = hash;
+
+ /*Notify listeners of the change*/
+ this.fireHistoryEvent(hash);
+ }
+
+};
+
+/*
+ historyStorage: An object that uses a hidden form to store history state across page loads. The mechanism for doing so relies on
+ the fact that browsers save the text in form data for the life of the browser session, which means the text is still there when
+ the user navigates back to the page. This object can be used independently of the dhtmlHistory object for caching of Ajax
+ session information.
+
+ dependencies:
+ * json2007.js (included in a separate file) or alternate JSON methods passed in through an options bundle.
+*/
+window.historyStorage = {
+
+ /*Public: Set up our historyStorage object for use by dhtmlHistory or other objects*/
+ setup: function(options) {
+
+ /*
+ options - object to store initialization parameters - passed in from dhtmlHistory or directly into historyStorage
+ options.debugMode - boolean that causes hidden form fields to be shown for development purposes.
+ options.toJSON - function to override default JSON stringifier
+ options.fromJSON - function to override default JSON parser
+ */
+
+ /*process init parameters*/
+ if (typeof options !== "undefined") {
+ if (options.debugMode) {
+ this.debugMode = options.debugMode;
+ }
+ if (options.toJSON) {
+ this.toJSON = options.toJSON;
+ }
+ if (options.fromJSON) {
+ this.fromJSON = options.fromJSON;
+ }
+ }
+
+ /*write a hidden form and textarea into the page; we'll stow our history stack here*/
+ var formID = "rshStorageForm";
+ var textareaID = "rshStorageField";
+ var formStyles = this.debugMode ? historyStorage.showStyles : historyStorage.hideStyles;
+ var textareaStyles = (historyStorage.debugMode
+ ? 'width: 800px;height:80px;border:1px solid black;'
+ : historyStorage.hideStyles
+ );
+ var textareaHTML = '<form id="' + formID + '" style="' + formStyles + '">'
+ + '<textarea id="' + textareaID + '" style="' + textareaStyles + '"></textarea>'
+ + '</form>';
+ document.write(textareaHTML);
+ this.storageField = document.getElementById(textareaID);
+ if (typeof window.opera !== "undefined") {
+ this.storageField.focus();/*Opera needs to focus this element before persisting values in it*/
+ }
+ },
+
+ /*Public*/
+ put: function(key, value) {
+ this.assertValidKey(key);
+ /*if we already have a value for this, remove the value before adding the new one*/
+ if (this.hasKey(key)) {
+ this.remove(key);
+ }
+ /*store this new key*/
+ this.storageHash[key] = value;
+ /*save and serialize the hashtable into the form*/
+ this.saveHashTable();
+ },
+
+ /*Public*/
+ get: function(key) {
+ this.assertValidKey(key);
+ /*make sure the hash table has been loaded from the form*/
+ this.loadHashTable();
+ var value = this.storageHash[key];
+ if (value === undefined) {
+ value = null;
+ }
+ return value;
+ },
+
+ /*Public*/
+ remove: function(key) {
+ this.assertValidKey(key);
+ /*make sure the hash table has been loaded from the form*/
+ this.loadHashTable();
+ /*delete the value*/
+ delete this.storageHash[key];
+ /*serialize and save the hash table into the form*/
+ this.saveHashTable();
+ },
+
+ /*Public: Clears out all saved data.*/
+ reset: function() {
+ this.storageField.value = "";
+ this.storageHash = {};
+ },
+
+ /*Public*/
+ hasKey: function(key) {
+ this.assertValidKey(key);
+ /*make sure the hash table has been loaded from the form*/
+ this.loadHashTable();
+ return (typeof this.storageHash[key] !== "undefined");
+ },
+
+ /*Public*/
+ isValidKey: function(key) {
+ return (typeof key === "string");
+ },
+
+ /*Public - CSS strings utilized by both objects to hide or show behind-the-scenes DOM elements*/
+ showStyles: 'border:0;margin:0;padding:0;',
+ hideStyles: 'left:-1000px;top:-1000px;width:1px;height:1px;border:0;position:absolute;',
+
+ /*Public - debug mode flag*/
+ debugMode: false,
+
+ /*- - - - - - - - - - - -*/
+
+ /*Private: Our hash of key name/values.*/
+ storageHash: {},
+
+ /*Private: If true, we have loaded our hash table out of the storage form.*/
+ hashLoaded: false,
+
+ /*Private: DOM reference to our history field*/
+ storageField: null,
+
+ /*Private: Assert that a key is valid; throw an exception if it not.*/
+ assertValidKey: function(key) {
+ var isValid = this.isValidKey(key);
+ if (!isValid && this.debugMode) {
+ throw new Error("Please provide a valid key for window.historyStorage. Invalid key = " + key + ".");
+ }
+ },
+
+ /*Private: Load the hash table up from the form.*/
+ loadHashTable: function() {
+ if (!this.hashLoaded) {
+ var serializedHashTable = this.storageField.value;
+ if (serializedHashTable !== "" && serializedHashTable !== null) {
+ this.storageHash = this.fromJSON(serializedHashTable);
+ this.hashLoaded = true;
+ }
+ }
+ },
+ /*Private: Save the hash table into the form.*/
+ saveHashTable: function() {
+ this.loadHashTable();
+ var serializedHashTable = this.toJSON(this.storageHash);
+ this.storageField.value = serializedHashTable;
+ },
+ /*Private: Bridges for our JSON implementations - both rely on 2007 JSON.org library - can be overridden by options bundle*/
+ toJSON: function(o) {
+ return o.toJSONString();
+ },
+ fromJSON: function(s) {
+ return s.parseJSON();
+ }
+};
Added: jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/spa.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/share/plugins/Jifty/Plugin/SinglePage/web/static/js/singlepage/spa.js Wed Feb 20 15:24:13 2008
@@ -0,0 +1,53 @@
+(function($) {
+ SPA = {
+ initialHash: "spa_start",
+ currentHash: null,
+ currentLocation: null,
+ historyChange: function(newLocation, historyData, first) {
+
+ /* reload if user goes to the first page */
+ if (newLocation == SPA.initialHash) {
+ location.href = location.pathname;
+ }
+
+ if (first) {
+ dhtmlHistory.add(newLocation, historyData);
+ } else {
+ if (historyStorage.hasKey(newLocation)) {
+ Jifty.update(historyStorage.get(newLocation), "");
+ }
+ }
+ }
+ };
+
+ /*
+ * If user paste /#/abc in location bar, or click the reload button,
+ * then we should redirect him to the right page
+ */
+ SPA.currentHash = location.hash;
+ if (SPA.currentHash.length) {
+ if (SPA.currentHash.charAt(0) == '#' && SPA.currentHash.charAt(1) == '/') {
+ SPA.currentLocation = SPA.currentHash.slice(1);
+ location.href = SPA.currentLocation;
+ }
+ }
+
+ $(document).ready(function(){
+ dhtmlHistory.initialize();
+ dhtmlHistory.addListener(SPA.historyChange);
+ if (dhtmlHistory.isFirstLoad()) {
+ dhtmlHistory.add(SPA.initialHash, "");
+ }
+ });
+
+})(jQuery);
+
+
+window.dhtmlHistory.create({
+ toJSON: function(o) {
+ return JSON.stringify(o);
+ }
+ , fromJSON: function(s) {
+ return JSON.parse(s);
+ }
+});
More information about the Jifty-commit
mailing list