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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
|
/**
* History/Remote - jQuery plugin for enabling history support and bookmarking
* @requires jQuery v1.0.3
*
* http://stilbuero.de/jquery/history/
*
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Version: 0.2.3
*/
(function($) { // block scope
/**
* Initialize the history manager. Subsequent calls will not result in additional history state change
* listeners. Should be called soonest when the DOM is ready, because in IE an iframe needs to be added
* to the body to enable history support.
*
* @example $.ajaxHistory.initialize();
*
* @param Function callback A single function that will be executed in case there is no fragment
* identifier in the URL, for example after navigating back to the initial
* state. Use to restore such an initial application state.
* Optional. If specified it will overwrite the default action of
* emptying all containers that are used to load content into.
* @type undefined
*
* @name $.ajaxHistory.initialize()
* @cat Plugins/History
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
$.ajaxHistory = new function() {
var RESET_EVENT = 'historyReset';
var _currentHash = location.hash;
var _intervalId = null;
var _observeHistory; // define outside if/else required by Opera
this.update = function() { }; // empty function body for graceful degradation
// create custom event for state reset
var _defaultReset = function() {
$('.remote-output').empty();
};
$(document).bind(RESET_EVENT, _defaultReset);
// TODO fix for Safari 3
// if ($.browser.msie)
// else if hash != _currentHash
// else check history length
if ($.browser.msie) {
var _historyIframe, initialized = false; // for IE
// add hidden iframe
$(function() {
_historyIframe = $('<iframe style="display: none;"></iframe>').appendTo(document.body).get(0);
var iframe = _historyIframe.contentWindow.document;
// create initial history entry
iframe.open();
iframe.close();
if (_currentHash && _currentHash != '#') {
iframe.location.hash = _currentHash.replace('#', '');
}
});
this.update = function(hash) {
_currentHash = hash;
var iframe = _historyIframe.contentWindow.document;
iframe.open();
iframe.close();
iframe.location.hash = hash.replace('#', '');
};
_observeHistory = function() {
var iframe = _historyIframe.contentWindow.document;
var iframeHash = iframe.location.hash;
if (iframeHash != _currentHash) {
_currentHash = iframeHash;
if (iframeHash && iframeHash != '#') {
// order does matter, set location.hash after triggering the click...
$('a[@href$="' + iframeHash + '"]').click();
location.hash = iframeHash;
} else if (initialized) {
location.hash = '';
$(document).trigger(RESET_EVENT);
}
}
initialized = true;
};
} else if ($.browser.mozilla || $.browser.opera) {
this.update = function(hash) {
_currentHash = hash;
};
_observeHistory = function() {
if (location.hash) {
if (_currentHash != location.hash) {
_currentHash = location.hash;
$('a[@href$="' + _currentHash + '"]').click();
}
} else if (_currentHash) {
_currentHash = '';
$(document).trigger(RESET_EVENT);
}
};
} else if ($.browser.safari) {
var _backStack, _forwardStack, _addHistory; // for Safari
// etablish back/forward stacks
$(function() {
_backStack = [];
_backStack.length = history.length;
_forwardStack = [];
});
var isFirst = false, initialized = false;
_addHistory = function(hash) {
_backStack.push(hash);
_forwardStack.length = 0; // clear forwardStack (true click occured)
isFirst = false;
};
this.update = function(hash) {
_currentHash = hash;
_addHistory(_currentHash);
};
_observeHistory = function() {
var historyDelta = history.length - _backStack.length;
if (historyDelta) { // back or forward button has been pushed
isFirst = false;
if (historyDelta < 0) { // back button has been pushed
// move items to forward stack
for (var i = 0; i < Math.abs(historyDelta); i++) _forwardStack.unshift(_backStack.pop());
} else { // forward button has been pushed
// move items to back stack
for (var i = 0; i < historyDelta; i++) _backStack.push(_forwardStack.shift());
}
var cachedHash = _backStack[_backStack.length - 1];
$('a[@href$="' + cachedHash + '"]').click();
_currentHash = location.hash;
} else if (_backStack[_backStack.length - 1] == undefined && !isFirst) {
// back button has been pushed to beginning and URL already pointed to hash (e.g. a bookmark)
// document.URL doesn't change in Safari
if (document.URL.indexOf('#') >= 0) {
$('a[@href$="' + '#' + document.URL.split('#')[1] + '"]').click();
} else if (initialized) {
$(document).trigger(RESET_EVENT);
}
isFirst = true;
}
initialized = true;
};
}
this.initialize = function(callback) {
// custom callback to reset app state (no hash in url)
if (typeof callback == 'function') {
$(document).unbind(RESET_EVENT, _defaultReset).bind(RESET_EVENT, callback);
}
// look for hash in current URL (not Safari)
if (location.hash && typeof _addHistory == 'undefined') {
$('a[@href$="' + location.hash + '"]').trigger('click');
}
// start observer
if (_observeHistory && _intervalId == null) {
_intervalId = setInterval(_observeHistory, 200); // Safari needs at least 200 ms
}
};
};
/**
* Implement Ajax driven links in a completely unobtrusive and accessible manner (also known as "Hijax")
* with support for the browser's back/forward navigation buttons and bookmarking.
*
* The link's href attribute gets altered to a fragment identifier, such as "#remote-1", so that the browser's
* URL gets updated on each click, whereas the former value of that attribute is used to load content via
* XmlHttpRequest from and update the specified element. If no target element is found, a new div element will be
* created and appended to the body to load the content into. The link informs the history manager of the
* state change on click and adds an entry to the browser's history.
*
* jQuery's Ajax implementation adds a custom request header of the form "X-Requested-With: XmlHttpRequest"
* to any Ajax request so that the called page can distinguish between a standard and an Ajax (XmlHttpRequest)
* request.
*
* @example $('a.remote').remote('#output');
* @before <a class="remote" href="/path/to/content.html">Update</a>
* @result <a class="remote" href="#remote-1">Update</a>
* @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
* "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
* @example $('a.remote').remote('#output', {hashPrefix: 'chapter'});
* @before <a class="remote" href="/path/to/content.html">Update</a>
* @result <a class="remote" href="#chapter-1">Update</a>
* @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
* "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
*
* @param String expr A string containing a CSS selector or basic XPath specifying the element to load
* content into via XmlHttpRequest.
* @param Object settings An object literal containing key/value pairs to provide optional settings.
* @option String hashPrefix A String that is used for constructing the hash the link's href attribute
* gets altered to, such as "#remote-1". Default value: "remote-".
* @param Function callback A single function that will be executed when the request is complete.
* @type jQuery
*
* @name remote
* @cat Plugins/Remote
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
/**
* Implement Ajax driven links in a completely unobtrusive and accessible manner (also known as "Hijax")
* with support for the browser's back/forward navigation buttons and bookmarking.
*
* The link's href attribute gets altered to a fragment identifier, such as "#remote-1", so that the browser's
* URL gets updated on each click, whereas the former value of that attribute is used to load content via
* XmlHttpRequest from and update the specified element. If no target element is found, a new div element will be
* created and appended to the body to load the content into. The link informs the history manager of the
* state change on click and adds an entry to the browser's history.
*
* jQuery's Ajax implementation adds a custom request header of the form "X-Requested-With: XmlHttpRequest"
* to any Ajax request so that the called page can distinguish between a standard and an Ajax (XmlHttpRequest)
* request.
*
* @example $('a.remote').remote( $('#output > div')[0] );
* @before <a class="remote" href="/path/to/content.html">Update</a>
* @result <a class="remote" href="#remote-1">Update</a>
* @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
* "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
* @example $('a.remote').remote('#output', {hashPrefix: 'chapter'});
* @before <a class="remote" href="/path/to/content.html">Update</a>
* @result <a class="remote" href="#chapter-1">Update</a>
* @desc Alter a link of the class "remote" to an Ajax-enhanced link and let it load content from
* "/path/to/content.html" via XmlHttpRequest into an element with the id "output".
*
* @param Element elem A DOM element to load content into via XmlHttpRequest.
* @param Object settings An object literal containing key/value pairs to provide optional settings.
* @option String hashPrefix A String that is used for constructing the hash the link's href attribute
* gets altered to, such as "#remote-1". Default value: "remote-".
* @param Function callback A single function that will be executed when the request is complete.
* @type jQuery
*
* @name remote
* @cat Plugins/Remote
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
$.fn.remote = function(output, settings, callback) {
callback = callback || function() {};
if (typeof settings == 'function') { // shift arguments
callback = settings;
}
settings = $.extend({
hashPrefix: 'remote-'
}, settings || {});
var target = $(output).size() && $(output) || $('<div></div>').appendTo('body');
target.addClass('remote-output');
return this.each(function(i) {
var href = this.href, hash = '#' + (this.title && this.title.replace(/\s/g, '_') || settings.hashPrefix + (i + 1)),
a = this;
this.href = hash;
$(this).click(function(e) {
// lock target to prevent double loading in Firefox
if (!target['locked']) {
// add to history only if true click occured, not a triggered click
if (e.clientX) {
$.ajaxHistory.update(hash);
}
target.load(href, function() {
target['locked'] = null;
callback.apply(a);
});
}
});
});
};
/**
* Provides the ability to use the back/forward navigation buttons in a DHTML application.
* A change of the application state is reflected by a change of the URL fragment identifier.
*
* The link's href attribute needs to point to a fragment identifier within the same resource,
* although that fragment id does not need to exist. On click the link changes the URL fragment
* identifier, informs the history manager of the state change and adds an entry to the browser's
* history.
*
* @param Function callback A single function that will be executed as the click handler of the
* matched element. It will be executed on click (adding an entry to
* the history) as well as in case the history manager needs to trigger
* it depending on the value of the URL fragment identifier, e.g. if its
* current value matches the href attribute of the matched element.
*
* @type jQuery
*
* @name history
* @cat Plugins/History
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
$.fn.history = function(callback) {
return this.click(function(e) {
// add to history only if true click occured,
// not a triggered click...
if (e.clientX) {
// ...and die if already active
if (this.hash == location.hash) {
return false;
}
$.ajaxHistory.update(this.hash);
}
if (typeof callback == 'function') {
callback.call(this);
}
});
};
})(jQuery);
/*
var logger;
$(function() {
logger = $('<div style="position: fixed; top: 0; overflow: hidden; border: 1px solid; padding: 3px; width: 120px; height: 150px; background: #fff; color: red;"></div>').appendTo(document.body);
});
function log(m) {
logger.prepend(m + '<br />');
};
*/
|