UNPKG

pjax

Version:

Easily enable fast AJAX navigation on any website (using pushState + XHR)

1,262 lines (1,066 loc) 35.4 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Pjax = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ var executeScripts = require("./lib/execute-scripts"); var forEachEls = require("./lib/foreach-els"); var parseOptions = require("./lib/parse-options"); var switches = require("./lib/switches"); var newUid = require("./lib/uniqueid"); var on = require("./lib/events/on"); var trigger = require("./lib/events/trigger"); var clone = require("./lib/util/clone"); var contains = require("./lib/util/contains"); var extend = require("./lib/util/extend"); var noop = require("./lib/util/noop"); var Pjax = function(options) { this.state = { numPendingSwitches: 0, href: null, options: null }; this.options = parseOptions(options); this.log("Pjax options", this.options); if (this.options.scrollRestoration && "scrollRestoration" in history) { history.scrollRestoration = "manual"; } this.maxUid = this.lastUid = newUid(); this.parseDOM(document); on( window, "popstate", function(st) { if (st.state) { var opt = clone(this.options); opt.url = st.state.url; opt.title = st.state.title; // Since state already exists, prevent it from being pushed again opt.history = false; opt.scrollPos = st.state.scrollPos; if (st.state.uid < this.lastUid) { opt.backward = true; } else { opt.forward = true; } this.lastUid = st.state.uid; // @todo implement history cache here, based on uid this.loadUrl(st.state.url, opt); } }.bind(this) ); }; Pjax.switches = switches; Pjax.prototype = { log: require("./lib/proto/log"), getElements: function(el) { return el.querySelectorAll(this.options.elements); }, parseDOM: function(el) { var parseElement = require("./lib/proto/parse-element"); forEachEls(this.getElements(el), parseElement, this); }, refresh: function(el) { this.parseDOM(el || document); }, reload: function() { window.location.reload(); }, attachLink: require("./lib/proto/attach-link"), attachForm: require("./lib/proto/attach-form"), forEachSelectors: function(cb, context, DOMcontext) { return require("./lib/foreach-selectors").bind(this)( this.options.selectors, cb, context, DOMcontext ); }, switchSelectors: function(selectors, fromEl, toEl, options) { return require("./lib/switches-selectors").bind(this)( this.options.switches, this.options.switchesOptions, selectors, fromEl, toEl, options ); }, latestChance: function(href) { window.location = href; }, onSwitch: function() { trigger(window, "resize scroll"); this.state.numPendingSwitches--; // debounce calls, so we only run this once after all switches are finished. if (this.state.numPendingSwitches === 0) { this.afterAllSwitches(); } }, loadContent: function(html, options) { if (typeof html !== "string") { trigger(document, "pjax:complete pjax:error", options); return; } var tmpEl = document.implementation.createHTMLDocument("pjax"); // parse HTML attributes to copy them // since we are forced to use documentElement.innerHTML (outerHTML can't be used for <html>) var htmlRegex = /<html[^>]+>/gi; var htmlAttribsRegex = /\s?[a-z:]+(?:=['"][^'">]+['"])*/gi; var matches = html.match(htmlRegex); if (matches && matches.length) { matches = matches[0].match(htmlAttribsRegex); if (matches.length) { matches.shift(); matches.forEach(function(htmlAttrib) { var attr = htmlAttrib.trim().split("="); if (attr.length === 1) { tmpEl.documentElement.setAttribute(attr[0], true); } else { tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1)); } }); } } tmpEl.documentElement.innerHTML = html; this.log( "load content", tmpEl.documentElement.attributes, tmpEl.documentElement.innerHTML.length ); // Clear out any focused controls before inserting new page contents. if ( document.activeElement && contains(document, this.options.selectors, document.activeElement) ) { try { document.activeElement.blur(); } catch (e) {} // eslint-disable-line no-empty } this.switchSelectors(this.options.selectors, tmpEl, document, options); }, abortRequest: require("./lib/abort-request"), doRequest: require("./lib/send-request"), handleResponse: require("./lib/proto/handle-response"), loadUrl: function(href, options) { options = typeof options === "object" ? extend({}, this.options, options) : clone(this.options); this.log("load href", href, options); // Abort any previous request this.abortRequest(this.request); trigger(document, "pjax:send", options); // Do the request this.request = this.doRequest( href, options, this.handleResponse.bind(this) ); }, afterAllSwitches: function() { // 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 = Array.prototype.slice .call(document.querySelectorAll("[autofocus]")) .pop(); if (autofocusEl && document.activeElement !== autofocusEl) { autofocusEl.focus(); } // execute scripts when DOM have been completely updated this.options.selectors.forEach(function(selector) { forEachEls(document.querySelectorAll(selector), function(el) { executeScripts(el); }); }); var state = this.state; if (state.options.history) { if (!window.history.state) { this.lastUid = this.maxUid = newUid(); window.history.replaceState( { url: window.location.href, title: document.title, uid: this.maxUid, scrollPos: [0, 0] }, document.title ); } // Update browser history this.lastUid = this.maxUid = newUid(); window.history.pushState( { url: state.href, title: state.options.title, uid: this.maxUid, scrollPos: [0, 0] }, state.options.title, state.href ); } this.forEachSelectors(function(el) { this.parseDOM(el); }, this); // Fire Events trigger(document, "pjax:complete pjax:success", state.options); if (typeof state.options.analytics === "function") { state.options.analytics(); } if (state.options.history) { // First parse url and check for hash to override scroll var a = document.createElement("a"); a.href = this.state.href; if (a.hash) { var name = a.hash.slice(1); name = decodeURIComponent(name); var curtop = 0; var target = document.getElementById(name) || document.getElementsByName(name)[0]; if (target) { // http://stackoverflow.com/questions/8111094/cross-browser-javascript-function-to-find-actual-position-of-an-element-in-page if (target.offsetParent) { do { curtop += target.offsetTop; target = target.offsetParent; } while (target); } } window.scrollTo(0, curtop); } else if (state.options.scrollTo !== false) { // Scroll page to top on new page load if (state.options.scrollTo.length > 1) { window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]); } else { window.scrollTo(0, state.options.scrollTo); } } } else if (state.options.scrollRestoration && state.options.scrollPos) { window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]); } this.state = { numPendingSwitches: 0, href: null, options: null }; } }; Pjax.isSupported = require("./lib/is-supported"); // arguably could do `if( require("./lib/is-supported")()) {` but that might be a little to simple if (Pjax.isSupported()) { module.exports = Pjax; } // if there isn’t required browser functions, returning stupid api else { var stupidPjax = noop; for (var key in Pjax.prototype) { if ( Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function" ) { stupidPjax[key] = noop; } } module.exports = stupidPjax; } },{"./lib/abort-request":2,"./lib/events/on":4,"./lib/events/trigger":5,"./lib/execute-scripts":6,"./lib/foreach-els":7,"./lib/foreach-selectors":8,"./lib/is-supported":9,"./lib/parse-options":10,"./lib/proto/attach-form":11,"./lib/proto/attach-link":12,"./lib/proto/handle-response":13,"./lib/proto/log":14,"./lib/proto/parse-element":15,"./lib/send-request":16,"./lib/switches":18,"./lib/switches-selectors":17,"./lib/uniqueid":19,"./lib/util/clone":20,"./lib/util/contains":21,"./lib/util/extend":22,"./lib/util/noop":23}],2:[function(require,module,exports){ var noop = require("./util/noop"); module.exports = function(request) { if (request && request.readyState < 4) { request.onreadystatechange = noop; request.abort(); } }; },{"./util/noop":23}],3:[function(require,module,exports){ module.exports = function(el) { var code = el.text || el.textContent || el.innerHTML || ""; var src = el.src || ""; var parent = el.parentNode || document.querySelector("head") || document.documentElement; var script = document.createElement("script"); if (code.match("document.write")) { if (console && console.log) { console.log( "Script contains document.write. Can’t be executed correctly. Code skipped ", el ); } return false; } script.type = "text/javascript"; script.id = el.id; /* istanbul ignore if */ if (src !== "") { script.src = src; script.async = false; // force synchronous loading of peripheral JS } if (code !== "") { try { script.appendChild(document.createTextNode(code)); } catch (e) { /* istanbul ignore next */ // old IEs have funky script nodes script.text = code; } } // execute parent.appendChild(script); // avoid pollution only in head or body tags if ( (parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) && parent.contains(script) ) { parent.removeChild(script); } return true; }; },{}],4:[function(require,module,exports){ var forEachEls = require("../foreach-els"); module.exports = function(els, events, listener, useCapture) { events = typeof events === "string" ? events.split(" ") : events; events.forEach(function(e) { forEachEls(els, function(el) { el.addEventListener(e, listener, useCapture); }); }); }; },{"../foreach-els":7}],5:[function(require,module,exports){ var forEachEls = require("../foreach-els"); module.exports = function(els, events, opts) { events = typeof events === "string" ? events.split(" ") : events; events.forEach(function(e) { var event; event = document.createEvent("HTMLEvents"); event.initEvent(e, true, true); event.eventName = e; if (opts) { Object.keys(opts).forEach(function(key) { event[key] = opts[key]; }); } forEachEls(els, function(el) { var domFix = false; if (!el.parentNode && el !== document && el !== window) { // THANK YOU IE (9/10/11) // dispatchEvent doesn't work if the element is not in the DOM domFix = true; document.body.appendChild(el); } el.dispatchEvent(event); if (domFix) { el.parentNode.removeChild(el); } }); }); }; },{"../foreach-els":7}],6:[function(require,module,exports){ var forEachEls = require("./foreach-els"); var evalScript = require("./eval-script"); // Finds and executes scripts (used for newly added elements) // Needed since innerHTML does not run scripts module.exports = function(el) { if (el.tagName.toLowerCase() === "script") { evalScript(el); } forEachEls(el.querySelectorAll("script"), function(script) { if (!script.type || script.type.toLowerCase() === "text/javascript") { if (script.parentNode) { script.parentNode.removeChild(script); } evalScript(script); } }); }; },{"./eval-script":3,"./foreach-els":7}],7:[function(require,module,exports){ /* global HTMLCollection: true */ module.exports = function(els, fn, context) { if ( els instanceof HTMLCollection || els instanceof NodeList || els instanceof Array ) { return Array.prototype.forEach.call(els, fn, context); } // assume simple DOM element return fn.call(context, els); }; },{}],8:[function(require,module,exports){ var forEachEls = require("./foreach-els"); module.exports = function(selectors, cb, context, DOMcontext) { DOMcontext = DOMcontext || document; selectors.forEach(function(selector) { forEachEls(DOMcontext.querySelectorAll(selector), cb, context); }); }; },{"./foreach-els":7}],9:[function(require,module,exports){ module.exports = function() { // Borrowed wholesale from https://github.com/defunkt/jquery-pjax return ( window.history && window.history.pushState && window.history.replaceState && // pushState isn’t reliable on iOS until 5. !navigator.userAgent.match( /((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/ ) ); }; },{}],10:[function(require,module,exports){ /* global _gaq: true, ga: true */ var defaultSwitches = require("./switches"); module.exports = function(options) { options = options || {}; options.elements = options.elements || "a[href], form[action]"; options.selectors = options.selectors || ["title", ".js-Pjax"]; options.switches = options.switches || {}; options.switchesOptions = options.switchesOptions || {}; options.history = typeof options.history === "undefined" ? true : options.history; options.analytics = typeof options.analytics === "function" || options.analytics === false ? options.analytics : defaultAnalytics; options.scrollTo = typeof options.scrollTo === "undefined" ? 0 : options.scrollTo; options.scrollRestoration = typeof options.scrollRestoration !== "undefined" ? options.scrollRestoration : true; options.cacheBust = typeof options.cacheBust === "undefined" ? true : options.cacheBust; options.debug = options.debug || false; options.timeout = options.timeout || 0; options.currentUrlFullReload = typeof options.currentUrlFullReload === "undefined" ? false : options.currentUrlFullReload; // We can’t replace body.outerHTML or head.outerHTML. // It creates a bug where a new body or head are created in the DOM. // If you set head.outerHTML, a new body tag is appended, so the DOM has 2 body nodes, and vice versa if (!options.switches.head) { options.switches.head = defaultSwitches.switchElementsAlt; } if (!options.switches.body) { options.switches.body = defaultSwitches.switchElementsAlt; } return options; }; /* istanbul ignore next */ function defaultAnalytics() { if (window._gaq) { _gaq.push(["_trackPageview"]); } if (window.ga) { ga("send", "pageview", { page: location.pathname, title: document.title }); } } },{"./switches":18}],11:[function(require,module,exports){ var on = require("../events/on"); var clone = require("../util/clone"); var attrState = "data-pjax-state"; var formAction = function(el, event) { if (isDefaultPrevented(event)) { return; } // Since loadUrl modifies options and we may add our own modifications below, // clone it so the changes don't persist var options = clone(this.options); // Initialize requestOptions options.requestOptions = { requestUrl: el.getAttribute("action") || window.location.href, requestMethod: el.getAttribute("method") || "GET" }; // create a testable virtual link of the form action var virtLinkElement = document.createElement("a"); virtLinkElement.setAttribute("href", options.requestOptions.requestUrl); var attrValue = checkIfShouldAbort(virtLinkElement, options); if (attrValue) { el.setAttribute(attrState, attrValue); return; } event.preventDefault(); if (el.enctype === "multipart/form-data") { options.requestOptions.formData = new FormData(el); } else { options.requestOptions.requestParams = parseFormElements(el); } el.setAttribute(attrState, "submit"); options.triggerElement = el; this.loadUrl(virtLinkElement.href, options); }; function parseFormElements(el) { var requestParams = []; var formElements = el.elements; for (var i = 0; i < formElements.length; i++) { var element = formElements[i]; var tagName = element.tagName.toLowerCase(); // jscs:disable disallowImplicitTypeConversion if ( !!element.name && element.attributes !== undefined && tagName !== "button" ) { // jscs:enable disallowImplicitTypeConversion var type = element.attributes.type; if ( !type || (type.value !== "checkbox" && type.value !== "radio") || element.checked ) { // Build array of values to submit var values = []; if (tagName === "select") { var opt; for (var j = 0; j < element.options.length; j++) { opt = element.options[j]; if (opt.selected && !opt.disabled) { values.push(opt.hasAttribute("value") ? opt.value : opt.text); } } } else { values.push(element.value); } for (var k = 0; k < values.length; k++) { requestParams.push({ name: encodeURIComponent(element.name), value: encodeURIComponent(values[k]) }); } } } } return requestParams; } function checkIfShouldAbort(virtLinkElement, options) { // Ignore external links. if ( virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host ) { return "external"; } // Ignore click if we are on an anchor on the same page if ( virtLinkElement.hash && virtLinkElement.href.replace(virtLinkElement.hash, "") === window.location.href.replace(location.hash, "") ) { return "anchor"; } // Ignore empty anchor "foo.html#" if (virtLinkElement.href === window.location.href.split("#")[0] + "#") { return "anchor-empty"; } // if declared as a full reload, just normally submit the form if ( options.currentUrlFullReload && virtLinkElement.href === window.location.href.split("#")[0] ) { return "reload"; } } var isDefaultPrevented = function(event) { return event.defaultPrevented || event.returnValue === false; }; module.exports = function(el) { var that = this; el.setAttribute(attrState, ""); on(el, "submit", function(event) { formAction.call(that, el, event); }); }; },{"../events/on":4,"../util/clone":20}],12:[function(require,module,exports){ var on = require("../events/on"); var clone = require("../util/clone"); var attrState = "data-pjax-state"; var linkAction = function(el, event) { if (isDefaultPrevented(event)) { return; } // Since loadUrl modifies options and we may add our own modifications below, // clone it so the changes don't persist var options = clone(this.options); var attrValue = checkIfShouldAbort(el, event); if (attrValue) { el.setAttribute(attrState, attrValue); return; } event.preventDefault(); // don’t do "nothing" if user try to reload the page by clicking the same link twice if ( this.options.currentUrlFullReload && el.href === window.location.href.split("#")[0] ) { el.setAttribute(attrState, "reload"); this.reload(); return; } el.setAttribute(attrState, "load"); options.triggerElement = el; this.loadUrl(el.href, options); }; function checkIfShouldAbort(el, event) { // Don’t break browser special behavior on links (like page in new window) if ( event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey ) { return "modifier"; } // we do test on href now to prevent unexpected behavior if for some reason // user have href that can be dynamically updated // Ignore external links. if ( el.protocol !== window.location.protocol || el.host !== window.location.host ) { return "external"; } // Ignore anchors on the same page (keep native behavior) if ( el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "") ) { return "anchor"; } // Ignore empty anchor "foo.html#" if (el.href === window.location.href.split("#")[0] + "#") { return "anchor-empty"; } } var isDefaultPrevented = function(event) { return event.defaultPrevented || event.returnValue === false; }; module.exports = function(el) { var that = this; el.setAttribute(attrState, ""); on(el, "click", function(event) { linkAction.call(that, el, event); }); on( el, "keyup", function(event) { if (event.keyCode === 13) { linkAction.call(that, el, event); } }.bind(this) ); }; },{"../events/on":4,"../util/clone":20}],13:[function(require,module,exports){ var clone = require("../util/clone"); var newUid = require("../uniqueid"); var trigger = require("../events/trigger"); module.exports = function(responseText, request, href, options) { options = clone(options || this.options); options.request = request; // Fail if unable to load HTML via AJAX if (responseText === false) { trigger(document, "pjax:complete pjax:error", options); return; } // push scroll position to history var currentState = window.history.state || {}; window.history.replaceState( { url: currentState.url || window.location.href, title: currentState.title || document.title, uid: currentState.uid || newUid(), scrollPos: [ document.documentElement.scrollLeft || document.body.scrollLeft, document.documentElement.scrollTop || document.body.scrollTop ] }, document.title, window.location.href ); // Check for redirects var oldHref = href; if (request.responseURL) { if (href !== request.responseURL) { href = request.responseURL; } } else if (request.getResponseHeader("X-PJAX-URL")) { href = request.getResponseHeader("X-PJAX-URL"); } else if (request.getResponseHeader("X-XHR-Redirected-To")) { href = request.getResponseHeader("X-XHR-Redirected-To"); } // Add back the hash if it was removed var a = document.createElement("a"); a.href = oldHref; var oldHash = a.hash; a.href = href; if (oldHash && !a.hash) { a.hash = oldHash; href = a.href; } this.state.href = href; this.state.options = options; try { this.loadContent(responseText, options); } catch (e) { trigger(document, "pjax:error", options); if (!this.options.debug) { if (console && console.error) { console.error("Pjax switch fail: ", e); } return this.latestChance(href); } else { throw e; } } }; },{"../events/trigger":5,"../uniqueid":19,"../util/clone":20}],14:[function(require,module,exports){ module.exports = function() { if (this.options.debug && console) { if (typeof console.log === "function") { console.log.apply(console, arguments); } // IE is weird else if (console.log) { console.log(arguments); } } }; },{}],15:[function(require,module,exports){ var attrState = "data-pjax-state"; module.exports = function(el) { switch (el.tagName.toLowerCase()) { case "a": // only attach link if el does not already have link attached if (!el.hasAttribute(attrState)) { this.attachLink(el); } break; case "form": // only attach link if el does not already have link attached if (!el.hasAttribute(attrState)) { this.attachForm(el); } break; default: throw "Pjax can only be applied on <a> or <form> submit"; } }; },{}],16:[function(require,module,exports){ var updateQueryString = require("./util/update-query-string"); module.exports = function(location, options, callback) { options = options || {}; var queryString; var requestOptions = options.requestOptions || {}; var requestMethod = (requestOptions.requestMethod || "GET").toUpperCase(); var requestParams = requestOptions.requestParams || null; var formData = requestOptions.formData || null; var requestPayload = null; var request = new XMLHttpRequest(); var timeout = options.timeout || 0; request.onreadystatechange = function() { if (request.readyState === 4) { if (request.status === 200) { callback(request.responseText, request, location, options); } else if (request.status !== 0) { callback(null, request, location, options); } } }; request.onerror = function(e) { console.log(e); callback(null, request, location, options); }; request.ontimeout = function() { callback(null, request, location, options); }; // Prepare the request payload for forms, if available if (requestParams && requestParams.length) { // Build query string queryString = requestParams .map(function(param) { return param.name + "=" + param.value; }) .join("&"); switch (requestMethod) { case "GET": // Reset query string to avoid an issue with repeat submissions where checkboxes that were // previously checked are incorrectly preserved location = location.split("?")[0]; // Append new query string location += "?" + queryString; break; case "POST": // Send query string as request payload requestPayload = queryString; break; } } else if (formData) { requestPayload = formData; } // Add a timestamp as part of the query string if cache busting is enabled if (options.cacheBust) { location = updateQueryString(location, "t", Date.now()); } request.open(requestMethod, location, true); request.timeout = timeout; request.setRequestHeader("X-Requested-With", "XMLHttpRequest"); request.setRequestHeader("X-PJAX", "true"); request.setRequestHeader( "X-PJAX-Selectors", JSON.stringify(options.selectors) ); // Send the proper header information for POST forms if (requestPayload && requestMethod === "POST" && !formData) { request.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" ); } request.send(requestPayload); return request; }; },{"./util/update-query-string":24}],17:[function(require,module,exports){ var forEachEls = require("./foreach-els"); var defaultSwitches = require("./switches"); module.exports = function( switches, switchesOptions, selectors, fromEl, toEl, options ) { var switchesQueue = []; selectors.forEach(function(selector) { var newEls = fromEl.querySelectorAll(selector); var oldEls = toEl.querySelectorAll(selector); if (this.log) { this.log("Pjax switch", selector, newEls, oldEls); } if (newEls.length !== oldEls.length) { throw "DOM doesn’t look the same on new loaded page: ’" + selector + "’ - new " + newEls.length + ", old " + oldEls.length; } forEachEls( newEls, function(newEl, i) { var oldEl = oldEls[i]; if (this.log) { this.log("newEl", newEl, "oldEl", oldEl); } var callback = switches[selector] ? switches[selector].bind( this, oldEl, newEl, options, switchesOptions[selector] ) : defaultSwitches.outerHTML.bind(this, oldEl, newEl, options); switchesQueue.push(callback); }, this ); }, this); this.state.numPendingSwitches = switchesQueue.length; switchesQueue.forEach(function(queuedSwitch) { queuedSwitch(); }); }; },{"./foreach-els":7,"./switches":18}],18:[function(require,module,exports){ var on = require("./events/on"); module.exports = { outerHTML: function(oldEl, newEl) { oldEl.outerHTML = newEl.outerHTML; this.onSwitch(); }, innerHTML: function(oldEl, newEl) { oldEl.innerHTML = newEl.innerHTML; if (newEl.className === "") { oldEl.removeAttribute("class"); } else { oldEl.className = newEl.className; } this.onSwitch(); }, switchElementsAlt: function(oldEl, newEl) { oldEl.innerHTML = newEl.innerHTML; // Copy attributes from the new element to the old one if (newEl.hasAttributes()) { var attrs = newEl.attributes; for (var i = 0; i < attrs.length; i++) { oldEl.attributes.setNamedItem(attrs[i].cloneNode()); } } this.onSwitch(); }, // Equivalent to outerHTML(), but doesn't require switchElementsAlt() for <head> and <body> replaceNode: function(oldEl, newEl) { oldEl.parentNode.replaceChild(newEl, oldEl); this.onSwitch(); }, sideBySide: function(oldEl, newEl, options, switchOptions) { var forEach = Array.prototype.forEach; var elsToRemove = []; var elsToAdd = []; var fragToAppend = document.createDocumentFragment(); var animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"; var animatedElsNumber = 0; var sexyAnimationEnd = function(e) { if (e.target !== e.currentTarget) { // end triggered by an animation on a child return; } animatedElsNumber--; if (animatedElsNumber <= 0 && elsToRemove) { elsToRemove.forEach(function(el) { // browsing quickly can make the el // already removed by last page update ? if (el.parentNode) { el.parentNode.removeChild(el); } }); elsToAdd.forEach(function(el) { el.className = el.className.replace( el.getAttribute("data-pjax-classes"), "" ); el.removeAttribute("data-pjax-classes"); }); elsToAdd = null; // free memory elsToRemove = null; // free memory // this is to trigger some repaint (example: picturefill) this.onSwitch(); } }.bind(this); switchOptions = switchOptions || {}; forEach.call(oldEl.childNodes, function(el) { elsToRemove.push(el); if (el.classList && !el.classList.contains("js-Pjax-remove")) { // for fast switch, clean element that just have been added, & not cleaned yet. if (el.hasAttribute("data-pjax-classes")) { el.className = el.className.replace( el.getAttribute("data-pjax-classes"), "" ); el.removeAttribute("data-pjax-classes"); } el.classList.add("js-Pjax-remove"); if (switchOptions.callbacks && switchOptions.callbacks.removeElement) { switchOptions.callbacks.removeElement(el); } if (switchOptions.classNames) { el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward); } animatedElsNumber++; on(el, animationEventNames, sexyAnimationEnd, true); } }); forEach.call(newEl.childNodes, function(el) { if (el.classList) { var addClasses = ""; if (switchOptions.classNames) { addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward); } if (switchOptions.callbacks && switchOptions.callbacks.addElement) { switchOptions.callbacks.addElement(el); } el.className += addClasses; el.setAttribute("data-pjax-classes", addClasses); elsToAdd.push(el); fragToAppend.appendChild(el); animatedElsNumber++; on(el, animationEventNames, sexyAnimationEnd, true); } }); // pass all className of the parent oldEl.className = newEl.className; oldEl.appendChild(fragToAppend); } }; },{"./events/on":4}],19:[function(require,module,exports){ module.exports = (function() { var counter = 0; return function() { var id = "pjax" + new Date().getTime() + "_" + counter; counter++; return id; }; })(); },{}],20:[function(require,module,exports){ module.exports = function(obj) { /* istanbul ignore if */ if (null === obj || "object" !== typeof obj) { return obj; } var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) { copy[attr] = obj[attr]; } } return copy; }; },{}],21:[function(require,module,exports){ module.exports = function contains(doc, selectors, el) { for (var i = 0; i < selectors.length; i++) { var selectedEls = doc.querySelectorAll(selectors[i]); for (var j = 0; j < selectedEls.length; j++) { if (selectedEls[j].contains(el)) { return true; } } } return false; }; },{}],22:[function(require,module,exports){ module.exports = function(target) { if (target == null) { return null; } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; if (source != null) { for (var key in source) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(source, key)) { to[key] = source[key]; } } } } return to; }; },{}],23:[function(require,module,exports){ module.exports = function() {}; },{}],24:[function(require,module,exports){ module.exports = function(uri, key, value) { var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); var separator = uri.indexOf("?") !== -1 ? "&" : "?"; if (uri.match(re)) { return uri.replace(re, "$1" + key + "=" + value + "$2"); } else { return uri + separator + key + "=" + value; } }; },{}]},{},[1])(1) });