38
38
// tested with, what browsers it has been tested in, and where the unit tests
39
39
// reside (so you can test it yourself).
41
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
42
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4,
43
// Chrome 4-5, Opera 9.6-10.1.
41
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
42
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
43
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
44
44
// Unit Tests - http://benalman.com/code/projects/jquery-bbq/unit/
46
46
// About: Release History
48
// 1.4pre - (1/15/2013) Removed $.browser reference to work with jQuery 1.9
49
// 1.3pre - (8/26/2010) Integrated <jQuery hashchange event> v1.3, which adds
50
// document.title and document.domain support in IE6/7, BlackBerry
51
// support, better Iframe hiding for accessibility reasons, and the new
52
// <jQuery.fn.hashchange> "shortcut" method. Added the
53
// <jQuery.param.sorted> method which reduces the possibility of
54
// extraneous hashchange event triggering. Added the
55
// <jQuery.param.fragment.ajaxCrawlable> method which can be used to
56
// enable Google "AJAX Crawlable mode."
48
57
// 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from
49
58
// <jQuery hashchange event> in BBQ, which was the main reason for the
50
59
// previous release!
308
319
jq_param[ str_querystring ] = curry( jq_param_sub, 0, get_querystring );
309
320
jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment );
322
// Method: jQuery.param.sorted
324
// Returns a params string equivalent to that returned by the internal
325
// jQuery.param method, but sorted, which makes it suitable for use as a
328
// For example, in most browsers jQuery.param({z:1,a:2}) returns "z=1&a=2"
329
// and jQuery.param({a:2,z:1}) returns "a=2&z=1". Even though both the
330
// objects being serialized and the resulting params strings are equivalent,
331
// if these params strings were set into the location.hash fragment
332
// sequentially, the hashchange event would be triggered unnecessarily, since
333
// the strings are different (even though the data described by them is the
334
// same). By sorting the params string, unecessary hashchange event triggering
339
// > jQuery.param.sorted( obj [, traditional ] );
343
// obj - (Object) An object to be serialized.
344
// traditional - (Boolean) Params deep/shallow serialization mode. See the
345
// documentation at http://api.jquery.com/jQuery.param/ for more detail.
349
// (String) A sorted params string.
351
jq_param.sorted = jq_param_sorted = function( a, traditional ) {
355
$.each( jq_param( a, traditional ).split( '&' ), function(i,v){
356
var key = v.replace( /(?:%5B|=).*$/, '' ),
357
key_obj = obj[ key ];
360
key_obj = obj[ key ] = [];
367
return $.map( arr.sort(), function(v){
311
372
// Method: jQuery.param.fragment.noEscape
313
374
// Specify characters that will be left unescaped when fragments are created
928
1024
// tested with, what browsers it has been tested in, and where the unit tests
929
1025
// reside (so you can test it yourself).
931
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
932
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1.
1027
// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
1028
// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
1029
// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
933
1030
// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
935
1032
// About: Known issues
937
// While this jQuery hashchange event implementation is quite stable and robust,
938
// there are a few unfortunate browser bugs surrounding expected hashchange
939
// event-based behaviors, independent of any JavaScript window.onhashchange
940
// abstraction. See the following examples for more information:
1034
// While this jQuery hashchange event implementation is quite stable and
1035
// robust, there are a few unfortunate browser bugs surrounding expected
1036
// hashchange event-based behaviors, independent of any JavaScript
1037
// window.onhashchange abstraction. See the following examples for more
942
1040
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
943
1041
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
944
1042
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
945
1043
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
1045
// Also note that should a browser natively support the window.onhashchange
1046
// event, but not report that it does, the fallback polling loop will be used.
947
1048
// About: Release History
1050
// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
1051
// "removable" for mobile-only development. Added IE6/7 document.title
1052
// support. Attempted to make Iframe as hidden as possible by using
1053
// techniques from http://www.paciellogroup.com/blog/?p=604. Added
1054
// support for the "shortcut" format $(window).hashchange( fn ) and
1055
// $(window).hashchange() like jQuery provides for built-in events.
1056
// Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
1057
// lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
1058
// and <jQuery.fn.hashchange.src> properties plus document-domain.html
1059
// file to address access denied issues when setting document.domain in
949
1061
// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
950
1062
// from a page on another domain would cause an error in Safari 4. Also,
951
1063
// IE6/7 Iframe is now inserted after the body (this actually works),
964
1076
(function($,window,undefined){
965
1077
'$:nomunge'; // Used by YUI compressor.
967
// Method / object references.
968
var fake_onhashchange,
969
jq_event_special = $.event.special,
972
str_location = 'location',
973
str_hashchange = 'hashchange',
976
// IE6/7 specifically need some special love when it comes to back-button
977
// support, so let's do a little browser sniffing..
979
mode = document.documentMode,
980
is_old_ie = browser.msie && ( mode === undefined || mode < 8 ),
982
// Does the browser support window.onhashchange? Test for IE version, since
983
// IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
984
supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;
1080
var str_hashchange = 'hashchange',
1082
// Method / object references.
1085
special = $.event.special,
1087
// Does the browser support window.onhashchange? Note that IE8 running in
1088
// IE7 compatibility mode reports true for 'onhashchange' in window, even
1089
// though the event isn't supported, so also test document.documentMode.
1090
doc_mode = doc.documentMode,
1091
supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
986
1093
// Get location.hash (or what you'd expect location.hash to be) sans any
987
1094
// leading #. Thanks for making this necessary, Firefox!
988
1095
function get_fragment( url ) {
989
url = url || window[ str_location ][ str_href ];
990
return url.replace( /^[^#]*#?(.*)$/, '$1' );
993
// Property: jQuery.hashchangeDelay
1096
url = url || location.href;
1097
return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
1100
// Method: jQuery.fn.hashchange
1102
// Bind a handler to the window.onhashchange event or trigger all bound
1103
// window.onhashchange event handlers. This behavior is consistent with
1104
// jQuery's built-in event handlers.
1108
// > jQuery(window).hashchange( [ handler ] );
1112
// handler - (Function) Optional handler to be bound to the hashchange
1113
// event. This is a "shortcut" for the more verbose form:
1114
// jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
1115
// all bound window.onhashchange event handlers will be triggered. This
1116
// is a shortcut for the more verbose
1117
// jQuery(window).trigger( 'hashchange' ). These forms are described in
1118
// the <hashchange event> section.
1122
// (jQuery) The initial jQuery collection of elements.
1124
// Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
1125
// $(elem).hashchange() for triggering, like jQuery does for built-in events.
1126
$.fn[ str_hashchange ] = function( fn ) {
1127
return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
1130
// Property: jQuery.fn.hashchange.delay
995
1132
// The numeric interval (in milliseconds) at which the <hashchange event>
996
// polling loop executes. Defaults to 100.
998
$[ str_hashchange + 'Delay' ] = 100;
1133
// polling loop executes. Defaults to 50.
1135
// Property: jQuery.fn.hashchange.domain
1137
// If you're setting document.domain in your JavaScript, and you want hash
1138
// history to work in IE6/7, not only must this property be set, but you must
1139
// also set document.domain BEFORE jQuery is loaded into the page. This
1140
// property is only applicable if you are supporting IE6/7 (or IE8 operating
1141
// in "IE7 compatibility" mode).
1143
// In addition, the <jQuery.fn.hashchange.src> property must be set to the
1144
// path of the included "document-domain.html" file, which can be renamed or
1145
// modified if necessary (note that the document.domain specified must be the
1146
// same in both your main JavaScript as well as in this file).
1150
// jQuery.fn.hashchange.domain = document.domain;
1152
// Property: jQuery.fn.hashchange.src
1154
// If, for some reason, you need to specify an Iframe src file (for example,
1155
// when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
1156
// do so using this property. Note that when using this property, history
1157
// won't be recorded in IE6/7 until the Iframe src file loads. This property
1158
// is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
1159
// compatibility" mode).
1163
// jQuery.fn.hashchange.src = 'path/to/file.html';
1165
$.fn[ str_hashchange ].delay = 50;
1167
$.fn[ str_hashchange ].domain = null;
1168
$.fn[ str_hashchange ].src = null;
1000
1171
// Event: hashchange event
1002
1173
// Fired when location.hash changes. In browsers that support it, the native
1003
// window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is
1004
// initialized, running every <jQuery.hashchangeDelay> milliseconds to see if
1005
// the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow
1006
// the back button and hash-based history to work.
1010
// > $(window).bind( 'hashchange', function(e) {
1011
// > var hash = location.hash;
1174
// HTML5 window.onhashchange event is used, otherwise a polling loop is
1175
// initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
1176
// see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
1177
// compatibility" mode), a hidden Iframe is created to allow the back button
1178
// and hash-based history to work.
1180
// Usage as described in <jQuery.fn.hashchange>:
1182
// > // Bind an event handler.
1183
// > jQuery(window).hashchange( function(e) {
1184
// > var hash = location.hash;
1188
// > // Manually trigger the event handler.
1189
// > jQuery(window).hashchange();
1191
// A more verbose usage that allows for event namespacing:
1193
// > // Bind an event handler.
1194
// > jQuery(window).bind( 'hashchange', function(e) {
1195
// > var hash = location.hash;
1199
// > // Manually trigger the event handler.
1200
// > jQuery(window).trigger( 'hashchange' );
1015
1202
// Additional Notes:
1017
// * The polling loop and Iframe are not created until at least one callback
1018
// is actually bound to 'hashchange'.
1019
// * If you need the bound callback(s) to execute immediately, in cases where
1020
// the page 'state' exists on page load (via bookmark or page refresh, for
1021
// example) use $(window).trigger( 'hashchange' );
1204
// * The polling loop and Iframe are not created until at least one handler
1205
// is actually bound to the 'hashchange' event.
1206
// * If you need the bound handler(s) to execute immediately, in cases where
1207
// a location.hash exists on page load, via bookmark or page refresh for
1208
// example, use jQuery(window).hashchange() or the more verbose
1209
// jQuery(window).trigger( 'hashchange' ).
1022
1210
// * The event can be bound before DOM ready, but since it won't be usable
1023
1211
// before then in IE6/7 (due to the necessary Iframe), recommended usage is
1024
// to bind it inside a $(document).ready() callback.
1212
// to bind it inside a DOM ready handler.
1026
jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
1214
// Override existing $.event.special.hashchange methods (allowing this plugin
1215
// to be defined after jQuery BBQ in BBQ's source code).
1216
special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
1028
1218
// Called only when the first 'hashchange' event is bound to window.
1029
1219
setup: function() {
1054
1244
fake_onhashchange = (function(){
1061
// Initialize. In IE 6/7, creates a hidden Iframe for history handling.
1063
// Most browsers don't need special methods here..
1064
set_history = get_history = function(val){ return val; };
1069
// Create hidden Iframe after the end of the body to prevent initial
1070
// page load from scrolling unnecessarily.
1071
iframe = $('<iframe src="javascript:0"/>').hide().insertAfter( 'body' )[0].contentWindow;
1073
// Get history by looking at the hidden Iframe's location.hash.
1074
get_history = function() {
1075
return get_fragment( iframe.document[ str_location ][ str_href ] );
1078
// Set a new history item by opening and then closing the Iframe
1079
// document, *then* setting its location.hash.
1080
set_history = function( hash, history_hash ) {
1081
if ( hash !== history_hash ) {
1082
var doc = iframe.document;
1084
doc[ str_location ].hash = '#' + hash;
1088
// Set initial history.
1089
set_history( get_fragment() );
1248
// Remember the initial hash so it doesn't get triggered immediately.
1249
last_hash = get_fragment(),
1251
fn_retval = function(val){ return val; },
1252
history_set = fn_retval,
1253
history_get = fn_retval;
1093
1255
// Start the polling loop.
1094
1256
self.start = function() {
1095
// Polling loop is already running!
1096
if ( timeout_id ) { return; }
1098
// Remember the initial hash so it doesn't get triggered immediately.
1099
var last_hash = get_fragment();
1101
// Initialize if not yet initialized.
1102
set_history || init();
1104
// This polling loop checks every $.hashchangeDelay milliseconds to see if
1105
// location.hash has changed, and triggers the 'hashchange' event on
1106
// window when necessary.
1108
var hash = get_fragment(),
1109
history_hash = get_history( last_hash );
1111
if ( hash !== last_hash ) {
1112
set_history( last_hash = hash, history_hash );
1114
$(window).trigger( str_hashchange );
1116
} else if ( history_hash !== last_hash ) {
1117
window[ str_location ][ str_href ] = window[ str_location ][ str_href ].replace( /#.*/, '' ) + '#' + history_hash;
1120
timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] );
1257
timeout_id || poll();
1124
// Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In
1125
// that case, even if there are no longer any bound event handlers, the
1126
// polling loop is still necessary for back/next to work at all!
1260
// Stop the polling loop.
1127
1261
self.stop = function() {
1129
timeout_id && clearTimeout( timeout_id );
1262
timeout_id && clearTimeout( timeout_id );
1263
timeout_id = undefined;
1266
// This polling loop checks every $.fn.hashchange.delay milliseconds to see
1267
// if location.hash has changed, and triggers the 'hashchange' event on
1268
// window when necessary.
1270
var hash = get_fragment(),
1271
history_hash = history_get( last_hash );
1273
if ( hash !== last_hash ) {
1274
history_set( last_hash = hash, history_hash );
1276
$(window).trigger( str_hashchange );
1278
} else if ( history_hash !== last_hash ) {
1279
location.href = location.href.replace( /#.*/, '' ) + history_hash;
1282
timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
1285
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1286
// vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
1287
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
1288
(navigator.userAgent.match(/MSIE/i) !== null) && !supports_onhashchange && (function(){
1289
// Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
1290
// when running in "IE7 compatibility" mode.
1295
// When the event is bound and polling starts in IE 6/7, create a hidden
1296
// Iframe for history handling.
1297
self.start = function(){
1299
iframe_src = $.fn[ str_hashchange ].src;
1300
iframe_src = iframe_src && iframe_src + get_fragment();
1302
// Create hidden Iframe. Attempt to make Iframe as hidden as possible
1303
// by using techniques from http://www.paciellogroup.com/blog/?p=604.
1304
iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
1306
// When Iframe has completely loaded, initialize the history and
1308
.one( 'load', function(){
1309
iframe_src || history_set( get_fragment() );
1313
// Load Iframe src if specified, otherwise nothing.
1314
.attr( 'src', iframe_src || 'javascript:0' )
1316
// Append Iframe after the end of the body to prevent unnecessary
1317
// initial page scrolling (yes, this works).
1318
.insertAfter( 'body' )[0].contentWindow;
1320
// Whenever `document.title` changes, update the Iframe's title to
1321
// prettify the back/next history menu entries. Since IE sometimes
1322
// errors with "Unspecified error" the very first time this is set
1323
// (yes, very useful) wrap this with a try/catch block.
1324
doc.onpropertychange = function(){
1326
if ( event.propertyName === 'title' ) {
1327
iframe.document.title = doc.title;
1335
// Override the "stop" method since an IE6/7 Iframe was created. Even
1336
// if there are no longer any bound event handlers, the polling loop
1337
// is still necessary for back/next to work at all!
1338
self.stop = fn_retval;
1340
// Get history by looking at the hidden Iframe's location.hash.
1341
history_get = function() {
1342
return get_fragment( iframe.location.href );
1345
// Set a new history item by opening and then closing the Iframe
1346
// document, *then* setting its location.hash. If document.domain has
1347
// been set, update that as well.
1348
history_set = function( hash, history_hash ) {
1349
var iframe_doc = iframe.document,
1350
domain = $.fn[ str_hashchange ].domain;
1352
if ( hash !== history_hash ) {
1353
// Update Iframe with any initial `document.title` that might be set.
1354
iframe_doc.title = doc.title;
1356
// Opening the Iframe's document after it has been closed is what
1357
// actually adds a history entry.
1360
// Set document.domain for the Iframe document as well, if necessary.
1361
domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
1365
// Update the Iframe's hash, for great justice.
1366
iframe.location.hash = hash;
1371
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1372
// ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
1373
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^