Commit 1367a7b5 by Qiang Xue

update pjax plugin to 1.8.0

parent c4efa31f
...@@ -27,15 +27,15 @@ ...@@ -27,15 +27,15 @@
// the options object. // the options object.
// //
// Returns the jQuery object // Returns the jQuery object
function fnPjax(selector, container, options) { function fnPjax(selector, container, options) {
var context = this var context = this
return this.on('click.pjax', selector, function(event) { return this.on('click.pjax', selector, function(event) {
var opts = $.extend({}, optionsFor(container, options)) var opts = $.extend({}, optionsFor(container, options))
if (!opts.container) if (!opts.container)
opts.container = $(this).attr('data-pjax') || context opts.container = $(this).attr('data-pjax') || context
handleClick(event, opts) handleClick(event, opts)
}) })
} }
// Public: pjax on click handler // Public: pjax on click handler
// //
...@@ -56,47 +56,48 @@ function fnPjax(selector, container, options) { ...@@ -56,47 +56,48 @@ function fnPjax(selector, container, options) {
// }) // })
// //
// Returns nothing. // 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
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"
// Middle click, cmd click, and ctrl click should open // Middle click, cmd click, and ctrl click should open
// links in a new tab as normal. // links in a new tab as normal.
if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey )
return return
// Ignore cross origin links // Ignore cross origin links
if ( location.protocol !== link.protocol || location.hostname !== link.hostname ) if ( location.protocol !== link.protocol || location.hostname !== link.hostname )
return return
// Ignore anchors on the same page // Ignore anchors on the same page
if (link.hash && link.href.replace(link.hash, '') === if (link.hash && link.href.replace(link.hash, '') ===
location.href.replace(location.hash, '')) location.href.replace(location.hash, ''))
return return
// Ignore empty anchor "foo.html#" // Ignore empty anchor "foo.html#"
if (link.href === location.href + '#') if (link.href === location.href + '#')
return return
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])
} }
}
// Public: pjax on form submit handler // Public: pjax on form submit handler
// //
...@@ -113,26 +114,26 @@ function handleClick(event, container, options) { ...@@ -113,26 +114,26 @@ function handleClick(event, container, options) {
// }) // })
// //
// Returns nothing. // Returns nothing.
function handleSubmit(event, container, options) { function handleSubmit(event, container, options) {
options = optionsFor(container, options) options = optionsFor(container, options)
var form = event.currentTarget var form = event.currentTarget
if (form.tagName.toUpperCase() !== 'FORM') if (form.tagName.toUpperCase() !== 'FORM')
throw "$.pjax.submit requires a form element" throw "$.pjax.submit requires a form element"
var defaults = { var defaults = {
type: form.method.toUpperCase(), type: form.method.toUpperCase(),
url: form.action, url: form.action,
data: $(form).serializeArray(), data: $(form).serializeArray(),
container: $(form).attr('data-pjax'), container: $(form).attr('data-pjax'),
target: form target: form
} }
pjax($.extend({}, defaults, options)) pjax($.extend({}, defaults, options))
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.
...@@ -153,212 +154,212 @@ function handleSubmit(event, container, options) { ...@@ -153,212 +154,212 @@ function handleSubmit(event, container, options) {
// 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)
if ($.isFunction(options.url)) { if ($.isFunction(options.url)) {
options.url = options.url() options.url = options.url()
} }
var target = options.target var target = options.target
var hash = parseURL(options.url).hash var hash = parseURL(options.url).hash
var context = options.context = findContainerFor(options.container) var context = options.context = findContainerFor(options.container)
// 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.
// Without adding this secret parameter, some browsers will often // Without adding this secret parameter, some browsers will often
// confuse the two. // confuse the two.
if (!options.data) options.data = {} if (!options.data) options.data = {}
options.data._pjax = context.selector options.data._pjax = context.selector
function fire(type, args) { function fire(type, args) {
var event = $.Event(type, { relatedTarget: target }) var event = $.Event(type, { relatedTarget: target })
context.trigger(event, args) context.trigger(event, args)
return !event.isDefaultPrevented() return !event.isDefaultPrevented()
} }
var timeoutTimer var timeoutTimer
options.beforeSend = function(xhr, settings) { options.beforeSend = function(xhr, settings) {
// No timeout for non-GET requests // No timeout for non-GET requests
// Its not safe to request the resource again with a fallback method. // Its not safe to request the resource again with a fallback method.
if (settings.type !== 'GET') { if (settings.type !== 'GET') {
settings.timeout = 0 settings.timeout = 0
} }
xhr.setRequestHeader('X-PJAX', 'true') xhr.setRequestHeader('X-PJAX', 'true')
xhr.setRequestHeader('X-PJAX-Container', context.selector) xhr.setRequestHeader('X-PJAX-Container', context.selector)
if (!fire('pjax:beforeSend', [xhr, settings])) if (!fire('pjax:beforeSend', [xhr, settings]))
return false return false
if (settings.timeout > 0) { if (settings.timeout > 0) {
timeoutTimer = setTimeout(function() { timeoutTimer = setTimeout(function() {
if (fire('pjax:timeout', [xhr, options])) if (fire('pjax:timeout', [xhr, options]))
xhr.abort('timeout') xhr.abort('timeout')
}, settings.timeout) }, settings.timeout)
// Clear timeout setting so jquerys internal timeout isn't invoked // Clear timeout setting so jquerys internal timeout isn't invoked
settings.timeout = 0 settings.timeout = 0
} }
options.requestUrl = parseURL(settings.url).href options.requestUrl = parseURL(settings.url).href
} }
options.complete = function(xhr, textStatus) { options.complete = function(xhr, textStatus) {
if (timeoutTimer) if (timeoutTimer)
clearTimeout(timeoutTimer) clearTimeout(timeoutTimer)
fire('pjax:complete', [xhr, textStatus, options]) fire('pjax:complete', [xhr, textStatus, options])
fire('pjax:end', [xhr, options]) fire('pjax:end', [xhr, options])
} }
options.error = function(xhr, textStatus, errorThrown) { options.error = function(xhr, textStatus, errorThrown) {
var container = extractContainer("", xhr, options) var container = extractContainer("", xhr, options)
var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]) var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
if (options.type == 'GET' && textStatus !== 'abort' && allowed) { if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
locationReplace(container.url) locationReplace(container.url)
} }
} }
options.success = function(data, status, xhr) { options.success = function(data, status, xhr) {
// 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
var latestVersion = xhr.getResponseHeader('X-PJAX-Version') var latestVersion = xhr.getResponseHeader('X-PJAX-Version')
var container = extractContainer(data, xhr, options) var container = extractContainer(data, xhr, options)
// If there is a layout version mismatch, hard load the new url // If there is a layout version mismatch, hard load the new url
if (currentVersion && latestVersion && currentVersion !== latestVersion) { if (currentVersion && latestVersion && currentVersion !== latestVersion) {
locationReplace(container.url) locationReplace(container.url)
return return
} }
// If the new response is missing a body, hard load the page // If the new response is missing a body, hard load the page
if (!container.contents) { if (!container.contents) {
locationReplace(container.url) locationReplace(container.url)
return return
} }
pjax.state = { pjax.state = {
id: options.id || uniqueId(), id: options.id || uniqueId(),
url: container.url, url: container.url,
title: container.title, title: container.title,
container: context.selector, container: context.selector,
fragment: options.fragment, fragment: options.fragment,
timeout: options.timeout timeout: options.timeout
} }
if (options.push || options.replace) { if (options.push || options.replace) {
window.history.replaceState(pjax.state, container.title, container.url) window.history.replaceState(pjax.state, container.title, container.url)
} }
// Clear out any focused controls before inserting new page contents. // Clear out any focused controls before inserting new page contents.
document.activeElement.blur() document.activeElement.blur()
if (container.title) document.title = container.title if (container.title) document.title = container.title
context.html(container.contents) context.html(container.contents)
// FF bug: Won't autofocus fields that are inserted via JS. // FF bug: Won't autofocus fields that are inserted via JS.
// This behavior is incorrect. So if theres no current focus, autofocus // This behavior is incorrect. So if theres no current focus, autofocus
// the last field. // the last field.
// //
// 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)
// Scroll to top by default // Scroll to top by default
if (typeof options.scrollTo === 'number') if (typeof options.scrollTo === 'number')
$(window).scrollTop(options.scrollTo) $(window).scrollTop(options.scrollTo)
// If the URL has a hash in it, make sure the browser // If the URL has a hash in it, make sure the browser
// knows to navigate to the hash. // knows to navigate to the hash.
if ( hash !== '' ) { if ( hash !== '' ) {
// Avoid using simple hash set here. Will add another history // Avoid using simple hash set here. Will add another history
// entry. Replace the url with replaceState and scroll to target // entry. Replace the url with replaceState and scroll to target
// by hand. // by hand.
// //
// window.location.hash = hash // window.location.hash = hash
var url = parseURL(container.url) var url = parseURL(container.url)
url.hash = hash url.hash = hash
pjax.state.url = url.href pjax.state.url = url.href
window.history.replaceState(pjax.state, container.title, url.href) window.history.replaceState(pjax.state, container.title, url.href)
var target = $(url.hash) var target = $(url.hash)
if (target.length) $(window).scrollTop(target.offset().top) if (target.length) $(window).scrollTop(target.offset().top)
} }
fire('pjax:success', [data, status, xhr, options]) fire('pjax:success', [data, status, xhr, options])
} }
// Initialize pjax.state for the initial page load. Assume we're // Initialize pjax.state for the initial page load. Assume we're
// using the container and options of the link we're loading for the // using the container and options of the link we're loading for the
// back button to the initial page. This ensures good back button // back button to the initial page. This ensures good back button
// behavior. // behavior.
if (!pjax.state) { if (!pjax.state) {
pjax.state = { pjax.state = {
id: uniqueId(), id: uniqueId(),
url: window.location.href, url: window.location.href,
title: document.title, title: document.title,
container: context.selector, container: context.selector,
fragment: options.fragment, fragment: options.fragment,
timeout: options.timeout timeout: options.timeout
} }
window.history.replaceState(pjax.state, document.title) window.history.replaceState(pjax.state, document.title)
} }
// Cancel the current request if we're already pjaxing // Cancel the current request if we're already pjaxing
var xhr = pjax.xhr var xhr = pjax.xhr
if ( xhr && xhr.readyState < 4) { if ( xhr && xhr.readyState < 4) {
xhr.onreadystatechange = $.noop xhr.onreadystatechange = $.noop
xhr.abort() xhr.abort()
} }
pjax.options = options pjax.options = options
var xhr = pjax.xhr = $.ajax(options) var xhr = pjax.xhr = $.ajax(options)
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, context.clone().contents()) cachePush(pjax.state.id, context.clone().contents())
window.history.pushState(null, "", stripPjaxParam(options.requestUrl)) window.history.pushState(null, "", stripPjaxParam(options.requestUrl))
} }
fire('pjax:start', [xhr, options]) fire('pjax:start', [xhr, options])
fire('pjax:send', [xhr, options]) fire('pjax:send', [xhr, options])
} }
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,
push: false, push: false,
replace: true, replace: true,
scrollTo: false scrollTo: false
} }
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.
// //
...@@ -366,133 +367,133 @@ function pjaxReload(container, options) { ...@@ -366,133 +367,133 @@ function pjaxReload(container, options) {
// 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, "", "#") window.history.replaceState(null, "", "#")
window.location.replace(url) window.location.replace(url)
} }
var initialPop = true var initialPop = true
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) {
var state = event.state var state = event.state
if (state && state.container) { if (state && state.container) {
// When coming forward from a separate history session, will get an // When coming forward from a separate history session, will get an
// initial pop with a state we are already at. Skip reloading the current // initial pop with a state we are already at. Skip reloading the current
// page. // page.
if (initialPop && initialURL == state.url) return if (initialPop && initialURL == state.url) return
// If popping back to the same state, just skip. // If popping back to the same state, just skip.
// Could be clicking back from hashchange rather than a pushState. // Could be clicking back from hashchange rather than a pushState.
if (pjax.state.id === state.id) return if (pjax.state.id === state.id) return
var container = $(state.container) var container = $(state.container)
if (container.length) { if (container.length) {
var direction, contents = cacheMapping[state.id] var direction, contents = cacheMapping[state.id]
if (pjax.state) { if (pjax.state) {
// Since state ids always increase, we can deduce the history // Since state ids always increase, we can deduce the history
// direction from the previous state. // direction from the previous state.
direction = pjax.state.id < state.id ? 'forward' : 'back' direction = pjax.state.id < state.id ? 'forward' : 'back'
// 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, pjax.state.id, container.clone().contents()) cachePop(direction, pjax.state.id, container.clone().contents())
} }
var popstateEvent = $.Event('pjax:popstate', { var popstateEvent = $.Event('pjax:popstate', {
state: state, state: state,
direction: direction direction: direction
}) })
container.trigger(popstateEvent) container.trigger(popstateEvent)
var options = { var options = {
id: state.id, id: state.id,
url: state.url, url: state.url,
container: container, container: container,
push: false, push: false,
fragment: state.fragment, fragment: state.fragment,
timeout: state.timeout, timeout: state.timeout,
scrollTo: false scrollTo: false
} }
if (contents) { if (contents) {
container.trigger('pjax:start', [null, options]) container.trigger('pjax:start', [null, options])
if (state.title) document.title = state.title if (state.title) document.title = state.title
container.html(contents) container.html(contents)
pjax.state = state pjax.state = state
container.trigger('pjax:end', [null, options]) container.trigger('pjax:end', [null, options])
} else { } else {
pjax(options) pjax(options)
} }
// 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
} else { } else {
locationReplace(location.href) locationReplace(location.href)
} }
} }
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'
var form = $('<form>', { var form = $('<form>', {
method: method === 'GET' ? 'GET' : 'POST', method: method === 'GET' ? 'GET' : 'POST',
action: url, action: url,
style: 'display:none' style: 'display:none'
}) })
if (method !== 'GET' && method !== 'POST') { if (method !== 'GET' && method !== 'POST') {
form.append($('<input>', { form.append($('<input>', {
type: 'hidden', type: 'hidden',
name: '_method', name: '_method',
value: method.toLowerCase() value: method.toLowerCase()
})) }))
} }
var data = options.data var data = options.data
if (typeof data === 'string') { if (typeof data === 'string') {
$.each(data.split('&'), function(index, value) { $.each(data.split('&'), function(index, value) {
var pair = value.split('=') var pair = value.split('=')
form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]})) form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
}) })
} else if (typeof data === 'object') { } else if (typeof data === 'object') {
for (key in data) for (key in data)
form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
} }
$(document.body).append(form) $(document.body).append(form)
form.submit() form.submit()
} }
// Internal: Generate unique id for state object. // Internal: Generate unique id for state object.
// //
...@@ -500,32 +501,32 @@ function fallbackPjax(options) { ...@@ -500,32 +501,32 @@ function fallbackPjax(options) {
// unique across page loads. // unique across page loads.
// //
// Returns Number. // Returns Number.
function uniqueId() { function uniqueId() {
return (new Date).getTime() return (new Date).getTime()
} }
// Internal: Strips _pjax param from url // Internal: Strips _pjax param from url
// //
// url - String // url - String
// //
// Returns String. // Returns String.
function stripPjaxParam(url) { function stripPjaxParam(url) {
return url return url
.replace(/\?_pjax=[^&]+&?/, '?') .replace(/\?_pjax=[^&]+&?/, '?')
.replace(/_pjax=[^&]+&?/, '') .replace(/_pjax=[^&]+&?/, '')
.replace(/[\?&]$/, '') .replace(/[\?&]$/, '')
} }
// 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: Build options Object for arguments. // Internal: Build options Object for arguments.
// //
...@@ -544,25 +545,25 @@ function parseURL(url) { ...@@ -544,25 +545,25 @@ function parseURL(url) {
// // => {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 // Both container and options
if ( container && options ) if ( container && options )
options.container = container options.container = container
// First argument is options Object // First argument is options Object
else if ( $.isPlainObject(container) ) else if ( $.isPlainObject(container) )
options = container options = container
// Only container // Only container
else else
options = {container: container} options = {container: container}
// Find and validate container // Find and validate container
if (options.container) if (options.container)
options.container = findContainerFor(options.container) options.container = findContainerFor(options.container)
return options return options
} }
// Internal: Find container element for a variety of inputs. // Internal: Find container element for a variety of inputs.
// //
...@@ -572,19 +573,19 @@ function optionsFor(container, options) { ...@@ -572,19 +573,19 @@ function optionsFor(container, options) {
// container - A selector String, jQuery object, or DOM Element. // container - A selector String, jQuery object, or DOM Element.
// //
// Returns a jQuery object whose context is `document` and has a selector. // Returns a jQuery object whose context is `document` and has a selector.
function findContainerFor(container) { function findContainerFor(container) {
container = $(container) container = $(container)
if ( !container.length ) { if ( !container.length ) {
throw "no pjax container for " + container.selector throw "no pjax container for " + container.selector
} else if ( container.selector !== '' && container.context === document ) { } else if ( container.selector !== '' && container.context === document ) {
return container return container
} else if ( container.attr('id') ) { } else if ( container.attr('id') ) {
return $('#' + container.attr('id')) return $('#' + container.attr('id'))
} else { } else {
throw "cant get selector for pjax container!" throw "cant get selector for pjax container!"
} }
} }
// Internal: Filter and find all elements matching the selector. // Internal: Filter and find all elements matching the selector.
// //
...@@ -595,13 +596,13 @@ function findContainerFor(container) { ...@@ -595,13 +596,13 @@ function findContainerFor(container) {
// 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.
// //
...@@ -614,69 +615,69 @@ function parseHTML(html) { ...@@ -614,69 +615,69 @@ function parseHTML(html) {
// 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 = {} var obj = {}
// Prefer X-PJAX-URL header if it was set, otherwise fallback to // Prefer X-PJAX-URL header if it was set, otherwise fallback to
// using the original requested url. // using the original requested url.
obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl) obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.requestUrl)
// Attempt to parse response html into elements // Attempt to parse response html into elements
if (/<html/i.test(data)) { if (/<html/i.test(data)) {
var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0])) var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]))
var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0]))
} else { } else {
var $head = $body = $(parseHTML(data)) var $head = $body = $(parseHTML(data))
} }
// If response data is empty, return fast // If response data is empty, return fast
if ($body.length === 0) if ($body.length === 0)
return obj return obj
// If there's a <title> tag in the header, use it as // If there's a <title> tag in the header, use it as
// the page's title. // the page's title.
obj.title = findAll($head, 'title').last().text() obj.title = findAll($head, 'title').last().text()
if (options.fragment) { if (options.fragment) {
// 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 var $fragment = $body
} else { } else {
var $fragment = findAll($body, options.fragment).first() var $fragment = findAll($body, options.fragment).first()
} }
if ($fragment.length) { if ($fragment.length) {
obj.contents = $fragment.contents() obj.contents = $fragment.contents()
// If there's no title, look for data-title and title attributes // If there's no title, look for data-title and title attributes
// on the fragment // on the fragment
if (!obj.title) if (!obj.title)
obj.title = $fragment.attr('title') || $fragment.data('title') obj.title = $fragment.attr('title') || $fragment.data('title')
} }
} else if (!/<html/i.test(data)) { } else if (!/<html/i.test(data)) {
obj.contents = $body obj.contents = $body
} }
// Clean up any <title> tags // Clean up any <title> tags
if (obj.contents) { if (obj.contents) {
// Remove any parent title elements // Remove any parent title elements
obj.contents = obj.contents.not(function() { return $(this).is('title') }) obj.contents = obj.contents.not(function() { return $(this).is('title') })
// Then scrub any titles from their descendants // Then scrub any titles from their descendants
obj.contents.find('title').remove() obj.contents.find('title').remove()
// Gather all script[src] elements // Gather all script[src] elements
obj.scripts = findAll(obj.contents, 'script[src]').remove() obj.scripts = findAll(obj.contents, 'script[src]').remove()
obj.contents = obj.contents.not(obj.scripts) obj.contents = obj.contents.not(obj.scripts)
} }
// Trim any whitespace off the title // Trim any whitespace off the title
if (obj.title) obj.title = $.trim(obj.title) if (obj.title) obj.title = $.trim(obj.title)
return obj return obj
} }
// Load an execute scripts using standard script request. // Load an execute scripts using standard script request.
// //
...@@ -686,29 +687,29 @@ function extractContainer(data, xhr, options) { ...@@ -686,29 +687,29 @@ function extractContainer(data, xhr, options) {
// 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
}) })
if (matchedScripts.length) return if (matchedScripts.length) return
var script = document.createElement('script') var script = document.createElement('script')
script.type = $(this).attr('type') script.type = $(this).attr('type')
script.src = $(this).attr('src') script.src = $(this).attr('src')
document.head.appendChild(script) document.head.appendChild(script)
}) })
} }
// 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
...@@ -718,19 +719,19 @@ var cacheBackStack = [] ...@@ -718,19 +719,19 @@ var cacheBackStack = []
// 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)
// Remove all entires in forward history stack after pushing // Remove all entires in forward history stack after pushing
// a new page. // a new page.
while (cacheForwardStack.length) while (cacheForwardStack.length)
delete cacheMapping[cacheForwardStack.shift()] delete cacheMapping[cacheForwardStack.shift()]
// Trim back history stack to max cache length. // Trim back history stack to max cache length.
while (cacheBackStack.length > pjax.defaults.maxCacheLength) while (cacheBackStack.length > pjax.defaults.maxCacheLength)
delete cacheMapping[cacheBackStack.shift()] delete cacheMapping[cacheBackStack.shift()]
} }
// 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
...@@ -741,32 +742,32 @@ function cachePush(id, value) { ...@@ -741,32 +742,32 @@ function cachePush(id, value) {
// 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
if (direction === 'forward') { if (direction === 'forward') {
pushStack = cacheBackStack pushStack = cacheBackStack
popStack = cacheForwardStack popStack = cacheForwardStack
} else { } else {
pushStack = cacheForwardStack pushStack = cacheForwardStack
popStack = cacheBackStack popStack = cacheBackStack
} }
pushStack.push(id) pushStack.push(id)
if (id = popStack.pop()) if (id = popStack.pop())
delete cacheMapping[id] delete cacheMapping[id]
} }
// 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')
return name && name.toUpperCase() === 'X-PJAX-VERSION' return name && name.toUpperCase() === 'X-PJAX-VERSION'
}).attr('content') }).attr('content')
} }
// Install pjax functions on $.pjax to enable pushState behavior. // Install pjax functions on $.pjax to enable pushState behavior.
// //
...@@ -777,26 +778,26 @@ function findVersion() { ...@@ -777,26 +778,26 @@ function findVersion() {
// $.pjax.enable() // $.pjax.enable()
// //
// Returns nothing. // Returns nothing.
function enable() { function enable() {
$.fn.pjax = fnPjax $.fn.pjax = fnPjax
$.pjax = pjax $.pjax = pjax
$.pjax.enable = $.noop $.pjax.enable = $.noop
$.pjax.disable = disable $.pjax.disable = disable
$.pjax.click = handleClick $.pjax.click = handleClick
$.pjax.submit = handleSubmit $.pjax.submit = handleSubmit
$.pjax.reload = pjaxReload $.pjax.reload = pjaxReload
$.pjax.defaults = { $.pjax.defaults = {
timeout: 650, timeout: 650,
push: true, push: true,
replace: false, replace: false,
type: 'GET', type: 'GET',
dataType: 'html', dataType: 'html',
scrollTo: 0, scrollTo: 0,
maxCacheLength: 20, maxCacheLength: 20,
version: findVersion version: findVersion
} }
$(window).on('popstate.pjax', onPjaxPopstate) $(window).on('popstate.pjax', onPjaxPopstate)
} }
// Disable pushState behavior. // Disable pushState behavior.
// //
...@@ -809,30 +810,30 @@ function enable() { ...@@ -809,30 +810,30 @@ function enable() {
// $.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
$.pjax.enable = enable $.pjax.enable = enable
$.pjax.disable = $.noop $.pjax.disable = $.noop
$.pjax.click = $.noop $.pjax.click = $.noop
$.pjax.submit = $.noop $.pjax.submit = $.noop
$.pjax.reload = function() { window.location.reload() } $.pjax.reload = function() { window.location.reload() }
$(window).off('popstate.pjax', onPjaxPopstate) $(window).off('popstate.pjax', onPjaxPopstate)
} }
// 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 ( $.inArray('state', $.event.props) < 0 )
$.event.props.push('state') $.event.props.push('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]|WebApps\/.+CFNetwork)/) !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
$.support.pjax ? enable() : disable() $.support.pjax ? enable() : disable()
})(jQuery); })(jQuery);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment