Ubuntu
/*!
* Copyright 2012, Chris Wanstrath
* Released under the MIT License
* https://github.com/defunkt/jquery-pjax
*/
(function($) {
// This is an auxiliary woodmart function to replace the outdated jquery method.
function wdTrim(data) {
if ( null == data ) {
return '';
} else if ( 'string' == typeof data ) {
return data.trim();
} else {
return (data + '').replace( '/^[\\s\uFEFF\xA0]+|[\\s\uFEFF\xA0]+$/g', '' );
}
}
// 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
// itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Exported as $.fn.pjax
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
//
// container - String selector for the element where to place the response body.
// 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.
//
// Returns the jQuery object
function fnPjax(selector, container, options) {
options = optionsFor(container, options);
return this.on('click.pjax', selector, function(event) {
var opts = options;
if (!opts.container) {
opts = $.extend({}, options);
opts.container = $(this).attr('data-pjax');
}
handleClick(event, opts);
});
}
// Public: pjax on click handler
//
// Exported as $.pjax.click.
//
// event - "click" jQuery.Event
// options - pjax options
//
// Examples
//
// $(document).on('click', 'a', $.pjax.click)
// // is the same as
// $(document).pjax('a')
//
// Returns nothing.
function handleClick(event, container, options) {
options = optionsFor(container, options);
var link = event.currentTarget;
var $link = $(link);
if (link.tagName.toUpperCase() !== 'A') {
throw '$.fn.pjax or $.pjax.click requires an anchor element';
}
// Middle click, cmd click, and ctrl click should open
// links in a new tab as normal.
if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
// Ignore cross origin links
if (location.protocol !== link.protocol || location.hostname !== link.hostname) {
return;
}
// Ignore case when a hash is being tacked on the current URL
if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location)) {
return;
}
// Ignore event with default prevented
if (event.isDefaultPrevented()) {
return;
}
var defaults = {
url : link.href,
container: $link.attr('data-pjax'),
target : link
};
var opts = $.extend({}, defaults, options);
var clickEvent = $.Event('pjax:click');
$link.trigger(clickEvent, [opts]);
if (!clickEvent.isDefaultPrevented()) {
pjax(opts);
event.preventDefault();
$link.trigger('pjax:clicked', [opts]);
}
}
// Public: pjax on form submit handler
//
// Exported as $.pjax.submit
//
// event - "click" jQuery.Event
// options - pjax options
//
// Examples
//
// $(document).on('submit', 'form', function(event) {
// $.pjax.submit(event, '[data-pjax-container]')
// })
//
// Returns nothing.
function handleSubmit(event, container, options) {
options = optionsFor(container, options);
var form = event.currentTarget;
var $form = $(form);
if (form.tagName.toUpperCase() !== 'FORM') {
throw '$.pjax.submit requires a form element';
}
var defaults = {
type : ($form.attr('method') || 'GET').toUpperCase(),
url : $form.attr('action'),
container: $form.attr('data-pjax'),
target : form
};
if (defaults.type !== 'GET' && window.FormData !== undefined) {
defaults.data = new FormData(form);
defaults.processData = false;
defaults.contentType = false;
} else {
// Can't handle file uploads, exit
if ($form.find(':file').length) {
return;
}
// Fallback to manually serializing the fields
defaults.data = $form.serializeArray();
}
pjax($.extend({}, defaults, options));
event.preventDefault();
}
// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - String selector for where to stick the response body.
// push - Whether to pushState the URL. Defaults to true (of course).
// replace - Want to use replaceState instead? That's cool.
//
// Use it just like $.ajax:
//
// var xhr = $.pjax({ url: this.href, container: '#main' })
// console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
function pjax(options) {
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options);
if (typeof options.url === 'function') {
options.url = options.url();
}
var hash = parseURL(options.url).hash;
var containerType = typeof 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
// for pjax'd partial page loads and one for normal page loads.
// Without adding this secret parameter, some browsers will often
// confuse the two.
if (!options.data) {
options.data = {};
}
if (Array.isArray(options.data)) {
options.data.push({
name : '_pjax',
value: options.container
});
} else {
options.data._pjax = options.container;
}
function fire(type, args, props) {
if (!props) {
props = {};
}
props.relatedTarget = options.target;
var event = $.Event(type, props);
context.trigger(event, args);
return !event.isDefaultPrevented();
}
var timeoutTimer;
options.beforeSend = function(xhr, settings) {
// No timeout for non-GET requests
// Its not safe to request the resource again with a fallback method.
if (settings.type !== 'GET') {
settings.timeout = 0;
}
xhr.setRequestHeader('X-PJAX', 'true');
xhr.setRequestHeader('X-PJAX-Container', options.container);
if (!fire('pjax:beforeSend', [
xhr,
settings
])) {
return false;
}
if (settings.timeout > 0) {
timeoutTimer = setTimeout(function() {
if (fire('pjax:timeout', [
xhr,
options
])) {
xhr.abort('timeout');
}
}, settings.timeout);
// Clear timeout setting so jquerys internal timeout isn't invoked
settings.timeout = 0;
}
var url = parseURL(settings.url);
if (hash) {
url.hash = hash;
}
options.requestUrl = stripInternalParams(url);
};
options.complete = function(xhr, textStatus) {
if (timeoutTimer) {
clearTimeout(timeoutTimer);
}
fire('pjax:complete', [
xhr,
textStatus,
options
]);
fire('pjax:end', [
xhr,
options
]);
};
options.error = function(xhr, textStatus, errorThrown) {
var container = extractContainer('', xhr, options);
var allowed = fire('pjax:error', [
xhr,
textStatus,
errorThrown,
options
]);
if (options.type == 'GET' && textStatus !== 'abort' && allowed) {
locationReplace(container.url);
}
};
options.success = function(data, status, xhr) {
var previousState = pjax.state;
// If $.pjax.defaults.version is a function, invoke it first.
// Otherwise it can be a static string.
var currentVersion = typeof $.pjax.defaults.version === 'function' ?
$.pjax.defaults.version() :
$.pjax.defaults.version;
var latestVersion = xhr.getResponseHeader('X-PJAX-Version');
var container = extractContainer(data, xhr, options);
var url = parseURL(container.url);
if (hash) {
url.hash = hash;
container.url = url.href;
}
// If there is a layout version mismatch, hard load the new url
if (currentVersion && latestVersion && currentVersion !== latestVersion) {
locationReplace(container.url);
return;
}
// If the new response is missing a body, hard load the page
if (!container.contents) {
locationReplace(container.url);
return;
}
pjax.state = {
id : options.id || uniqueId(),
url : container.url,
title : container.title,
container: options.container,
fragment : options.fragment,
timeout : options.timeout
};
if (options.push || options.replace) {
window.history.replaceState(pjax.state, container.title, container.url);
}
// Only blur the focus if the focused element is within the container.
var blurFocus = $.contains(context, document.activeElement);
// Clear out any focused controls before inserting new page contents.
if (blurFocus) {
try {
document.activeElement.blur();
}
catch (e) { /* ignore */
}
}
if (container.title) {
document.title = container.title;
}
fire('pjax:beforeReplace', [
container.contents,
options
], {
state : pjax.state,
previousState: previousState
});
if ('function' === typeof options.renderCallback) {
options.renderCallback(context, container.contents, afterRender);
} else {
context.html(container.contents);
afterRender();
}
function afterRender() {
// FF bug: Won't autofocus fields that are inserted via JS.
// This behavior is incorrect. So if theres no current focus, autofocus
// the last field.
//
// http://www.w3.org/html/wg/drafts/html/master/forms.html
var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0];
if (autofocusEl && document.activeElement !== autofocusEl) {
autofocusEl.trigger('focus');
}
executeScriptTags(container.scripts);
var scrollTo = options.scrollTo;
// Ensure browser scrolls to the element referenced by the URL anchor
if (hash) {
var name = decodeURIComponent(hash.slice(1));
var target = document.getElementById(name) || document.getElementsByName(name)[0];
if (target) {
scrollTo = $(target).offset().top;
}
}
if (typeof scrollTo == 'number') {
$(window).scrollTop(scrollTo);
}
fire('pjax:success', [
data,
status,
xhr,
options
]);
}
};
// Initialize pjax.state for the initial page load. Assume we're
// using the container and options of the link we're loading for the
// back button to the initial page. This ensures good back button
// behavior.
if (!pjax.state) {
pjax.state = {
id : uniqueId(),
url : window.location.href,
title : document.title,
container: options.container,
fragment : options.fragment,
timeout : options.timeout
};
window.history.replaceState(pjax.state, document.title);
}
// Cancel the current request if we're already pjaxing
abortXHR(pjax.xhr);
pjax.options = options;
var xhr = pjax.xhr = $.ajax(options);
if (xhr.readyState > 0) {
if (options.push && !options.replace) {
// Cache current container element before replacing it
cachePush(pjax.state.id, [
options.container,
cloneContents(context)
]);
window.history.pushState(null, '', options.requestUrl);
}
fire('pjax:start', [
xhr,
options
]);
fire('pjax:send', [
xhr,
options
]);
}
return pjax.xhr;
}
// Public: Reload current page with pjax.
//
// Returns whatever $.pjax returns.
function pjaxReload(container, options) {
var defaults = {
url : window.location.href,
push : false,
replace : true,
scrollTo: false
};
return pjax($.extend(defaults, optionsFor(container, options)));
}
// Internal: Hard replace current state with url.
//
// Work for around WebKit
// https://bugs.webkit.org/show_bug.cgi?id=93506
//
// Returns nothing.
function locationReplace(url) {
window.history.replaceState(null, '', pjax.state.url);
window.location.replace(url);
}
var initialPop = true;
var initialURL = window.location.href;
var initialState = window.history.state;
// Initialize $.pjax.state if possible
// Happens when reloading a page and coming forward from a different
// session history.
if (initialState && initialState.container) {
pjax.state = initialState;
}
// Non-webkit browsers don't fire an initial popstate event
if ('state' in window.history) {
initialPop = false;
}
// popstate handler takes care of the back and forward buttons
//
// You probably shouldn't use pjax on pages with other pushState
// stuff yet.
function onPjaxPopstate(event) {
// Hitting back or forward should override any pending PJAX request.
if (!initialPop) {
abortXHR(pjax.xhr);
}
var previousState = pjax.state;
var state = event.state;
var direction;
if (state && state.container) {
// When coming forward from a separate history session, will get an
// initial pop with a state we are already at. Skip reloading the current
// page.
if (initialPop && initialURL == state.url) {
return;
}
if (previousState) {
// If popping back to the same state, just skip.
// Could be clicking back from hashchange rather than a pushState.
if (previousState.id === state.id) {
return;
}
// Since state IDs always increase, we can deduce the navigation direction
direction = previousState.id < state.id ? 'forward' : 'back';
}
var cache = cacheMapping[state.id] || [];
var containerSelector = cache[0] || state.container;
var container = $(containerSelector), contents = cache[1];
if (container.length) {
if (previousState) {
// Cache current container before replacement and inform the
// cache which direction the history shifted.
cachePop(direction, previousState.id, [
containerSelector,
cloneContents(container)
]);
}
var popstateEvent = $.Event('pjax:popstate', {
state : state,
direction: direction
});
container.trigger(popstateEvent);
var options = {
id : state.id,
url : state.url,
container: containerSelector,
push : false,
fragment : state.fragment,
timeout : state.timeout,
scrollTo : false
};
if (contents) {
container.trigger('pjax:start', [
null,
options
]);
pjax.state = state;
if (state.title) {
document.title = state.title;
}
var beforeReplaceEvent = $.Event('pjax:beforeReplace', {
state : state,
previousState: previousState
});
container.trigger(beforeReplaceEvent, [
contents,
options
]);
container.html(contents);
container.trigger('pjax:end', [
null,
options
]);
} else {
pjax(options);
}
// Force reflow/relayout before the browser tries to restore the
// scroll position.
container[0].offsetHeight; // eslint-disable-line no-unused-expressions
} else {
locationReplace(location.href);
}
}
initialPop = false;
}
// Fallback version of main pjax function for browsers that don't
// support pushState.
//
// Returns nothing since it retriggers a hard form submission.
function fallbackPjax(options) {
var url = typeof options.url === 'function' ? options.url() : options.url,
method = options.type ? options.type.toUpperCase() : 'GET';
var form = $('