1/*! 2 * Copyright 2012, Chris Wanstrath 3 * Released under the MIT License 4 * https://github.com/defunkt/jquery-pjax 5 */ 6 7(function($){ 8 9// When called on a container with a selector, fetches the href with 10// ajax into the container or with the data-pjax attribute on the link 11// itself. 12// 13// Tries to make sure the back button and ctrl+click work the way 14// you'd expect. 15// 16// Exported as $.fn.pjax 17// 18// Accepts a jQuery ajax options object that may include these 19// pjax specific options: 20// 21// 22// container - String selector for the element where to place the response body. 23// push - Whether to pushState the URL. Defaults to true (of course). 24// replace - Want to use replaceState instead? That's cool. 25// history - Work with window.history. Defaults to true 26// cache - Whether to cache pages HTML. Defaults to true 27// pushRedirect - Whether to add a browser history entry upon redirect. Defaults to false. 28// replaceRedirect - Whether to replace URL without adding a browser history entry upon redirect. Defaults to true. 29// skipOuterContainers - When pjax containers are nested and this option is true, 30// the closest pjax block will handle the event. Otherwise, the top 31// container will handle the event. Defaults to false. 32// ieRedirectCompatibility - Whether to add `X-Ie-Redirect-Compatibility` header for the request on IE. 33// See https://github.com/yiisoft/jquery-pjax/issues/37 34// 35// For convenience the second parameter can be either the container or 36// the options object. 37// 38// Returns the jQuery object 39function fnPjax(selector, container, options) { 40 options = optionsFor(container, options) 41 var handler = function(event) { 42 var opts = options 43 if (!opts.container) { 44 opts = $.extend({history: true}, options) 45 opts.container = $(this).attr('data-pjax') 46 } 47 handleClick(event, opts) 48 } 49 $(selector).removeClass('data-pjax'); 50 return this 51 .off('click.pjax', selector, handler) 52 .on('click.pjax', selector, handler); 53} 54 55// Public: pjax on click handler 56// 57// Exported as $.pjax.click. 58// 59// event - "click" jQuery.Event 60// options - pjax options 61// 62// If the click event target has 'data-pjax="0"' attribute, the event is ignored, and no pjax call is made. 63// 64// Examples 65// 66// $(document).on('click', 'a', $.pjax.click) 67// // is the same as 68// $(document).pjax('a') 69// 70// Returns nothing. 71function handleClick(event, container, options) { 72 options = optionsFor(container, options) 73 74 var link = event.currentTarget 75 var $link = $(link) 76 77 // Ignore links with data-pjax="0" 78 if (parseInt($link.data('pjax')) === 0) { 79 return 80 } 81 82 if (link.tagName.toUpperCase() !== 'A') 83 throw "$.fn.pjax or $.pjax.click requires an anchor element" 84 85 // Middle click, cmd click, and ctrl click should open 86 // links in a new tab as normal. 87 if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) 88 return 89 90 // Ignore cross origin links 91 if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) 92 return 93 94 // Ignore case when a hash is being tacked on the current URL 95 if ( link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location) ) 96 return 97 98 // Ignore event with default prevented 99 if (event.isDefaultPrevented()) 100 return 101 102 var defaults = { 103 url: link.href, 104 container: $link.attr('data-pjax'), 105 target: link 106 } 107 108 var opts = $.extend({}, defaults, options) 109 var clickEvent = $.Event('pjax:click') 110 $link.trigger(clickEvent, [opts]) 111 112 if (!clickEvent.isDefaultPrevented()) { 113 pjax(opts) 114 event.preventDefault() 115 $link.trigger('pjax:clicked', [opts]) 116 } 117} 118 119// Public: pjax on form submit handler 120// 121// Exported as $.pjax.submit 122// 123// event - "click" jQuery.Event 124// options - pjax options 125// 126// Examples 127// 128// $(document).on('submit', 'form', function(event) { 129// $.pjax.submit(event, '[data-pjax-container]') 130// }) 131// 132// Returns nothing. 133function handleSubmit(event, container, options) { 134 // check result of previous handlers 135 if (event.result === false) 136 return false;
137 138 options = optionsFor(container, options) 139 140 var form = event.currentTarget 141 var $form = $(form) 142 143 if (form.tagName.toUpperCase() !== 'FORM') 144 throw "$.pjax.submit requires a form element" 145 146 var defaults = { 147 type: ($form.attr('method') || 'GET').toUpperCase(), 148 url: $form.attr('action'), 149 container: $form.attr('data-pjax'), 150 target: form 151 } 152 153 if (defaults.type !== 'GET' && window.FormData !== undefined) { 154 defaults.data = new FormData(form) 155 defaults.processData = false 156 defaults.contentType = false 157 } else { 158 // Can't handle file uploads, exit 159 if ($form.find(':file').length) { 160 return 161 } 162 163 // Fallback to manually serializing the fields 164 defaults.data = $form.serializeArray() 165 } 166 167 pjax($.extend({}, defaults, options)) 168 169 event.preventDefault() 170} 171 172// Loads a URL with ajax, puts the response body inside a container, 173// then pushState()'s the loaded URL. 174// 175// Works just like $.ajax in that it accepts a jQuery ajax 176// settings object (with keys like url, type, data, etc). 177// 178// Accepts these extra keys: 179// 180// container - String selector for where to stick the response body. 181// push - Whether to pushState the URL. Defaults to true (of course). 182// replace - Want to use replaceState instead? That's cool. 183// 184// Use it just like $.ajax: 185// 186// var xhr = $.pjax({ url: this.href, container: '#main' }) 187// console.log( xhr.readyState ) 188// 189// Returns whatever $.ajax returns. 190function pjax(options) { 191 options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) 192 193 if ($.isFunction(options.url)) { 194 options.url = options.url() 195 } 196 197 var hash = parseURL(options.url).hash 198 199 var containerType = $.type(options.container) 200 if (containerType !== 'string') { 201 throw "expected string value for 'container' option; got " + containerType 202 } 203 var context = options.context = $(options.container) 204 if (!context.length) { 205 throw "the container selector '" + options.container + "' did not match anything" 206 } 207 208 // We want the browser to maintain two separate internal caches: one 209 // for pjax'd partial page loads and one for normal page loads. 210 // Without adding this secret parameter, some browsers will often 211 // confuse the two. 212 if (!options.data) options.data = {} 213 if ($.isArray(options.data)) { 214 options.data = $.grep(options.data, function(obj) { return '_pjax' !== obj.name }) 215 options.data.push({name: '_pjax', value: options.container}) 216 } else { 217 options.data._pjax = options.container 218 } 219 220 function fire(type, args, props) { 221 if (!props) props = {} 222 props.relatedTarget = options.target 223 var event = $.Event(type, props) 224 context.trigger(event, args) 225 return !event.isDefaultPrevented() 226 } 227 228 var timeoutTimer 229 230 options.beforeSend = function(xhr, settings) { 231 // No timeout for non-GET requests 232 // Its not safe to request the resource again with a fallback method. 233 if (settings.type !== 'GET') { 234 settings.timeout = 0 235 } 236 237 xhr.setRequestHeader('X-PJAX', 'true') 238 xhr.setRequestHeader('X-PJAX-Container', options.container) 239 240 if (settings.ieRedirectCompatibility) { 241 var ua = window.navigator.userAgent 242 if (ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0 || ua.indexOf('Edge/') > 0) { 243 xhr.setRequestHeader('X-Ie-Redirect-Compatibility', 'true') 244 } 245 } 246 247 if (!fire('pjax:beforeSend', [xhr, settings])) 248 return false 249 250 if (settings.timeout > 0) { 251 timeoutTimer = setTimeout(function() { 252 if (fire('pjax:timeout', [xhr, options])) 253 xhr.abort('timeout') 254 }, settings.timeout) 255 256 // Clear timeout setting so jquerys internal timeout isn't invoked 257 settings.timeout = 0 258 } 259 260 var url = parseURL(settings.url) 261 if (hash) url.hash = hash 262 options.requestUrl = stripInternalParams(url) 263 } 264 265 options.complete = function(xhr, textStatus) { 266 if (timeoutTimer) 267 clearTimeout(timeoutTimer) 268 269 fire('pjax:complete', [xhr, textStatus, options]) 270 271 fire('pjax:end', [xhr, options]) 272 } 273 274 options.error = function(xhr, textStatus, errorThrown) { 275 var container = extractContainer("", xhr, options) 276 // Check redirect status code 277 var redirect = xhr.status >= 301 && xhr.status <= 303
278 // Do not fire pjax::error in case of redirect 279 var allowed = redirect || fire('pjax:error', [xhr, textStatus, errorThrown, options]) 280 if (redirect || options.type == 'GET' && textStatus !== 'abort' && allowed) { 281 if (options.replaceRedirect) { 282 locationReplace(container.url) 283 } else if (options.pushRedirect) { 284 window.history.pushState(null, "", container.url) 285 window.location.replace(container.url) 286 } 287 } 288 } 289 290 options.success = function(data, status, xhr) { 291 var previousState = pjax.state 292 293 // If $.pjax.defaults.version is a function, invoke it first. 294 // Otherwise it can be a static string. 295 var currentVersion = typeof $.pjax.defaults.version === 'function' ? 296 $.pjax.defaults.version() : 297 $.pjax.defaults.version 298 299 var latestVersion = xhr.getResponseHeader('X-PJAX-Version') 300 301 var container = extractContainer(data, xhr, options) 302 303 var url = parseURL(container.url) 304 if (hash) { 305 url.hash = hash 306 container.url = url.href 307 } 308 309 // If there is a layout version mismatch, hard load the new url 310 if (currentVersion && latestVersion && currentVersion !== latestVersion) { 311 locationReplace(container.url) 312 return 313 } 314 315 // If the new response is missing a body, hard load the page 316 if (!container.contents) { 317 locationReplace(container.url) 318 return 319 } 320 321 pjax.state = { 322 id: options.id || uniqueId(), 323 url: container.url, 324 title: container.title, 325 container: options.container, 326 fragment: options.fragment, 327 timeout: options.timeout, 328 cache: options.cache 329 } 330 331 if (options.history && (options.push || options.replace)) { 332 window.history.replaceState(pjax.state, container.title, container.url) 333 } 334 335 // Only blur the focus if the focused element is within the container. 336 var blurFocus = $.contains(context, document.activeElement) 337 338 // Clear out any focused controls before inserting new page contents. 339 if (blurFocus) { 340 try { 341 document.activeElement.blur() 342 } catch (e) { /* ignore */ } 343 } 344 345 if (container.title) document.title = container.title 346 347 fire('pjax:beforeReplace', [container.contents, options], { 348 state: pjax.state, 349 previousState: previousState 350 }) 351 context.html(container.contents) 352 353 // FF bug: Won't autofocus fields that are inserted via JS. 354 // This behavior is incorrect. So if theres no current focus, autofocus 355 // the last field. 356 // 357 // http://www.w3.org/html/wg/drafts/html/master/forms.html 358 var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0] 359 if (autofocusEl && document.activeElement !== autofocusEl) { 360 autofocusEl.focus() 361 } 362 363 executeScriptTags(container.scripts, context) 364 loadLinkTags(container.links) 365 366 367 if (typeof options.scrollTo === 'function') { 368 var scrollTo = options.scrollTo(context, hash) 369 } else { 370 var scrollTo = options.scrollTo 371 // Ensure browser scrolls to the element referenced by the URL anchor 372 if (hash || true === scrollTo) { 373 var name = decodeURIComponent(hash.slice(1)) 374 var target = true === scrollTo ? context : (document.getElementById(name) || document.getElementsByName(name)[0]) 375 if (target) scrollTo = $(target).offset().top 376 } 377 } 378 379 if (typeof options.scrollOffset === 'function') 380 var scrollOffset = options.scrollOffset(scrollTo) 381 else 382 var scrollOffset = options.scrollOffset 383 384 if (typeof scrollTo === 'number') { 385 scrollTo = scrollTo + scrollOffset; 386 if (scrollTo < 0) scrollTo = 0 387 $(window).scrollTop(scrollTo) 388 } 389 390 fire('pjax:success', [data, status, xhr, options]) 391 } 392 393 394 // Initialize pjax.state for the initial page load. Assume we're 395 // using the container and options of the link we're loading for the 396 // back button to the initial page. This ensures good back button 397 // behavior. 398 if (!pjax.state) { 399 pjax.state = { 400 id: uniqueId(), 401 url: window.location.href, 402 title: document.title, 403 container: options.container, 404 fragment: options.fragment, 405 timeout: options.timeout, 406 cache: options.cache 407 } 408 if (options.history) 409 window.history.replaceState(pjax.state, document.title) 410 } 411 412 // New request can not override the existing one when option skipOuterContainers is set to true 413 if (pjax.xhr && pjax.xhr.readyState < 4 && pjax.options.skipOuterContainers
413) { 414 return 415 } 416 // Cancel the current request if we're already pjaxing 417 abortXHR(pjax.xhr) 418 419 pjax.options = options 420 var xhr = pjax.xhr = $.ajax(options) 421 422 if (xhr.readyState > 0) { 423 if (options.history && (options.push && !options.replace)) { 424 // Cache current container element before replacing it 425 cachePush(pjax.state.id, [options.container, cloneContents(context)]) 426 427 window.history.pushState(null, "", options.requestUrl) 428 } 429 430 fire('pjax:start', [xhr, options]) 431 fire('pjax:send', [xhr, options]) 432 } 433 434 return pjax.xhr 435} 436 437// Public: Reload current page with pjax. 438// 439// Returns whatever $.pjax returns. 440function pjaxReload(container, options) { 441 var defaults = { 442 url: window.location.href, 443 push: false, 444 replace: true, 445 scrollTo: false 446 } 447 448 return pjax($.extend(defaults, optionsFor(container, options))) 449} 450 451// Internal: Hard replace current state with url. 452// 453// Work for around WebKit 454// https://bugs.webkit.org/show_bug.cgi?id=93506 455// 456// Returns nothing. 457function locationReplace(url) { 458 if (!pjax.options.history) return 459 window.history.replaceState(null, "", pjax.state.url) 460 window.location.replace(url) 461} 462 463 464var initialPop = true 465var initialURL = window.location.href 466var initialState = window.history.state 467 468// Initialize $.pjax.state if possible 469// Happens when reloading a page and coming forward from a different 470// session history. 471if (initialState && initialState.container) { 472 pjax.state = initialState 473} 474 475// Non-webkit browsers don't fire an initial popstate event 476if ('state' in window.history) { 477 initialPop = false 478} 479 480// popstate handler takes care of the back and forward buttons 481// 482// You probably shouldn't use pjax on pages with other pushState 483// stuff yet. 484function onPjaxPopstate(event) { 485 486 // Hitting back or forward should override any pending PJAX request. 487 if (!initialPop) { 488 abortXHR(pjax.xhr) 489 } 490 491 var previousState = pjax.state 492 var state = event.state 493 var direction 494 495 if (state && state.container) { 496 // When coming forward from a separate history session, will get an 497 // initial pop with a state we are already at. Skip reloading the current 498 // page. 499 if (initialPop && initialURL == state.url) return 500 501 if (previousState) { 502 // If popping back to the same state, just skip. 503 // Could be clicking back from hashchange rather than a pushState. 504 if (previousState.id === state.id) return 505 506 // Since state IDs always increase, we can deduce the navigation direction 507 direction = previousState.id < state.id ? 'forward' : 'back' 508 } 509 510 var cache = cacheMapping[state.id] || [] 511 var containerSelector = cache[0] || state.container 512 var container = $(containerSelector), contents = cache[1] 513 514 if (container.length) { 515 var options = { 516 id: state.id, 517 url: state.url, 518 container: containerSelector, 519 push: false, 520 fragment: state.fragment, 521 timeout: state.timeout, 522 cache: state.cache, 523 scrollTo: false 524 } 525 526 if (previousState && options.cache) { 527 // Cache current container before replacement and inform the 528 // cache which direction the history shifted. 529 cachePop(direction, previousState.id, [containerSelector, cloneContents(container)]) 530 } 531 532 var popstateEvent = $.Event('pjax:popstate', { 533 state: state, 534 direction: direction 535 }) 536 container.trigger(popstateEvent) 537 538 if (contents) { 539 container.trigger('pjax:start', [null, options]) 540 541 pjax.state = state 542 if (state.title) document.title = state.title 543 var beforeReplaceEvent = $.Event('pjax:beforeReplace', { 544 state: state, 545 previousState: previousState 546 }) 547 container.trigger(beforeReplaceEvent, [contents, options]) 548 container.html(contents) 549 550 container.trigger('pjax:end', [null, options]) 551 } else { 552 pjax(options) 553 } 554 555 // Force reflow/relayout before the browser tries to restore the 556 // scroll position. 557 container[0].offsetHeight // eslint-disable-line no-unused-expressions 558 } else { 559 locationReplace(location.href) 560 } 561 } 562 initialPop = false 563} 564
565// Fallback version of main pjax function for browsers that don't 566// support pushState. 567// 568// Returns nothing since it retriggers a hard form submission. 569function fallbackPjax(options) { 570 var url = $.isFunction(options.url) ? options.url() : options.url, 571 method = options.type ? options.type.toUpperCase() : 'GET' 572 573 var form = $('<form>', { 574 method: method === 'GET' ? 'GET' : 'POST', 575 action: url, 576 style: 'display:none' 577 }) 578 579 if (method !== 'GET' && method !== 'POST') { 580 form.append($('<input>', { 581 type: 'hidden', 582 name: '_method', 583 value: method.toLowerCase() 584 })) 585 } 586 587 var data = options.data 588 if (typeof data === 'string') { 589 $.each(data.split('&'), function(index, value) { 590 var pair = value.split('=') 591 form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]})) 592 }) 593 } else if ($.isArray(data)) { 594 $.each(data, function(index, value) { 595 form.append($('<input>', {type: 'hidden', name: value.name, value: value.value})) 596 }) 597 } else if (typeof data === 'object') { 598 var key 599 for (key in data) 600 form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) 601 } 602 603 $(document.body).append(form) 604 form.submit() 605} 606 607// Internal: Abort an XmlHttpRequest if it hasn't been completed, 608// also removing its event handlers. 609function abortXHR(xhr) { 610 if ( xhr && xhr.readyState < 4) { 611 xhr.onreadystatechange = $.noop 612 xhr.abort() 613 } 614} 615 616// Internal: Generate unique id for state object. 617// 618// Use a timestamp instead of a counter since ids should still be 619// unique across page loads. 620// 621// Returns Number. 622function uniqueId() { 623 return (new Date).getTime() 624} 625 626function cloneContents(container) { 627 var cloned = container.clone() 628 // Unmark script tags as already being eval'd so they can get executed again 629 // when restored from cache. HAXX: Uses jQuery internal method. 630 cloned.find('script').each(function(){ 631 if (!this.src) $._data(this, 'globalEval', false) 632 }) 633 return cloned.contents() 634} 635 636// Internal: Strip internal query params from parsed URL. 637// 638// Returns sanitized url.href String. 639function stripInternalParams(url) { 640 url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '') 641 return url.href.replace(/\?($|#)/, '$1') 642} 643 644// Internal: Parse URL components and returns a Locationish object. 645// 646// url - String URL 647// 648// Returns HTMLAnchorElement that acts like Location. 649function parseURL(url) { 650 var a = document.createElement('a') 651 a.href = url 652 return a 653} 654 655// Internal: Return the `href` component of given URL object with the hash 656// portion removed. 657// 658// location - Location or HTMLAnchorElement 659// 660// Returns String 661function stripHash(location) { 662 return location.href.replace(/#.*/, '') 663} 664 665// Internal: Build options Object for arguments. 666// 667// For convenience the first parameter can be either the container or 668// the options object. 669// 670// Examples 671// 672// optionsFor('#container') 673// // => {container: '#container'} 674// 675// optionsFor('#container', {push: true}) 676// // => {container: '#container', push: true} 677// 678// optionsFor({container: '#container', push: true}) 679// // => {container: '#container', push: true} 680// 681// Returns options Object. 682function optionsFor(container, options) { 683 if (container && options) { 684 options = $.extend({}, options) 685 options.container = container 686 return options 687 } else if ($.isPlainObject(container)) { 688 return container 689 } else { 690 return {container: container} 691 } 692} 693 694// Internal: Filter and find all elements matching the selector. 695// 696// Where $.fn.find only matches descendants, findAll will test all the 697// top level elements in the jQuery object as well. 698// 699// elems - jQuery object of Elements 700// selector - String selector to match 701// 702// Returns a jQuery object. 703function findAll(elems, selector) { 704 return elems.filter(selector).add(elems.find(selector)) 705} 706 707function parseHTML(html) { 708 return $.parseHTML(html, document, true) 709} 710 711// Internal: Extracts container and metadata from response. 712// 713// 1. Extracts X-PJAX-URL header if set 714// 2. Extracts inline <title> tags 715// 3. Builds response Element and extracts fragment if set 716// 717// data - String response data 718// xhr - XHR response 719// options - pjax options Object 720// 721// Returns an Object with url, title, and contents keys. 722function extractContainer(data, xhr, options) { 723 var obj = {}, fullDocument = /<html/i.test(data) 724 725 // Prefer X-PJAX-URL header if it was set, otherwise fallback to 726 // using the original requested url. 727 var serverUrl = xhr.getResponseHeader('X-PJAX-URL')
728 obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl 729 730 var $head, $body 731 // Attempt to parse response html into elements 732 if (fullDocument) { 733 $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) 734 var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i) 735 $head = head != null ? $(parseHTML(head[0])) : $body 736 } else { 737 $head = $body = $(parseHTML(data)) 738 } 739 740 // If response data is empty, return fast 741 if ($body.length === 0) 742 return obj 743 744 // If there's a <title> tag in the header, use it as 745 // the page's title. 746 obj.title = findAll($head, 'title').last().text() 747 748 if (options.fragment) { 749 var $fragment = $body 750 // If they specified a fragment, look for it in the response 751 // and pull it out. 752 if (options.fragment !== 'body') { 753 $fragment = findAll($fragment, options.fragment).first() 754 } 755 756 if ($fragment.length) { 757 obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents() 758 759 // If there's no title, look for data-title and title attributes 760 // on the fragment 761 if (!obj.title) 762 obj.title = $fragment.attr('title') || $fragment.data('title') 763 } 764 765 } else if (!fullDocument) { 766 obj.contents = $body 767 } 768 769 // Clean up any <title> tags 770 if (obj.contents) { 771 // Remove any parent title elements 772 obj.contents = obj.contents.not(function() { return $(this).is('title') }) 773 774 // Then scrub any titles from their descendants 775 obj.contents.find('title').remove() 776 777 // Gather all script elements 778 obj.scripts = findAll(obj.contents, 'script').remove() 779 obj.contents = obj.contents.not(obj.scripts) 780 781 // Gather all link[href] elements 782 obj.links = findAll(obj.contents, 'link[href]').remove() 783 obj.contents = obj.contents.not(obj.links) 784 } 785 786 // Trim any whitespace off the title 787 if (obj.title) obj.title = $.trim(obj.title) 788 789 return obj 790} 791 792// Load an execute scripts using standard script request. 793// 794// Avoids jQuery's traditional $.getScript which does a XHR request and 795// globalEval. 796// 797// scripts - jQuery object of script Elements 798// context - jQuery object whose context is `document` and has a selector 799// 800// Returns nothing. 801function executeScriptTags(scripts, context) { 802 if (!scripts) return 803 804 var existingScripts = $('script[src]') 805 806 var cb = function (next) { 807 var src = this.src 808 var matchedScripts = existingScripts.filter(function () { 809 return this.src === src 810 }) 811 812 if (matchedScripts.length) { 813 next() 814 return 815 } 816 817 if (src) { 818 $.getScript(src).done(next).fail(next) 819 document.head.appendChild(this) 820 } else { 821 context.append(this) 822 next() 823 } 824 } 825 826 var i = 0 827 var next = function () { 828 if (i >= scripts.length) { 829 return 830 } 831 var script = scripts[i] 832 i++ 833 cb.call(script, next) 834 } 835 next() 836} 837 838// Load an links using standard request. 839// 840// links - jQuery object of link Elements 841// 842// Returns nothing. 843function loadLinkTags(links) { 844 if (!links) return 845 846 var existingLinks = $('link[href]') 847 848 links.each(function() { 849 var href = this.href, 850 alreadyLoadedLinks = existingLinks.filter(function() { 851 return this.href === href 852 }) 853 if (alreadyLoadedLinks.length) return 854 855 document.head.appendChild(this) 856 }) 857} 858 859// Internal: History DOM caching class. 860var cacheMapping = {} 861var cacheForwardStack = [] 862var cacheBackStack = [] 863 864// Push previous state id and container contents into the history 865// cache. Should be called in conjunction with `pushState` to save the 866// previous container contents. 867// 868// id - State ID Number 869// value - DOM Element to cache 870// 871// Returns nothing. 872function cachePush(id, value) { 873 if (!pjax.options.cache) { 874 return 875 } 876 cacheMapping[id] = value 877 cacheBackStack.push(id) 878 879 // Remove all entries in forward history stack after pushing a new page. 880 trimCacheStack(cacheForwardStack, 0) 881 882 // Trim back history stack to max cache length. 883 trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength) 884} 885 886// Shifts cache from directional history cache. Should be 887// called on `popstate` with the previous state id and container 888// contents. 889// 890// direction - "forward" or "back" String 891// id - State ID Number 892// value - DOM Element to cache 893// 894// Returns nothing. 895function cachePop(direction, id, value) { 896 var pushStack, popStack 897 cacheMapping[id] = value 898 899 if (direction === 'forward') { 900 pushStack = cacheBackStack 901 popStack = cacheForwardStack 902 } else { 903 pushStack = cacheForwardStack 904 popStack = cacheBackStack 905 } 906 907 pushStack.push(id) 908 id = popStack.pop() 909 if (id) delete cacheMapping[id] 910 911 // Trim whichever stack we just pushed to to max cache length. 912 trimCacheStack(pushStack, pjax.defaults.maxCacheLength) 913} 914 915// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no 916// longer than the specified length, deleting cached DOM elements as necessary. 917// 918// stack - Array of state IDs 919// length - Maximum length to trim to 920// 921// Returns nothing. 922function trimCacheStack(stack, length) { 923 while (stack.length > length) 924 delete cacheMapping[stack.shift()] 925} 926 927// Public: Find version identifier for the initial page load. 928// 929// Returns String version or undefined. 930function findVersion() { 931 return $('meta').filter(function() { 932 var name = $(this).attr('http-equiv') 933 return name && name.toUpperCase() === 'X-PJAX-VERSION' 934 }).attr('content') 935} 936 937// Install pjax functions on $.pjax to enable pushState behavior. 938// 939// Does nothing if already enabled. 940// 941// Examples 942// 943// $.pjax.enable() 944// 945// Returns nothing. 946function enable() { 947 $.fn.pjax = fnPjax 948 $.pjax = pjax 949 $.pjax.enable = $.noop 950 $.pjax.disable = disable 951 $.pjax.click = handleClick 952 $.pjax.submit = handleSubmit 953 $.pjax.reload = pjaxReload 954 $.pjax.defaults = { 955 history: true, 956 cache: true, 957 timeout: 650, 958 push: true, 959 replace: false, 960 type: 'GET', 961 dataType: 'html', 962 scrollTo: 0, 963 scrollOffset: 0, 964 maxCacheLength: 20, 965 version: findVersion, 966 pushRedirect: false, 967 replaceRedirect: true, 968 skipOuterContainers: false, 969 ieRedirectCompatibility: true 970 } 971 $(window).on('popstate.pjax', onPjaxPopstate) 972} 973 974// Disable pushState behavior. 975// 976// This is the case when a browser doesn't support pushState. It is 977// sometimes useful to disable pushState for debugging on a modern 978// browser. 979// 980// Examples 981// 982// $.pjax.disable() 983// 984// Returns nothing. 985function disable() { 986 $.fn.pjax = function() { return this } 987 $.pjax = fallbackPjax 988 $.pjax.enable = enable 989 $.pjax.disable = $.noop 990 $.pjax.click = $.noop 991 $.pjax.submit = $.noop 992 $.pjax.reload = function() { window.location.reload() } 993 994 $(window).off('popstate.pjax', onPjaxPopstate) 995} 996 997 998// Add the state property to jQuery's event object so we can use it in 999// $(window).bind('popstate') 1000if ($.event.props && $.inArray('state', $.event.props) < 0) { 1001 $.event.props.push('state') 1002} else if (!('state' in $.Event.prototype)) { 1003 $.event.addProp('state') 1004} 1005 1006// Is pjax supported by this browser?
1007$.support.pjax = 1008 window.history && window.history.pushState && window.history.replaceState && 1009 // pushState isn't reliable on iOS until 5. 1010 !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/) 1011 1012if ($.support.pjax) { 1013 enable() 1014} else { 1015 disable() 1016} 1017 1018})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.