diff --git a/example/example.js b/example/example.js index 9880439..7551df3 100644 --- a/example/example.js +++ b/example/example.js @@ -1,51 +1,49 @@ /* global Pjax */ var pjax; var initButtons = function() { - var buttons = document.querySelectorAll("button[data-manual-trigger]") + var buttons = document.querySelectorAll("button[data-manual-trigger]"); if (!buttons) { - return + return; } // jshint -W083 for (var i = 0; i < buttons.length; i++) { buttons[i].addEventListener("click", function(e) { - var el = e.currentTarget + var el = e.currentTarget; if (el.getAttribute("data-manual-trigger-override") === "true") { // Manually load URL with overridden Pjax instance options - pjax.loadUrl("/example/page2.html", {cacheBust: false}) - } - else - { + pjax.loadUrl("/example/page2.html", { cacheBust: false }); + } else { // Manually load URL with current Pjax instance options - pjax.loadUrl("/example/page2.html") + pjax.loadUrl("/example/page2.html"); } - }) + }); } // jshint +W083 -} +}; -console.log("Document initialized:", window.location.href) +console.log("Document initialized:", window.location.href); document.addEventListener("pjax:send", function() { - console.log("Event: pjax:send", arguments) -}) + console.log("Event: pjax:send", arguments); +}); document.addEventListener("pjax:complete", function() { - console.log("Event: pjax:complete", arguments) -}) + console.log("Event: pjax:complete", arguments); +}); document.addEventListener("pjax:error", function() { - console.log("Event: pjax:error", arguments) -}) + console.log("Event: pjax:error", arguments); +}); document.addEventListener("pjax:success", function() { - console.log("Event: pjax:success", arguments) + console.log("Event: pjax:success", arguments); // Init page content - initButtons() -}) + initButtons(); +}); document.addEventListener("DOMContentLoaded", function() { // Init Pjax instance @@ -53,9 +51,9 @@ document.addEventListener("DOMContentLoaded", function() { elements: [".js-Pjax"], selectors: [".body", "title"], cacheBust: true - }) - console.log("Pjax initialized.", pjax) + }); + console.log("Pjax initialized.", pjax); // Init page content - initButtons() -}) + initButtons(); +}); diff --git a/index.js b/index.js index 8f82c83..53a3378 100644 --- a/index.js +++ b/index.js @@ -1,164 +1,189 @@ -var executeScripts = require("./lib/execute-scripts.js") -var forEachEls = require("./lib/foreach-els.js") -var parseOptions = require("./lib/parse-options.js") -var switches = require("./lib/switches") -var newUid = require("./lib/uniqueid.js") +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.js") -var trigger = require("./lib/events/trigger.js") +var on = require("./lib/events/on"); +var trigger = require("./lib/events/trigger"); -var clone = require("./lib/util/clone.js") -var contains = require("./lib/util/contains.js") -var extend = require("./lib/util/extend.js") -var noop = require("./lib/util/noop") +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.state = { + numPendingSwitches: 0, + href: null, + options: null + }; + this.options = parseOptions(options); + this.log("Pjax options", this.options); - 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)) + if (this.options.scrollRestoration && "scrollRestoration" in history) { + history.scrollRestoration = "manual"; } -Pjax.switches = switches + 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.js"), + log: require("./lib/proto/log"), getElements: function(el) { - return el.querySelectorAll(this.options.elements) + return el.querySelectorAll(this.options.elements); }, parseDOM: function(el) { - var parseElement = require("./lib/proto/parse-element") - forEachEls(this.getElements(el), parseElement, this) + var parseElement = require("./lib/proto/parse-element"); + forEachEls(this.getElements(el), parseElement, this); }, refresh: function(el) { - this.parseDOM(el || document) + this.parseDOM(el || document); }, reload: function() { - window.location.reload() + window.location.reload(); }, - attachLink: require("./lib/proto/attach-link.js"), + attachLink: require("./lib/proto/attach-link"), - attachForm: require("./lib/proto/attach-form.js"), + attachForm: require("./lib/proto/attach-form"), forEachSelectors: function(cb, context, DOMcontext) { - return require("./lib/foreach-selectors.js").bind(this)(this.options.selectors, 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.js").bind(this)(this.options.switches, this.options.switchesOptions, 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 + window.location = href; }, onSwitch: function() { - trigger(window, "resize scroll") + trigger(window, "resize scroll"); - this.state.numPendingSwitches-- + this.state.numPendingSwitches--; // debounce calls, so we only run this once after all switches are finished. if (this.state.numPendingSwitches === 0) { - this.afterAllSwitches() + this.afterAllSwitches(); } }, loadContent: function(html, options) { - var tmpEl = document.implementation.createHTMLDocument("pjax") + 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 ) - var htmlRegex = /]+>/gi - var htmlAttribsRegex = /\s?[a-z:]+(?:\=(?:\'|\")[^\'\">]+(?:\'|\"))*/gi - var matches = html.match(htmlRegex) + var htmlRegex = /]+>/gi; + var htmlAttribsRegex = /\s?[a-z:]+(?:=['"][^'">]+['"])*/gi; + var matches = html.match(htmlRegex); if (matches && matches.length) { - matches = matches[0].match(htmlAttribsRegex) + matches = matches[0].match(htmlAttribsRegex); if (matches.length) { - matches.shift() + matches.shift(); matches.forEach(function(htmlAttrib) { - var attr = htmlAttrib.trim().split("=") + var attr = htmlAttrib.trim().split("="); if (attr.length === 1) { - tmpEl.documentElement.setAttribute(attr[0], true) + tmpEl.documentElement.setAttribute(attr[0], true); + } else { + tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1)); } - 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) + 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)) { + if ( + document.activeElement && + contains(document, this.options.selectors, document.activeElement) + ) { try { - document.activeElement.blur() - } catch (e) { } + document.activeElement.blur(); + } catch (e) {} // eslint-disable-line no-empty } - this.switchSelectors(this.options.selectors, tmpEl, document, options) + this.switchSelectors(this.options.selectors, tmpEl, document, options); }, - abortRequest: require("./lib/abort-request.js"), + abortRequest: require("./lib/abort-request"), - doRequest: require("./lib/send-request.js"), + doRequest: require("./lib/send-request"), - handleResponse: require("./lib/proto/handle-response.js"), + handleResponse: require("./lib/proto/handle-response"), loadUrl: function(href, options) { - options = typeof options === "object" ? - extend({}, this.options, options) : - clone(this.options) + options = + typeof options === "object" + ? extend({}, this.options, options) + : clone(this.options); - this.log("load href", href, options) + this.log("load href", href, options); // Abort any previous request - this.abortRequest(this.request) + this.abortRequest(this.request); - trigger(document, "pjax:send", options) + trigger(document, "pjax:send", options); // Do the request - this.request = this.doRequest(href, options, this.handleResponse.bind(this)) + this.request = this.doRequest( + href, + options, + this.handleResponse.bind(this) + ); }, afterAllSwitches: function() { @@ -167,114 +192,121 @@ Pjax.prototype = { // the last field. // // http://www.w3.org/html/wg/drafts/html/master/forms.html - var autofocusEl = Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop() + var autofocusEl = Array.prototype.slice + .call(document.querySelectorAll("[autofocus]")) + .pop(); if (autofocusEl && document.activeElement !== autofocusEl) { - autofocusEl.focus() + autofocusEl.focus(); } // execute scripts when DOM have been completely updated this.options.selectors.forEach(function(selector) { forEachEls(document.querySelectorAll(selector), function(el) { - executeScripts(el) - }) - }) + executeScripts(el); + }); + }); - var state = this.state + var state = this.state; if (state.options.history) { if (!window.history.state) { - this.lastUid = this.maxUid = newUid() - window.history.replaceState({ + this.lastUid = this.maxUid = newUid(); + window.history.replaceState( + { url: window.location.href, title: document.title, uid: this.maxUid, scrollPos: [0, 0] }, - document.title) + document.title + ); } // Update browser history - this.lastUid = this.maxUid = newUid() + this.lastUid = this.maxUid = newUid(); - window.history.pushState({ + window.history.pushState( + { url: state.href, title: state.options.title, uid: this.maxUid, scrollPos: [0, 0] }, state.options.title, - state.href) + state.href + ); } this.forEachSelectors(function(el) { - this.parseDOM(el) - }, this) + this.parseDOM(el); + }, this); // Fire Events - trigger(document,"pjax:complete pjax:success", state.options) + trigger(document, "pjax:complete pjax:success", state.options); if (typeof state.options.analytics === "function") { - state.options.analytics() + 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 + var a = document.createElement("a"); + a.href = this.state.href; if (a.hash) { - var name = a.hash.slice(1) - name = decodeURIComponent(name) + var name = a.hash.slice(1); + name = decodeURIComponent(name); - var curtop = 0 - var target = document.getElementById(name) || document.getElementsByName(name)[0] + 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 + curtop += target.offsetTop; - target = target.offsetParent - } while (target) + target = target.offsetParent; + } while (target); } } - window.scrollTo(0, curtop) - } - else if (state.options.scrollTo !== false) { + 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) + 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]) + } 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.js") +Pjax.isSupported = require("./lib/is-supported"); -// arguably could do `if( require("./lib/is-supported.js")()) {` but that might be a little to simple +// arguably could do `if( require("./lib/is-supported")()) {` but that might be a little to simple if (Pjax.isSupported()) { - module.exports = Pjax + module.exports = Pjax; } // if there isn’t required browser functions, returning stupid api else { - var stupidPjax = noop + var stupidPjax = noop; for (var key in Pjax.prototype) { - if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") { - stupidPjax[key] = noop + if ( + Pjax.prototype.hasOwnProperty(key) && + typeof Pjax.prototype[key] === "function" + ) { + stupidPjax[key] = noop; } } - module.exports = stupidPjax + module.exports = stupidPjax; } diff --git a/lib/abort-request.js b/lib/abort-request.js index e68f01b..5166bd9 100644 --- a/lib/abort-request.js +++ b/lib/abort-request.js @@ -1,8 +1,8 @@ -var noop = require("./util/noop") +var noop = require("./util/noop"); module.exports = function(request) { if (request && request.readyState < 4) { - request.onreadystatechange = noop - request.abort() + request.onreadystatechange = noop; + request.abort(); } -} +}; diff --git a/lib/eval-script.js b/lib/eval-script.js index 73e0493..bf8cd29 100644 --- a/lib/eval-script.js +++ b/lib/eval-script.js @@ -1,42 +1,48 @@ 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") + 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) + console.log( + "Script contains document.write. Can’t be executed correctly. Code skipped ", + el + ); } - return false + return false; } - script.type = "text/javascript" + 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 + script.src = src; + script.async = false; // force synchronous loading of peripheral JS } if (code !== "") { try { - script.appendChild(document.createTextNode(code)) - } - catch (e) { + script.appendChild(document.createTextNode(code)); + } catch (e) { /* istanbul ignore next */ // old IEs have funky script nodes - script.text = code + script.text = code; } } // execute - parent.appendChild(script) + parent.appendChild(script); // avoid pollution only in head or body tags - if ((parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) && parent.contains(script)) { - parent.removeChild(script) + if ( + (parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) && + parent.contains(script) + ) { + parent.removeChild(script); } - return true -} + return true; +}; diff --git a/lib/execute-scripts.js b/lib/execute-scripts.js index 089736f..9436af3 100644 --- a/lib/execute-scripts.js +++ b/lib/execute-scripts.js @@ -1,18 +1,18 @@ -var forEachEls = require("./foreach-els") -var evalScript = require("./eval-script") +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) + evalScript(el); } forEachEls(el.querySelectorAll("script"), function(script) { if (!script.type || script.type.toLowerCase() === "text/javascript") { if (script.parentNode) { - script.parentNode.removeChild(script) + script.parentNode.removeChild(script); } - evalScript(script) + evalScript(script); } - }) -} + }); +}; diff --git a/lib/foreach-els.js b/lib/foreach-els.js index bd2de43..f4bd07d 100644 --- a/lib/foreach-els.js +++ b/lib/foreach-els.js @@ -1,9 +1,13 @@ /* 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) + 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) -} + return fn.call(context, els); +}; diff --git a/lib/foreach-selectors.js b/lib/foreach-selectors.js index 7a8479c..fd57099 100644 --- a/lib/foreach-selectors.js +++ b/lib/foreach-selectors.js @@ -1,8 +1,8 @@ -var forEachEls = require("./foreach-els") +var forEachEls = require("./foreach-els"); module.exports = function(selectors, cb, context, DOMcontext) { - DOMcontext = DOMcontext || document + DOMcontext = DOMcontext || document; selectors.forEach(function(selector) { - forEachEls(DOMcontext.querySelectorAll(selector), cb, context) - }) -} + forEachEls(DOMcontext.querySelectorAll(selector), cb, context); + }); +}; diff --git a/lib/is-supported.js b/lib/is-supported.js index 697479c..5bf862b 100644 --- a/lib/is-supported.js +++ b/lib/is-supported.js @@ -1,8 +1,12 @@ module.exports = function() { // Borrowed wholesale from https://github.com/defunkt/jquery-pjax - return window.history && + 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)/) -} + !navigator.userAgent.match( + /((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/ + ) + ); +}; diff --git a/lib/parse-options.js b/lib/parse-options.js index 228522d..414b72e 100644 --- a/lib/parse-options.js +++ b/lib/parse-options.js @@ -1,41 +1,53 @@ /* global _gaq: true, ga: true */ -var defaultSwitches = require("./switches") +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 + 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 + options.switches.head = defaultSwitches.switchElementsAlt; } if (!options.switches.body) { - options.switches.body = defaultSwitches.switchElementsAlt + options.switches.body = defaultSwitches.switchElementsAlt; } - return options -} + return options; +}; /* istanbul ignore next */ function defaultAnalytics() { if (window._gaq) { - _gaq.push(["_trackPageview"]) + _gaq.push(["_trackPageview"]); } if (window.ga) { - ga("send", "pageview", {page: location.pathname, title: document.title}) + ga("send", "pageview", { page: location.pathname, title: document.title }); } } diff --git a/lib/proto/handle-response.js b/lib/proto/handle-response.js index 389a951..e8196a8 100644 --- a/lib/proto/handle-response.js +++ b/lib/proto/handle-response.js @@ -1,70 +1,71 @@ -var clone = require("../util/clone.js") -var newUid = require("../uniqueid.js") -var trigger = require("../events/trigger.js") +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 + 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) + trigger(document, "pjax:complete pjax:error", options); - return + return; } // push scroll position to history - var currentState = window.history.state || {} - window.history.replaceState({ + 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] + scrollPos: [ + document.documentElement.scrollLeft || document.body.scrollLeft, + document.documentElement.scrollTop || document.body.scrollTop + ] }, - document.title, window.location) + document.title, + window.location.href + ); // Check for redirects - var oldHref = href + var oldHref = href; if (request.responseURL) { if (href !== request.responseURL) { - 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") + } 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 + 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 + a.hash = oldHash; + href = a.href; } - this.state.href = href - this.state.options = options + this.state.href = href; + this.state.options = options; try { - this.loadContent(responseText, options) - } - catch (e) { - trigger(document, "pjax:error", options) + 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) + console.error("Pjax switch fail: ", e); } - return this.latestChance(href) - } - else { - throw e + return this.latestChance(href); + } else { + throw e; } } -} +}; diff --git a/lib/send-request.js b/lib/send-request.js index 916d6c3..ad7b1b1 100644 --- a/lib/send-request.js +++ b/lib/send-request.js @@ -1,78 +1,86 @@ 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 + 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 + 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) + 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) - } + console.log(e); + callback(null, request, location, options); + }; request.ontimeout = function() { - callback(null, request, location, options) - } + 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("&") + 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] + location = location.split("?")[0]; // Append new query string - location += "?" + queryString - break + location += "?" + queryString; + break; case "POST": // Send query string as request payload - requestPayload = queryString - break + requestPayload = queryString; + break; } - } - else if (formData) { - requestPayload = formData + } 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()) + 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)) + 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.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); } - request.send(requestPayload) + request.send(requestPayload); - return request -} + return request; +}; diff --git a/lib/switches-selectors.js b/lib/switches-selectors.js index 1877883..3ce680d 100644 --- a/lib/switches-selectors.js +++ b/lib/switches-selectors.js @@ -1,37 +1,59 @@ -var forEachEls = require("./foreach-els") +var forEachEls = require("./foreach-els"); -var defaultSwitches = require("./switches") +var defaultSwitches = require("./switches"); -module.exports = function(switches, switchesOptions, selectors, fromEl, toEl, options) { - var switchesQueue = [] +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) + var newEls = fromEl.querySelectorAll(selector); + var oldEls = toEl.querySelectorAll(selector); if (this.log) { - this.log("Pjax switch", selector, newEls, oldEls) + 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 + 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) - } + 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) + 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) + switchesQueue.push(callback); + }, + this + ); + }, this); - this.state.numPendingSwitches = switchesQueue.length + this.state.numPendingSwitches = switchesQueue.length; switchesQueue.forEach(function(queuedSwitch) { - queuedSwitch() - }) -} + queuedSwitch(); + }); +}; diff --git a/lib/switches.js b/lib/switches.js index 0502fc2..ce89fe5 100644 --- a/lib/switches.js +++ b/lib/switches.js @@ -1,122 +1,140 @@ -var on = require("./events/on.js") +var on = require("./events/on"); module.exports = { outerHTML: function(oldEl, newEl) { - oldEl.outerHTML = newEl.outerHTML - this.onSwitch() + oldEl.outerHTML = newEl.outerHTML; + this.onSwitch(); }, innerHTML: function(oldEl, newEl) { - oldEl.innerHTML = newEl.innerHTML + oldEl.innerHTML = newEl.innerHTML; if (newEl.className === "") { - oldEl.removeAttribute("class") - } - else { - oldEl.className = newEl.className + oldEl.removeAttribute("class"); + } else { + oldEl.className = newEl.className; } - this.onSwitch() + this.onSwitch(); }, switchElementsAlt: function(oldEl, newEl) { - oldEl.innerHTML = newEl.innerHTML + oldEl.innerHTML = newEl.innerHTML; // Copy attributes from the new element to the old one if (newEl.hasAttributes()) { - var attrs = newEl.attributes + var attrs = newEl.attributes; for (var i = 0; i < attrs.length; i++) { - oldEl.attributes.setNamedItem(attrs[i].cloneNode()) + oldEl.attributes.setNamedItem(attrs[i].cloneNode()); } } - this.onSwitch() + this.onSwitch(); }, // Equivalent to outerHTML(), but doesn't require switchElementsAlt() for and replaceNode: function(oldEl, newEl) { - oldEl.parentNode.replaceChild(newEl, oldEl) - this.onSwitch() + 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 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 + 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); } + }); - 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.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 - elsToAdd = null // free memory - elsToRemove = null // free memory + // this is to trigger some repaint (example: picturefill) + this.onSwitch(); + } + }.bind(this); - // this is to trigger some repaint (example: picturefill) - this.onSwitch() - } - }.bind(this) - - switchOptions = switchOptions || {} + switchOptions = switchOptions || {}; forEach.call(oldEl.childNodes, function(el) { - elsToRemove.push(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.className = el.className.replace( + el.getAttribute("data-pjax-classes"), + "" + ); + el.removeAttribute("data-pjax-classes"); } - el.classList.add("js-Pjax-remove") + el.classList.add("js-Pjax-remove"); if (switchOptions.callbacks && switchOptions.callbacks.removeElement) { - switchOptions.callbacks.removeElement(el) + switchOptions.callbacks.removeElement(el); } if (switchOptions.classNames) { - el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward) + el.className += + " " + + switchOptions.classNames.remove + + " " + + (options.backward + ? switchOptions.classNames.backward + : switchOptions.classNames.forward); } - animatedElsNumber++ - on(el, animationEventNames, sexyAnimationEnd, true) + animatedElsNumber++; + on(el, animationEventNames, sexyAnimationEnd, true); } - }) + }); forEach.call(newEl.childNodes, function(el) { if (el.classList) { - var addClasses = "" + var addClasses = ""; if (switchOptions.classNames) { - addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward) + 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) + 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) + 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) + oldEl.className = newEl.className; + oldEl.appendChild(fragToAppend); } -} +}; diff --git a/lib/uniqueid.js b/lib/uniqueid.js index 52a9bf0..81b8238 100644 --- a/lib/uniqueid.js +++ b/lib/uniqueid.js @@ -1,8 +1,8 @@ module.exports = (function() { - var counter = 0 + var counter = 0; return function() { - var id = ("pjax" + (new Date().getTime())) + "_" + counter - counter++ - return id - } -})() + var id = "pjax" + new Date().getTime() + "_" + counter; + counter++; + return id; + }; +})(); diff --git a/tests/lib/parse-options.js b/tests/lib/parse-options.js index 9c7785a..36d2728 100644 --- a/tests/lib/parse-options.js +++ b/tests/lib/parse-options.js @@ -1,49 +1,50 @@ -var tape = require("tape") +var tape = require("tape"); + +var parseOptions = require("../../lib/parse-options.js"); -var parseOptions = require("../../lib/parse-options.js") tape("test parse initalization options function", function(t) { t.test("- default options", function(t) { - var pjax = {} - pjax.options = parseOptions({}) + var pjax = {}; + pjax.options = parseOptions({}); - t.equal(pjax.options.elements, "a[href], form[action]") - t.equal(pjax.options.selectors.length, 2, "selectors length") - t.equal(pjax.options.selectors[0], "title") - t.equal(pjax.options.selectors[1], ".js-Pjax") + t.equal(pjax.options.elements, "a[href], form[action]"); + t.equal(pjax.options.selectors.length, 2, "selectors length"); + t.equal(pjax.options.selectors[0], "title"); + t.equal(pjax.options.selectors[1], ".js-Pjax"); - t.equal(typeof pjax.options.switches, "object") - t.equal(Object.keys(pjax.options.switches).length, 2)// head and body + t.equal(typeof pjax.options.switches, "object"); + t.equal(Object.keys(pjax.options.switches).length, 2); // head and body - t.equal(typeof pjax.options.switchesOptions, "object") - t.equal(Object.keys(pjax.options.switchesOptions).length, 0) + t.equal(typeof pjax.options.switchesOptions, "object"); + t.equal(Object.keys(pjax.options.switchesOptions).length, 0); - t.equal(pjax.options.history, true) - t.equal(typeof pjax.options.analytics, "function") - t.equal(pjax.options.scrollTo, 0) - t.equal(pjax.options.scrollRestoration, true) - t.equal(pjax.options.cacheBust, true) - t.equal(pjax.options.debug, false) - t.equal(pjax.options.currentUrlFullReload, false) - t.end() - }) + t.equal(pjax.options.history, true); + t.equal(typeof pjax.options.analytics, "function"); + t.equal(pjax.options.scrollTo, 0); + t.equal(pjax.options.scrollRestoration, true); + t.equal(pjax.options.cacheBust, true); + t.equal(pjax.options.debug, false); + t.equal(pjax.options.currentUrlFullReload, false); + t.end(); + }); // verify analytics always ends up as a function even when passed not a function t.test("- analytics is a function", function(t) { - var pjax = {} - pjax.options = parseOptions({analytics: "some string"}) + var pjax = {}; + pjax.options = parseOptions({ analytics: "some string" }); - t.deepEqual(typeof pjax.options.analytics, "function") - t.end() - }) + t.deepEqual(typeof pjax.options.analytics, "function"); + t.end(); + }); // verify that the value false for scrollTo is not squashed t.test("- scrollTo remains false", function(t) { - var pjax = {} - pjax.options = parseOptions({scrollTo: false}) + var pjax = {}; + pjax.options = parseOptions({ scrollTo: false }); - t.deepEqual(pjax.options.scrollTo, false) - t.end() - }) + t.deepEqual(pjax.options.scrollTo, false); + t.end(); + }); - t.end() -}) + t.end(); +}); diff --git a/tests/setup.js b/tests/setup.js index 120fe61..f8c78c2 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,6 +1,6 @@ var jsdomOptions = { url: "https://example.org/", runScripts: "dangerously" -} +}; -require("jsdom-global")("", jsdomOptions) +require("jsdom-global")("", jsdomOptions);