/dev/trunk

To get this branch, use:
bzr branch http://darksoft.org/webbzr/dev/trunk

« back to all changes in this revision

Viewing changes to includes/rsh.js.orig

  • Committer: Suren A. Chilingaryan
  • Date: 2008-04-02 10:23:22 UTC
  • Revision ID: csa@dside.dyndns.org-20080402102322-okib92sicg2dx3o3
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
};