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 = true; //(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 |
var fixWaitTime = function() { |
|
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 |
/*End of atomic location change block for IE*/
|
|
203 |
that.ieAtomicLocationChange = false; |
|
204 |
}
|
|
205 |
||
206 |
/*Remove any leading hash symbols on newLocation*/
|
|
207 |
newLocation = that.removeHash(newLocation); |
|
208 |
||
209 |
/*IE has a strange bug; if the newLocation is the same as _any_ preexisting id in the
|
|
210 |
document, then the history action gets recorded twice; throw a programmer exception if
|
|
211 |
there is an element with this ID*/
|
|
212 |
if (document.getElementById(newLocation) && that.debugMode) { |
|
213 |
var e = "Exception: History locations can not have the same value as _any_ IDs that might be in the document," |
|
214 |
+ " due to a bug in IE; please ask the developer to choose a history location that does not match any HTML" |
|
215 |
+ " IDs in this document. The following ID is already taken and cannot be a location: " + newLocation; |
|
216 |
throw new Error(e); |
|
217 |
}
|
|
218 |
||
219 |
/*Store the history data into history storage*/
|
|
220 |
historyStorage.put(newLocation, historyData); |
|
221 |
||
222 |
/*Indicate to the browser to ignore this upcomming location change since we're making it programmatically*/
|
|
223 |
that.ignoreLocationChange = true; |
|
224 |
||
225 |
/*Indicate to IE that this is an atomic location change block*/
|
|
226 |
that.ieAtomicLocationChange = true; |
|
227 |
||
228 |
/*Save this as our current location*/
|
|
229 |
that.currentLocation = newLocation; |
|
230 |
||
231 |
/*Change the browser location*/
|
|
232 |
window.location.hash = newLocation; |
|
233 |
||
234 |
/*Change the hidden iframe's location if on IE*/
|
|
235 |
if (that.isIE) { |
|
236 |
that.iframe.src = "blank.html?" + newLocation; |
|
237 |
}
|
|
238 |
||
239 |
if (that.waitTime) { |
|
240 |
window.setTimeout(fixWaitTime, that.waitTime); |
|
241 |
} else { |
|
242 |
/*End of atomic location change block for IE*/
|
|
243 |
that.ieAtomicLocationChange = false; |
|
244 |
}
|
|
245 |
};
|
|
246 |
||
247 |
// Check?
|
|
248 |
this.ieAtomicLocationChange = true; |
|
249 |
||
250 |
if (this.currentWaitTime) { |
|
251 |
/*Now queue up this add request*/
|
|
252 |
window.setTimeout(addImpl, this.currentWaitTime); |
|
253 |
||
254 |
/*Indicate that the next request will have to wait for awhile*/
|
|
255 |
this.currentWaitTime = this.currentWaitTime + this.waitTime; |
|
256 |
} else { |
|
257 |
this.currentWaitTime = this.currentWaitTime + this.waitTime; |
|
258 |
addImpl(); |
|
259 |
}
|
|
260 |
||
261 |
}
|
|
262 |
},
|
|
263 |
||
264 |
/*Public*/
|
|
265 |
isFirstLoad: function() { |
|
266 |
return this.firstLoad; |
|
267 |
},
|
|
268 |
||
269 |
/*Public*/
|
|
270 |
getVersion: function() { |
|
271 |
return "0.6"; |
|
272 |
},
|
|
273 |
||
274 |
/*Get browser's current hash location; for Safari, read value from a hidden form field*/
|
|
275 |
||
276 |
/*Public*/
|
|
277 |
getCurrentLocation: function() { |
|
278 |
return this.getCurrentHash(); |
|
279 |
/*
|
|
280 |
This causes problems in Safari and looks not really needed,
|
|
281 |
at least Mac 3.0.4 and Win 3.1 working well without special
|
|
282 |
handling of Safari.
|
|
283 |
var r = (this.isSafari
|
|
284 |
? this.getSafariState()
|
|
285 |
: this.getCurrentHash()
|
|
286 |
);
|
|
287 |
return r;
|
|
288 |
*/
|
|
289 |
},
|
|
290 |
||
291 |
/*Public: Manually parse the current url for a hash; tip of the hat to YUI*/
|
|
292 |
getCurrentHash: function() { |
|
293 |
if (this.isIE) { |
|
294 |
var r = this.getIframeHash(); |
|
295 |
if (r) return r; |
|
296 |
}
|
|
297 |
||
298 |
var r = window.location.href; |
|
299 |
var i = r.indexOf("#"); |
|
300 |
return (i >= 0 |
|
301 |
? r.substr(i+1) |
|
302 |
: "" |
|
303 |
);
|
|
304 |
},
|
|
305 |
||
306 |
/*- - - - - - - - - - - -*/
|
|
307 |
||
308 |
/*Private: Constant for our own internal history event called when the page is loaded*/
|
|
309 |
PAGELOADEDSTRING: "DhtmlHistory_pageLoaded", |
|
310 |
||
311 |
/*Private: Our history change listener.*/
|
|
312 |
listener: null, |
|
313 |
||
314 |
/*Private: MS to wait between add requests - will be reset for certain browsers*/
|
|
315 |
waitTime: 200, |
|
316 |
||
317 |
/*Private: MS before an add request can execute*/
|
|
318 |
currentWaitTime: 0, |
|
319 |
||
320 |
/*Private: Our current hash location, without the "#" symbol.*/
|
|
321 |
currentLocation: null, |
|
322 |
||
323 |
/*Private: Hidden iframe used to IE to detect history changes*/
|
|
324 |
iframe: null, |
|
325 |
||
326 |
/*Private: Flags and DOM references used only by Safari*/
|
|
327 |
safariHistoryStartPoint: null, |
|
328 |
safariStack: null, |
|
329 |
safariLength: null, |
|
330 |
||
331 |
/*Private: Flag used to keep checkLocation() from doing anything when it discovers location changes we've made ourselves
|
|
332 |
programmatically with the add() method. Basically, add() sets this to true. When checkLocation() discovers it's true,
|
|
333 |
it refrains from firing our listener, then resets the flag to false for next cycle. That way, our listener only gets fired on
|
|
334 |
history change events triggered by the user via back/forward buttons and manual hash changes. This flag also helps us set up
|
|
335 |
IE's special iframe-based method of handling history changes.*/
|
|
336 |
ignoreLocationChange: null, |
|
337 |
||
338 |
/*Private: A flag that indicates that we should fire a history change event when we are ready, i.e. after we are initialized and
|
|
339 |
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
|
|
340 |
then return, we must fire this as a history change event. Unfortunately, we have lost all references to listeners from earlier,
|
|
341 |
because JavaScript clears out.*/
|
|
342 |
fireOnNewListener: null, |
|
343 |
||
344 |
/*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
|
|
345 |
for another one, and then return, the page's onload listener fires again. We need a way to differentiate between the first page
|
|
346 |
load and subsequent ones. This variable works hand in hand with the pageLoaded variable we store into historyStorage.*/
|
|
347 |
firstLoad: null, |
|
348 |
||
349 |
/*Private: A variable to handle an important edge case in IE. In IE, if a user manually types an address into their browser's
|
|
350 |
location bar, we must intercept this by calling checkLocation() at regular intervals. However, if we are programmatically
|
|
351 |
changing the location bar ourselves using the add() method, we need to ignore these changes in checkLocation(). Unfortunately,
|
|
352 |
these changes take several lines of code to complete, so for the duration of those lines of code, we set this variable to true.
|
|
353 |
That signals to checkLocation() to ignore the change-in-progress. Once we're done with our chunk of location-change code in
|
|
354 |
add(), we set this back to false. We'll do the same thing when capturing user-entered address changes in checkLocation itself.*/
|
|
355 |
ieAtomicLocationChange: null, |
|
356 |
||
357 |
/*Private: Create IE-specific DOM nodes and overrides*/
|
|
358 |
createIE: function(initialHash) { |
|
359 |
/*write out a hidden iframe for IE and set the amount of time to wait between add() requests*/
|
|
360 |
this.waitTime = 400;/*IE needs longer between history updates*/ |
|
361 |
var styles = (historyStorage.debugMode |
|
362 |
? 'width: 800px;height:80px;border:1px solid black;' |
|
363 |
: historyStorage.hideStyles |
|
364 |
);
|
|
365 |
var iframeID = "rshHistoryFrame"; |
|
366 |
/*
|
|
367 |
var iframeHTML = '<iframe frameborder="0" id="' + iframeID + '" style="' + styles + '" src="blank.html?' + initialHash + '"></iframe>';
|
|
368 |
document.write(iframeHTML);
|
|
369 |
this.iframe = document.getElementById(iframeID);
|
|
370 |
*/
|
|
371 |
||
372 |
||
373 |
var node = document.createElement('iframe'); |
|
374 |
node.setAttribute('frameborder', '0'); |
|
375 |
node.setAttribute('id', iframeID); |
|
376 |
if (typeof node.style.cssText == "string") |
|
377 |
node.style.cssText = styles; |
|
378 |
else
|
|
379 |
node.setAttribute('style', styles); |
|
380 |
||
381 |
node.setAttribute("src", "blank.html?" + initialHash); |
|
382 |
this.iframe = node; |
|
383 |
||
384 |
document.body.appendChild(node); |
|
385 |
},
|
|
386 |
||
387 |
/*Private: Create Opera-specific DOM nodes and overrides*/
|
|
388 |
createOpera: function() { |
|
389 |
this.waitTime = 400;/*Opera needs longer between history updates*/ |
|
390 |
/*
|
|
391 |
var imgHTML = '<img src="javascript:location.href=\'javascript:dhtmlHistory.checkLocation();\';" style="' + historyStorage.hideStyles + '" />';
|
|
392 |
document.write(imgHTML);
|
|
393 |
*/
|
|
394 |
var node = document.createElement('img'); |
|
395 |
if (typeof node.style.cssText == "string") |
|
396 |
node.style.cssText = historyStorage.hideStyles; |
|
397 |
else
|
|
398 |
node.setAttribute('style', historyStorage.hideStyles); |
|
399 |
node.setAttribute('src', "javascript:location.href=\'javascript:dhtmlHistory.checkLocation();\';"); |
|
400 |
||
401 |
document.body.appendChild(node); |
|
402 |
||
403 |
},
|
|
404 |
||
405 |
/*Private: Create Safari-specific DOM nodes and overrides*/
|
|
406 |
createSafari: function() { |
|
407 |
var formID = "rshSafariForm"; |
|
408 |
var stackID = "rshSafariStack"; |
|
409 |
var lengthID = "rshSafariLength"; |
|
410 |
var formStyles = historyStorage.debugMode ? historyStorage.showStyles : historyStorage.hideStyles; |
|
411 |
var inputStyles = (historyStorage.debugMode |
|
412 |
? 'width:800px;height:20px;border:1px solid black;margin:0;padding:0;' |
|
413 |
: historyStorage.hideStyles |
|
414 |
);
|
|
415 |
||
416 |
/*
|
|
417 |
var safariHTML = '<form id="' + formID + '" style="' + formStyles + '">'
|
|
418 |
+ '<input type="text" style="' + inputStyles + '" id="' + stackID + '" value="[]"/>'
|
|
419 |
+ '<input type="text" style="' + inputStyles + '" id="' + lengthID + '" value=""/>'
|
|
420 |
+ '</form>';
|
|
421 |
document.write(safariHTML);
|
|
422 |
this.safariStack = document.getElementById(stackID);
|
|
423 |
this.safariLength = document.getElementById(lengthID);
|
|
424 |
*/
|
|
425 |
||
426 |
var fnode = document.createElement('form'); |
|
427 |
if (typeof fnode.style.cssText == "string") |
|
428 |
fnode.style.cssText = formStyles; |
|
429 |
else
|
|
430 |
fnode.setAttribute('style', formStyles); |
|
431 |
fnode.setAttribute('id', formID); |
|
432 |
||
433 |
var node = document.createElement('input'); |
|
434 |
node.setAttribute('type', "text"); |
|
435 |
if (typeof node.style.cssText == "string") |
|
436 |
node.style.cssText = inputStyles; |
|
437 |
else
|
|
438 |
node.setAttribute('style', inputStyles); |
|
439 |
node.setAttribute('id', stackID); |
|
440 |
node.setAttribute('value', "[]"); |
|
441 |
||
442 |
fnode.appendChild(node); |
|
443 |
this.safariStack = node; |
|
444 |
||
445 |
node = document.createElement('input'); |
|
446 |
node.setAttribute('type', "text"); |
|
447 |
if (typeof node.style.cssText == "string") |
|
448 |
node.style.cssText = inputStyles; |
|
449 |
else
|
|
450 |
node.setAttribute('style', inputStyles); |
|
451 |
node.setAttribute('id', lengthID); |
|
452 |
node.setAttribute('value', ""); |
|
453 |
||
454 |
fnode.appendChild(node); |
|
455 |
this.safariLength = node; |
|
456 |
||
457 |
document.body.appendChild(fnode); |
|
458 |
||
459 |
if (!historyStorage.hasKey(this.PAGELOADEDSTRING)) { |
|
460 |
this.safariHistoryStartPoint = history.length; |
|
461 |
this.safariLength.value = this.safariHistoryStartPoint; |
|
462 |
} else { |
|
463 |
this.safariHistoryStartPoint = this.safariLength.value; |
|
464 |
}
|
|
465 |
},
|
|
466 |
||
467 |
/*Private: Safari method to read the history stack from a hidden form field*/
|
|
468 |
getSafariStack: function() { |
|
469 |
var r = this.safariStack.value; |
|
470 |
return historyStorage.fromJSON(r); |
|
471 |
},
|
|
472 |
||
473 |
/*Private: Safari method to read from the history stack*/
|
|
474 |
getSafariState: function() { |
|
475 |
var stack = this.getSafariStack(); |
|
476 |
var state = stack[history.length - this.safariHistoryStartPoint - 1]; |
|
477 |
return state; |
|
478 |
},
|
|
479 |
/*Private: Safari method to write the history stack to a hidden form field*/
|
|
480 |
putSafariState: function(newLocation) { |
|
481 |
var stack = this.getSafariStack(); |
|
482 |
stack[history.length - this.safariHistoryStartPoint] = newLocation; |
|
483 |
this.safariStack.value = historyStorage.toJSON(stack); |
|
484 |
},
|
|
485 |
||
486 |
/*Private: Notify the listener of new history changes.*/
|
|
487 |
fireHistoryEvent: function(newHash) { |
|
488 |
/*extract the value from our history storage for this hash*/
|
|
489 |
var historyData = historyStorage.get(newHash); |
|
490 |
/*call our listener*/
|
|
491 |
this.listener.call(null, newHash, historyData); |
|
492 |
},
|
|
493 |
||
494 |
/*Private: See if the browser has changed location. This is the primary history mechanism for Firefox. For IE, we use this to
|
|
495 |
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
|
|
496 |
to intercept this and notify any history listener.*/
|
|
497 |
checkLocation: function() { |
|
498 |
||
499 |
/*Ignore any location changes that we made ourselves for browsers other than IE*/
|
|
500 |
if (!this.isIE && this.ignoreLocationChange) { |
|
501 |
this.ignoreLocationChange = false; |
|
502 |
return; |
|
503 |
}
|
|
504 |
||
505 |
/*If we are dealing with IE and we are in the middle of making a location change from an iframe, ignore it*/
|
|
506 |
if (this.isIE && this.ieAtomicLocationChange) { |
|
507 |
return; |
|
508 |
}
|
|
509 |
||
510 |
/*Get hash location*/
|
|
511 |
var hash = this.getCurrentLocation(); |
|
512 |
||
513 |
/*Do nothing if there's been no change*/
|
|
514 |
if (hash == this.currentLocation) { |
|
515 |
return; |
|
516 |
}
|
|
517 |
// alert(hash + ' - ' + this.currentLocation);
|
|
518 |
||
519 |
/*In IE, users manually entering locations into the browser; we do this by comparing the browser's location against the
|
|
520 |
iframe's location; if they differ, we are dealing with a manual event and need to place it inside our history, otherwise
|
|
521 |
we can return*/
|
|
522 |
this.ieAtomicLocationChange = true; |
|
523 |
||
524 |
if (this.isIE) { |
|
525 |
this.iframe.src = "blank.html?" + hash; |
|
526 |
window.location.hash = hash; |
|
527 |
}
|
|
528 |
||
529 |
/*
|
|
530 |
if (this.isIE && this.getIframeHash() != hash) {
|
|
531 |
this.iframe.src = "blank.html?" + hash;
|
|
532 |
}
|
|
533 |
else if (this.isIE) {
|
|
534 |
return;
|
|
535 |
}
|
|
536 |
*/
|
|
537 |
||
538 |
/*Save this new location*/
|
|
539 |
this.currentLocation = hash; |
|
540 |
||
541 |
this.ieAtomicLocationChange = false; |
|
542 |
||
543 |
/*Notify listeners of the change*/
|
|
544 |
this.fireHistoryEvent(hash); |
|
545 |
},
|
|
546 |
||
547 |
/*Private: Get the current location of IE's hidden iframe.*/
|
|
548 |
getIframeHash: function() { |
|
549 |
if (!this.iframe) return ""; |
|
550 |
||
551 |
var doc = this.iframe.contentWindow.document; |
|
552 |
var hash = String(doc.location.search); |
|
553 |
if (hash.length == 1 && hash.charAt(0) == "?") { |
|
554 |
hash = ""; |
|
555 |
}
|
|
556 |
else if (hash.length >= 2 && hash.charAt(0) == "?") { |
|
557 |
hash = hash.substring(1); |
|
558 |
}
|
|
559 |
return hash; |
|
560 |
},
|
|
561 |
||
562 |
/*Private: Remove any leading hash that might be on a location.*/
|
|
563 |
removeHash: function(hashValue) { |
|
564 |
var r; |
|
565 |
if (hashValue === null || hashValue === undefined) { |
|
566 |
r = null; |
|
567 |
}
|
|
568 |
else if (hashValue === "") { |
|
569 |
r = ""; |
|
570 |
}
|
|
571 |
else if (hashValue.length == 1 && hashValue.charAt(0) == "#") { |
|
572 |
r = ""; |
|
573 |
}
|
|
574 |
else if (hashValue.length > 1 && hashValue.charAt(0) == "#") { |
|
575 |
r = hashValue.substring(1); |
|
576 |
}
|
|
577 |
else { |
|
578 |
r = hashValue; |
|
579 |
}
|
|
580 |
return r; |
|
581 |
},
|
|
582 |
||
583 |
/*Private: For IE, tell when the hidden iframe has finished loading.*/
|
|
584 |
iframeLoaded: function(newLocation) { |
|
585 |
/*ignore any location changes that we made ourselves*/
|
|
586 |
if (this.ignoreLocationChange) { |
|
587 |
this.ignoreLocationChange = false; |
|
588 |
return; |
|
589 |
}
|
|
590 |
||
591 |
/*Get the new location*/
|
|
592 |
var hash = String(newLocation.search); |
|
593 |
if (hash.length == 1 && hash.charAt(0) == "?") { |
|
594 |
hash = ""; |
|
595 |
}
|
|
596 |
else if (hash.length >= 2 && hash.charAt(0) == "?") { |
|
597 |
hash = hash.substring(1); |
|
598 |
}
|
|
599 |
/*Keep the browser location bar in sync with the iframe hash*/
|
|
600 |
window.location.hash = hash; |
|
601 |
||
602 |
/*Notify listeners of the change*/
|
|
603 |
this.fireHistoryEvent(hash); |
|
604 |
}
|
|
605 |
||
606 |
};
|
|
607 |
||
608 |
/*
|
|
609 |
historyStorage: An object that uses a hidden form to store history state across page loads. The mechanism for doing so relies on
|
|
610 |
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
|
|
611 |
the user navigates back to the page. This object can be used independently of the dhtmlHistory object for caching of Ajax
|
|
612 |
session information.
|
|
613 |
|
|
614 |
dependencies:
|
|
615 |
* json2007.js (included in a separate file) or alternate JSON methods passed in through an options bundle.
|
|
616 |
*/
|
|
617 |
window.historyStorage = { |
|
618 |
||
619 |
/*Public: Set up our historyStorage object for use by dhtmlHistory or other objects*/
|
|
620 |
setup: function(options) { |
|
621 |
||
622 |
/*
|
|
623 |
options - object to store initialization parameters - passed in from dhtmlHistory or directly into historyStorage
|
|
624 |
options.debugMode - boolean that causes hidden form fields to be shown for development purposes.
|
|
625 |
options.toJSON - function to override default JSON stringifier
|
|
626 |
options.fromJSON - function to override default JSON parser
|
|
627 |
*/
|
|
628 |
||
629 |
/*process init parameters*/
|
|
630 |
if (typeof options !== "undefined") { |
|
631 |
if (options.debugMode) { |
|
632 |
this.debugMode = options.debugMode; |
|
633 |
}
|
|
634 |
if (options.toJSON) { |
|
635 |
this.toJSON = options.toJSON; |
|
636 |
}
|
|
637 |
if (options.fromJSON) { |
|
638 |
this.fromJSON = options.fromJSON; |
|
639 |
}
|
|
640 |
}
|
|
641 |
||
642 |
/*write a hidden form and textarea into the page; we'll stow our history stack here*/
|
|
643 |
var formID = "rshStorageForm"; |
|
644 |
var textareaID = "rshStorageField"; |
|
645 |
var formStyles = this.debugMode ? historyStorage.showStyles : historyStorage.hideStyles; |
|
646 |
var textareaStyles = (historyStorage.debugMode |
|
647 |
? 'width: 800px;height:80px;border:1px solid black;' |
|
648 |
: historyStorage.hideStyles |
|
649 |
);
|
|
650 |
||
651 |
/*
|
|
652 |
var textareaHTML = '<form id="' + formID + '" style="' + formStyles + '">'
|
|
653 |
+ '<textarea id="' + textareaID + '" style="' + textareaStyles + '"></textarea>'
|
|
654 |
+ '</form>';
|
|
655 |
document.write(textareaHTML);
|
|
656 |
this.storageField = document.getElementById(textareaID);
|
|
657 |
*/
|
|
658 |
||
659 |
var node = document.createElement('textarea'); |
|
660 |
node.setAttribute('id', textareaID); |
|
661 |
if (typeof node.style.cssText == "string") |
|
662 |
node.style.cssText = textareaStyles; |
|
663 |
else
|
|
664 |
node.setAttribute('style', textareaStyles); |
|
665 |
||
666 |
var fnode = document.createElement('form'); |
|
667 |
fnode.setAttribute('id', formID); |
|
668 |
if (typeof fnode.style.cssText == "string") |
|
669 |
fnode.style.cssText = formStyles; |
|
670 |
else
|
|
671 |
fnode.setAttribute('style', formStyles); |
|
672 |
fnode.appendChild(node); |
|
673 |
||
674 |
this.storageField = node; |
|
675 |
document.body.appendChild(fnode); |
|
676 |
||
677 |
if (typeof window.opera !== "undefined") { |
|
678 |
this.storageField.focus();/*Opera needs to focus this element before persisting values in it*/ |
|
679 |
}
|
|
680 |
},
|
|
681 |
||
682 |
/*Public*/
|
|
683 |
put: function(key, value) { |
|
684 |
this.assertValidKey(key); |
|
685 |
/*if we already have a value for this, remove the value before adding the new one*/
|
|
686 |
if (this.hasKey(key)) { |
|
687 |
this.remove(key); |
|
688 |
}
|
|
689 |
/*store this new key*/
|
|
690 |
this.storageHash[key] = value; |
|
691 |
/*save and serialize the hashtable into the form*/
|
|
692 |
this.saveHashTable(); |
|
693 |
},
|
|
694 |
||
695 |
/*Public*/
|
|
696 |
get: function(key) { |
|
697 |
this.assertValidKey(key); |
|
698 |
/*make sure the hash table has been loaded from the form*/
|
|
699 |
this.loadHashTable(); |
|
700 |
var value = this.storageHash[key]; |
|
701 |
if (value === undefined) { |
|
702 |
value = null; |
|
703 |
}
|
|
704 |
return value; |
|
705 |
},
|
|
706 |
||
707 |
/*Public*/
|
|
708 |
remove: function(key) { |
|
709 |
this.assertValidKey(key); |
|
710 |
/*make sure the hash table has been loaded from the form*/
|
|
711 |
this.loadHashTable(); |
|
712 |
/*delete the value*/
|
|
713 |
delete this.storageHash[key]; |
|
714 |
/*serialize and save the hash table into the form*/
|
|
715 |
this.saveHashTable(); |
|
716 |
},
|
|
717 |
||
718 |
/*Public: Clears out all saved data.*/
|
|
719 |
reset: function() { |
|
720 |
this.storageField.value = ""; |
|
721 |
this.storageHash = {}; |
|
722 |
},
|
|
723 |
||
724 |
/*Public*/
|
|
725 |
hasKey: function(key) { |
|
726 |
this.assertValidKey(key); |
|
727 |
/*make sure the hash table has been loaded from the form*/
|
|
728 |
this.loadHashTable(); |
|
729 |
return (typeof this.storageHash[key] !== "undefined"); |
|
730 |
},
|
|
731 |
||
732 |
/*Public*/
|
|
733 |
isValidKey: function(key) { |
|
734 |
return (typeof key === "string"); |
|
735 |
},
|
|
736 |
||
737 |
/*Public - CSS strings utilized by both objects to hide or show behind-the-scenes DOM elements*/
|
|
738 |
showStyles: 'border:0;margin:0;padding:0;', |
|
739 |
hideStyles: 'left:-1000px;top:-1000px;width:1px;height:1px;border:0;position:absolute;', |
|
740 |
||
741 |
/*Public - debug mode flag*/
|
|
742 |
debugMode: false, |
|
743 |
||
744 |
/*- - - - - - - - - - - -*/
|
|
745 |
||
746 |
/*Private: Our hash of key name/values.*/
|
|
747 |
storageHash: {}, |
|
748 |
||
749 |
/*Private: If true, we have loaded our hash table out of the storage form.*/
|
|
750 |
hashLoaded: false, |
|
751 |
||
752 |
/*Private: DOM reference to our history field*/
|
|
753 |
storageField: null, |
|
754 |
||
755 |
/*Private: Assert that a key is valid; throw an exception if it not.*/
|
|
756 |
assertValidKey: function(key) { |
|
757 |
var isValid = this.isValidKey(key); |
|
758 |
if (!isValid && this.debugMode) { |
|
759 |
throw new Error("Please provide a valid key for window.historyStorage. Invalid key = " + key + "."); |
|
760 |
}
|
|
761 |
},
|
|
762 |
||
763 |
/*Private: Load the hash table up from the form.*/
|
|
764 |
loadHashTable: function() { |
|
765 |
if (!this.hashLoaded) { |
|
766 |
var serializedHashTable = this.storageField.value; |
|
767 |
if (serializedHashTable !== "" && serializedHashTable !== null) { |
|
768 |
this.storageHash = this.fromJSON(serializedHashTable); |
|
769 |
this.hashLoaded = true; |
|
770 |
}
|
|
771 |
}
|
|
772 |
},
|
|
773 |
/*Private: Save the hash table into the form.*/
|
|
774 |
saveHashTable: function() { |
|
775 |
this.loadHashTable(); |
|
776 |
var serializedHashTable = this.toJSON(this.storageHash); |
|
777 |
this.storageField.value = serializedHashTable; |
|
778 |
},
|
|
779 |
/*Private: Bridges for our JSON implementations - both rely on 2007 JSON.org library - can be overridden by options bundle*/
|
|
780 |
toJSON: function(o) { |
|
781 |
return o.toJSONString(); |
|
782 |
},
|
|
783 |
fromJSON: function(s) { |
|
784 |
return s.parseJSON(); |
|
785 |
}
|
|
786 |
};
|