bzr branch
http://darksoft.org/webbzr/adei/trunk
1
by Suren A. Chilingaryan
Initial import |
1 |
/*
|
2 |
Copyright (c) 2007 Brian Dillard and Brad Neuberg:
|
|
3 |
Brian Dillard | Project Lead | bdillard@pathf.com | http://blogs.pathf.com/agileajax/
|
|
4 |
Brad Neuberg | Original Project Creator | http://codinginparadise.org
|
|
5 |
|
|
6 |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
|
|
7 |
(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
8 |
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
|
|
9 |
so, subject to the following conditions:
|
|
10 |
||
11 |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
12 |
||
13 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
14 |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
15 |
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
16 |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
17 |
*/
|
|
18 |
||
19 |
/*
|
|
20 |
dhtmlHistory: An object that provides history, history data, and bookmarking for DHTML and Ajax applications.
|
|
21 |
|
|
22 |
dependencies:
|
|
23 |
* the historyStorage object included in this file.
|
|
24 |
||
25 |
*/
|
|
26 |
window.dhtmlHistory = { |
|
27 |
|
|
28 |
/*Public: User-agent booleans*/ |
|
29 |
isIE: false, |
|
30 |
isOpera: false, |
|
31 |
isSafari: false, |
|
32 |
isKonquerer: false, |
|
33 |
isGecko: false, |
|
34 |
isSupported: false, |
|
35 |
|
|
36 |
/*Public: Create the DHTML history infrastructure*/ |
|
37 |
create: function(options) { |
|
38 |
|
|
39 |
/* |
|
40 |
options - object to store initialization parameters
|
|
41 |
options.debugMode - boolean that causes hidden form fields to be shown for development purposes.
|
|
42 |
options.toJSON - function to override default JSON stringifier
|
|
43 |
options.fromJSON - function to override default JSON parser
|
|
44 |
*/
|
|
45 |
||
46 |
var that = this; |
|
47 |
||
48 |
/*set user-agent flags*/ |
|
49 |
var UA = navigator.userAgent.toLowerCase(); |
|
50 |
var platform = navigator.platform.toLowerCase(); |
|
51 |
var vendor = navigator.vendor || ""; |
|
52 |
if (vendor === "KDE") { |
|
53 |
this.isKonqueror = true; |
|
54 |
this.isSupported = false; |
|
55 |
} else if (typeof window.opera !== "undefined") { |
|
56 |
this.isOpera = true; |
|
57 |
this.isSupported = true; |
|
58 |
} else if (typeof document.all !== "undefined") { |
|
59 |
this.isIE = true; |
|
60 |
this.isSupported = true; |
|
61 |
} else if (vendor.indexOf("Apple Computer, Inc.") > -1) { |
|
62 |
this.isSafari = true; |
|
63 |
this.isSupported = (platform.indexOf("mac") > -1); |
|
64 |
} else if (UA.indexOf("gecko") != -1) { |
|
65 |
this.isGecko = true; |
|
66 |
this.isSupported = true; |
|
67 |
} |
|
68 |
||
69 |
/*Set up the historyStorage object; pass in init parameters*/ |
|
70 |
window.historyStorage.setup(options); |
|
71 |
||
72 |
/*Execute browser-specific setup methods*/ |
|
73 |
if (this.isSafari) { |
|
74 |
this.createSafari(); |
|
75 |
} else if (this.isOpera) { |
|
76 |
this.createOpera(); |
|
77 |
} |
|
78 |
|
|
79 |
/*Get our initial location*/ |
|
80 |
var initialHash = this.getCurrentLocation(); |
|
81 |
||
82 |
/*Save it as our current location*/ |
|
83 |
this.currentLocation = initialHash; |
|
84 |
||
85 |
/*Now that we have a hash, create IE-specific code*/ |
|
86 |
if (this.isIE) { |
|
87 |
this.createIE(initialHash); |
|
88 |
} |
|
89 |
||
90 |
/*Add an unload listener for the page; this is needed for FF 1.5+ because this browser caches all dynamic updates to the |
|
91 |
page, which can break some of our logic related to testing whether this is the first instance a page has loaded or whether
|
|
92 |
it is being pulled from the cache*/
|
|
93 |
||
94 |
var unloadHandler = function() { |
|
95 |
that.firstLoad = null; |
|
96 |
}; |
|
97 |
|
|
98 |
this.addEventListener(window,'unload',unloadHandler); |
|
99 |
||
100 |
/*Determine if this is our first page load; for IE, we do this in this.iframeLoaded(), which is fired on pageload. We do it |
|
101 |
there because we have no historyStorage at this point, which only exists after the page is finished loading in IE*/
|
|
102 |
if (this.isIE) { |
|
103 |
/*The iframe will get loaded on page load, and we want to ignore this fact*/ |
|
104 |
this.ignoreLocationChange = true; |
|
105 |
} else { |
|
106 |
if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) { |
|
107 |
/*This is our first page load, so ignore the location change and add our special history entry*/ |
|
108 |
this.ignoreLocationChange = true; |
|
109 |
this.firstLoad = true; |
|
110 |
historyStorage.put(this.PAGELOADEDSTRING, true); |
|
111 |
} else { |
|
112 |
/*This isn't our first page load, so indicate that we want to pay attention to this location change*/ |
|
113 |
this.ignoreLocationChange = false; |
|
114 |
/*For browsers other than IE, fire a history change event; on IE, the event will be thrown automatically when its |
|
115 |
hidden iframe reloads on page load. Unfortunately, we don't have any listeners yet; indicate that we want to fire
|
|
116 |
an event when a listener is added.*/
|
|
117 |
this.fireOnNewListener = true; |
|
118 |
} |
|
119 |
} |
|
120 |
||
121 |
/*Other browsers can use a location handler that checks at regular intervals as their primary mechanism; we use it for IE as |
|
122 |
well to handle an important edge case; see checkLocation() for details*/
|
|
123 |
var locationHandler = function() { |
|
124 |
that.checkLocation(); |
|
125 |
}; |
|
126 |
setInterval(locationHandler, 100); |
|
127 |
}, |
|
128 |
|
|
129 |
/*Public: Initialize our DHTML history. You must call this after the page is finished loading.*/ |
|
130 |
initialize: function() { |
|
131 |
/*IE needs to be explicitly initialized. IE doesn't autofill form data until the page is finished loading, so we have to wait*/ |
|
132 |
if (this.isIE) { |
|
133 |
/*If this is the first time this page has loaded*/ |
|
134 |
if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) { |
|
135 |
/*For IE, we do this in initialize(); for other browsers, we do it in create()*/ |
|
136 |
this.fireOnNewListener = false; |
|
137 |
this.firstLoad = true; |
|
138 |
historyStorage.put(this.PAGELOADEDSTRING, true); |
|
139 |
} |
|
140 |
/*Else if this is a fake onload event*/ |
|
141 |
else { |
|
142 |
this.fireOnNewListener = true; |
|
143 |
this.firstLoad = false; |
|
144 |
} |
|
145 |
} |
|
146 |
}, |
|
147 |
||
148 |
/*Public: Adds a history change listener. Note that only one listener is supported at this time.*/ |
|
149 |
addListener: function(listener) { |
|
150 |
this.listener = listener; |
|
151 |
/*If the page was just loaded and we should not ignore it, fire an event to our new listener now*/ |
|
152 |
if (this.fireOnNewListener) { |
|
153 |
this.fireHistoryEvent(this.currentLocation); |
|
154 |
this.fireOnNewListener = false; |
|
155 |
} |
|
156 |
}, |
|
157 |
|
|
158 |
/*Public: Generic utility function for attaching events*/ |
|
159 |
addEventListener: function(o,e,l) { |
|
160 |
if (o.addEventListener) { |
|
161 |
o.addEventListener(e,l,false); |
|
162 |
} else if (o.attachEvent) { |
|
163 |
o.attachEvent('on'+e,function() { |
|
164 |
l(window.event); |
|
165 |
}); |
|
166 |
} |
|
167 |
}, |
|
168 |
|
|
169 |
/*Public: Add a history point.*/ |
|
170 |
add: function(newLocation, historyData) { |
|
171 |
|
|
172 |
if (this.isSafari) { |
|
173 |
|
|
174 |
/*Remove any leading hash symbols on newLocation*/ |
|
175 |
newLocation = this.removeHash(newLocation); |
|
176 |
||
177 |
/*Store the history data into history storage*/ |
|
178 |
historyStorage.put(newLocation, historyData); |
|
179 |
||
180 |
/*Save this as our current location*/ |
|
181 |
this.currentLocation = newLocation; |
|
182 |
|
|
183 |
/*Change the browser location*/ |
|
184 |
window.location.hash = newLocation; |
|
185 |
|
|
186 |
/*Save this to the Safari form field*/ |
|
187 |
this.putSafariState(newLocation); |
|
188 |
||
189 |
} else { |
|
190 |
|
|
191 |
/*Most browsers require that we wait a certain amount of time before changing the location, such |
|
192 |
as 200 MS; rather than forcing external callers to use window.setTimeout to account for this,
|
|
193 |
we internally handle it by putting requests in a queue.*/
|
|
194 |
var that = this; |
|
195 |
var addImpl = function() { |
|
196 |
||
197 |
/*Indicate that the current wait time is now less*/ |
|
198 |
if (that.currentWaitTime > 0) { |
|
199 |
that.currentWaitTime = that.currentWaitTime - that.waitTime; |
|
200 |
} |
|
201 |
|
|
202 |
/*Remove any leading hash symbols on newLocation*/ |
|
203 |
newLocation = that.removeHash(newLocation); |
|
204 |
||
205 |
/*IE has a strange bug; if the newLocation is the same as _any_ preexisting id in the |
|
206 |
document, then the history action gets recorded twice; throw a programmer exception if
|
|
207 |
there is an element with this ID*/
|
|
208 |
if (document.getElementById(newLocation) && that.debugMode) { |
|
209 |
var e = "Exception: History locations can not have the same value as _any_ IDs that might be in the document," |
|
210 |
+ " due to a bug in IE; please ask the developer to choose a history location that does not match any HTML" |
|
211 |
+ " IDs in this document. The following ID is already taken and cannot be a location: " + newLocation; |
|
212 |
throw new Error(e); |
|
213 |
} |
|
214 |
||
215 |
/*Store the history data into history storage*/ |
|
216 |
historyStorage.put(newLocation, historyData); |
|
217 |
||
218 |
/*Indicate to the browser to ignore this upcomming location change since we're making it programmatically*/ |
|
219 |
that.ignoreLocationChange = true; |
|
220 |
||
221 |
/*Indicate to IE that this is an atomic location change block*/ |
|
222 |
that.ieAtomicLocationChange = true; |
|
223 |
||
224 |
/*Save this as our current location*/ |
|
225 |
that.currentLocation = newLocation; |
|
226 |
|
|
227 |
/*Change the browser location*/ |
|
228 |
window.location.hash = newLocation; |
|
229 |
||
230 |
/*Change the hidden iframe's location if on IE*/ |
|
231 |
if (that.isIE) { |
|
232 |
that.iframe.src = "blank.html?" + newLocation; |
|
233 |
} |
|
234 |
||
235 |
/*End of atomic location change block for IE*/ |
|
236 |
that.ieAtomicLocationChange = false; |
|
237 |
}; |
|
238 |
||
239 |
/*Now queue up this add request*/ |
|
240 |
window.setTimeout(addImpl, this.currentWaitTime); |
|
241 |
||
242 |
/*Indicate that the next request will have to wait for awhile*/ |
|
243 |
this.currentWaitTime = this.currentWaitTime + this.waitTime; |
|
244 |
} |
|
245 |
}, |
|
246 |
||
247 |
/*Public*/ |
|
248 |
isFirstLoad: function() { |
|
249 |
return this.firstLoad; |
|
250 |
}, |
|
251 |
||
252 |
/*Public*/ |
|
253 |
getVersion: function() { |
|
254 |
return "0.6"; |
|
255 |
}, |
|
256 |
||
257 |
/*Get browser's current hash location; for Safari, read value from a hidden form field*/ |
|
258 |
||
259 |
/*Public*/ |
|
260 |
getCurrentLocation: function() { |
|
261 |
var r = (this.isSafari |
|
262 |
? this.getSafariState() |
|
263 |
: this.getCurrentHash() |
|
264 |
); |
|
265 |
return r; |
|
266 |
}, |
|
267 |
|
|
268 |
/*Public: Manually parse the current url for a hash; tip of the hat to YUI*/ |
|
269 |
getCurrentHash: function() { |
|
270 |
var r = window.location.href; |
|
271 |
var i = r.indexOf("#"); |
|
272 |
return (i >= 0 |
|
273 |
? r.substr(i+1) |
|
274 |
: "" |
|
275 |
); |
|
276 |
}, |
|
277 |
|
|
278 |
/*- - - - - - - - - - - -*/ |
|
279 |
|
|
280 |
/*Private: Constant for our own internal history event called when the page is loaded*/ |
|
281 |
PAGELOADEDSTRING: "DhtmlHistory_pageLoaded", |
|
282 |
|
|
283 |
/*Private: Our history change listener.*/ |
|
284 |
listener: null, |
|
285 |
||
286 |
/*Private: MS to wait between add requests - will be reset for certain browsers*/ |
|
287 |
waitTime: 200, |
|
288 |
|
|
289 |
/*Private: MS before an add request can execute*/ |
|
290 |
currentWaitTime: 0, |
|
291 |
||
292 |
/*Private: Our current hash location, without the "#" symbol.*/ |
|
293 |
currentLocation: null, |
|
294 |
||
295 |
/*Private: Hidden iframe used to IE to detect history changes*/ |
|
296 |
iframe: null, |
|
297 |
||
298 |
/*Private: Flags and DOM references used only by Safari*/ |
|
299 |
safariHistoryStartPoint: null, |
|
300 |
safariStack: null, |
|
301 |
safariLength: null, |
|
302 |
||
303 |
/*Private: Flag used to keep checkLocation() from doing anything when it discovers location changes we've made ourselves |
|
304 |
programmatically with the add() method. Basically, add() sets this to true. When checkLocation() discovers it's true,
|
|
305 |
it refrains from firing our listener, then resets the flag to false for next cycle. That way, our listener only gets fired on
|
|
306 |
history change events triggered by the user via back/forward buttons and manual hash changes. This flag also helps us set up
|
|
307 |
IE's special iframe-based method of handling history changes.*/
|
|
308 |
ignoreLocationChange: null, |
|
309 |
||
310 |
/*Private: A flag that indicates that we should fire a history change event when we are ready, i.e. after we are initialized and |
|
311 |
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
|
|
312 |
then return, we must fire this as a history change event. Unfortunately, we have lost all references to listeners from earlier,
|
|
313 |
because JavaScript clears out.*/
|
|
314 |
fireOnNewListener: null, |
|
315 |
||
316 |
/*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 |
|
317 |
for another one, and then return, the page's onload listener fires again. We need a way to differentiate between the first page
|
|
318 |
load and subsequent ones. This variable works hand in hand with the pageLoaded variable we store into historyStorage.*/
|
|
319 |
firstLoad: null, |
|
320 |
||
321 |
/*Private: A variable to handle an important edge case in IE. In IE, if a user manually types an address into their browser's |
|
322 |
location bar, we must intercept this by calling checkLocation() at regular intervals. However, if we are programmatically
|
|
323 |
changing the location bar ourselves using the add() method, we need to ignore these changes in checkLocation(). Unfortunately,
|
|
324 |
these changes take several lines of code to complete, so for the duration of those lines of code, we set this variable to true.
|
|
325 |
That signals to checkLocation() to ignore the change-in-progress. Once we're done with our chunk of location-change code in
|
|
326 |
add(), we set this back to false. We'll do the same thing when capturing user-entered address changes in checkLocation itself.*/
|
|
327 |
ieAtomicLocationChange: null, |
|
328 |
|
|
329 |
/*Private: Create IE-specific DOM nodes and overrides*/ |
|
330 |
createIE: function(initialHash) { |
|
331 |
/*write out a hidden iframe for IE and set the amount of time to wait between add() requests*/ |
|
332 |
this.waitTime = 400;/*IE needs longer between history updates*/ |
|
333 |
var styles = (historyStorage.debugMode |
|
334 |
? 'width: 800px;height:80px;border:1px solid black;' |
|
335 |
: historyStorage.hideStyles |
|
336 |
); |
|
337 |
var iframeID = "rshHistoryFrame"; |
|
338 |
var iframeHTML = '<iframe frameborder="0" id="' + iframeID + '" style="' + styles + '" src="blank.html?' + initialHash + '"></iframe>'; |
|
339 |
document.write(iframeHTML); |
|
340 |
this.iframe = document.getElementById(iframeID); |
|
341 |
}, |
|
342 |
|
|
343 |
/*Private: Create Opera-specific DOM nodes and overrides*/ |
|
344 |
createOpera: function() { |
|
345 |
this.waitTime = 400;/*Opera needs longer between history updates*/ |
|
346 |
var imgHTML = '<img src="javascript:location.href=\'javascript:dhtmlHistory.checkLocation();\';" style="' + historyStorage.hideStyles + '" />'; |
|
347 |
document.write(imgHTML); |
|
348 |
}, |
|
349 |
|
|
350 |
/*Private: Create Safari-specific DOM nodes and overrides*/ |
|
351 |
createSafari: function() { |
|
352 |
var formID = "rshSafariForm"; |
|
353 |
var stackID = "rshSafariStack"; |
|
354 |
var lengthID = "rshSafariLength"; |
|
355 |
var formStyles = historyStorage.debugMode ? historyStorage.showStyles : historyStorage.hideStyles; |
|
356 |
var inputStyles = (historyStorage.debugMode |
|
357 |
? 'width:800px;height:20px;border:1px solid black;margin:0;padding:0;' |
|
358 |
: historyStorage.hideStyles |
|
359 |
); |
|
360 |
var safariHTML = '<form id="' + formID + '" style="' + formStyles + '">' |
|
361 |
+ '<input type="text" style="' + inputStyles + '" id="' + stackID + '" value="[]"/>' |
|
362 |
+ '<input type="text" style="' + inputStyles + '" id="' + lengthID + '" value=""/>' |
|
363 |
+ '</form>'; |
|
364 |
document.write(safariHTML); |
|
365 |
this.safariStack = document.getElementById(stackID); |
|
366 |
this.safariLength = document.getElementById(lengthID); |
|
367 |
if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) { |
|
368 |
this.safariHistoryStartPoint = history.length; |
|
369 |
this.safariLength.value = this.safariHistoryStartPoint; |
|
370 |
} else { |
|
371 |
this.safariHistoryStartPoint = this.safariLength.value; |
|
372 |
} |
|
373 |
}, |
|
374 |
|
|
375 |
/*Private: Safari method to read the history stack from a hidden form field*/ |
|
376 |
getSafariStack: function() { |
|
377 |
var r = this.safariStack.value; |
|
378 |
return historyStorage.fromJSON(r); |
|
379 |
}, |
|
380 |
||
381 |
/*Private: Safari method to read from the history stack*/ |
|
382 |
getSafariState: function() { |
|
383 |
var stack = this.getSafariStack(); |
|
384 |
var state = stack[history.length - this.safariHistoryStartPoint - 1]; |
|
385 |
return state; |
|
386 |
}, |
|
387 |
/*Private: Safari method to write the history stack to a hidden form field*/ |
|
388 |
putSafariState: function(newLocation) { |
|
389 |
var stack = this.getSafariStack(); |
|
390 |
stack[history.length - this.safariHistoryStartPoint] = newLocation; |
|
391 |
this.safariStack.value = historyStorage.toJSON(stack); |
|
392 |
}, |
|
393 |
||
394 |
/*Private: Notify the listener of new history changes.*/ |
|
395 |
fireHistoryEvent: function(newHash) { |
|
396 |
/*extract the value from our history storage for this hash*/ |
|
397 |
var historyData = historyStorage.get(newHash); |
|
398 |
/*call our listener*/ |
|
399 |
this.listener.call(null, newHash, historyData); |
|
400 |
}, |
|
401 |
|
|
402 |
/*Private: See if the browser has changed location. This is the primary history mechanism for Firefox. For IE, we use this to |
|
403 |
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
|
|
404 |
to intercept this and notify any history listener.*/
|
|
405 |
checkLocation: function() { |
|
406 |
|
|
407 |
/*Ignore any location changes that we made ourselves for browsers other than IE*/ |
|
408 |
if (!this.isIE && this.ignoreLocationChange) { |
|
409 |
this.ignoreLocationChange = false; |
|
410 |
return; |
|
411 |
} |
|
412 |
||
413 |
/*If we are dealing with IE and we are in the middle of making a location change from an iframe, ignore it*/ |
|
414 |
if (!this.isIE && this.ieAtomicLocationChange) { |
|
415 |
return; |
|
416 |
} |
|
417 |
|
|
418 |
/*Get hash location*/ |
|
419 |
var hash = this.getCurrentLocation(); |
|
420 |
||
421 |
/*Do nothing if there's been no change*/ |
|
422 |
if (hash == this.currentLocation) { |
|
423 |
return; |
|
424 |
} |
|
425 |
||
426 |
/*In IE, users manually entering locations into the browser; we do this by comparing the browser's location against the |
|
427 |
iframe's location; if they differ, we are dealing with a manual event and need to place it inside our history, otherwise
|
|
428 |
we can return*/
|
|
429 |
this.ieAtomicLocationChange = true; |
|
430 |
||
431 |
if (this.isIE && this.getIframeHash() != hash) { |
|
432 |
this.iframe.src = "blank.html?" + hash; |
|
433 |
} |
|
434 |
else if (this.isIE) { |
|
435 |
/*the iframe is unchanged*/ |
|
436 |
return; |
|
437 |
} |
|
438 |
||
439 |
/*Save this new location*/ |
|
440 |
this.currentLocation = hash; |
|
441 |
||
442 |
this.ieAtomicLocationChange = false; |
|
443 |
||
444 |
/*Notify listeners of the change*/ |
|
445 |
this.fireHistoryEvent(hash); |
|
446 |
}, |
|
447 |
||
448 |
/*Private: Get the current location of IE's hidden iframe.*/ |
|
449 |
getIframeHash: function() { |
|
450 |
var doc = this.iframe.contentWindow.document; |
|
451 |
var hash = String(doc.location.search); |
|
452 |
if (hash.length == 1 && hash.charAt(0) == "?") { |
|
453 |
hash = ""; |
|
454 |
} |
|
455 |
else if (hash.length >= 2 && hash.charAt(0) == "?") { |
|
456 |
hash = hash.substring(1); |
|
457 |
} |
|
458 |
return hash; |
|
459 |
}, |
|
460 |
||
461 |
/*Private: Remove any leading hash that might be on a location.*/ |
|
462 |
removeHash: function(hashValue) { |
|
463 |
var r; |
|
464 |
if (hashValue === null || hashValue === undefined) { |
|
465 |
r = null; |
|
466 |
} |
|
467 |
else if (hashValue === "") { |
|
468 |
r = ""; |
|
469 |
} |
|
470 |
else if (hashValue.length == 1 && hashValue.charAt(0) == "#") { |
|
471 |
r = ""; |
|
472 |
} |
|
473 |
else if (hashValue.length > 1 && hashValue.charAt(0) == "#") { |
|
474 |
r = hashValue.substring(1); |
|
475 |
} |
|
476 |
else { |
|
477 |
r = hashValue; |
|
478 |
} |
|
479 |
return r; |
|
480 |
}, |
|
481 |
||
482 |
/*Private: For IE, tell when the hidden iframe has finished loading.*/ |
|
483 |
iframeLoaded: function(newLocation) { |
|
484 |
/*ignore any location changes that we made ourselves*/ |
|
485 |
if (this.ignoreLocationChange) { |
|
486 |
this.ignoreLocationChange = false; |
|
487 |
return; |
|
488 |
} |
|
489 |
||
490 |
/*Get the new location*/ |
|
491 |
var hash = String(newLocation.search); |
|
492 |
if (hash.length == 1 && hash.charAt(0) == "?") { |
|
493 |
hash = ""; |
|
494 |
} |
|
495 |
else if (hash.length >= 2 && hash.charAt(0) == "?") { |
|
496 |
hash = hash.substring(1); |
|
497 |
} |
|
498 |
/*Keep the browser location bar in sync with the iframe hash*/ |
|
499 |
window.location.hash = hash; |
|
500 |
||
501 |
/*Notify listeners of the change*/ |
|
502 |
this.fireHistoryEvent(hash); |
|
503 |
} |
|
504 |
||
505 |
}; |
|
506 |
||
507 |
/*
|
|
508 |
historyStorage: An object that uses a hidden form to store history state across page loads. The mechanism for doing so relies on
|
|
509 |
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
|
|
510 |
the user navigates back to the page. This object can be used independently of the dhtmlHistory object for caching of Ajax
|
|
511 |
session information.
|
|
512 |
|
|
513 |
dependencies:
|
|
514 |
* json2007.js (included in a separate file) or alternate JSON methods passed in through an options bundle.
|
|
515 |
*/
|
|
516 |
window.historyStorage = { |
|
517 |
|
|
518 |
/*Public: Set up our historyStorage object for use by dhtmlHistory or other objects*/ |
|
519 |
setup: function(options) { |
|
520 |
|
|
521 |
/* |
|
522 |
options - object to store initialization parameters - passed in from dhtmlHistory or directly into historyStorage
|
|
523 |
options.debugMode - boolean that causes hidden form fields to be shown for development purposes.
|
|
524 |
options.toJSON - function to override default JSON stringifier
|
|
525 |
options.fromJSON - function to override default JSON parser
|
|
526 |
*/
|
|
527 |
|
|
528 |
/*process init parameters*/ |
|
529 |
if (typeof options !== "undefined") { |
|
530 |
if (options.debugMode) { |
|
531 |
this.debugMode = options.debugMode; |
|
532 |
} |
|
533 |
if (options.toJSON) { |
|
534 |
this.toJSON = options.toJSON; |
|
535 |
} |
|
536 |
if (options.fromJSON) { |
|
537 |
this.fromJSON = options.fromJSON; |
|
538 |
} |
|
539 |
} |
|
540 |
|
|
541 |
/*write a hidden form and textarea into the page; we'll stow our history stack here*/ |
|
542 |
var formID = "rshStorageForm"; |
|
543 |
var textareaID = "rshStorageField"; |
|
544 |
var formStyles = this.debugMode ? historyStorage.showStyles : historyStorage.hideStyles; |
|
545 |
var textareaStyles = (historyStorage.debugMode |
|
546 |
? 'width: 800px;height:80px;border:1px solid black;' |
|
547 |
: historyStorage.hideStyles |
|
548 |
); |
|
549 |
var textareaHTML = '<form id="' + formID + '" style="' + formStyles + '">' |
|
550 |
+ '<textarea id="' + textareaID + '" style="' + textareaStyles + '"></textarea>' |
|
551 |
+ '</form>'; |
|
552 |
document.write(textareaHTML); |
|
553 |
this.storageField = document.getElementById(textareaID); |
|
554 |
if (typeof window.opera !== "undefined") { |
|
555 |
this.storageField.focus();/*Opera needs to focus this element before persisting values in it*/ |
|
556 |
} |
|
557 |
}, |
|
558 |
|
|
559 |
/*Public*/ |
|
560 |
put: function(key, value) { |
|
561 |
this.assertValidKey(key); |
|
562 |
/*if we already have a value for this, remove the value before adding the new one*/ |
|
563 |
if (this.hasKey(key)) { |
|
564 |
this.remove(key); |
|
565 |
} |
|
566 |
/*store this new key*/ |
|
567 |
this.storageHash[key] = value; |
|
568 |
/*save and serialize the hashtable into the form*/ |
|
569 |
this.saveHashTable(); |
|
570 |
}, |
|
571 |
||
572 |
/*Public*/ |
|
573 |
get: function(key) { |
|
574 |
this.assertValidKey(key); |
|
575 |
/*make sure the hash table has been loaded from the form*/ |
|
576 |
this.loadHashTable(); |
|
577 |
var value = this.storageHash[key]; |
|
578 |
if (value === undefined) { |
|
579 |
value = null; |
|
580 |
} |
|
581 |
return value; |
|
582 |
}, |
|
583 |
||
584 |
/*Public*/ |
|
585 |
remove: function(key) { |
|
586 |
this.assertValidKey(key); |
|
587 |
/*make sure the hash table has been loaded from the form*/ |
|
588 |
this.loadHashTable(); |
|
589 |
/*delete the value*/ |
|
590 |
delete this.storageHash[key]; |
|
591 |
/*serialize and save the hash table into the form*/ |
|
592 |
this.saveHashTable(); |
|
593 |
}, |
|
594 |
||
595 |
/*Public: Clears out all saved data.*/ |
|
596 |
reset: function() { |
|
597 |
this.storageField.value = ""; |
|
598 |
this.storageHash = {}; |
|
599 |
}, |
|
600 |
||
601 |
/*Public*/ |
|
602 |
hasKey: function(key) { |
|
603 |
this.assertValidKey(key); |
|
604 |
/*make sure the hash table has been loaded from the form*/ |
|
605 |
this.loadHashTable(); |
|
606 |
return (typeof this.storageHash[key] !== "undefined"); |
|
607 |
}, |
|
608 |
||
609 |
/*Public*/ |
|
610 |
isValidKey: function(key) { |
|
611 |
return (typeof key === "string"); |
|
612 |
}, |
|
613 |
|
|
614 |
/*Public - CSS strings utilized by both objects to hide or show behind-the-scenes DOM elements*/ |
|
615 |
showStyles: 'border:0;margin:0;padding:0;', |
|
616 |
hideStyles: 'left:-1000px;top:-1000px;width:1px;height:1px;border:0;position:absolute;', |
|
617 |
|
|
618 |
/*Public - debug mode flag*/ |
|
619 |
debugMode: false, |
|
620 |
|
|
621 |
/*- - - - - - - - - - - -*/ |
|
622 |
||
623 |
/*Private: Our hash of key name/values.*/ |
|
624 |
storageHash: {}, |
|
625 |
||
626 |
/*Private: If true, we have loaded our hash table out of the storage form.*/ |
|
627 |
hashLoaded: false, |
|
628 |
||
629 |
/*Private: DOM reference to our history field*/ |
|
630 |
storageField: null, |
|
631 |
||
632 |
/*Private: Assert that a key is valid; throw an exception if it not.*/ |
|
633 |
assertValidKey: function(key) { |
|
634 |
var isValid = this.isValidKey(key); |
|
635 |
if (!isValid && this.debugMode) { |
|
636 |
throw new Error("Please provide a valid key for window.historyStorage. Invalid key = " + key + "."); |
|
637 |
} |
|
638 |
}, |
|
639 |
||
640 |
/*Private: Load the hash table up from the form.*/ |
|
641 |
loadHashTable: function() { |
|
642 |
if (!this.hashLoaded) { |
|
643 |
var serializedHashTable = this.storageField.value; |
|
644 |
if (serializedHashTable !== "" && serializedHashTable !== null) { |
|
645 |
this.storageHash = this.fromJSON(serializedHashTable); |
|
646 |
this.hashLoaded = true; |
|
647 |
} |
|
648 |
} |
|
649 |
}, |
|
650 |
/*Private: Save the hash table into the form.*/ |
|
651 |
saveHashTable: function() { |
|
652 |
this.loadHashTable(); |
|
653 |
var serializedHashTable = this.toJSON(this.storageHash); |
|
654 |
this.storageField.value = serializedHashTable; |
|
655 |
}, |
|
656 |
/*Private: Bridges for our JSON implementations - both rely on 2007 JSON.org library - can be overridden by options bundle*/ |
|
657 |
toJSON: function(o) { |
|
658 |
return o.toJSONString(); |
|
659 |
}, |
|
660 |
fromJSON: function(s) { |
|
661 |
return s.parseJSON(); |
|
662 |
} |
|
663 |
}; |