feat: 更新 pjax.js

pull/620/head
耗子 2023-11-15 22:46:52 +08:00
parent b381bedd07
commit 9f0f64036b
1 changed files with 423 additions and 449 deletions

View File

@ -6,62 +6,58 @@
(function($){ (function($){
// When called on a container with a selector, fetches the href with // When called on a container with a selector, fetches the href with
// ajax into the container or with the data-pjax attribute on the link // ajax into the container or with the data-pjax attribute on the link
// itself. // itself.
// //
// Tries to make sure the back button and ctrl+click work the way // Tries to make sure the back button and ctrl+click work the way
// you'd expect. // you'd expect.
// //
// Exported as $.fn.pjax // Exported as $.fn.pjax
// //
// Accepts a jQuery ajax options object that may include these // Accepts a jQuery ajax options object that may include these
// pjax specific options: // pjax specific options:
// //
// //
// container - Where to stick the response body. Usually a String selector. // container - String selector for the element where to place the response body.
// $(container).html(xhr.responseBody) // push - Whether to pushState the URL. Defaults to true (of course).
// (default: current jquery context) // replace - Want to use replaceState instead? That's cool.
// push - Whether to pushState the URL. Defaults to true (of course). //
// replace - Want to use replaceState instead? That's cool. // For convenience the second parameter can be either the container or
// // the options object.
// For convenience the second parameter can be either the container or //
// the options object. // Returns the jQuery object
//
// Returns the jQuery object
function fnPjax(selector, container, options) { function fnPjax(selector, container, options) {
var context = this options = optionsFor(container, options)
return this.on('click.pjax', selector, function(event) { return this.on('click.pjax', selector, function(event) {
var opts = $.extend({}, optionsFor(container, options)) var opts = options
if (!opts.container) if (!opts.container) {
opts.container = $(this).attr('data-pjax') || context opts = $.extend({}, options)
opts.container = $(this).attr('data-pjax')
}
handleClick(event, opts) handleClick(event, opts)
}) })
} }
// Public: pjax on click handler // Public: pjax on click handler
// //
// Exported as $.pjax.click. // Exported as $.pjax.click.
// //
// event - "click" jQuery.Event // event - "click" jQuery.Event
// options - pjax options // options - pjax options
// //
// Examples // Examples
// //
// $(document).on('click', 'a', $.pjax.click) // $(document).on('click', 'a', $.pjax.click)
// // is the same as // // is the same as
// $(document).pjax('a') // $(document).pjax('a')
// //
// $(document).on('click', 'a', function(event) { // Returns nothing.
// var container = $(this).closest('[data-pjax-container]')
// $.pjax.click(event, container)
// })
//
// Returns nothing.
function handleClick(event, container, options) { function handleClick(event, container, options) {
options = optionsFor(container, options) options = optionsFor(container, options)
var link = event.currentTarget var link = event.currentTarget
var $link = $(link)
if (link.tagName.toUpperCase() !== 'A') if (link.tagName.toUpperCase() !== 'A')
throw "$.fn.pjax or $.pjax.click requires an anchor element" throw "$.fn.pjax or $.pjax.click requires an anchor element"
@ -85,36 +81,35 @@
var defaults = { var defaults = {
url: link.href, url: link.href,
container: $(link).attr('data-pjax'), container: $link.attr('data-pjax'),
target: link target: link
} }
var opts = $.extend({}, defaults, options) var opts = $.extend({}, defaults, options)
var clickEvent = $.Event('pjax:click') var clickEvent = $.Event('pjax:click')
$(link).trigger(clickEvent, [opts]) $link.trigger(clickEvent, [opts])
if (!clickEvent.isDefaultPrevented()) { if (!clickEvent.isDefaultPrevented()) {
pjax(opts) pjax(opts)
event.preventDefault() event.preventDefault()
$(link).trigger('pjax:clicked', [opts]) $link.trigger('pjax:clicked', [opts])
} }
} }
// Public: pjax on form submit handler // Public: pjax on form submit handler
// //
// Exported as $.pjax.submit // Exported as $.pjax.submit
// //
// event - "click" jQuery.Event // event - "click" jQuery.Event
// options - pjax options // options - pjax options
// //
// Examples // Examples
// //
// $(document).on('submit', 'form', function(event) { // $(document).on('submit', 'form', function(event) {
// var container = $(this).closest('[data-pjax-container]') // $.pjax.submit(event, '[data-pjax-container]')
// $.pjax.submit(event, container) // })
// }) //
// // Returns nothing.
// Returns nothing.
function handleSubmit(event, container, options) { function handleSubmit(event, container, options) {
options = optionsFor(container, options) options = optionsFor(container, options)
@ -132,17 +127,17 @@
} }
if (defaults.type !== 'GET' && window.FormData !== undefined) { if (defaults.type !== 'GET' && window.FormData !== undefined) {
defaults.data = new FormData(form); defaults.data = new FormData(form)
defaults.processData = false; defaults.processData = false
defaults.contentType = false; defaults.contentType = false
} else { } else {
// Can't handle file uploads, exit // Can't handle file uploads, exit
if ($(form).find(':file').length) { if ($form.find(':file').length) {
return; return
} }
// Fallback to manually serializing the fields // Fallback to manually serializing the fields
defaults.data = $(form).serializeArray(); defaults.data = $form.serializeArray()
} }
pjax($.extend({}, defaults, options)) pjax($.extend({}, defaults, options))
@ -150,25 +145,24 @@
event.preventDefault() event.preventDefault()
} }
// Loads a URL with ajax, puts the response body inside a container, // Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL. // then pushState()'s the loaded URL.
// //
// Works just like $.ajax in that it accepts a jQuery ajax // Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc). // settings object (with keys like url, type, data, etc).
// //
// Accepts these extra keys: // Accepts these extra keys:
// //
// container - Where to stick the response body. // container - String selector for where to stick the response body.
// $(container).html(xhr.responseBody) // push - Whether to pushState the URL. Defaults to true (of course).
// push - Whether to pushState the URL. Defaults to true (of course). // replace - Want to use replaceState instead? That's cool.
// replace - Want to use replaceState instead? That's cool. //
// // Use it just like $.ajax:
// Use it just like $.ajax: //
// // var xhr = $.pjax({ url: this.href, container: '#main' })
// var xhr = $.pjax({ url: this.href, container: '#main' }) // console.log( xhr.readyState )
// console.log( xhr.readyState ) //
// // Returns whatever $.ajax returns.
// Returns whatever $.ajax returns.
function pjax(options) { function pjax(options) {
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
@ -176,11 +170,16 @@
options.url = options.url() options.url = options.url()
} }
var target = options.target
var hash = parseURL(options.url).hash var hash = parseURL(options.url).hash
var context = options.context = findContainerFor(options.container) var containerType = $.type(options.container)
if (containerType !== 'string') {
throw "expected string value for 'container' option; got " + containerType
}
var context = options.context = $(options.container)
if (!context.length) {
throw "the container selector '" + options.container + "' did not match anything"
}
// We want the browser to maintain two separate internal caches: one // We want the browser to maintain two separate internal caches: one
// for pjax'd partial page loads and one for normal page loads. // for pjax'd partial page loads and one for normal page loads.
@ -188,14 +187,14 @@
// confuse the two. // confuse the two.
if (!options.data) options.data = {} if (!options.data) options.data = {}
if ($.isArray(options.data)) { if ($.isArray(options.data)) {
options.data.push({ name: '_pjax', value: context.selector }) options.data.push({name: '_pjax', value: options.container})
} else { } else {
options.data._pjax = context.selector options.data._pjax = options.container
} }
//
function fire(type, args, props) { function fire(type, args, props) {
if (!props) props = {} if (!props) props = {}
props.relatedTarget = target props.relatedTarget = options.target
var event = $.Event(type, props) var event = $.Event(type, props)
context.trigger(event, args) context.trigger(event, args)
return !event.isDefaultPrevented() return !event.isDefaultPrevented()
@ -211,7 +210,7 @@
} }
xhr.setRequestHeader('X-PJAX', 'true') xhr.setRequestHeader('X-PJAX', 'true')
xhr.setRequestHeader('X-PJAX-Container', context.selector) xhr.setRequestHeader('X-PJAX-Container', options.container)
if (!fire('pjax:beforeSend', [xhr, settings])) if (!fire('pjax:beforeSend', [xhr, settings]))
return false return false
@ -250,11 +249,11 @@
} }
options.success = function(data, status, xhr) { options.success = function(data, status, xhr) {
var previousState = pjax.state; var previousState = pjax.state
// If $.pjax.defaults.version is a function, invoke it first. // If $.pjax.defaults.version is a function, invoke it first.
// Otherwise it can be a static string. // Otherwise it can be a static string.
var currentVersion = (typeof $.pjax.defaults.version === 'function') ? var currentVersion = typeof $.pjax.defaults.version === 'function' ?
$.pjax.defaults.version() : $.pjax.defaults.version() :
$.pjax.defaults.version $.pjax.defaults.version
@ -284,7 +283,7 @@
id: options.id || uniqueId(), id: options.id || uniqueId(),
url: container.url, url: container.url,
title: container.title, title: container.title,
container: context.selector, container: options.container,
fragment: options.fragment, fragment: options.fragment,
timeout: options.timeout timeout: options.timeout
} }
@ -294,13 +293,13 @@
} }
// Only blur the focus if the focused element is within the container. // Only blur the focus if the focused element is within the container.
var blurFocus = $.contains(options.container, document.activeElement) var blurFocus = $.contains(context, document.activeElement)
// Clear out any focused controls before inserting new page contents. // Clear out any focused controls before inserting new page contents.
if (blurFocus) { if (blurFocus) {
try { try {
document.activeElement.blur() document.activeElement.blur()
} catch (e) { } } catch (e) { /* ignore */ }
} }
if (container.title) document.title = container.title if (container.title) document.title = container.title
@ -318,7 +317,7 @@
// http://www.w3.org/html/wg/drafts/html/master/forms.html // http://www.w3.org/html/wg/drafts/html/master/forms.html
var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0] var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]
if (autofocusEl && document.activeElement !== autofocusEl) { if (autofocusEl && document.activeElement !== autofocusEl) {
autofocusEl.focus(); autofocusEl.focus()
} }
executeScriptTags(container.scripts) executeScriptTags(container.scripts)
@ -347,7 +346,7 @@
id: uniqueId(), id: uniqueId(),
url: window.location.href, url: window.location.href,
title: document.title, title: document.title,
container: context.selector, container: options.container,
fragment: options.fragment, fragment: options.fragment,
timeout: options.timeout timeout: options.timeout
} }
@ -363,7 +362,7 @@
if (xhr.readyState > 0) { if (xhr.readyState > 0) {
if (options.push && !options.replace) { if (options.push && !options.replace) {
// Cache current container element before replacing it // Cache current container element before replacing it
cachePush(pjax.state.id, cloneContents(context)) cachePush(pjax.state.id, [options.container, cloneContents(context)])
window.history.pushState(null, "", options.requestUrl) window.history.pushState(null, "", options.requestUrl)
} }
@ -375,9 +374,9 @@
return pjax.xhr return pjax.xhr
} }
// Public: Reload current page with pjax. // Public: Reload current page with pjax.
// //
// Returns whatever $.pjax returns. // Returns whatever $.pjax returns.
function pjaxReload(container, options) { function pjaxReload(container, options) {
var defaults = { var defaults = {
url: window.location.href, url: window.location.href,
@ -389,12 +388,12 @@
return pjax($.extend(defaults, optionsFor(container, options))) return pjax($.extend(defaults, optionsFor(container, options)))
} }
// Internal: Hard replace current state with url. // Internal: Hard replace current state with url.
// //
// Work for around WebKit // Work for around WebKit
// https://bugs.webkit.org/show_bug.cgi?id=93506 // https://bugs.webkit.org/show_bug.cgi?id=93506
// //
// Returns nothing. // Returns nothing.
function locationReplace(url) { function locationReplace(url) {
window.history.replaceState(null, "", pjax.state.url) window.history.replaceState(null, "", pjax.state.url)
window.location.replace(url) window.location.replace(url)
@ -405,23 +404,24 @@
var initialURL = window.location.href var initialURL = window.location.href
var initialState = window.history.state var initialState = window.history.state
// Initialize $.pjax.state if possible // Initialize $.pjax.state if possible
// Happens when reloading a page and coming forward from a different // Happens when reloading a page and coming forward from a different
// session history. // session history.
if (initialState && initialState.container) { if (initialState && initialState.container) {
pjax.state = initialState pjax.state = initialState
} }
// Non-webkit browsers don't fire an initial popstate event // Non-webkit browsers don't fire an initial popstate event
if ('state' in window.history) { if ('state' in window.history) {
initialPop = false initialPop = false
} }
// popstate handler takes care of the back and forward buttons // popstate handler takes care of the back and forward buttons
// //
// You probably shouldn't use pjax on pages with other pushState // You probably shouldn't use pjax on pages with other pushState
// stuff yet. // stuff yet.
function onPjaxPopstate(event) { function onPjaxPopstate(event) {
// Hitting back or forward should override any pending PJAX request. // Hitting back or forward should override any pending PJAX request.
if (!initialPop) { if (!initialPop) {
abortXHR(pjax.xhr) abortXHR(pjax.xhr)
@ -445,16 +445,16 @@
// Since state IDs always increase, we can deduce the navigation direction // Since state IDs always increase, we can deduce the navigation direction
direction = previousState.id < state.id ? 'forward' : 'back' direction = previousState.id < state.id ? 'forward' : 'back'
} }
//changed by jackchain cancel cache;
var cache = cacheMapping[state.id] || [] var cache = cacheMapping[state.id] || []
//console.log(cache); var containerSelector = cache[0] || state.container
var container = $(cache[0] || state.container), contents = cache[1] var container = $(containerSelector), contents = cache[1]
//console.log(container);
if (container.length) { if (container.length) {
if (previousState) { if (previousState) {
// Cache current container before replacement and inform the // Cache current container before replacement and inform the
// cache which direction the history shifted. // cache which direction the history shifted.
cachePop(direction, previousState.id, cloneContents(container)) cachePop(direction, previousState.id, [containerSelector, cloneContents(container)])
} }
var popstateEvent = $.Event('pjax:popstate', { var popstateEvent = $.Event('pjax:popstate', {
@ -466,7 +466,7 @@
var options = { var options = {
id: state.id, id: state.id,
url: state.url, url: state.url,
container: container, container: containerSelector,
push: false, push: false,
fragment: state.fragment, fragment: state.fragment,
timeout: state.timeout, timeout: state.timeout,
@ -492,7 +492,7 @@
// Force reflow/relayout before the browser tries to restore the // Force reflow/relayout before the browser tries to restore the
// scroll position. // scroll position.
container[0].offsetHeight container[0].offsetHeight // eslint-disable-line no-unused-expressions
} else { } else {
locationReplace(location.href) locationReplace(location.href)
} }
@ -500,10 +500,10 @@
initialPop = false initialPop = false
} }
// Fallback version of main pjax function for browsers that don't // Fallback version of main pjax function for browsers that don't
// support pushState. // support pushState.
// //
// Returns nothing since it retriggers a hard form submission. // Returns nothing since it retriggers a hard form submission.
function fallbackPjax(options) { function fallbackPjax(options) {
var url = $.isFunction(options.url) ? options.url() : options.url, var url = $.isFunction(options.url) ? options.url() : options.url,
method = options.type ? options.type.toUpperCase() : 'GET' method = options.type ? options.type.toUpperCase() : 'GET'
@ -542,8 +542,8 @@
form.submit() form.submit()
} }
// Internal: Abort an XmlHttpRequest if it hasn't been completed, // Internal: Abort an XmlHttpRequest if it hasn't been completed,
// also removing its event handlers. // also removing its event handlers.
function abortXHR(xhr) { function abortXHR(xhr) {
if ( xhr && xhr.readyState < 4) { if ( xhr && xhr.readyState < 4) {
xhr.onreadystatechange = $.noop xhr.onreadystatechange = $.noop
@ -551,12 +551,12 @@
} }
} }
// Internal: Generate unique id for state object. // Internal: Generate unique id for state object.
// //
// Use a timestamp instead of a counter since ids should still be // Use a timestamp instead of a counter since ids should still be
// unique across page loads. // unique across page loads.
// //
// Returns Number. // Returns Number.
function uniqueId() { function uniqueId() {
return (new Date).getTime() return (new Date).getTime()
} }
@ -566,127 +566,97 @@
// Unmark script tags as already being eval'd so they can get executed again // Unmark script tags as already being eval'd so they can get executed again
// when restored from cache. HAXX: Uses jQuery internal method. // when restored from cache. HAXX: Uses jQuery internal method.
cloned.find('script').each(function(){ cloned.find('script').each(function(){
if (!this.src) jQuery._data(this, 'globalEval', false) if (!this.src) $._data(this, 'globalEval', false)
}) })
return [container.selector, cloned.contents()] return cloned.contents()
} }
// Internal: Strip internal query params from parsed URL. // Internal: Strip internal query params from parsed URL.
// //
// Returns sanitized url.href String. // Returns sanitized url.href String.
function stripInternalParams(url) { function stripInternalParams(url) {
url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '') url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '').replace(/^&/, '')
return url.href.replace(/\?($|#)/, '$1') return url.href.replace(/\?($|#)/, '$1')
} }
// Internal: Parse URL components and returns a Locationish object. // Internal: Parse URL components and returns a Locationish object.
// //
// url - String URL // url - String URL
// //
// Returns HTMLAnchorElement that acts like Location. // Returns HTMLAnchorElement that acts like Location.
function parseURL(url) { function parseURL(url) {
var a = document.createElement('a') var a = document.createElement('a')
a.href = url a.href = url
return a return a
} }
// Internal: Return the `href` component of given URL object with the hash // Internal: Return the `href` component of given URL object with the hash
// portion removed. // portion removed.
// //
// location - Location or HTMLAnchorElement // location - Location or HTMLAnchorElement
// //
// Returns String // Returns String
function stripHash(location) { function stripHash(location) {
return location.href.replace(/#.*/, '') return location.href.replace(/#.*/, '')
} }
// Internal: Build options Object for arguments. // Internal: Build options Object for arguments.
// //
// For convenience the first parameter can be either the container or // For convenience the first parameter can be either the container or
// the options object. // the options object.
// //
// Examples // Examples
// //
// optionsFor('#container') // optionsFor('#container')
// // => {container: '#container'} // // => {container: '#container'}
// //
// optionsFor('#container', {push: true}) // optionsFor('#container', {push: true})
// // => {container: '#container', push: true} // // => {container: '#container', push: true}
// //
// optionsFor({container: '#container', push: true}) // optionsFor({container: '#container', push: true})
// // => {container: '#container', push: true} // // => {container: '#container', push: true}
// //
// Returns options Object. // Returns options Object.
function optionsFor(container, options) { function optionsFor(container, options) {
// Both container and options if (container && options) {
if ( container && options ) options = $.extend({}, options)
options.container = container options.container = container
// First argument is options Object
else if ( $.isPlainObject(container) )
options = container
// Only container
else
options = {container: container}
// Find and validate container
if (options.container)
options.container = findContainerFor(options.container)
return options return options
} } else if ($.isPlainObject(container)) {
// Internal: Find container element for a variety of inputs.
//
// Because we can't persist elements using the history API, we must be
// able to find a String selector that will consistently find the Element.
//
// container - A selector String, jQuery object, or DOM Element.
//
// Returns a jQuery object whose context is `document` and has a selector.
function findContainerFor(container) {
container = $(container)
if ( !container.length ) {
throw "no pjax container for " + container.selector
} else if ( container.selector !== '' && container.context === document ) {
return container return container
} else if ( container.attr('id') ) {
return $('#' + container.attr('id'))
} else { } else {
throw "cant get selector for pjax container!" return {container: container}
} }
} }
// Internal: Filter and find all elements matching the selector. // Internal: Filter and find all elements matching the selector.
// //
// Where $.fn.find only matches descendants, findAll will test all the // Where $.fn.find only matches descendants, findAll will test all the
// top level elements in the jQuery object as well. // top level elements in the jQuery object as well.
// //
// elems - jQuery object of Elements // elems - jQuery object of Elements
// selector - String selector to match // selector - String selector to match
// //
// Returns a jQuery object. // Returns a jQuery object.
function findAll(elems, selector) { function findAll(elems, selector) {
return elems.filter(selector).add(elems.find(selector)); return elems.filter(selector).add(elems.find(selector))
} }
function parseHTML(html) { function parseHTML(html) {
return $.parseHTML(html, document, true) return $.parseHTML(html, document, true)
} }
// Internal: Extracts container and metadata from response. // Internal: Extracts container and metadata from response.
// //
// 1. Extracts X-PJAX-URL header if set // 1. Extracts X-PJAX-URL header if set
// 2. Extracts inline <title> tags // 2. Extracts inline <title> tags
// 3. Builds response Element and extracts fragment if set // 3. Builds response Element and extracts fragment if set
// //
// data - String response data // data - String response data
// xhr - XHR response // xhr - XHR response
// options - pjax options Object // options - pjax options Object
// //
// Returns an Object with url, title, and contents keys. // Returns an Object with url, title, and contents keys.
function extractContainer(data, xhr, options) { function extractContainer(data, xhr, options) {
var obj = {}, fullDocument = /<html/i.test(data) var obj = {}, fullDocument = /<html/i.test(data)
@ -695,12 +665,14 @@
var serverUrl = xhr.getResponseHeader('X-PJAX-URL') var serverUrl = xhr.getResponseHeader('X-PJAX-URL')
obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl
var $head, $body
// Attempt to parse response html into elements // Attempt to parse response html into elements
if (fullDocument) { if (fullDocument) {
var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0])) $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) var head = data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)
$head = head != null ? $(parseHTML(head[0])) : $body
} else { } else {
var $head = $body = $(parseHTML(data)) $head = $body = $(parseHTML(data))
} }
// If response data is empty, return fast // If response data is empty, return fast
@ -712,12 +684,11 @@
obj.title = findAll($head, 'title').last().text() obj.title = findAll($head, 'title').last().text()
if (options.fragment) { if (options.fragment) {
var $fragment = $body
// If they specified a fragment, look for it in the response // If they specified a fragment, look for it in the response
// and pull it out. // and pull it out.
if (options.fragment === 'body') { if (options.fragment !== 'body') {
var $fragment = $body $fragment = findAll($fragment, options.fragment).first()
} else {
var $fragment = findAll($body, options.fragment).first()
} }
if ($fragment.length) { if ($fragment.length) {
@ -742,12 +713,8 @@
obj.contents.find('title').remove() obj.contents.find('title').remove()
// Gather all script[src] elements // Gather all script[src] elements
//console.log(obj.contents); obj.scripts = findAll(obj.contents, 'script[src]').remove()
//obj.scripts = findAll(obj.contents, 'script[src]').remove() obj.contents = obj.contents.not(obj.scripts)
//console.log(obj.contents);
//console.log(obj.scripts);
//obj.contents = obj.contents.not(obj.scripts)
//console.log(obj.contents);
} }
// Trim any whitespace off the title // Trim any whitespace off the title
@ -756,20 +723,21 @@
return obj return obj
} }
// Load an execute scripts using standard script request. // Load an execute scripts using standard script request.
// //
// Avoids jQuery's traditional $.getScript which does a XHR request and // Avoids jQuery's traditional $.getScript which does a XHR request and
// globalEval. // globalEval.
// //
// scripts - jQuery object of script Elements // scripts - jQuery object of script Elements
// //
// Returns nothing. // Returns nothing.
function executeScriptTags(scripts) { function executeScriptTags(scripts) {
if (!scripts) return if (!scripts) return
var existingScripts = $('script[src]') var existingScripts = $('script[src]')
scripts.each(function() { scripts.each(function() {
var src = this.src; var src = this.src
var matchedScripts = existingScripts.filter(function() { var matchedScripts = existingScripts.filter(function() {
return this.src === src return this.src === src
}) })
@ -783,19 +751,19 @@
}) })
} }
// Internal: History DOM caching class. // Internal: History DOM caching class.
var cacheMapping = {} var cacheMapping = {}
var cacheForwardStack = [] var cacheForwardStack = []
var cacheBackStack = [] var cacheBackStack = []
// Push previous state id and container contents into the history // Push previous state id and container contents into the history
// cache. Should be called in conjunction with `pushState` to save the // cache. Should be called in conjunction with `pushState` to save the
// previous container contents. // previous container contents.
// //
// id - State ID Number // id - State ID Number
// value - DOM Element to cache // value - DOM Element to cache
// //
// Returns nothing. // Returns nothing.
function cachePush(id, value) { function cachePush(id, value) {
cacheMapping[id] = value cacheMapping[id] = value
cacheBackStack.push(id) cacheBackStack.push(id)
@ -807,15 +775,15 @@
trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength) trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength)
} }
// Shifts cache from directional history cache. Should be // Shifts cache from directional history cache. Should be
// called on `popstate` with the previous state id and container // called on `popstate` with the previous state id and container
// contents. // contents.
// //
// direction - "forward" or "back" String // direction - "forward" or "back" String
// id - State ID Number // id - State ID Number
// value - DOM Element to cache // value - DOM Element to cache
// //
// Returns nothing. // Returns nothing.
function cachePop(direction, id, value) { function cachePop(direction, id, value) {
var pushStack, popStack var pushStack, popStack
cacheMapping[id] = value cacheMapping[id] = value
@ -829,28 +797,28 @@
} }
pushStack.push(id) pushStack.push(id)
if (id = popStack.pop()) id = popStack.pop()
delete cacheMapping[id] if (id) delete cacheMapping[id]
// Trim whichever stack we just pushed to to max cache length. // Trim whichever stack we just pushed to to max cache length.
trimCacheStack(pushStack, pjax.defaults.maxCacheLength) trimCacheStack(pushStack, pjax.defaults.maxCacheLength)
} }
// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no // Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no
// longer than the specified length, deleting cached DOM elements as necessary. // longer than the specified length, deleting cached DOM elements as necessary.
// //
// stack - Array of state IDs // stack - Array of state IDs
// length - Maximum length to trim to // length - Maximum length to trim to
// //
// Returns nothing. // Returns nothing.
function trimCacheStack(stack, length) { function trimCacheStack(stack, length) {
while (stack.length > length) while (stack.length > length)
delete cacheMapping[stack.shift()] delete cacheMapping[stack.shift()]
} }
// Public: Find version identifier for the initial page load. // Public: Find version identifier for the initial page load.
// //
// Returns String version or undefined. // Returns String version or undefined.
function findVersion() { function findVersion() {
return $('meta').filter(function() { return $('meta').filter(function() {
var name = $(this).attr('http-equiv') var name = $(this).attr('http-equiv')
@ -858,15 +826,15 @@
}).attr('content') }).attr('content')
} }
// Install pjax functions on $.pjax to enable pushState behavior. // Install pjax functions on $.pjax to enable pushState behavior.
// //
// Does nothing if already enabled. // Does nothing if already enabled.
// //
// Examples // Examples
// //
// $.pjax.enable() // $.pjax.enable()
// //
// Returns nothing. // Returns nothing.
function enable() { function enable() {
$.fn.pjax = fnPjax $.fn.pjax = fnPjax
$.pjax = pjax $.pjax = pjax
@ -882,24 +850,23 @@
type: 'GET', type: 'GET',
dataType: 'html', dataType: 'html',
scrollTo: 0, scrollTo: 0,
maxCacheLength: 0, maxCacheLength: 20,
//cache:false,//changed by jackchain fire IE cache bug
version: findVersion version: findVersion
} }
$(window).on('popstate.pjax', onPjaxPopstate) $(window).on('popstate.pjax', onPjaxPopstate)
} }
// Disable pushState behavior. // Disable pushState behavior.
// //
// This is the case when a browser doesn't support pushState. It is // This is the case when a browser doesn't support pushState. It is
// sometimes useful to disable pushState for debugging on a modern // sometimes useful to disable pushState for debugging on a modern
// browser. // browser.
// //
// Examples // Examples
// //
// $.pjax.disable() // $.pjax.disable()
// //
// Returns nothing. // Returns nothing.
function disable() { function disable() {
$.fn.pjax = function() { return this } $.fn.pjax = function() { return this }
$.pjax = fallbackPjax $.pjax = fallbackPjax
@ -913,17 +880,24 @@
} }
// Add the state property to jQuery's event object so we can use it in // Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate') // $(window).bind('popstate')
if ( $.inArray('state', $.event.props) < 0 ) if ($.event.props && $.inArray('state', $.event.props) < 0) {
$.event.props.push('state') $.event.props.push('state')
} else if (!('state' in $.Event.prototype)) {
$.event.addProp('state')
}
// Is pjax supported by this browser? // Is pjax supported by this browser?
$.support.pjax = $.support.pjax =
window.history && window.history.pushState && window.history.replaceState && window.history && window.history.pushState && window.history.replaceState &&
// pushState isn't reliable on iOS until 5. // pushState isn't reliable on iOS until 5.
!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/) !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
$.support.pjax ? enable() : disable() if ($.support.pjax) {
enable()
} else {
disable()
}
})(jQuery); })(jQuery)