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 if (typeof (options.async) !== 'undefined' && !options.async) { 265 fire('pjax:start', [xhr, options]) 266 fire('pjax:send', [xhr, options]) 267 } 268 } 269 270 options.complete = function(xhr, textStatus) { 271 if (timeoutTimer) 272 clearTimeout(timeoutTimer) 273 274 fire('pjax:complete', [xhr, textStatus, options]) 275 276 fire('pjax:end', [xhr, options]) 277 } 278 279 options.error = function(xhr, textStatus, errorThrown) { 280 var container = extractContainer("", xhr, options) 281 // Check redirect status code 282 var redirect = xhr.status >= 301 && xhr.status <= 303
283 // Do not fire pjax::error in case of redirect 284 var allowed = redirect || fire('pjax:error', [xhr, textStatus, errorThrown, options]) 285 if (redirect || options.type == 'GET' && textStatus !== 'abort' && allowed) { 286 if (options.replaceRedirect) { 287 locationReplace(container.url) 288 } else if (options.pushRedirect) { 289 window.history.pushState(null, "", container.url) 290 window.location.replace(container.url) 291 } 292 } 293 } 294 295 options.success = function(data, status, xhr) { 296 var previousState = pjax.state 297 298 // If $.pjax.defaults.version is a function, invoke it first. 299 // Otherwise it can be a static string. 300 var currentVersion = typeof $.pjax.defaults.version === 'function' ? 301 $.pjax.defaults.version() : 302 $.pjax.defaults.version 303 304 var latestVersion = xhr.getResponseHeader('X-PJAX-Version') 305 306 var container = extractContainer(data, xhr, options) 307 308 var url = parseURL(container.url) 309 if (hash) { 310 url.hash = hash 311 container.url = url.href 312 } 313 314 // If there is a layout version mismatch, hard load the new url 315 if (currentVersion && latestVersion && currentVersion !== latestVersion) { 316 locationReplace(container.url) 317 return 318 } 319 320 // If the new response is missing a body, hard load the page 321 if (!container.contents) { 322 locationReplace(container.url) 323 return 324 } 325 326 pjax.state = { 327 id: options.id || uniqueId(), 328 url: container.url, 329 title: container.title, 330 container: options.container, 331 fragment: options.fragment, 332 timeout: options.timeout, 333 cache: options.cache 334 } 335 336 if (options.history && (options.push || options.replace)) { 337 window.history.replaceState(pjax.state, container.title, container.url) 338 } 339 340 // Only blur the focus if the focused element is within the container. 341 var blurFocus = $.contains(context, document.activeElement) 342 343 // Clear out any focused controls before inserting new page contents. 344 if (blurFocus) { 345 try { 346 document.activeElement.blur() 347 } catch (e) { /* ignore */ } 348 } 349 350 if (container.title) document.title = container.title 351 352 fire('pjax:beforeReplace', [container.contents, options], { 353 state: pjax.state, 354 previousState: previousState 355 }) 356 context.html(container.contents) 357 358 // FF bug: Won't autofocus fields that are inserted via JS. 359 // This behavior is incorrect. So if theres no current focus, autofocus 360 // the last field. 361 // 362 // http://www.w3.org/html/wg/drafts/html/master/forms.html 363 var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0] 364 if (autofocusEl && document.activeElement !== autofocusEl) { 365 autofocusEl.focus() 366 } 367 368 executeScriptTags(container.scripts, context) 369 loadLinkTags(container.links) 370 371 372 if (typeof options.scrollTo === 'function') { 373 var scrollTo = options.scrollTo(context, hash) 374 } else { 375 var scrollTo = options.scrollTo 376 // Ensure browser scrolls to the element referenced by the URL anchor 377 if (hash || true === scrollTo) { 378 var name = decodeURIComponent(hash.slice(1)) 379 var target = true === scrollTo ? context : (document.getElementById(name) || document.getElementsByName(name)[0]) 380 if (target) scrollTo = $(target).offset().top 381 } 382 } 383 384 if (typeof options.scrollOffset === 'function') 385 var scrollOffset = options.scrollOffset(scrollTo) 386 else 387 var scrollOffset = options.scrollOffset 388 389 if (typeof scrollTo === 'number') { 390 scrollTo = scrollTo + scrollOffset; 391 if (scrollTo < 0) scrollTo = 0 392 $(window).scrollTop(scrollTo) 393 } 394 395 fire('pjax:success', [data, status, xhr, options]) 396 } 397 398 399 // Initialize pjax.state for the initial page load. Assume we're 400 // using the container and options of the link we're loading for the 401 // back button to the initial page. This ensures good back button 402 // behavior. 403 if (!pjax.state) { 404 pjax.state = { 405 id: uniqueId(), 406 url: window.location.href, 407 title: document.title, 408 container: options.container, 409 fragment: options.fragment, 410 timeout: options.timeout, 411 cache: options.cache 412 } 413 if (options.history) 414 window.history.replaceState(pjax.state, document.title) 415 } 416 417 // New request can not override the existing one when option skipOuterContainers is set to true 418 if (pjax.xhr && pjax.xhr.readyState < 4 && pjax.options.skipOuterContainers
418) { 419 return 420 } 421 // Cancel the current request if we're already pjaxing 422 abortXHR(pjax.xhr) 423 424 pjax.options = options 425 var xhr = pjax.xhr = $.ajax(options) 426 427 if (xhr.readyState > 0) { 428 if (options.history && (options.push && !options.replace)) { 429 // Cache current container element before replacing it 430 cachePush(pjax.state.id, [options.container, cloneContents(context)]) 431 432 window.history.pushState(null, "", options.requestUrl) 433 } 434 435 if (typeof (options.async) === 'undefined' || options.async) { 436 fire('pjax:start', [xhr, options]) 437 fire('pjax:send', [xhr, options]) 438 } 439 } 440 441 return pjax.xhr 442} 443 444// Public: Reload current page with pjax. 445// 446// Returns whatever $.pjax returns. 447function pjaxReload(container, options) { 448 var defaults = { 449 url: window.location.href, 450 push: false, 451 replace: true, 452 scrollTo: false 453 } 454 455 return pjax($.extend(defaults, optionsFor(container, options))) 456} 457 458// Internal: Hard replace current state with url. 459// 460// Work for around WebKit 461// https://bugs.webkit.org/show_bug.cgi?id=93506 462// 463// Returns nothing. 464function locationReplace(url) { 465 if (!pjax.options.history) return 466 window.history.replaceState(null, "", pjax.state.url) 467 window.location.replace(url) 468} 469 470 471var initialPop = true 472var initialURL = window.location.href 473var initialState = window.history.state 474 475// Initialize $.pjax.state if possible 476// Happens when reloading a page and coming forward from a different 477// session history. 478if (initialState && initialState.container) { 479 pjax.state = initialState 480} 481 482// Non-webkit browsers don't fire an initial popstate event 483if ('state' in window.history) { 484 initialPop = false 485} 486 487// popstate handler takes care of the back and forward buttons 488// 489// You probably shouldn't use pjax on pages with other pushState 490// stuff yet. 491function onPjaxPopstate(event) { 492 493 // Hitting back or forward should override any pending PJAX request. 494 if (!initialPop) { 495 abortXHR(pjax.xhr) 496 } 497 498 var previousState = pjax.state 499 var state = event.state 500 var direction 501 502 if (state && state.container) { 503 // When coming forward from a separate history session, will get an 504 // initial pop with a state we are already at. Skip reloading the current 505 // page. 506 if (initialPop && initialURL == state.url) return 507 508 if (previousState) { 509 // If popping back to the same state, just skip. 510 // Could be clicking back from hashchange rather than a pushState. 511 if (previousState.id === state.id) return 512 513 // Since state IDs always increase, we can deduce the navigation direction 514 direction = previousState.id < state.id ? 'forward' : 'back' 515 } 516 517 var cache = cacheMapping[state.id] || [] 518 var containerSelector = cache[0] || state.container 519 var container = $(containerSelector), contents = cache[1] 520 521 if (container.length) { 522 var options = { 523 id: state.id, 524 url: state.url, 525 container: containerSelector, 526 push: false, 527 fragment: state.fragment, 528 timeout: state.timeout, 529 cache: state.cache, 530 scrollTo: false 531 } 532 533 if (previousState && options.cache) { 534 // Cache current container before replacement and inform the 535 // cache which direction the history shifted. 536 cachePop(direction, previousState.id, [containerSelector, cloneContents(container)]) 537 } 538 539 var popstateEvent = $.Event('pjax:popstate', { 540 state: state, 541 direction: direction 542 }) 543 container.trigger(popstateEvent) 544 545 if (contents) { 546 container.trigger('pjax:start', [null, options]) 547 548 pjax.state = state 549 if (state.title) document.title = state.title 550 var beforeReplaceEvent = $.Event('pjax:beforeReplace', { 551 state: state, 552 previousState: previousState 553 }) 554 container.trigger(beforeReplaceEvent, [contents, options]) 555 container.html(contents) 556 557 container.trigger('pjax:end', [null, options]) 558 } else { 559 pjax(options) 560 } 561 562 // Force reflow/relayout before the browser tries to restore the 563 // scroll position. 564 container[0].offsetHeight // eslint-disable-line no-unused-expressions 565 } else { 566 locationReplace(location.href) 567 } 568 } 569 initialPop = false 570} 571
572// Fallback version of main pjax function for browsers that don't 573// support pushState. 574// 575// Returns nothing since it retriggers a hard form submission. 576function fallbackPjax(options) { 577 var url = $.isFunction(options.url) ? options.url() : options.url, 578 method = options.type ? options.type.toUpperCase() : 'GET' 579 580 var form = $('<form>', { 581 method: method === 'GET' ? 'GET' : 'POST', 582 action: url, 583 style: 'display:none' 584 }) 585 586 if (method !== 'GET' && method !== 'POST') { 587 form.append($('<input>', { 588 type: 'hidden', 589 name: '_method', 590 value: method.toLowerCase() 591 })) 592 } 593 594 var data = options.data 595 if (typeof data === 'string') { 596 $.each(data.split('&'), function(index, value) { 597 var pair = value.split('=') 598 form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]})) 599 }) 600 } else if ($.isArray(data)) { 601 $.each(data, function(index, value) { 602 form.append($('<input>', {type: 'hidden', name: value.name, value: value.value})) 603 }) 604 } else if (typeof data === 'object') { 605 var key 606 for (key in data) 607 form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) 608 } 609 610 $(document.body).append(form) 611 form.submit() 612} 613 614// Internal: Abort an XmlHttpRequest if it hasn't been completed, 615// also removing its event handlers. 616function abortXHR(xhr) { 617 if ( xhr && xhr.readyState < 4) { 618 xhr.onreadystatechange = $.noop 619 xhr.abort() 620 } 621} 622 623// Internal: Generate unique id for state object. 624// 625// Use a timestamp instead of a counter since ids should still be 626// unique across page loads. 627// 628// Returns Number. 629function uniqueId() { 630 return (new Date).getTime() 631} 632 633function cloneContents(container) { 634 var cloned = container.clone() 635 // Unmark script tags as already being eval'd so they can get executed again 636 // when restored from cache. HAXX: Uses jQuery internal method. 637 cloned.find('script').each(function(){ 638 if (!this.src) $._data(this, 'globalEval', false) 639 }) 640 return cloned.contents() 641} 642 643// Internal: Strip internal query params from parsed URL. 644// 645// Returns sanitized url.href String. 646function stripInternalParams(url) { 647 url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '') 648 return url.href.replace(/\?($|#)/, '$1') 649} 650 651// Internal: Parse URL components and returns a Locationish object. 652// 653// url - String URL 654// 655// Returns HTMLAnchorElement that acts like Location. 656function parseURL(url) { 657 var a = document.createElement('a') 658 a.href = url 659 return a 660} 661 662// Internal: Return the `href` component of given URL object with the hash 663// portion removed. 664// 665// location - Location or HTMLAnchorElement 666// 667// Returns String 668function stripHash(location) { 669 return location.href.replace(/#.*/, '') 670} 671 672// Internal: Build options Object for arguments. 673// 674// For convenience the first parameter can be either the container or 675// the options object. 676// 677// Examples 678// 679// optionsFor('#container') 680// // => {container: '#container'} 681// 682// optionsFor('#container', {push: true}) 683// // => {container: '#container', push: true} 684// 685// optionsFor({container: '#container', push: true}) 686// // => {container: '#container', push: true} 687// 688// Returns options Object. 689function optionsFor(container, options) { 690 if (container && options) { 691 options = $.extend({}, options) 692 options.container = container 693 return options 694 } else if ($.isPlainObject(container)) { 695 return container 696 } else { 697 return {container: container} 698 } 699} 700 701// Internal: Filter and find all elements matching the selector. 702// 703// Where $.fn.find only matches descendants, findAll will test all the 704// top level elements in the jQuery object as well. 705// 706// elems - jQuery object of Elements 707// selector - String selector to match 708// 709// Returns a jQuery object. 710function findAll(elems, selector) { 711 return elems.filter(selector).add(elems.find(selector)) 712} 713 714function parseHTML(html) { 715 return $.parseHTML(html, document, true) 716} 717 718// Internal: Extracts container and metadata from response. 719// 720// 1. Extracts X-PJAX-URL header if set 721// 2. Extracts inline <title> tags 722// 3. Builds response Element and extracts fragment if set 723// 724// data - String response data 725// xhr - XHR response 726// options - pjax options Object 727// 728// Returns an Object with url, title, and contents keys. 729function extractContainer(data, xhr, options) { 730 var obj = {}, fullDocument = /<html/i.test(data) 731 732 // Prefer X-PJAX-URL header if it was set, otherwise fallback to 733 // using the original requested url. 734 var serverUrl = xhr.getResponseHeader('X-PJAX-URL')
735 obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl 736 737 var $head, $body 738 // Attempt to parse response html into elements 739 if (fullDocument) { 740 $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) 741 var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i) 742 $head = head != null ? $(parseHTML(head[0])) : $body 743 } else { 744 $head = $body = $(parseHTML(data)) 745 } 746 747 // If response data is empty, return fast 748 if ($body.length === 0) 749 return obj 750 751 // If there's a <title> tag in the header, use it as 752 // the page's title. 753 obj.title = findAll($head, 'title').last().text() 754 755 if (options.fragment) { 756 var $fragment = $body 757 // If they specified a fragment, look for it in the response 758 // and pull it out. 759 if (options.fragment !== 'body') { 760 $fragment = findAll($fragment, options.fragment).first() 761 } 762 763 if ($fragment.length) { 764 obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents() 765 766 // If there's no title, look for data-title and title attributes 767 // on the fragment 768 if (!obj.title) 769 obj.title = $fragment.attr('title') || $fragment.data('title') 770 } 771 772 } else if (!fullDocument) { 773 obj.contents = $body 774 } 775 776 // Clean up any <title> tags 777 if (obj.contents) { 778 // Remove any parent title elements 779 obj.contents = obj.contents.not(function() { return $(this).is('title') }) 780 781 // Then scrub any titles from their descendants 782 obj.contents.find('title').remove() 783 784 // Gather all script elements 785 obj.scripts = findAll(obj.contents, 'script').remove() 786 obj.contents = obj.contents.not(obj.scripts) 787 788 // Gather all link[href] elements 789 obj.links = findAll(obj.contents, 'link[href]').remove() 790 obj.contents = obj.contents.not(obj.links) 791 } 792 793 // Trim any whitespace off the title 794 if (obj.title) obj.title = $.trim(obj.title) 795 796 return obj 797} 798 799// Load an execute scripts using standard script request. 800// 801// Avoids jQuery's traditional $.getScript which does a XHR request and 802// globalEval. 803// 804// scripts - jQuery object of script Elements 805// context - jQuery object whose context is `document` and has a selector 806// 807// Returns nothing. 808function executeScriptTags(scripts, context) { 809 if (!scripts) return 810 811 var existingScripts = $('script[src]') 812 813 var cb = function (next) { 814 var src = this.src 815 var matchedScripts = existingScripts.filter(function () { 816 return this.src === src 817 }) 818 819 if (matchedScripts.length) { 820 next() 821 return 822 } 823 824 if (src) { 825 $.getScript(src).done(next).fail(next) 826 document.head.appendChild(this) 827 } else { 828 context.append(this) 829 next() 830 } 831 } 832 833 var i = 0 834 var next = function () { 835 if (i >= scripts.length) { 836 return 837 } 838 var script = scripts[i] 839 i++ 840 cb.call(script, next) 841 } 842 next() 843} 844 845// Load an links using standard request. 846// 847// links - jQuery object of link Elements 848// 849// Returns nothing. 850function loadLinkTags(links) { 851 if (!links) return 852 853 var existingLinks = $('link[href]') 854 855 links.each(function() { 856 var href = this.href, 857 alreadyLoadedLinks = existingLinks.filter(function() { 858 return this.href === href 859 }) 860 if (alreadyLoadedLinks.length) return 861 862 document.head.appendChild(this) 863 }) 864} 865 866// Internal: History DOM caching class. 867var cacheMapping = {} 868var cacheForwardStack = [] 869var cacheBackStack = [] 870 871// Push previous state id and container contents into the history 872// cache. Should be called in conjunction with `pushState` to save the 873// previous container contents. 874// 875// id - State ID Number 876// value - DOM Element to cache 877// 878// Returns nothing. 879function cachePush(id, value) { 880 if (!pjax.options.cache) { 881 return 882 } 883 cacheMapping[id] = value 884 cacheBackStack.push(id) 885 886 // Remove all entries in forward history stack after pushing a new page. 887 trimCacheStack(cacheForwardStack, 0) 888 889 // Trim back history stack to max cache length. 890 trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength) 891} 892 893// Shifts cache from directional history cache. Should be 894// called on `popstate` with the previous state id and container 895// contents. 896// 897// direction - "forward" or "back" String 898// id - State ID Number 899// value - DOM Element to cache 900// 901// Returns nothing. 902function cachePop(direction, id, value) { 903 var pushStack, popStack 904 cacheMapping[id] = value 905 906 if (direction === 'forward') { 907 pushStack = cacheBackStack 908 popStack = cacheForwardStack 909 } else { 910 pushStack = cacheForwardStack 911 popStack = cacheBackStack 912 } 913 914 pushStack.push(id) 915 id = popStack.pop() 916 if (id) delete cacheMapping[id] 917 918 // Trim whichever stack we just pushed to to max cache length. 919 trimCacheStack(pushStack, pjax.defaults.maxCacheLength) 920} 921 922// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no 923// longer than the specified length, deleting cached DOM elements as necessary. 924// 925// stack - Array of state IDs 926// length - Maximum length to trim to 927// 928// Returns nothing. 929function trimCacheStack(stack, length) { 930 while (stack.length > length) 931 delete cacheMapping[stack.shift()] 932} 933 934// Public: Find version identifier for the initial page load. 935// 936// Returns String version or undefined. 937function findVersion() { 938 return $('meta').filter(function() { 939 var name = $(this).attr('http-equiv') 940 return name && name.toUpperCase() === 'X-PJAX-VERSION' 941 }).attr('content') 942} 943 944// Install pjax functions on $.pjax to enable pushState behavior. 945// 946// Does nothing if already enabled. 947// 948// Examples 949// 950// $.pjax.enable() 951// 952// Returns nothing. 953function enable() { 954 $.fn.pjax = fnPjax 955 $.pjax = pjax 956 $.pjax.enable = $.noop 957 $.pjax.disable = disable 958 $.pjax.click = handleClick 959 $.pjax.submit = handleSubmit 960 $.pjax.reload = pjaxReload 961 $.pjax.defaults = { 962 history: true, 963 cache: true, 964 timeout: 650, 965 push: true, 966 replace: false, 967 type: 'GET', 968 dataType: 'html', 969 scrollTo: 0, 970 scrollOffset: 0, 971 maxCacheLength: 20, 972 version: findVersion, 973 pushRedirect: false, 974 replaceRedirect: true, 975 skipOuterContainers: false, 976 ieRedirectCompatibility: true 977 } 978 $(window).on('popstate.pjax', onPjaxPopstate) 979} 980 981// Disable pushState behavior. 982// 983// This is the case when a browser doesn't support pushState. It is 984// sometimes useful to disable pushState for debugging on a modern 985// browser. 986// 987// Examples 988// 989// $.pjax.disable() 990// 991// Returns nothing. 992function disable() { 993 $.fn.pjax = function() { return this } 994 $.pjax = fallbackPjax 995 $.pjax.enable = enable 996 $.pjax.disable = $.noop 997 $.pjax.click = $.noop 998 $.pjax.submit = $.noop 999 $.pjax.reload = function() { window.location.reload() } 1000 1001 $(window).off('popstate.pjax', onPjaxPopstate) 1002} 1003 1004 1005// Add the state property to jQuery's event object so we can use it in 1006// $(window).bind('popstate') 1007if ($.event.props && $.inArray('state', $.event.props) < 0) { 1008 $.event.props.push('state') 1009} else if (!('state' in $.Event.prototype)) { 1010 $.event.addProp('state') 1011} 1012 1013// Is pjax supported by this browser?
1014$.support.pjax = 1015 window.history && window.history.pushState && window.history.replaceState && 1016 // pushState isn't reliable on iOS until 5. 1017 !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/) 1018 1019if ($.support.pjax) { 1020 enable() 1021} else { 1022 disable() 1023} 1024 1025})(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.