/
jquery.history.js
205 lines (170 loc) · 5.32 KB
/
jquery.history.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
* jQuery Awesome History Plugin
* -----------------------------
*
* Copyright (C) 2006-2009 Taku Sano (Mikage Sawatari)
* Copyright (C) 2009 Lincoln Cooper
* Copyright (C) 2010 Marcello Barnaba
*
* Licensed under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
*
*
* Based on Taku Sano's jQuery History plugin, web page:
* http://www.mikage.to/jquery/jquery_history.html
*
* Modified by Lincoln Cooper to add Safari support and
* only call the callback once during initialization for
* MSIE when no initial hash supplied.
*
* Rewritten by Marcello Barnaba to make it more compatible
* with IE quirks, more performant and more easy on the eyes.
*
*/
(function ($) {
var _current, _callback;
$.history = {
/**
* Initializes AJAX history, the given callback will be called
* when history changes.
*
* The core of the implementation is the setInterval argument,
* that checks whether the current document hash has changed.
* It gets called every 100ms.
*
* If we're running on MSIE, we need an iFrame to manipulate
* history entries, more on this topic below.
*/
init: function (callback) {
_callback = callback;
_current = '#';
if ($.browser.msie && ($.browser.version < 8 || document.documentMode < 8))
_iframe.init ();
else if ($.browser.opera)
history.navigationMode = 'compatible';
setInterval (function () {
var hash;
if (_iframe.inited)
hash = _iframe.get ();
else
hash = location.hash || '#';
hash = normalize (hash);
if (!changed (hash))
return;
$.history.save (hash, false);
invoke ();
}, 100);
},
/**
* Saves the given hash into history if it is different than
* the one currently loaded and invokes the user provided
* callback.
*/
load: function (hash) {
if (!hash)
return;
hash = normalize (hash)
if (!changed (hash))
return;
$.history.save (hash);
invoke ();
},
/**
* Saves the given hash into history. Iff the second optional
* argument is true, the current location hash (either in the
* MSIE iFrame or in the addressbar) is not updated: it's set
* true by the monitor function (called every 100ms) in order
* to not alter the browser history stack when an user clicks
* on the back or forward button.
*/
save: function (hash, update) {
hash = normalize (hash);
if (!hash)
return;
if (update === undefined)
update = true;
if (update || _iframe.inited) // On IE the hash must be always
$.location.setAnchor (hash); // updated, as it doesn't create
// new history entries
if (update && _iframe.inited) // But the iFrame must be updated
_iframe.set (hash); // only when it is needed because
// it creates new history entries
// $.log ("saving " + hash);
_current = hash;
},
/**
* Returns the currently loaded hash.
*/
current: function () {
return _current;
}
};
//////////// Private ///////////////
/**
* Handle an iFrame object for IE. The theory behind using iFrames
* is that, unlike other engines (Webkit, Gecko and Opera), IE doesn't
* add new history entries when the location.hash is changed via JS:
*
* the only way to add history entries is to rewrite the contents of
* an hidden iFrame, and that's what the following code does.
*
* You MUST have the following markup in your DOM:
* <iframe src="javascript:false" style="display:none" id="ie_history"></iframe>
*
* or an exception will be thrown.
*/
var _iframe = {
init: function () {
this.element = $('#ie_history')[0];
if (this.element.length == 0)
throw ('BUG: no "ie_history" element ID found in DOM!');
if (this.element.src != 'javascript:false')
throw ('BUG: the "ie_history" iFrame MUST have a src="javascript:false"');
this.inited = true;
},
set: function (hash) {
try {
/* After initialization, IE doesn't save the current page,
* but if it is saved DURING initialization, previous history
* is lost. So we update the iFrame twice here, to preserve
* the page visualized during initialization.
*/
if (this.get () == 'false')
this.write (_current);
this.write (hash);
return true;
} catch (e) {
return false;
}
},
get: function () {
try {
return this.element.contentWindow.document.body.innerText;
} catch (e) {
return '';
}
},
write: function (hash) {
var doc = this.element.contentWindow.document;
doc.open();
doc.write('<html><body>' + hash + '</body></html>');
doc.close();
}
};
var invoke = function () {
if (_current)
_callback ($.location.decodeAnchor (_current));
};
var normalize = function (hash) {
try {
return $.location.encodeAnchor (hash) || '#';
} catch (e) {
return undefined;
}
};
var changed = function (hash) {
if (hash && hash != 'false' && hash != _current) {
return true;
}
};
})(jQuery);