From f7cce3c04d13e6325efa6876d86310ffdce0e1cb Mon Sep 17 00:00:00 2001 From: Anthony Hughes Date: Sat, 1 Oct 2016 19:23:57 +0200 Subject: [PATCH 01/92] Fixed issue where couldn't set history state to 'false' --- lib/proto/parse-options.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 5384e5b..ea765e1 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -6,7 +6,7 @@ module.exports = function(options){ this.options.selectors = this.options.selectors || ["title", ".js-Pjax"] this.options.switches = this.options.switches || {} this.options.switchesOptions = this.options.switchesOptions || {} - this.options.history = this.options.history || true + this.options.history = (typeof this.options.history === 'undefined') ? true : this.options.history this.options.analytics = this.options.analytics || function() { // options.backward or options.foward can be true or undefined // by default, we do track back/foward hit @@ -35,4 +35,4 @@ module.exports = function(options){ if (typeof options.analytics !== "function") { options.analytics = function() {} } -} \ No newline at end of file +} -- 2.49.1 From ace7609784c2a9f4441e90ce9883e3475c10d842 Mon Sep 17 00:00:00 2001 From: Anthony Hughes Date: Sat, 8 Oct 2016 14:01:32 +0200 Subject: [PATCH 02/92] Fix regression issue for IE and older discovered at https://github.com/MoOx/pjax/pull/16/commits --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index efa2517..be13c30 100644 --- a/index.js +++ b/index.js @@ -81,7 +81,7 @@ Pjax.prototype = { }, loadContent: function(html, options) { - var tmpEl = document.implementation.createHTMLDocument() + var tmpEl = document.implementation.createHTMLDocument("") // parse HTML attributes to copy them // since we are forced to use documentElement.innerHTML (outerHTML can't be used for ) -- 2.49.1 From 6dffeba21ad7e165551a63465a7a0a61fadf5445 Mon Sep 17 00:00:00 2001 From: CPTechnikVX Date: Mon, 28 Nov 2016 15:39:14 +0100 Subject: [PATCH 03/92] Fix bug on IE11 preventing from ajax page refresh It used to be fixed a long time ago, but was perhaps merged away... --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index efa2517..be13c30 100644 --- a/index.js +++ b/index.js @@ -81,7 +81,7 @@ Pjax.prototype = { }, loadContent: function(html, options) { - var tmpEl = document.implementation.createHTMLDocument() + var tmpEl = document.implementation.createHTMLDocument("") // parse HTML attributes to copy them // since we are forced to use documentElement.innerHTML (outerHTML can't be used for ) -- 2.49.1 From 109e78347f02686b20f6cfc9c46f3a793cc21a58 Mon Sep 17 00:00:00 2001 From: Maxime Thirouin <157534+MoOx@users.noreply.github.com> Date: Thu, 14 Sep 2017 21:45:39 +0200 Subject: [PATCH 04/92] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f3c54cf..3b6a6b1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ Especially for user that have low bandwidth connection._ **No more full page reload. No more lots of HTTP request.** +--- + + Sponsor + +--- + ## Demo -- 2.49.1 From 86e5a2281a25c4e5e50c422a2cdbae2e83244685 Mon Sep 17 00:00:00 2001 From: markusfluer Date: Mon, 18 Sep 2017 14:13:39 +0200 Subject: [PATCH 05/92] Added support do do a push-state ajax request with forms --- index.js | 6 ++- lib/eval-script.js | 9 ++-- lib/execute-scripts.js | 5 ++ lib/proto/attach-form.js | 93 ++++++++++++++++++++++++++++++++ lib/proto/attach-link.js | 2 +- lib/proto/parse-element.js | 7 ++- lib/request.js | 17 ++++-- package.json | 1 + tests/lib/proto/attach-form.js | 78 +++++++++++++++++++++++++++ tests/lib/proto/parse-element.js | 10 ++-- 10 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 lib/proto/attach-form.js create mode 100644 tests/lib/proto/attach-form.js diff --git a/index.js b/index.js index be13c30..4d6039d 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,7 @@ var Pjax = function(options) { opt.url = st.state.url opt.title = st.state.title opt.history = false - + opt.requestOptions = {}; if (st.state.uid < this.lastUid) { opt.backward = true } @@ -55,6 +55,8 @@ Pjax.prototype = { attachLink: require("./lib/proto/attach-link.js"), + attachForm: require("./lib/proto/attach-form.js"), + forEachSelectors: function(cb, context, DOMcontext) { return require("./lib/foreach-selectors.js").bind(this)(this.options.selectors, cb, context, DOMcontext) }, @@ -151,7 +153,7 @@ Pjax.prototype = { trigger(document, "pjax:send", options); // Do the request - this.doRequest(href, function(html) { + this.doRequest(href, options.requestOptions, function(html) { // Fail if unable to load HTML via AJAX if (html === false) { trigger(document,"pjax:complete pjax:error", options) diff --git a/lib/eval-script.js b/lib/eval-script.js index d34436a..9591fd3 100644 --- a/lib/eval-script.js +++ b/lib/eval-script.js @@ -2,7 +2,7 @@ module.exports = function(el) { // console.log("going to execute script", el) var code = (el.text || el.textContent || el.innerHTML || "") - var head = document.querySelector("head") || document.documentElement + var parent = el.parentNode || document.querySelector("head") || document.documentElement var script = document.createElement("script") if (code.match("document.write")) { @@ -22,8 +22,11 @@ module.exports = function(el) { } // execute - head.insertBefore(script, head.firstChild) - head.removeChild(script) // avoid pollution + parent.appendChild(script); + // avoid pollution only in head or body tags + if (["head","body"].indexOf(parent.tagName.toLowerCase()) > 0) { + parent.removeChild(script) + } return true } diff --git a/lib/execute-scripts.js b/lib/execute-scripts.js index d6392b9..04ece08 100644 --- a/lib/execute-scripts.js +++ b/lib/execute-scripts.js @@ -4,6 +4,11 @@ var evalScript = require("./eval-script") // Needed since innerHTML does not run scripts module.exports = function(el) { // console.log("going to execute scripts for ", 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) { diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js new file mode 100644 index 0000000..06fa447 --- /dev/null +++ b/lib/proto/attach-form.js @@ -0,0 +1,93 @@ +require("../polyfills/Function.prototype.bind") + +var on = require("../events/on") +var clone = require("../clone") + +var attrClick = "data-pjax-click-state" + +var formAction = function(el, event){ + + this.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', this.options.requestOptions.requestUrl); + + // Ignore external links. + if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) { + el.setAttribute(attrClick, "external"); + return + } + + // Ignore click if we are on an anchor on the same page + if (virtLinkElement.pathname === window.location.pathname && virtLinkElement.hash.length > 0) { + el.setAttribute(attrClick, "anchor-present"); + return + } + + // Ignore empty anchor "foo.html#" + if (virtLinkElement.href === window.location.href.split("#")[0] + "#") { + el.setAttribute(attrClick, "anchor-empty") + return + } + + // if declared as a full reload, just normally submit the form + if ( this.options.currentUrlFullReload) { + el.setAttribute(attrClick, "reload"); + return; + } + + event.preventDefault() + + var paramObject = []; + for(var elementKey in el.elements) { + var element = el.elements[elementKey]; + if (!!element.name && element.attributes !== undefined && element.tagName.toLowerCase() !== 'button'){ + if ((element.attributes.type !== 'checkbox' && element.attributes.type !== 'radio') || element.checked) { + paramObject.push({ name: encodeURIComponent(element.name), value: encodeURIComponent(element.value)}); + } + } + } + + //Creating a getString + var paramsString = (paramObject.map(function(value){return value.name+"="+value.value;})).join('&'); + + this.options.requestOptions.requestPayload = paramObject; + this.options.requestOptions.requestPayloadString = paramsString; + + el.setAttribute(attrClick, "submit"); + + this.loadUrl(virtLinkElement.href, clone(this.options)) + +}; + +var isDefaultPrevented = function(event) { + return event.defaultPrevented || event.returnValue === false; +}; + + +module.exports = function(el) { + var that = this + + on(el, "submit", function(event) { + if (isDefaultPrevented(event)) { + return + } + + formAction.call(that, el, event) + }) + + on(el, "keyup", function(event) { + if (isDefaultPrevented(event)) { + return + } + + + if (event.keyCode == 13) { + formAction.call(that, el, event) + } + }.bind(this)) +} diff --git a/lib/proto/attach-link.js b/lib/proto/attach-link.js index 73e6986..b093f3a 100644 --- a/lib/proto/attach-link.js +++ b/lib/proto/attach-link.js @@ -51,7 +51,7 @@ var linkAction = function(el, event) { this.reload() return } - + this.options.requestOptions = this.options.requestOptions || {}; el.setAttribute(attrClick, "load") this.loadUrl(el.href, clone(this.options)) } diff --git a/lib/proto/parse-element.js b/lib/proto/parse-element.js index 736fdd7..af88ca3 100644 --- a/lib/proto/parse-element.js +++ b/lib/proto/parse-element.js @@ -7,8 +7,11 @@ module.exports = function(el) { } break - case "form": - throw "Pjax doesnt support
yet." + case "form": + // only attach link if el does not already have link attached + if (!el.hasAttribute('data-pjax-click-state')) { + this.attachForm(el) + } break default: diff --git a/lib/request.js b/lib/request.js index 892e28d..da83e71 100644 --- a/lib/request.js +++ b/lib/request.js @@ -1,4 +1,7 @@ -module.exports = function(location, callback) { +module.exports = function(location, options, callback) { + options = options || {}; + var requestMethod = options.requestMethod || "GET"; + var requestPayload = options.requestPayloadString || null; var request = new XMLHttpRequest() request.onreadystatechange = function() { @@ -17,8 +20,16 @@ module.exports = function(location, callback) { location += (!/[?&]/.test(location) ? "?" : "&") + new Date().getTime() } - request.open("GET", location, true) + request.open(requestMethod.toUpperCase(), location, true) request.setRequestHeader("X-Requested-With", "XMLHttpRequest") - request.send(null) + + // Add the request payload if available + if (options.requestPayloadString != undefined && options.requestPayloadString != "") { + // Send the proper header information along with the request + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } + + request.send(requestPayload) + return request } diff --git a/package.json b/package.json index 6d1be88..bef1b41 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "scripts": { "lint": "jscs **/*.js && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", + "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", "tests": "testling", "test": "npm run lint && npm run standalone && npm run tests", "test--html": "testling --html > tests/scripts/index.html", diff --git a/tests/lib/proto/attach-form.js b/tests/lib/proto/attach-form.js new file mode 100644 index 0000000..d23a4da --- /dev/null +++ b/tests/lib/proto/attach-form.js @@ -0,0 +1,78 @@ +var tape = require("tape") + +var on = require("../../../lib/events/on") +var trigger = require("../../../lib/events/trigger") +var attachForm = require("../../../lib/proto/attach-form") + +var form = document.createElement("form") +var attr = "data-pjax-click-state" +var preventDefault = function(e) { e.preventDefault() } + +tape("test attach form prototype method", function(t) { + t.plan(7) + + attachForm.call({ + options: {}, + reload: function() { + t.equal(form.getAttribute(attr), "reload", "triggering a simple reload will just submit the form") + }, + loadUrl: function() { + t.equal(form.getAttribute(attr), "submit", "triggering a post to the next page") + } + }, form) + + var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search + + form.action = "http://external.com/" + trigger(form, "submit") + t.equal(form.getAttribute(attr), "external", "external url stop behavior") + + form.action = internalUri + "#anchor" + trigger(form, "submit") + t.equal(form.getAttribute(attr), "anchor-present", "internal anchor stop behavior") + + window.location.hash = "#anchor" + form.action = internalUri + "#another-anchor" + trigger(form, "submit") + t.notEqual(form.getAttribute(attr), "anchor", "differents anchors stop behavior") + window.location.hash = "" + + form.action = internalUri + "#" + trigger(form, "submit") + t.equal(form.getAttribute(attr), "anchor-empty", "empty anchor stop behavior") + + form.action = internalUri + trigger(form, "submit") + // see reload defined above + + form.action = window.location.protocol + "//" + window.location.host + "/internal" + form.method = 'POST' + trigger(form, "submit") + // see post defined above + + form.action = window.location.protocol + "//" + window.location.host + "/internal" + form.method = 'GET' + trigger(form, "submit") + // see post defined above + + t.end() +}) + +tape("test attach form preventDefaulted events", function(t) { + var callbacked = false + var form = document.createElement("form") + + attachForm.call({ + options: {}, + loadUrl: function() { + callbacked = true + } + }, form) + + form.action = "#" + on(form, "submit", preventDefault) + trigger(form, "submit") + t.equal(callbacked, false, "events that are preventDefaulted should not fire callback") + + t.end() +}) diff --git a/tests/lib/proto/parse-element.js b/tests/lib/proto/parse-element.js index f9b760f..082c6ac 100644 --- a/tests/lib/proto/parse-element.js +++ b/tests/lib/proto/parse-element.js @@ -1,17 +1,21 @@ var tape = require("tape") var parseElement = require("../../../lib/proto/parse-element") -var protoMock = {attachLink: function() { return true}} +var protoMock = { + attachLink: function() { return true }, + attachForm: function() { return true } +} + tape("test parse element prototype method", function(t) { t.doesNotThrow(function() { var a = document.createElement("a") parseElement.call(protoMock, a) }, " element can be parsed") - t.throws(function() { + t.doesNotThrow(function() { var form = document.createElement("form") parseElement.call(protoMock, form) - }, " cannot be used (for now)") + }, " element can be parsed") t.end() }) -- 2.49.1 From b17457f5a27d076bad4f617b4501acc131be5c47 Mon Sep 17 00:00:00 2001 From: markusfluer Date: Mon, 18 Sep 2017 14:23:44 +0200 Subject: [PATCH 06/92] Fixed tests for request.js --- tests/lib/request.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/request.js b/tests/lib/request.js index ec0639a..b53f754 100644 --- a/tests/lib/request.js +++ b/tests/lib/request.js @@ -21,7 +21,7 @@ tape("test xhr request", function(t) { cacheBust: true, }, }); - var r = requestCacheBust("https://api.github.com/", function(result) { + var r = requestCacheBust("https://api.github.com/", {}, function(result) { t.equal(r.responseURL.indexOf("?"), 23, "XHR URL is cache-busted when configured to be") try { result = JSON.parse(result) @@ -39,7 +39,7 @@ tape("test xhr request", function(t) { cacheBust: false, }, }); - var r = requestNoCacheBust("https://api.github.com/", function() { + var r = requestNoCacheBust("https://api.github.com/", {}, function() { t.equal(r.responseURL, "https://api.github.com/", "XHR URL is left untouched") t.end() }) -- 2.49.1 From 09f14fc86cd161f3a27e988b56212322b08e2a16 Mon Sep 17 00:00:00 2001 From: markusfluer Date: Thu, 2 Nov 2017 12:40:30 +0100 Subject: [PATCH 07/92] Added evaluation of remote script tags --- lib/eval-script.js | 22 ++++++++++++++++------ lib/execute-scripts.js | 4 ++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/eval-script.js b/lib/eval-script.js index 9591fd3..232090d 100644 --- a/lib/eval-script.js +++ b/lib/eval-script.js @@ -2,6 +2,7 @@ module.exports = function(el) { // console.log("going to execute script", 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") @@ -13,12 +14,21 @@ module.exports = function(el) { } script.type = "text/javascript" - try { - script.appendChild(document.createTextNode(code)) + + if (src != "") { + script.src = src; + script.onload = function() { document.dispatchEvent((new Event("pjax:complete"))); } + script.async = false; // force asynchronous loading of peripheral js } - catch (e) { - // old IEs have funky script nodes - script.text = code + + if (code != "") { + try { + script.appendChild(document.createTextNode(code)) + } + catch (e) { + // old IEs have funky script nodes + script.text = code + } } // execute @@ -28,5 +38,5 @@ module.exports = function(el) { parent.removeChild(script) } - return true + return true; } diff --git a/lib/execute-scripts.js b/lib/execute-scripts.js index 04ece08..d5fab5a 100644 --- a/lib/execute-scripts.js +++ b/lib/execute-scripts.js @@ -14,7 +14,7 @@ module.exports = function(el) { if (script.parentNode) { script.parentNode.removeChild(script) } - evalScript(script) + evalScript(script); } - }) + }); } -- 2.49.1 From af57adaafba5613f3adc46c3e8460ee7413ff102 Mon Sep 17 00:00:00 2001 From: markusfluer Date: Thu, 2 Nov 2017 13:02:17 +0100 Subject: [PATCH 08/92] Fixed #77 Fails on Internet Explorer --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 4d6039d..e628021 100644 --- a/index.js +++ b/index.js @@ -83,7 +83,7 @@ Pjax.prototype = { }, loadContent: function(html, options) { - var tmpEl = document.implementation.createHTMLDocument("") + 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 ) -- 2.49.1 From 352e7114b68354b36bba86f5934f58661cc67cad Mon Sep 17 00:00:00 2001 From: Maxime Thirouin <157534+MoOx@users.noreply.github.com> Date: Mon, 4 Dec 2017 14:53:01 +0100 Subject: [PATCH 09/92] =?UTF-8?q?Bye=20codesponsor=20=F0=9F=98=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 3b6a6b1..2e6cf08 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,6 @@ Especially for user that have low bandwidth connection._ **No more full page reload. No more lots of HTTP request.** ---- - - Sponsor - ---- - - ## Demo [You can see this running on my website](http://moox.io), with sexy CSS animations when switching pages. -- 2.49.1 From 75eddfcab6b4617aa88651e8a2a0c9a12e275ede Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Tue, 19 Dec 2017 07:49:26 -0500 Subject: [PATCH 10/92] Add default switches to Pjax.switches (#92) Fixes #68 and reverts #74 --- README.md | 2 +- index.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e6cf08..6b706c9 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ with or without [WOW.js](https://github.com/matthieua/WOW). new Pjax({ selectors: ["title", ".js-Pjax"], switches: { - ".js-Pjax": require("pjax/lib/switches.js").sideBySide + ".js-Pjax": Pjax.switches.sideBySide }, switchesOptions: { ".js-Pjax": { diff --git a/index.js b/index.js index e628021..b7f7b3b 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,8 @@ var on = require("./lib/events/on.js") // var off = require("./lib/events/on.js") var trigger = require("./lib/events/trigger.js") +var defaultSwitches = require("./lib/switches") + var Pjax = function(options) { this.firstrun = true @@ -42,6 +44,8 @@ var Pjax = function(options) { }.bind(this)) } +Pjax.switches = defaultSwitches + Pjax.prototype = { log: require("./lib/proto/log.js"), -- 2.49.1 From 1e40a0d70bbfa2618f083a527551e61bb43e312a Mon Sep 17 00:00:00 2001 From: Bradley B Smith Date: Tue, 19 Dec 2017 07:56:30 -0500 Subject: [PATCH 11/92] Send the X-PJAX header with request (#80) Make it more compatible with the jQuery PJAX and lets the server choose to optimize the response. Sending the list of selectors would be nice, too. --- lib/request.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/request.js b/lib/request.js index da83e71..2054b6d 100644 --- a/lib/request.js +++ b/lib/request.js @@ -22,6 +22,7 @@ module.exports = function(location, options, callback) { request.open(requestMethod.toUpperCase(), location, true) request.setRequestHeader("X-Requested-With", "XMLHttpRequest") + request.setRequestHeader("X-PJAX", "true") // Add the request payload if available if (options.requestPayloadString != undefined && options.requestPayloadString != "") { -- 2.49.1 From be5d58d550c87458a40c28ff6b8c2f78f0946b4e Mon Sep 17 00:00:00 2001 From: Oskar Date: Tue, 19 Dec 2017 13:58:22 +0100 Subject: [PATCH 12/92] Asynchronous switch functions (Make sure the DOM is parsed after switching) (#79) * Make sure the DOM is parsed after switching * Fix reload-link in the example --- example/example.js | 3 ++- example/index.html | 2 +- example/page2.html | 2 +- index.js | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/example/example.js b/example/example.js index 9441179..f52f8bf 100644 --- a/example/example.js +++ b/example/example.js @@ -19,7 +19,8 @@ document.addEventListener("pjax:success", function() { document.addEventListener("DOMContentLoaded", function() { var pjax = new Pjax({ - selectors: [".body"], + elements: [".js-Pjax"], + selectors: [".body"] // currentUrlFullReload: true, }) console.log("Pjax initialized.", pjax) diff --git a/example/index.html b/example/index.html index 8e259b9..cc95a19 100644 --- a/example/index.html +++ b/example/index.html @@ -10,7 +10,7 @@

Index

Hello. - Go to Page 2 and view your console to see Pjax events. + Go to Page 2 and view your console to see Pjax events. Clicking on this page will just reload the page entierly.
diff --git a/example/page2.html b/example/page2.html index b997abb..36d2e33 100644 --- a/example/page2.html +++ b/example/page2.html @@ -9,7 +9,7 @@

Page 2

- Hello. Go to Index. + Hello. Go to Index.
diff --git a/index.js b/index.js index b7f7b3b..bd7bc56 100644 --- a/index.js +++ b/index.js @@ -83,6 +83,7 @@ Pjax.prototype = { }, onSwitch: function() { + this.parseDOM(document) trigger(window, "resize scroll") }, -- 2.49.1 From a7b584c469d412ae7333a795a0ac8d3616a96a1f Mon Sep 17 00:00:00 2001 From: Maxime Thirouin Date: Tue, 19 Dec 2017 14:11:55 +0100 Subject: [PATCH 13/92] Add direct download link Ref #57 --- README.md | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 6b706c9..34f1659 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,22 @@ Especially for user that have low bandwidth connection._ [You can see this running on my website](http://moox.io), with sexy CSS animations when switching pages. +## Installation + +You can install pjax from **npm** + +```shell +$ npm install pjax +``` + +Pjax can obviously be downloaded directly. + +[https://unpkg.com/pjax/pjax.js](https://unpkg.com/pjax/pjax.js) + +## No dependencies + +_There is nothing you need. No jQuery or something._ + ## How Pjax works Pjax loads page using ajax & updates the browser's current url using pushState without reloading your page's layout or any resources (js, css), giving a fast page load. @@ -97,26 +113,6 @@ _Magic! For real!_ **There is completely no need to do anything on server side!* - Allow page transition with CSS animations, - Can be easily hacked since every method is public (so overridable) -## Installation - -You can install pjax from **npm** - -```shell -$ npm install pjax -``` - -Or using **bower** - -```shell -$ bower install pjax -``` - -Pjax can obviously be downloaded directly. - -## No dependencies - -_There is nothing you need. No jQuery or something._ - ## Compatibility Pjax only works with [browsers that support the `history.pushState` API](http://caniuse.com/#search=pushstate). -- 2.49.1 From 0781f820eebe433c84ce6eda2c96931670b0778c Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 19 Dec 2017 15:56:48 -0500 Subject: [PATCH 14/92] Add switchElementsAlt() to the default switches Fixes #83. --- lib/proto/parse-options.js | 6 ++++-- lib/switches.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 5384e5b..706a466 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -1,5 +1,7 @@ /* global _gaq: true, ga: true */ +var defaultSwitches = require("../switches") + module.exports = function(options){ this.options = options this.options.elements = this.options.elements || "a[href], form[action]" @@ -27,10 +29,10 @@ module.exports = function(options){ // if you set head.outerHTML, a new body tag is appended, so the dom get 2 body // & it break the switchFallback which replace head & body if (!this.options.switches.head) { - this.options.switches.head = this.switchElementsAlt + this.options.switches.head = defaultSwitches.switchElementsAlt } if (!this.options.switches.body) { - this.options.switches.body = this.switchElementsAlt + this.options.switches.body = defaultSwitches.switchElementsAlt } if (typeof options.analytics !== "function") { options.analytics = function() {} diff --git a/lib/switches.js b/lib/switches.js index 9f5fcec..d87ae0b 100644 --- a/lib/switches.js +++ b/lib/switches.js @@ -15,6 +15,20 @@ module.exports = { this.onSwitch() }, + switchElementsAlt: function(oldEl, newEl) { + oldEl.innerHTML = newEl.innerHTML + + // Copy attributes from the new element to the old one + if (newEl.hasAttributes()) { + const attrs = newEl.attributes; + for (var i = 0; i < attrs.length; i++) { + oldEl.attributes.setNamedItem(attrs[i]) + } + } + + this.onSwitch() + }, + sideBySide: function(oldEl, newEl, options, switchOptions) { var forEach = Array.prototype.forEach var elsToRemove = [] -- 2.49.1 From d3d5ef7a11186d2da717e5a5beb503b38f8df2d8 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Thu, 21 Dec 2017 00:12:09 -0500 Subject: [PATCH 15/92] Pass the element that triggered Pjax to the pjax:send event Closes #62. --- lib/proto/attach-form.js | 4 +++- lib/proto/attach-link.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js index 06fa447..ef8a63a 100644 --- a/lib/proto/attach-form.js +++ b/lib/proto/attach-form.js @@ -60,7 +60,9 @@ var formAction = function(el, event){ el.setAttribute(attrClick, "submit"); - this.loadUrl(virtLinkElement.href, clone(this.options)) + const options = clone(this.options); + options.triggerElement = el; + this.loadUrl(virtLinkElement.href, options); }; diff --git a/lib/proto/attach-link.js b/lib/proto/attach-link.js index b093f3a..e48b702 100644 --- a/lib/proto/attach-link.js +++ b/lib/proto/attach-link.js @@ -53,7 +53,10 @@ var linkAction = function(el, event) { } this.options.requestOptions = this.options.requestOptions || {}; el.setAttribute(attrClick, "load") - this.loadUrl(el.href, clone(this.options)) + + const options = clone(this.options) + options.triggerElement = el + this.loadUrl(el.href, options) } var isDefaultPrevented = function(event) { -- 2.49.1 From 6491e3243787556664dc254dcc55b6af226b6786 Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Sun, 7 Jan 2018 23:56:11 -0500 Subject: [PATCH 16/92] Add an option to set a timeout for XHR requests (#95) Closes #24. --- README.md | 4 ++++ index.js | 1 + lib/proto/parse-options.js | 3 ++- lib/request.js | 5 +++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 34f1659..7e6a1ce 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,10 @@ Useful to debug page layout differences. When set to true, clicking on a link that point the current url trigger a full page reload. +##### `timeout` (Integer, default to 0) + +The timeout in milliseconds for the XHR requests. Set to 0 to disable the timeout. + ### Events Pjax fires a number of events regardless of how its invoked. diff --git a/index.js b/index.js index bd7bc56..24e3df5 100644 --- a/index.js +++ b/index.js @@ -158,6 +158,7 @@ Pjax.prototype = { trigger(document, "pjax:send", options); // Do the request + options.requestOptions.timeout = this.options.timeout this.doRequest(href, options.requestOptions, function(html) { // Fail if unable to load HTML via AJAX if (html === false) { diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 706a466..d773cad 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -23,6 +23,7 @@ module.exports = function(options){ this.options.scrollTo = (typeof this.options.scrollTo === 'undefined') ? 0 : this.options.scrollTo; this.options.cacheBust = (typeof this.options.cacheBust === 'undefined') ? true : this.options.cacheBust this.options.debug = this.options.debug || false + this.options.timeout = this.options.timeout || 0 // we can’t replace body.outerHTML or head.outerHTML // it create a bug where new body or new head are created in the dom @@ -37,4 +38,4 @@ module.exports = function(options){ if (typeof options.analytics !== "function") { options.analytics = function() {} } -} \ No newline at end of file +} diff --git a/lib/request.js b/lib/request.js index 2054b6d..299a136 100644 --- a/lib/request.js +++ b/lib/request.js @@ -15,12 +15,17 @@ module.exports = function(location, options, callback) { } } + request.ontimeout = function() { + callback(null, request) + } + // Add a timestamp as part of the query string if cache busting is enabled if (this.options.cacheBust) { location += (!/[?&]/.test(location) ? "?" : "&") + new Date().getTime() } request.open(requestMethod.toUpperCase(), location, true) + request.timeout = options.timeout request.setRequestHeader("X-Requested-With", "XMLHttpRequest") request.setRequestHeader("X-PJAX", "true") -- 2.49.1 From a2e6cfc0af51d463b9be0d10ca2430816bcf38d4 Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Mon, 8 Jan 2018 17:21:18 -0500 Subject: [PATCH 17/92] Fix tests (#96) Closes #63 - Switch from testling to jsdom for browser APIs - Switch from coverify to nyc for coverage reports - Clean up related dead code and tooling - Update Travis to use Node v6 and v8, since we need ES6 features for jsdom. --- .gitignore | 1 + .travis.yml | 9 +- package.json | 35 +- tests/index.html | 12496 ----------------------------------------- tests/index.js | 6 + tests/lib/events.js | 3 +- tests/lib/request.js | 10 +- 7 files changed, 28 insertions(+), 12532 deletions(-) delete mode 100644 tests/index.html create mode 100644 tests/index.js diff --git a/.gitignore b/.gitignore index ecd1403..2128eae 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ tests/scripts/index.html pjax.js +.nyc_output/ diff --git a/.travis.yml b/.travis.yml index 06ddbfa..5c51c69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,4 @@ language: "node_js" -before_script: - # testling use headless browser - # on travis-ci, firefox is the default one - # & it needs a display to works - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start +node_js: + - "6" + - "8" diff --git a/package.json b/package.json index bef1b41..48adb94 100644 --- a/package.json +++ b/package.json @@ -22,44 +22,29 @@ ], "devDependencies": { "browserify": "^3.46.0", - "coverify": "^1.0.6", "jscs": "^1.6.2", + "jsdom": "^11.5.1", + "jsdom-global": "^3.0.2", "jshint": "^2.5.6", "npmpub": "^3.1.0", + "nyc": "^11.4.1", "opn-cli": "^3.1.0", "serve": "1.4.0", - "tape": "^3.0.0", - "testling": "^1.6.1" + "tap-nyc": "^1.0.3", + "tap-spec": "^4.1.1", + "tape": "^3.0.0" }, "scripts": { "lint": "jscs **/*.js && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", - "tests": "testling", - "test": "npm run lint && npm run standalone && npm run tests", - "test--html": "testling --html > tests/scripts/index.html", - "coverage": "browserify -t coverify tests/**/*.js | testling | coverify", + "tests": "tape -r ./tests/index.js ./tests/**/*.js", + "test": "npm run lint && npm run tests | tap-spec", + "coverage-tests": "npm run tests | tap-nyc", + "coverage": "nyc -x \"tests/**\" npm run coverage-tests", "example": "opn http://localhost:3000/example/; serve .", "prepublish": "npm run standalone", "#release": "testling does not work in a process launch by npm... :facepalm:", "release": "echo \"npmpub --skip-test --dry && npm test && npmpub --skip-test --skip-cleanup\"" - }, - "testling": { - "files": "tests/**/*.js", - "browsers": [ - "ie/10..latest", - "firefox/4.0", - "firefox/latest", - "firefox/nightly", - "chrome/10", - "chrome/latest", - "chrome/canary", - "opera/12..latest", - "opera/next", - "safari/5.1..latest", - "ipad/6.0..latest", - "iphone/6.0..latest", - "android-browser/4.2..latest" - ] } } diff --git a/tests/index.html b/tests/index.html deleted file mode 100644 index a9ef972..0000000 --- a/tests/index.html +++ /dev/null @@ -1,12496 +0,0 @@ -

diff --git a/tests/index.js b/tests/index.js
new file mode 100644
index 0000000..120fe61
--- /dev/null
+++ b/tests/index.js
@@ -0,0 +1,6 @@
+var jsdomOptions = {
+  url: "https://example.org/",
+  runScripts: "dangerously"
+}
+
+require("jsdom-global")("", jsdomOptions)
diff --git a/tests/lib/events.js b/tests/lib/events.js
index 4eb7824..94fda00 100644
--- a/tests/lib/events.js
+++ b/tests/lib/events.js
@@ -102,7 +102,8 @@ tape("test events on top level elements", function(t) {
   el = window;
 
   el.className = ""
-  on(el, "click", classCb)
+  // With jsdom, the default this is global, not window, so we need to explicitly bind to window.
+  on(el, "click", classCb.bind(window))
   trigger(el, "click")
   t.equal(el.className, "on", "attached callback has been fired properly on window")
 
diff --git a/tests/lib/request.js b/tests/lib/request.js
index b53f754..f99763e 100644
--- a/tests/lib/request.js
+++ b/tests/lib/request.js
@@ -15,14 +15,16 @@ if (!('responseURL' in XMLHttpRequest.prototype)) {
 }
 
 tape("test xhr request", function(t) {
+  var url = "https://httpbin.org/get"
+
   t.test("- request is made, gets a result, and is cache-busted", function(t) {
     var requestCacheBust = request.bind({
       options: {
         cacheBust: true,
       },
     });
-    var r = requestCacheBust("https://api.github.com/", {}, function(result) {
-      t.equal(r.responseURL.indexOf("?"), 23, "XHR URL is cache-busted when configured to be")
+    var r = requestCacheBust(url, {}, function(result) {
+      t.equal(r.responseURL.indexOf("?"), url.length, "XHR URL is cache-busted when configured to be")
       try {
         result = JSON.parse(result)
       }
@@ -39,8 +41,8 @@ tape("test xhr request", function(t) {
         cacheBust: false,
       },
     });
-    var r = requestNoCacheBust("https://api.github.com/", {}, function() {
-      t.equal(r.responseURL, "https://api.github.com/", "XHR URL is left untouched")
+    var r = requestNoCacheBust(url, {}, function() {
+      t.equal(r.responseURL, url, "XHR URL is left untouched")
       t.end()
     })
   })
-- 
2.49.1


From e58644096468d1149ffaff3848b7375f3306a0f0 Mon Sep 17 00:00:00 2001
From: Behind The Math 
Date: Tue, 9 Jan 2018 00:44:04 -0500
Subject: [PATCH 18/92] Update JSCS includes and excludes

---
 .jscsrc      | 3 ++-
 package.json | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.jscsrc b/.jscsrc
index 29720b3..570b19e 100644
--- a/.jscsrc
+++ b/.jscsrc
@@ -1,6 +1,7 @@
 {
   "excludeFiles": [
-    "node_modules/**"
+    "node_modules/**",
+    "pjax.js"
   ],
   "fileExtensions": [
     ".js"
diff --git a/package.json b/package.json
index 48adb94..4fc395f 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
     "tape": "^3.0.0"
   },
   "scripts": {
-    "lint": "jscs **/*.js && jshint . --exclude-path .gitignore",
+    "lint": "jscs . && jshint . --exclude-path .gitignore",
     "standalone": "browserify index.js --standalone Pjax > pjax.js",
     "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js",
     "tests": "tape -r ./tests/index.js ./tests/**/*.js",
-- 
2.49.1


From c0d64e41b88d71151bb3c744e1176e12ac3743f0 Mon Sep 17 00:00:00 2001
From: Behind The Math 
Date: Tue, 9 Jan 2018 00:44:20 -0500
Subject: [PATCH 19/92] Fix linting errors

---
 index.js                         | 18 +++---
 lib/proto/attach-form.js         | 30 +++++-----
 lib/proto/parse-element.js       | 20 +++----
 lib/proto/parse-options.js       |  6 +-
 tests/lib/proto/attach-form.js   |  4 +-
 tests/lib/proto/parse-options.js | 97 +++++++++++++++++---------------
 tests/lib/request.js             |  4 +-
 tests/lib/switch-selectors.js    |  6 +-
 8 files changed, 95 insertions(+), 90 deletions(-)

diff --git a/index.js b/index.js
index 24e3df5..7794b1a 100644
--- a/index.js
+++ b/index.js
@@ -1,5 +1,5 @@
-var clone = require('./lib/clone.js')
-var executeScripts = require('./lib/execute-scripts.js')
+var clone = require("./lib/clone.js")
+var executeScripts = require("./lib/execute-scripts.js")
 
 var forEachEls = require("./lib/foreach-els.js")
 
@@ -71,12 +71,12 @@ Pjax.prototype = {
 
   // too much problem with the code below
   // + it’s too dangerous
-//   switchFallback: function(fromEl, toEl) {
-//     this.switchSelectors(["head", "body"], fromEl, toEl)
-//     // execute script when DOM is like it should be
-//     Pjax.executeScripts(document.querySelector("head"))
-//     Pjax.executeScripts(document.querySelector("body"))
-//   }
+  //   switchFallback: function(fromEl, toEl) {
+  //     this.switchSelectors(["head", "body"], fromEl, toEl)
+  //     // execute script when DOM is like it should be
+  //     Pjax.executeScripts(document.querySelector("head"))
+  //     Pjax.executeScripts(document.querySelector("body"))
+  //   }
 
   latestChance: function(href) {
     window.location = href
@@ -233,7 +233,7 @@ Pjax.prototype = {
 
 Pjax.isSupported = require("./lib/is-supported.js");
 
-//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.js")()) {` but that might be a little to simple
 if (Pjax.isSupported()) {
   module.exports = Pjax
 }
diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js
index ef8a63a..1fdb537 100644
--- a/lib/proto/attach-form.js
+++ b/lib/proto/attach-form.js
@@ -5,16 +5,15 @@ var clone = require("../clone")
 
 var attrClick = "data-pjax-click-state"
 
-var formAction = function(el, event){
-
+var formAction = function(el, event) {
   this.options.requestOptions = {
-    requestUrl : el.getAttribute('action') || window.location.href,
-    requestMethod : el.getAttribute('method') || 'GET',
+    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', this.options.requestOptions.requestUrl);
+  // create a testable virtual link of the form action
+  var virtLinkElement = document.createElement("a");
+  virtLinkElement.setAttribute("href", this.options.requestOptions.requestUrl);
 
   // Ignore external links.
   if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) {
@@ -35,7 +34,7 @@ var formAction = function(el, event){
   }
 
   // if declared as a full reload, just normally submit the form
-  if ( this.options.currentUrlFullReload) {
+  if (this.options.currentUrlFullReload) {
     el.setAttribute(attrClick, "reload");
     return;
   }
@@ -43,17 +42,19 @@ var formAction = function(el, event){
   event.preventDefault()
 
   var paramObject = [];
-  for(var elementKey in el.elements) {
+  for (var elementKey in el.elements) {
     var element = el.elements[elementKey];
-    if (!!element.name && element.attributes !== undefined && element.tagName.toLowerCase() !== 'button'){
-      if ((element.attributes.type !== 'checkbox' && element.attributes.type !== 'radio') || element.checked) {
-        paramObject.push({ name: encodeURIComponent(element.name), value: encodeURIComponent(element.value)});
+    // jscs:disable disallowImplicitTypeConversion
+    if (!!element.name && element.attributes !== undefined && element.tagName.toLowerCase() !== "button") {
+      // jscs:enable disallowImplicitTypeConversion
+      if ((element.attributes.type !== "checkbox" && element.attributes.type !== "radio") || element.checked) {
+        paramObject.push({name: encodeURIComponent(element.name), value: encodeURIComponent(element.value)});
       }
     }
   }
 
-  //Creating a getString
-  var paramsString = (paramObject.map(function(value){return value.name+"="+value.value;})).join('&');
+  // Creating a getString
+  var paramsString = (paramObject.map(function(value) {return value.name + "=" + value.value;})).join("&");
 
   this.options.requestOptions.requestPayload = paramObject;
   this.options.requestOptions.requestPayloadString = paramsString;
@@ -63,7 +64,6 @@ var formAction = function(el, event){
   const options = clone(this.options);
   options.triggerElement = el;
   this.loadUrl(virtLinkElement.href, options);
-
 };
 
 var isDefaultPrevented = function(event) {
diff --git a/lib/proto/parse-element.js b/lib/proto/parse-element.js
index af88ca3..e79b4e0 100644
--- a/lib/proto/parse-element.js
+++ b/lib/proto/parse-element.js
@@ -1,20 +1,20 @@
 module.exports = function(el) {
   switch (el.tagName.toLowerCase()) {
-  case "a":
-    // only attach link if el does not already have link attached
-    if (!el.hasAttribute('data-pjax-click-state')) {
-      this.attachLink(el)
-    }
-    break
+    case "a":
+      // only attach link if el does not already have link attached
+      if (!el.hasAttribute("data-pjax-click-state")) {
+        this.attachLink(el)
+      }
+      break
 
     case "form":
       // only attach link if el does not already have link attached
-      if (!el.hasAttribute('data-pjax-click-state')) {
+      if (!el.hasAttribute("data-pjax-click-state")) {
         this.attachForm(el)
       }
-    break
+      break
 
-  default:
-    throw "Pjax can only be applied on  or  submit"
+    default:
+      throw "Pjax can only be applied on  or  submit"
   }
 }
diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js
index d773cad..fce3ec8 100644
--- a/lib/proto/parse-options.js
+++ b/lib/proto/parse-options.js
@@ -2,7 +2,7 @@
 
 var defaultSwitches = require("../switches")
 
-module.exports = function(options){
+module.exports = function(options) {
   this.options = options
   this.options.elements = this.options.elements || "a[href], form[action]"
   this.options.selectors = this.options.selectors || ["title", ".js-Pjax"]
@@ -20,8 +20,8 @@ module.exports = function(options){
       ga("send", "pageview", {page: location.pathname, title: document.title})
     }
   }
-  this.options.scrollTo = (typeof this.options.scrollTo === 'undefined') ? 0 : this.options.scrollTo;
-  this.options.cacheBust = (typeof this.options.cacheBust === 'undefined') ? true : this.options.cacheBust
+  this.options.scrollTo = (typeof this.options.scrollTo === "undefined") ? 0 : this.options.scrollTo;
+  this.options.cacheBust = (typeof this.options.cacheBust === "undefined") ? true : this.options.cacheBust
   this.options.debug = this.options.debug || false
   this.options.timeout = this.options.timeout || 0
 
diff --git a/tests/lib/proto/attach-form.js b/tests/lib/proto/attach-form.js
index d23a4da..701b89f 100644
--- a/tests/lib/proto/attach-form.js
+++ b/tests/lib/proto/attach-form.js
@@ -46,12 +46,12 @@ tape("test attach form prototype method", function(t) {
   // see reload defined above
 
   form.action = window.location.protocol + "//" + window.location.host + "/internal"
-  form.method = 'POST'
+  form.method = "POST"
   trigger(form, "submit")
   // see post defined above
 
   form.action = window.location.protocol + "//" + window.location.host + "/internal"
-  form.method = 'GET'
+  form.method = "GET"
   trigger(form, "submit")
   // see post defined above
 
diff --git a/tests/lib/proto/parse-options.js b/tests/lib/proto/parse-options.js
index e9c2044..2b79435 100644
--- a/tests/lib/proto/parse-options.js
+++ b/tests/lib/proto/parse-options.js
@@ -4,70 +4,75 @@ var parseOptions = require("../../../lib/proto/parse-options.js")
 tape("test parse initalization options function", function(t) {
   // via http://stackoverflow.com/questions/1173549/how-to-determine-if-an-object-is-an-object-literal-in-javascript
   function isObjLiteral(_obj) {
-  var _test  = _obj;
-  return (  typeof _obj !== 'object' || _obj === null ?
-      false :  
-      (
-        (function () {
-          while (!false) {
-            if (  Object.getPrototypeOf( _test = Object.getPrototypeOf(_test)  ) === null) {
-              break;
-            }      
-          }
-          return Object.getPrototypeOf(_obj) === _test;
-        })()
-      )
+    var _test = _obj;
+    return (typeof _obj !== "object" || _obj === null ?
+        false :
+        (
+          (function() {
+            while (!false) {
+              if (Object.getPrototypeOf(_test = Object.getPrototypeOf(_test)) === null) {
+                break;
+              }
+            }
+            return Object.getPrototypeOf(_obj) === _test;
+          })()
+        )
     );
   }
+
   function enumerableKeys(_obj) {
     var c = 0;
-    for(var n in _obj){ n = n; c++; }
+    for (var n in _obj) {
+      n = n;
+      c++;
+    }
     return c;
   }
-  t.test("- default options", function(t){
-    var body_1 = {};
-    var options_1 = {};
-    parseOptions.apply(body_1,[options_1]);
 
-    t.deepEqual(body_1.options.elements,"a[href], form[action]");
-    t.deepEqual(body_1.options.selectors.length,2,"selectors length");
-    t.deepEqual(body_1.options.selectors[0],"title");
-    t.deepEqual(body_1.options.selectors[1],".js-Pjax");
-    
-    t.deepEqual(isObjLiteral(body_1.options.switches),true);
-    t.deepEqual(enumerableKeys(body_1.options.switches),2);//head and body
+  t.test("- default options", function(t) {
+    var body1 = {};
+    var options1 = {};
+    parseOptions.apply(body1, [options1]);
 
-    t.deepEqual(isObjLiteral(body_1.options.switchesOptions),true);
-    t.deepEqual(enumerableKeys(body_1.options.switchesOptions),0);
+    t.deepEqual(body1.options.elements, "a[href], form[action]");
+    t.deepEqual(body1.options.selectors.length, 2, "selectors length");
+    t.deepEqual(body1.options.selectors[0], "title");
+    t.deepEqual(body1.options.selectors[1], ".js-Pjax");
 
-    t.deepEqual(body_1.options.history,true);
+    t.deepEqual(isObjLiteral(body1.options.switches), true);
+    t.deepEqual(enumerableKeys(body1.options.switches), 2);// head and body
 
-    //TODO analytics is a little weird right now
-    t.deepEqual(typeof body_1.options.analytics,"function");
+    t.deepEqual(isObjLiteral(body1.options.switchesOptions), true);
+    t.deepEqual(enumerableKeys(body1.options.switchesOptions), 0);
 
-    t.deepEqual(body_1.options.scrollTo,0);
-    t.deepEqual(body_1.options.cacheBust,true);
-    t.deepEqual(body_1.options.debug,false);
+    t.deepEqual(body1.options.history, true);
+
+    // TODO analytics is a little weird right now
+    t.deepEqual(typeof body1.options.analytics, "function");
+
+    t.deepEqual(body1.options.scrollTo, 0);
+    t.deepEqual(body1.options.cacheBust, true);
+    t.deepEqual(body1.options.debug, 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 body_2 = {};
-    var options_2 = {analytics:"some string"};
-    parseOptions.apply(body_2,[options_2]);
+  // verify analytics always ends up as a function even when passed not a function
+  t.test("- analytics is a function", function(t) {
+    var body2 = {};
+    var options2 = {analytics: "some string"};
+    parseOptions.apply(body2, [options2]);
 
-    t.deepEqual(typeof body_2.options.analytics,"function");
+    t.deepEqual(typeof body2.options.analytics, "function");
     t.end();
   });
-  //verify that the value false for scrollTo is not squashed
-  t.test("- scrollTo remains false", function(t){
-    var body_3 = {};
-    var options_3 = {scrollTo:false};
-    parseOptions.apply(body_3,[options_3]);
+  // verify that the value false for scrollTo is not squashed
+  t.test("- scrollTo remains false", function(t) {
+    var body3 = {};
+    var options3 = {scrollTo: false};
+    parseOptions.apply(body3, [options3]);
 
-    t.deepEqual( body_3.options.scrollTo,false);
+    t.deepEqual(body3.options.scrollTo, false);
     t.end();
   });
   t.end()
-})
\ No newline at end of file
+})
diff --git a/tests/lib/request.js b/tests/lib/request.js
index f99763e..0f1e365 100644
--- a/tests/lib/request.js
+++ b/tests/lib/request.js
@@ -6,9 +6,9 @@ var request = require("../../lib/request.js")
 // just for the purposes of this test
 // This polyfill is not complete; it won't show the updated location if a
 // redirection occurred, but it's fine for our purposes.
-if (!('responseURL' in XMLHttpRequest.prototype)) {
+if (!("responseURL" in XMLHttpRequest.prototype)) {
   var nativeOpen = XMLHttpRequest.prototype.open
-  XMLHttpRequest.prototype.open = function (method, url) {
+  XMLHttpRequest.prototype.open = function(method, url) {
     this.responseURL = url
     return nativeOpen.apply(this, arguments)
   }
diff --git a/tests/lib/switch-selectors.js b/tests/lib/switch-selectors.js
index 66c3421..8653227 100644
--- a/tests/lib/switch-selectors.js
+++ b/tests/lib/switch-selectors.js
@@ -8,7 +8,7 @@ tape("test switchesSelectors", function(t) {
   // should really be passed in instead so I'll leave it here as a TODO:
   var pjax = {
     onSwitch: function() {
-      console.log('Switched')
+      console.log("Switched")
     }
   }
 
@@ -29,13 +29,13 @@ tape("test switchesSelectors", function(t) {
   switchesSelectors.bind(pjax)(
     {}, // switches
     {}, // switchesOptions
-    ['p'], //selectors,
+    ["p"], // selectors,
     tmpEl, // fromEl
     document, // toEl,
     {} // options
   )
 
-  t.equals(container.innerHTML, '

New Text

No Change', 'Elements correctly switched') + t.equals(container.innerHTML, "

New Text

No Change", "Elements correctly switched") t.end() }) -- 2.49.1 From 3bd101bb1d2f2fe8b93a9bab0371a8380f6869bd Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 9 Jan 2018 00:48:27 -0500 Subject: [PATCH 20/92] Update JSCS to the latest version --- .jscsrc | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.jscsrc b/.jscsrc index 570b19e..f548996 100644 --- a/.jscsrc +++ b/.jscsrc @@ -122,7 +122,7 @@ "requireCapitalizedConstructors": true, "safeContextKeyword": "that", "requireDotNotation": true, - "validateJSDoc": { + "jsDoc": { "checkParamNames": true, "checkRedundantParams": true, "requireParamTypes": true diff --git a/package.json b/package.json index 4fc395f..550629a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ ], "devDependencies": { "browserify": "^3.46.0", - "jscs": "^1.6.2", + "jscs": "^3.0.7", "jsdom": "^11.5.1", "jsdom-global": "^3.0.2", "jshint": "^2.5.6", -- 2.49.1 From 92d5e09494bade7972113eca93f553ff57c58d88 Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Tue, 9 Jan 2018 15:59:15 -0500 Subject: [PATCH 21/92] Update tooling (#99) - Update browserify, serve, and tape to the latest versions - update the example script to reflect serve's new CLI options - Remove the uneeded dependency on opn-cli - Force Travis to use NPM 5.x - Remove bower.json --- .travis.yml | 3 +++ README.md | 4 ++-- bower.json | 29 ----------------------------- example/index.html | 2 +- package.json | 9 ++++----- 5 files changed, 10 insertions(+), 37 deletions(-) delete mode 100644 bower.json diff --git a/.travis.yml b/.travis.yml index 5c51c69..9c9521d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,6 @@ language: "node_js" node_js: - "6" - "8" +# Force Travis to use npm v5 +# https://github.com/travis-ci/travis-ci/issues/4653#issuecomment-194051953 +before_install: if [[ `npm -v` != 5* ]]; then npm i -g npm@5; fi diff --git a/README.md b/README.md index 7e6a1ce..18a2c93 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Pjax -[![Build Status](http://img.shields.io/travis/MoOx/pjax.svg)](https://travis-ci.org/MoOx/pjax) [@todo fix CI](https://github.com/MoOx/pjax/issues/63). +[![Build Status](http://img.shields.io/travis/MoOx/pjax.svg)](https://travis-ci.org/MoOx/pjax). > Easily enable fast Ajax navigation on any website (using pushState + xhr) @@ -522,7 +522,7 @@ wrapper on each page (to avoid differences of DOM between pages) ## Examples -Clone this repository and run `npm run example`, then open `http://localhost:3000/example` in your browser. +Clone this repository and run `npm run example`, which will open the example app in your browser. --- diff --git a/bower.json b/bower.json deleted file mode 100644 index 6cc7c13..0000000 --- a/bower.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "pjax", - "version": "0.2.4", - "description": "Easily enable fast Ajax navigation on any website (using pushState + xhr)", - "keywords": [ - "pjax", - "push", - "state", - "ajax", - "navigation", - "transition", - "animation" - ], - "main": "pjax.js", - "homepage": "https://github.com/MoOx/pjax", - "authors": [ - "Maxime Thirouin" - ], - "license": "MIT", - "moduleType": [ - "node" - ], - "ignore": [ - "**/.*", - "node_modules", - "test", - "tests" - ] -} diff --git a/example/index.html b/example/index.html index cc95a19..4d375e0 100644 --- a/example/index.html +++ b/example/index.html @@ -11,7 +11,7 @@

Index

Hello. Go to
Page 2 and view your console to see Pjax events. - Clicking on this page will just reload the page entierly. + Clicking on this page will just reload the page entirely. diff --git a/package.json b/package.json index 550629a..d6855b9 100644 --- a/package.json +++ b/package.json @@ -21,18 +21,17 @@ "pjax.js" ], "devDependencies": { - "browserify": "^3.46.0", + "browserify": "^15.0.0", "jscs": "^3.0.7", "jsdom": "^11.5.1", "jsdom-global": "^3.0.2", "jshint": "^2.5.6", "npmpub": "^3.1.0", "nyc": "^11.4.1", - "opn-cli": "^3.1.0", - "serve": "1.4.0", + "serve": "^6.4.4", "tap-nyc": "^1.0.3", "tap-spec": "^4.1.1", - "tape": "^3.0.0" + "tape": "^4.8.0" }, "scripts": { "lint": "jscs . && jshint . --exclude-path .gitignore", @@ -42,7 +41,7 @@ "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", - "example": "opn http://localhost:3000/example/; serve .", + "example": "serve -o -p 3000 ./example", "prepublish": "npm run standalone", "#release": "testling does not work in a process launch by npm... :facepalm:", "release": "echo \"npmpub --skip-test --dry && npm test && npmpub --skip-test --skip-cleanup\"" -- 2.49.1 From cb9c37fcb330d8431176c3f1ca266d0b64378ce6 Mon Sep 17 00:00:00 2001 From: Maxime Thirouin <157534+MoOx@users.noreply.github.com> Date: Tue, 9 Jan 2018 21:48:45 +0100 Subject: [PATCH 22/92] Use npmpub like it should since testling is gone --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index d6855b9..31579d2 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "coverage": "nyc -x \"tests/**\" npm run coverage-tests", "example": "serve -o -p 3000 ./example", "prepublish": "npm run standalone", - "#release": "testling does not work in a process launch by npm... :facepalm:", - "release": "echo \"npmpub --skip-test --dry && npm test && npmpub --skip-test --skip-cleanup\"" + "release": "npmpub" } } -- 2.49.1 From 6fa51e58f81494022abeade2ab410e8950edb25c Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Wed, 10 Jan 2018 15:45:55 -0500 Subject: [PATCH 23/92] Add checks for XHR redirects (#101) Fixes #7 This checks for redirects by looking for the following, in sequence: - XMLHttpRequest.responseURL - the X-PJAX-URL header (like jquery-pjax) - the X-XHR-Redirected-To header (like Turbolinks) --- index.js | 14 +++++++++++++- lib/request.js | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 7794b1a..da4aa8f 100644 --- a/index.js +++ b/index.js @@ -159,7 +159,7 @@ Pjax.prototype = { // Do the request options.requestOptions.timeout = this.options.timeout - this.doRequest(href, options.requestOptions, function(html) { + this.doRequest(href, options.requestOptions, function(html, request) { // Fail if unable to load HTML via AJAX if (html === false) { trigger(document,"pjax:complete pjax:error", options) @@ -170,6 +170,18 @@ Pjax.prototype = { // Clear out any focused controls before inserting new page contents. document.activeElement.blur() + 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") + } + try { this.loadContent(html, options) } diff --git a/lib/request.js b/lib/request.js index 299a136..9a4e5b8 100644 --- a/lib/request.js +++ b/lib/request.js @@ -15,6 +15,11 @@ module.exports = function(location, options, callback) { } } + request.onerror = function(e) { + console.log(e) + callback(null, request) + } + request.ontimeout = function() { callback(null, request) } -- 2.49.1 From 36ed7079b187801fcb7f2cebcfa0882c8f31f910 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 15 Jan 2018 16:36:29 -0500 Subject: [PATCH 24/92] Fix tooling for the example Apparently we do need opn-cli. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 31579d2..eee7708 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "jshint": "^2.5.6", "npmpub": "^3.1.0", "nyc": "^11.4.1", + "opn-cli": "^3.1.0", "serve": "^6.4.4", "tap-nyc": "^1.0.3", "tap-spec": "^4.1.1", @@ -41,7 +42,7 @@ "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", - "example": "serve -o -p 3000 ./example", + "example": "opn http://localhost:3000/example/ && serve -p 3000 .", "prepublish": "npm run standalone", "release": "npmpub" } -- 2.49.1 From b98e3ef9144553e3e8c70c82b04b5220f2dab589 Mon Sep 17 00:00:00 2001 From: Robin North Date: Wed, 17 Jan 2018 19:12:12 +0000 Subject: [PATCH 25/92] Fix DOMException in switchElementsAlt() Clone attribute nodes before setting on `oldEl` to prevent the following error: ``` DOMException: Failed to execute 'setNamedItem' on 'NamedNodeMap': The node provided is an attribute node that is already an attribute of another Element; attribute nodes must be explicitly cloned. ``` --- lib/switches.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/switches.js b/lib/switches.js index d87ae0b..86beff3 100644 --- a/lib/switches.js +++ b/lib/switches.js @@ -22,7 +22,7 @@ module.exports = { if (newEl.hasAttributes()) { const attrs = newEl.attributes; for (var i = 0; i < attrs.length; i++) { - oldEl.attributes.setNamedItem(attrs[i]) + oldEl.attributes.setNamedItem(attrs[i].cloneNode()) } } -- 2.49.1 From cd09cc88d1954791dcc29153fe0cdc49d4aa5521 Mon Sep 17 00:00:00 2001 From: Robin North Date: Thu, 18 Jan 2018 20:47:49 +0000 Subject: [PATCH 26/92] Replace instances of ES6 `const` keyword with `var` --- lib/proto/attach-form.js | 2 +- lib/proto/attach-link.js | 2 +- lib/switches.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js index 1fdb537..96bf39d 100644 --- a/lib/proto/attach-form.js +++ b/lib/proto/attach-form.js @@ -61,7 +61,7 @@ var formAction = function(el, event) { el.setAttribute(attrClick, "submit"); - const options = clone(this.options); + var options = clone(this.options); options.triggerElement = el; this.loadUrl(virtLinkElement.href, options); }; diff --git a/lib/proto/attach-link.js b/lib/proto/attach-link.js index e48b702..2a0e641 100644 --- a/lib/proto/attach-link.js +++ b/lib/proto/attach-link.js @@ -54,7 +54,7 @@ var linkAction = function(el, event) { this.options.requestOptions = this.options.requestOptions || {}; el.setAttribute(attrClick, "load") - const options = clone(this.options) + var options = clone(this.options) options.triggerElement = el this.loadUrl(el.href, options) } diff --git a/lib/switches.js b/lib/switches.js index 86beff3..405d6bf 100644 --- a/lib/switches.js +++ b/lib/switches.js @@ -20,7 +20,7 @@ module.exports = { // Copy attributes from the new element to the old one if (newEl.hasAttributes()) { - const attrs = newEl.attributes; + var attrs = newEl.attributes; for (var i = 0; i < attrs.length; i++) { oldEl.attributes.setNamedItem(attrs[i].cloneNode()) } -- 2.49.1 From ca61c4a8402f5159065326d48b14b25bce5d277d Mon Sep 17 00:00:00 2001 From: Robin North Date: Fri, 19 Jan 2018 01:55:27 +0000 Subject: [PATCH 27/92] Fix incorrect `main` field in npm package (#105) Point npm package `main` field at module source --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eee7708..7bea2a5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "repository": "https://github.com/MoOx/pjax.git", "author": "Maxime Thirouin", "license": "MIT", - "main": "src/pjax.js", + "main": "index.js", "files": [ "index.js", "lib", -- 2.49.1 From b5c2120d085d2a4d5d6896b9638c14e06c905e45 Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Mon, 22 Jan 2018 10:55:29 -0500 Subject: [PATCH 28/92] Fix async switches (#110) If any switches are async, the subsequent code will execute before the switches are finished. This PR moves all that code to a new function, and debounces the calls to onSwitch() so it only executes once, after all the switches finish. Fizes #72. --- index.js | 125 ++++++++++++++++++++-------------- lib/switches-selectors.js | 20 ++++-- package.json | 2 +- tests/lib/switch-selectors.js | 3 +- tests/{index.js => setup.js} | 0 5 files changed, 92 insertions(+), 58 deletions(-) rename tests/{index.js => setup.js} (100%) diff --git a/index.js b/index.js index da4aa8f..eb2f853 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,11 @@ var defaultSwitches = require("./lib/switches") var Pjax = function(options) { this.firstrun = true + this.state = { + numPendingSwitches: 0, + href: null, + options: null + } var parseOptions = require("./lib/proto/parse-options.js"); parseOptions.apply(this,[options]) @@ -83,8 +88,12 @@ Pjax.prototype = { }, onSwitch: function() { - this.parseDOM(document) - 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) { @@ -125,22 +134,6 @@ Pjax.prototype = { // try { this.switchSelectors(this.options.selectors, tmpEl, document, options) - // 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) - }) - }) // } // catch(e) { // if (this.options.debug) { @@ -181,6 +174,8 @@ Pjax.prototype = { else if (request.getResponseHeader("X-XHR-Redirected-To")) { href = request.getResponseHeader("X-XHR-Redirected-To") } + this.state.href = href + this.state.options = clone(options) try { this.loadContent(html, options) @@ -197,49 +192,79 @@ Pjax.prototype = { throw e } } + }.bind(this)) + }, - if (options.history) { - if (this.firstrun) { - this.lastUid = this.maxUid = newUid() - this.firstrun = false - window.history.replaceState({ + afterAllSwitches: function() { + trigger(window, "resize scroll") + + // 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 (this.firstrun) { + this.lastUid = this.maxUid = newUid() + this.firstrun = false + window.history.replaceState({ url: window.location.href, title: document.title, uid: this.maxUid }, document.title) - } + } - // Update browser history - this.lastUid = this.maxUid = newUid() - window.history.pushState({ - url: href, - title: options.title, + // Update browser history + this.lastUid = this.maxUid = newUid() + + window.history.pushState({ + url: state.href, + title: state.options.title, uid: this.maxUid }, - options.title, - href) + state.options.title, + state.href) + } + + this.forEachSelectors(function(el) { + this.parseDOM(el) + }, this) + + // Fire Events + trigger(document,"pjax:complete pjax:success", state.options) + + state.options.analytics() + + // Scroll page to top on new page load + if (state.options.scrollTo !== false) { + if (state.options.scrollTo.length > 1) { + window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]) } - - this.forEachSelectors(function(el) { - this.parseDOM(el) - }, this) - - // Fire Events - trigger(document,"pjax:complete pjax:success", options) - - options.analytics() - - // Scroll page to top on new page load - if (options.scrollTo !== false) { - if (options.scrollTo.length > 1) { - window.scrollTo(options.scrollTo[0], options.scrollTo[1]) - } - else { - window.scrollTo(0, options.scrollTo) - } + else { + window.scrollTo(0, state.options.scrollTo) } - }.bind(this)) + } + + this.state = { + numPendingSwitches: 0, + href: null, + options: null + } } } diff --git a/lib/switches-selectors.js b/lib/switches-selectors.js index 5ae919f..e55c205 100644 --- a/lib/switches-selectors.js +++ b/lib/switches-selectors.js @@ -3,6 +3,8 @@ 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) @@ -24,12 +26,18 @@ module.exports = function(switches, switchesOptions, selectors, fromEl, toEl, op if (this.log) { this.log("newEl", newEl, "oldEl", oldEl) } - if (switches[selector]) { - switches[selector].bind(this)(oldEl, newEl, options, switchesOptions[selector]) - } - else { - 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) + + this.state.numPendingSwitches = switchesQueue.length + + switchesQueue.forEach(function(queuedSwitch) { + queuedSwitch() + }) } diff --git a/package.json b/package.json index 7bea2a5..5d2949c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "lint": "jscs . && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", - "tests": "tape -r ./tests/index.js ./tests/**/*.js", + "tests": "tape -r ./tests/setup.js ./tests/**/*.js", "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", diff --git a/tests/lib/switch-selectors.js b/tests/lib/switch-selectors.js index 8653227..1e32e04 100644 --- a/tests/lib/switch-selectors.js +++ b/tests/lib/switch-selectors.js @@ -9,7 +9,8 @@ tape("test switchesSelectors", function(t) { var pjax = { onSwitch: function() { console.log("Switched") - } + }, + state: {} } var tmpEl = document.implementation.createHTMLDocument() diff --git a/tests/index.js b/tests/setup.js similarity index 100% rename from tests/index.js rename to tests/setup.js -- 2.49.1 From 37d303ed66095e78da424326743d41b4a8d7c397 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 11:27:13 -0500 Subject: [PATCH 29/92] Save scroll position with history Save scroll position when navigating away from a page, and restore it when navigating back to that page. Fixes #30. --- index.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index eb2f853..3895d6b 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,6 @@ var defaultSwitches = require("./lib/switches") var Pjax = function(options) { - this.firstrun = true this.state = { numPendingSwitches: 0, href: null, @@ -35,6 +34,7 @@ var Pjax = function(options) { opt.title = st.state.title opt.history = false opt.requestOptions = {}; + opt.scrollPos = st.state.scrollPos if (st.state.uid < this.lastUid) { opt.backward = true } @@ -160,6 +160,17 @@ Pjax.prototype = { 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) + // Clear out any focused controls before inserting new page contents. document.activeElement.blur() @@ -218,13 +229,13 @@ Pjax.prototype = { var state = this.state if (state.options.history) { - if (this.firstrun) { + if (!window.history.state) { this.lastUid = this.maxUid = newUid() - this.firstrun = false window.history.replaceState({ url: window.location.href, title: document.title, - uid: this.maxUid + uid: this.maxUid, + scrollPos: 0 }, document.title) } @@ -235,7 +246,8 @@ Pjax.prototype = { window.history.pushState({ url: state.href, title: state.options.title, - uid: this.maxUid + uid: this.maxUid, + scrollPos: 0 }, state.options.title, state.href) @@ -250,15 +262,20 @@ Pjax.prototype = { state.options.analytics() - // Scroll page to top on new page load - if (state.options.scrollTo !== false) { - if (state.options.scrollTo.length > 1) { - window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]) - } - else { - window.scrollTo(0, state.options.scrollTo) + if (state.options.history) { + // Scroll page to top on new page load + if (state.options.scrollTo !== false) { + if (state.options.scrollTo.length > 1) { + window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]) + } + else { + window.scrollTo(0, state.options.scrollTo) + } } } + else { + window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]) + } this.state = { numPendingSwitches: 0, -- 2.49.1 From bc2432b18c0f0d99944a42e9418f0fe6e8460e48 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Sun, 21 Jan 2018 19:04:50 -0500 Subject: [PATCH 30/92] Scroll to element position when URL contains a hash When the URL contains a hash, try to find the corresponding element, and if found, scroll to its position. Based on darylteo/pjax@4893a2a6574b3490396f83a1863499e8a9ba171e Fixes #22. --- index.js | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 3895d6b..7bd2c4c 100644 --- a/index.js +++ b/index.js @@ -174,6 +174,7 @@ Pjax.prototype = { // Clear out any focused controls before inserting new page contents. document.activeElement.blur() + var oldHref = href if (request.responseURL) { if (href !== request.responseURL) { href = request.responseURL @@ -185,6 +186,17 @@ Pjax.prototype = { 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 = clone(options) @@ -263,8 +275,29 @@ Pjax.prototype = { state.options.analytics() if (state.options.history) { - // Scroll page to top on new page load - if (state.options.scrollTo !== false) { + // 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]) } -- 2.49.1 From 546b9abba3f9c453de349c68260dbb912983f343 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 17:32:59 -0500 Subject: [PATCH 31/92] Small bug fix --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 7bd2c4c..5655c91 100644 --- a/index.js +++ b/index.js @@ -247,7 +247,7 @@ Pjax.prototype = { url: window.location.href, title: document.title, uid: this.maxUid, - scrollPos: 0 + scrollPos: [0, 0] }, document.title) } @@ -259,7 +259,7 @@ Pjax.prototype = { url: state.href, title: state.options.title, uid: this.maxUid, - scrollPos: 0 + scrollPos: [0, 0] }, state.options.title, state.href) -- 2.49.1 From e4b395258904f24ffa1b82fa56c8cd9d3846e4f9 Mon Sep 17 00:00:00 2001 From: Tim Trinidad Date: Mon, 22 Jan 2018 13:31:11 -0500 Subject: [PATCH 32/92] Clone options before modifying it for form submissions --- lib/proto/attach-form.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js index 96bf39d..1a0ca83 100644 --- a/lib/proto/attach-form.js +++ b/lib/proto/attach-form.js @@ -6,14 +6,18 @@ var clone = require("../clone") var attrClick = "data-pjax-click-state" var formAction = function(el, event) { - this.options.requestOptions = { + // Since we'll be modifying request options, clone the existing options + // so these changes don't persist + var options = clone(this.options); + + 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", this.options.requestOptions.requestUrl); + virtLinkElement.setAttribute("href", options.requestOptions.requestUrl); // Ignore external links. if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) { @@ -34,7 +38,7 @@ var formAction = function(el, event) { } // if declared as a full reload, just normally submit the form - if (this.options.currentUrlFullReload) { + if (options.currentUrlFullReload) { el.setAttribute(attrClick, "reload"); return; } @@ -56,12 +60,12 @@ var formAction = function(el, event) { // Creating a getString var paramsString = (paramObject.map(function(value) {return value.name + "=" + value.value;})).join("&"); - this.options.requestOptions.requestPayload = paramObject; - this.options.requestOptions.requestPayloadString = paramsString; + options.requestOptions.requestPayload = paramObject; + options.requestOptions.requestPayloadString = paramsString; el.setAttribute(attrClick, "submit"); - var options = clone(this.options); + options.triggerElement = el; this.loadUrl(virtLinkElement.href, options); }; -- 2.49.1 From e7935d9c7412e75adf07e9b41471e937bd17eb43 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 18:56:22 -0500 Subject: [PATCH 33/92] Add scrollRestoration option --- README.md | 4 ++++ index.js | 2 +- lib/proto/parse-options.js | 1 + tests/lib/proto/parse-options.js | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18a2c93..ec01cff 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,10 @@ It's called every time a page is switched, even for history buttons. Value (in px) to scrollTo when a page is switched. +##### `scrollRestoration` (Boolean, default true) + +When set to true, attempt to restore the scroll position when navigating backwards or forwards. + ##### `cacheBust` (Boolean, default true) When set to true, diff --git a/index.js b/index.js index 5655c91..5bb7a4e 100644 --- a/index.js +++ b/index.js @@ -306,7 +306,7 @@ Pjax.prototype = { } } } - else { + else if (state.options.scrollRestoration && state.options.scrollPos) { window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]) } diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index fce3ec8..37c16c7 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -24,6 +24,7 @@ module.exports = function(options) { this.options.cacheBust = (typeof this.options.cacheBust === "undefined") ? true : this.options.cacheBust this.options.debug = this.options.debug || false this.options.timeout = this.options.timeout || 0 + this.options.scrollRestoration = (typeof this.options.scrollRestoration !== "undefined") ? this.options.scrollRestoration : true // we can’t replace body.outerHTML or head.outerHTML // it create a bug where new body or new head are created in the dom diff --git a/tests/lib/proto/parse-options.js b/tests/lib/proto/parse-options.js index 2b79435..8f57a65 100644 --- a/tests/lib/proto/parse-options.js +++ b/tests/lib/proto/parse-options.js @@ -53,6 +53,7 @@ tape("test parse initalization options function", function(t) { t.deepEqual(body1.options.scrollTo, 0); t.deepEqual(body1.options.cacheBust, true); t.deepEqual(body1.options.debug, false); + t.deepEqual(body1.options.scrollRestoration, true) t.end(); }); -- 2.49.1 From 6000ad5620869cc596ec37dd4365769b92a87650 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 20:42:57 -0500 Subject: [PATCH 34/92] Restore the trigger for the resize and scroll events to onSwitch --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 5bb7a4e..e8ca6e6 100644 --- a/index.js +++ b/index.js @@ -88,6 +88,8 @@ Pjax.prototype = { }, onSwitch: function() { + trigger(window, "resize scroll") + this.state.numPendingSwitches-- // debounce calls, so we only run this once after all switches are finished. @@ -219,8 +221,6 @@ Pjax.prototype = { }, afterAllSwitches: function() { - trigger(window, "resize scroll") - // 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. -- 2.49.1 From 486ef0e0ba3e8bbb7611ba540bdbe3ed58402b60 Mon Sep 17 00:00:00 2001 From: Robin North Date: Tue, 23 Jan 2018 15:09:57 +0000 Subject: [PATCH 35/92] Abort previous pending XHR when navigating --- example/index.html | 2 +- example/page3.html | 15 +++++++ index.js | 9 +++- lib/abort-request.js | 8 ++++ lib/{request.js => send-request.js} | 0 lib/util/noop | 1 + tests/lib/abort-request.js | 54 +++++++++++++++++++++++ tests/lib/{request.js => send-request.js} | 6 +-- 8 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 example/page3.html create mode 100644 lib/abort-request.js rename lib/{request.js => send-request.js} (100%) create mode 100644 lib/util/noop create mode 100644 tests/lib/abort-request.js rename tests/lib/{request.js => send-request.js} (90%) diff --git a/example/index.html b/example/index.html index 4d375e0..f9e3da5 100644 --- a/example/index.html +++ b/example/index.html @@ -10,7 +10,7 @@

Index

Hello. - Go to Page 2 and view your console to see Pjax events. + Go to Page 2 or Page 3 and view your console to see Pjax events. Clicking on this page will just reload the page entirely.
diff --git a/example/page3.html b/example/page3.html new file mode 100644 index 0000000..7546dd1 --- /dev/null +++ b/example/page3.html @@ -0,0 +1,15 @@ + + + + + Hello + + + + +
+

Page 3

+ Hello. Go to Index. +
+ + diff --git a/index.js b/index.js index e8ca6e6..b9802e9 100644 --- a/index.js +++ b/index.js @@ -145,16 +145,21 @@ Pjax.prototype = { // } }, - doRequest: require("./lib/request.js"), + abortRequest: require("./lib/abort-request.js"), + + doRequest: require("./lib/send-request.js"), loadUrl: function(href, options) { this.log("load href", href, options) + // Abort any previous request + this.abortRequest(this.request) + trigger(document, "pjax:send", options); // Do the request options.requestOptions.timeout = this.options.timeout - this.doRequest(href, options.requestOptions, function(html, request) { + this.request = this.doRequest(href, options.requestOptions, function(html, request) { // Fail if unable to load HTML via AJAX if (html === false) { trigger(document,"pjax:complete pjax:error", options) diff --git a/lib/abort-request.js b/lib/abort-request.js new file mode 100644 index 0000000..e68f01b --- /dev/null +++ b/lib/abort-request.js @@ -0,0 +1,8 @@ +var noop = require("./util/noop") + +module.exports = function(request) { + if (request && request.readyState < 4) { + request.onreadystatechange = noop + request.abort() + } +} diff --git a/lib/request.js b/lib/send-request.js similarity index 100% rename from lib/request.js rename to lib/send-request.js diff --git a/lib/util/noop b/lib/util/noop new file mode 100644 index 0000000..756c6be --- /dev/null +++ b/lib/util/noop @@ -0,0 +1 @@ +module.exports = function() {} diff --git a/tests/lib/abort-request.js b/tests/lib/abort-request.js new file mode 100644 index 0000000..edaa774 --- /dev/null +++ b/tests/lib/abort-request.js @@ -0,0 +1,54 @@ +var tape = require("tape") + +var abortRequest = require("../../lib/abort-request.js") +var sendRequest = require("../../lib/send-request.js") + +// Polyfill responseURL property into XMLHttpRequest if it doesn't exist, +// just for the purposes of this test +// This polyfill is not complete; it won't show the updated location if a +// redirection occurred, but it's fine for our purposes. +if (!("responseURL" in XMLHttpRequest.prototype)) { + var nativeOpen = XMLHttpRequest.prototype.open + XMLHttpRequest.prototype.open = function(method, url) { + this.responseURL = url + return nativeOpen.apply(this, arguments) + } +} + +tape("test aborting xhr request", function(t) { + var requestCacheBust = sendRequest.bind({ + options: { + cacheBust: true, + }, + }) + + t.test("- pending request is aborted", function(t) { + var r = requestCacheBust("https://httpbin.org/delay/10", {}, function() {}) + t.equal(r.readyState, 1, "xhr readyState is '1' (SENT)") + abortRequest(r) + t.equal(r.readyState, 0, "xhr readyState is '0' (ABORTED)") + t.equal(r.status, 0, "xhr HTTP status is '0' (ABORTED)") + t.equal(r.responseText, "", "xhr response is empty") + t.end() + }) + t.test("- request is not aborted if it has already completed", function(t) { + var r = requestCacheBust("https://httpbin.org/get", {}, function() { + abortRequest(r) + t.equal(r.readyState, 4, "xhr readyState is '4' (DONE)") + t.equal(r.status, 200, "xhr HTTP status is '200' (OK)") + t.end() + }) + }) + t.test("- request is not aborted if it is undefined", function(t) { + var r + try { + abortRequest(r) + } + catch (e) { + t.fail("aborting an undefined request threw an error") + } + t.equal(typeof r, "undefined", "undefined xhr was ignored") + t.end() + }) + t.end() +}) diff --git a/tests/lib/request.js b/tests/lib/send-request.js similarity index 90% rename from tests/lib/request.js rename to tests/lib/send-request.js index 0f1e365..f33b28c 100644 --- a/tests/lib/request.js +++ b/tests/lib/send-request.js @@ -1,6 +1,6 @@ var tape = require("tape") -var request = require("../../lib/request.js") +var sendRequest = require("../../lib/send-request.js") // Polyfill responseURL property into XMLHttpRequest if it doesn't exist, // just for the purposes of this test @@ -18,7 +18,7 @@ tape("test xhr request", function(t) { var url = "https://httpbin.org/get" t.test("- request is made, gets a result, and is cache-busted", function(t) { - var requestCacheBust = request.bind({ + var requestCacheBust = sendRequest.bind({ options: { cacheBust: true, }, @@ -36,7 +36,7 @@ tape("test xhr request", function(t) { }) }) t.test("- request is not cache-busted when configured not to be", function(t) { - var requestNoCacheBust = request.bind({ + var requestNoCacheBust = sendRequest.bind({ options: { cacheBust: false, }, -- 2.49.1 From 526a0883a20a6a3f3b188638840ffe1ea170bc9f Mon Sep 17 00:00:00 2001 From: Tim Trinidad Date: Tue, 23 Jan 2018 13:22:31 -0500 Subject: [PATCH 36/92] Clone options in attach-link to prevent requestOptions changes from persisting --- lib/proto/attach-form.js | 5 +++-- lib/proto/attach-link.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js index 1a0ca83..f6775a2 100644 --- a/lib/proto/attach-form.js +++ b/lib/proto/attach-form.js @@ -6,10 +6,11 @@ var clone = require("../clone") var attrClick = "data-pjax-click-state" var formAction = function(el, event) { - // Since we'll be modifying request options, clone the existing options - // so these changes don't persist + // 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", diff --git a/lib/proto/attach-link.js b/lib/proto/attach-link.js index 2a0e641..69945bc 100644 --- a/lib/proto/attach-link.js +++ b/lib/proto/attach-link.js @@ -7,6 +7,13 @@ var attrClick = "data-pjax-click-state" var attrKey = "data-pjax-keyup-state" var linkAction = function(el, event) { + // 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 since loadUrl expects it to be an object + options.requestOptions = {}; + // 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) { el.setAttribute(attrClick, "modifier") @@ -51,10 +58,9 @@ var linkAction = function(el, event) { this.reload() return } - this.options.requestOptions = this.options.requestOptions || {}; + el.setAttribute(attrClick, "load") - var options = clone(this.options) options.triggerElement = el this.loadUrl(el.href, options) } -- 2.49.1 From f196604d7391e90a3d7298c3bf1a59d56fc19133 Mon Sep 17 00:00:00 2001 From: Tim Trinidad Date: Tue, 23 Jan 2018 13:22:48 -0500 Subject: [PATCH 37/92] Add tests to ensure options are not accidentally modified --- package.json | 2 +- tests/lib/proto/attach-form.js | 18 ++++++++++++++++++ tests/lib/proto/attach-link.js | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5d2949c..c23931e 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "lint": "jscs . && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", - "tests": "tape -r ./tests/setup.js ./tests/**/*.js", + "tests": "tape -r ./tests/setup.js ./tests/lib/*.js ./tests/lib/proto/*.js", "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", diff --git a/tests/lib/proto/attach-form.js b/tests/lib/proto/attach-form.js index 701b89f..ab89543 100644 --- a/tests/lib/proto/attach-form.js +++ b/tests/lib/proto/attach-form.js @@ -76,3 +76,21 @@ tape("test attach form preventDefaulted events", function(t) { t.end() }) + +tape("test options are not modified by attachForm", function(t) { + var form = document.createElement("form") + var options = {foo: "bar"} + var loadUrl = () => {} + + attachForm.call({options, loadUrl}, form) + + var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search + form.action = internalUri + form.method = "GET" + trigger(form, "submit") + + t.equal(1, Object.keys(options).length, "options object that is passed in should not be modified") + t.equal("bar", options.foo, "options object that is passed in should not be modified") + + t.end(); +}) diff --git a/tests/lib/proto/attach-link.js b/tests/lib/proto/attach-link.js index fef1bfc..fbaae4e 100644 --- a/tests/lib/proto/attach-link.js +++ b/tests/lib/proto/attach-link.js @@ -75,3 +75,21 @@ tape("test attach link preventDefaulted events", function(t) { t.end() }) + +tape("test options are not modified by attachLink", function(t) { + var a = document.createElement("a") + var options = {foo: "bar"} + var loadUrl = () => {}; + + attachLink.call({options, loadUrl}, a) + + var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search + a.href = internalUri + + trigger(a, "click") + + t.equal(1, Object.keys(options).length, "options object that is passed in should not be modified") + t.equal("bar", options.foo, "options object that is passed in should not be modified") + + t.end(); +}) -- 2.49.1 From feb85382f2a4cfb5d004326b734b884013d525d2 Mon Sep 17 00:00:00 2001 From: Tim Trinidad Date: Tue, 23 Jan 2018 15:05:31 -0500 Subject: [PATCH 38/92] Revert back to using '**' glob for tests, wrap in quotes to force node to parse the args --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c23931e..a317664 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "lint": "jscs . && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", - "tests": "tape -r ./tests/setup.js ./tests/lib/*.js ./tests/lib/proto/*.js", + "tests": "tape -r ./tests/setup.js './tests/**/*.js'", "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", -- 2.49.1 From b74fbc4178bafc1702696f32931aec3a1b67f8cf Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 23 Jan 2018 17:53:10 -0500 Subject: [PATCH 39/92] Add a minified version of Pjax to NPM Fixes #108. --- .gitignore | 1 + README.md | 20 ++++++++++++-------- package.json | 9 ++++++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 2128eae..561eeb2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ tests/scripts/index.html pjax.js .nyc_output/ +pjax.min.js diff --git a/README.md b/README.md index ec01cff..961305d 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,19 @@ Especially for user that have low bandwidth connection._ ## Installation -You can install pjax from **npm** +- You can install pjax from **npm**: + ```shell + npm install pjax + ``` -```shell -$ npm install pjax -``` - -Pjax can obviously be downloaded directly. - -[https://unpkg.com/pjax/pjax.js](https://unpkg.com/pjax/pjax.js) +- You can also link directly to the [bundle](https://cdn.jsdelivr.net/npm/pjax/pjax.js): + ```html + + ``` + Or the [minified bundle](https://cdn.jsdelivr.net/npm/pjax/pjax.min.js): + ```html + + ``` ## No dependencies diff --git a/package.json b/package.json index a317664..e9049c4 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "files": [ "index.js", "lib", - "pjax.js" + "pjax.js", + "pjax.min.js" ], "devDependencies": { "browserify": "^15.0.0", @@ -32,18 +33,20 @@ "serve": "^6.4.4", "tap-nyc": "^1.0.3", "tap-spec": "^4.1.1", - "tape": "^4.8.0" + "tape": "^4.8.0", + "uglify-js": "^3.3.8" }, "scripts": { "lint": "jscs . && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", + "build": "npm run standalone && uglifyjs pjax.js -o pjax.min.js", "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", "tests": "tape -r ./tests/setup.js './tests/**/*.js'", "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", "example": "opn http://localhost:3000/example/ && serve -p 3000 .", - "prepublish": "npm run standalone", + "prepublish": "npm run build", "release": "npmpub" } } -- 2.49.1 From 12f3be21fb4cd4814690f9340388e972c5ed5688 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 23 Jan 2018 19:35:18 -0500 Subject: [PATCH 40/92] Exclude pjax.min.js fom JSCS --- .jscsrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.jscsrc b/.jscsrc index f548996..27bc03b 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,7 +1,8 @@ { "excludeFiles": [ "node_modules/**", - "pjax.js" + "pjax.js", + "pjax.min.js" ], "fileExtensions": [ ".js" -- 2.49.1 From 3c8cd087c5352b92fb7f1090b0c39b29cfa7a31a Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 23 Jan 2018 19:40:13 -0500 Subject: [PATCH 41/92] Use double quotes in CLI arguments Single quotes don't work in all environments. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a317664..173d4b3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "lint": "jscs . && jshint . --exclude-path .gitignore", "standalone": "browserify index.js --standalone Pjax > pjax.js", "build-debug": "browserify index.js --debug --standalone Pjax > pjax.js", - "tests": "tape -r ./tests/setup.js './tests/**/*.js'", + "tests": "tape -r ./tests/setup.js \"./tests/**/*.js\"", "test": "npm run lint && npm run tests | tap-spec", "coverage-tests": "npm run tests | tap-nyc", "coverage": "nyc -x \"tests/**\" npm run coverage-tests", -- 2.49.1 From ff622896838b014b6abc460a3fdd8d9c53c2048c Mon Sep 17 00:00:00 2001 From: Robin North Date: Wed, 24 Jan 2018 14:51:16 +0000 Subject: [PATCH 42/92] Make use of new `noop` module --- index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index b9802e9..6db68c1 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,8 @@ var forEachEls = require("./lib/foreach-els.js") var newUid = require("./lib/uniqueid.js") +var noop = require("./lib/util/noop") + var on = require("./lib/events/on.js") // var off = require("./lib/events/on.js") var trigger = require("./lib/events/trigger.js") @@ -331,10 +333,10 @@ if (Pjax.isSupported()) { } // if there isn’t required browser functions, returning stupid api else { - var stupidPjax = function() {} + var stupidPjax = noop for (var key in Pjax.prototype) { if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") { - stupidPjax[key] = stupidPjax + stupidPjax[key] = noop } } -- 2.49.1 From f7f68b2e503a5eb835cdeaaf2670ad983a27a6b3 Mon Sep 17 00:00:00 2001 From: Robin North Date: Wed, 24 Jan 2018 23:15:40 +0000 Subject: [PATCH 43/92] Fix module filename --- lib/util/{noop => noop.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/util/{noop => noop.js} (100%) diff --git a/lib/util/noop b/lib/util/noop.js similarity index 100% rename from lib/util/noop rename to lib/util/noop.js -- 2.49.1 From 137322543c18567ae5f84f6716762620f480f460 Mon Sep 17 00:00:00 2001 From: Robin North Date: Wed, 24 Jan 2018 23:20:36 +0000 Subject: [PATCH 44/92] Explicitly flag if aborting a request fails due to request completing --- tests/lib/abort-request.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/lib/abort-request.js b/tests/lib/abort-request.js index edaa774..8fa257a 100644 --- a/tests/lib/abort-request.js +++ b/tests/lib/abort-request.js @@ -23,7 +23,9 @@ tape("test aborting xhr request", function(t) { }) t.test("- pending request is aborted", function(t) { - var r = requestCacheBust("https://httpbin.org/delay/10", {}, function() {}) + var r = requestCacheBust("https://httpbin.org/delay/10", {}, function() { + t.fail("xhr was not aborted") + }) t.equal(r.readyState, 1, "xhr readyState is '1' (SENT)") abortRequest(r) t.equal(r.readyState, 0, "xhr readyState is '0' (ABORTED)") -- 2.49.1 From f559ca29149977d94cd5b12ef08a5027626ca5c8 Mon Sep 17 00:00:00 2001 From: Robin North Date: Mon, 22 Jan 2018 19:06:45 +0000 Subject: [PATCH 45/92] Cleanup default analytics function --- index.js | 4 +++- lib/proto/parse-options.js | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index e8ca6e6..8499ff3 100644 --- a/index.js +++ b/index.js @@ -272,7 +272,9 @@ Pjax.prototype = { // Fire Events trigger(document,"pjax:complete pjax:success", state.options) - state.options.analytics() + if (typeof state.options.analytics === "function") { + state.options.analytics() + } if (state.options.history) { // First parse url and check for hash to override scroll diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 37c16c7..2d89b99 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -10,9 +10,6 @@ module.exports = function(options) { this.options.switchesOptions = this.options.switchesOptions || {} this.options.history = this.options.history || true this.options.analytics = this.options.analytics || function() { - // options.backward or options.foward can be true or undefined - // by default, we do track back/foward hit - // https://productforums.google.com/forum/#!topic/analytics/WVwMDjLhXYk if (window._gaq) { _gaq.push(["_trackPageview"]) } @@ -36,7 +33,4 @@ module.exports = function(options) { if (!this.options.switches.body) { this.options.switches.body = defaultSwitches.switchElementsAlt } - if (typeof options.analytics !== "function") { - options.analytics = function() {} - } } -- 2.49.1 From cc384b9b160d483069e132f77d2202094d11983b Mon Sep 17 00:00:00 2001 From: Robin North Date: Tue, 23 Jan 2018 09:23:39 +0000 Subject: [PATCH 46/92] Improve check for analytics function option --- lib/proto/parse-options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 2d89b99..116476f 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -9,7 +9,7 @@ module.exports = function(options) { this.options.switches = this.options.switches || {} this.options.switchesOptions = this.options.switchesOptions || {} this.options.history = this.options.history || true - this.options.analytics = this.options.analytics || function() { + this.options.analytics = (typeof this.options.analytics === "function") ? this.options.analytics : function() { if (window._gaq) { _gaq.push(["_trackPageview"]) } -- 2.49.1 From f642eec04714acbb739d6d0660e12d9bab85a725 Mon Sep 17 00:00:00 2001 From: Robin North Date: Wed, 24 Jan 2018 11:26:13 +0000 Subject: [PATCH 47/92] Preserve ability to disable analytics behavior, explicitly document this option --- README.md | 7 ++++--- lib/proto/parse-options.js | 16 ++++++++------- tests/lib/proto/parse-options.js | 34 +++++++++++++------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 961305d..5bd22e3 100644 --- a/README.md +++ b/README.md @@ -371,12 +371,14 @@ Enable pushState. Only disable if you are crazy. Internaly, this option is used when `popstate` is used (to not pushState again). You should forget that option. -##### `analytics` (Function, default to a function that push `_gaq` `trackPageview` or send `ga` `pageview` +##### `analytics` (Function|Boolean, default to a function that pushes `_gaq` `_trackPageview` or sends `ga` `pageview` -Function that allow you to add behavior for analytics. By default it try to track +Function that allows you to add behavior for analytics. By default it tries to track a pageview with Google Analytics. It's called every time a page is switched, even for history buttons. +Set to `false` to disable this behavior. + ##### `scrollTo` (Integer, default to 0) Value (in px) to scrollTo when a page is switched. @@ -543,4 +545,3 @@ Clone this repository and run `npm run example`, which will open the example app ## [CHANGELOG](CHANGELOG.md) ## [LICENSE](LICENSE) - diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 116476f..3679cb6 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -9,14 +9,16 @@ module.exports = function(options) { this.options.switches = this.options.switches || {} this.options.switchesOptions = this.options.switchesOptions || {} this.options.history = this.options.history || true - this.options.analytics = (typeof this.options.analytics === "function") ? this.options.analytics : function() { - if (window._gaq) { - _gaq.push(["_trackPageview"]) + this.options.analytics = (typeof this.options.analytics === "function" || this.options.analytics === false) ? + this.options.analytics : + function() { + if (window._gaq) { + _gaq.push(["_trackPageview"]) + } + if (window.ga) { + ga("send", "pageview", {page: location.pathname, title: document.title}) + } } - if (window.ga) { - ga("send", "pageview", {page: location.pathname, title: document.title}) - } - } this.options.scrollTo = (typeof this.options.scrollTo === "undefined") ? 0 : this.options.scrollTo; this.options.cacheBust = (typeof this.options.cacheBust === "undefined") ? true : this.options.cacheBust this.options.debug = this.options.debug || false diff --git a/tests/lib/proto/parse-options.js b/tests/lib/proto/parse-options.js index 8f57a65..3c4a4b3 100644 --- a/tests/lib/proto/parse-options.js +++ b/tests/lib/proto/parse-options.js @@ -34,26 +34,20 @@ tape("test parse initalization options function", function(t) { var options1 = {}; parseOptions.apply(body1, [options1]); - t.deepEqual(body1.options.elements, "a[href], form[action]"); - t.deepEqual(body1.options.selectors.length, 2, "selectors length"); - t.deepEqual(body1.options.selectors[0], "title"); - t.deepEqual(body1.options.selectors[1], ".js-Pjax"); - - t.deepEqual(isObjLiteral(body1.options.switches), true); - t.deepEqual(enumerableKeys(body1.options.switches), 2);// head and body - - t.deepEqual(isObjLiteral(body1.options.switchesOptions), true); - t.deepEqual(enumerableKeys(body1.options.switchesOptions), 0); - - t.deepEqual(body1.options.history, true); - - // TODO analytics is a little weird right now - t.deepEqual(typeof body1.options.analytics, "function"); - - t.deepEqual(body1.options.scrollTo, 0); - t.deepEqual(body1.options.cacheBust, true); - t.deepEqual(body1.options.debug, false); - t.deepEqual(body1.options.scrollRestoration, true) + t.equal(body1.options.elements, "a[href], form[action]"); + t.equal(body1.options.selectors.length, 2, "selectors length"); + t.equal(body1.options.selectors[0], "title"); + t.equal(body1.options.selectors[1], ".js-Pjax"); + t.equal(isObjLiteral(body1.options.switches), true); + t.equal(enumerableKeys(body1.options.switches), 2);// head and body + t.equal(isObjLiteral(body1.options.switchesOptions), true); + t.equal(enumerableKeys(body1.options.switchesOptions), 0); + t.equal(body1.options.history, true); + t.equal(typeof body1.options.analytics, "function"); + t.equal(body1.options.scrollTo, 0); + t.equal(body1.options.scrollRestoration, true); + t.equal(body1.options.cacheBust, true); + t.equal(body1.options.debug, false); t.end(); }); -- 2.49.1 From 01fb72ceeb6f883daf7154aaa9acbbd4f78b700a Mon Sep 17 00:00:00 2001 From: Robin North Date: Wed, 24 Jan 2018 11:30:46 +0000 Subject: [PATCH 48/92] Simplify options assignment --- lib/proto/parse-options.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 3679cb6..358b8ff 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -3,14 +3,13 @@ var defaultSwitches = require("../switches") module.exports = function(options) { - this.options = options - this.options.elements = this.options.elements || "a[href], form[action]" - this.options.selectors = this.options.selectors || ["title", ".js-Pjax"] - this.options.switches = this.options.switches || {} - this.options.switchesOptions = this.options.switchesOptions || {} - this.options.history = this.options.history || true - this.options.analytics = (typeof this.options.analytics === "function" || this.options.analytics === false) ? - this.options.analytics : + 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 = options.history || true + options.analytics = (typeof options.analytics === "function" || options.analytics === false) ? + options.analytics : function() { if (window._gaq) { _gaq.push(["_trackPageview"]) @@ -19,20 +18,22 @@ module.exports = function(options) { ga("send", "pageview", {page: location.pathname, title: document.title}) } } - this.options.scrollTo = (typeof this.options.scrollTo === "undefined") ? 0 : this.options.scrollTo; - this.options.cacheBust = (typeof this.options.cacheBust === "undefined") ? true : this.options.cacheBust - this.options.debug = this.options.debug || false - this.options.timeout = this.options.timeout || 0 - this.options.scrollRestoration = (typeof this.options.scrollRestoration !== "undefined") ? this.options.scrollRestoration : true + 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 // we can’t replace body.outerHTML or head.outerHTML // it create a bug where new body or new head are created in the dom // if you set head.outerHTML, a new body tag is appended, so the dom get 2 body // & it break the switchFallback which replace head & body - if (!this.options.switches.head) { - this.options.switches.head = defaultSwitches.switchElementsAlt + if (!options.switches.head) { + options.switches.head = defaultSwitches.switchElementsAlt } - if (!this.options.switches.body) { - this.options.switches.body = defaultSwitches.switchElementsAlt + if (!options.switches.body) { + options.switches.body = defaultSwitches.switchElementsAlt } + + this.options = options } -- 2.49.1 From fa05e94f07dc2f65e6d5015e6a81a8b692218cc2 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Wed, 24 Jan 2018 20:18:41 -0500 Subject: [PATCH 49/92] Only blur element if it's contained by one of the selectors Previously, Pjax would blur (remove focus) from the active element regardless of where it was on the page. This restricts that to happen only if the active element is contained by one of the elements represented by options.selectors, because only those are affected by Pjax. Fixes #4 --- index.js | 3 ++- lib/util/contains.js | 12 ++++++++++++ tests/lib/util/contains.js | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 lib/util/contains.js create mode 100644 tests/lib/util/contains.js diff --git a/index.js b/index.js index 6db68c1..37e1187 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,7 @@ var forEachEls = require("./lib/foreach-els.js") var newUid = require("./lib/uniqueid.js") var noop = require("./lib/util/noop") +var contains = require("./lib/util/contains.js") var on = require("./lib/events/on.js") // var off = require("./lib/events/on.js") @@ -129,7 +130,7 @@ Pjax.prototype = { // Clear out any focused controls before inserting new page contents. // we clear focus on non form elements - if (document.activeElement && !document.activeElement.value) { + if (document.activeElement && !document.activeElement.value && contains(this.options.selectors, document.activeElement)) { try { document.activeElement.blur() } catch (e) { } diff --git a/lib/util/contains.js b/lib/util/contains.js new file mode 100644 index 0000000..9f169a7 --- /dev/null +++ b/lib/util/contains.js @@ -0,0 +1,12 @@ +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 +} diff --git a/tests/lib/util/contains.js b/tests/lib/util/contains.js new file mode 100644 index 0000000..533bbe2 --- /dev/null +++ b/tests/lib/util/contains.js @@ -0,0 +1,16 @@ +var tape = require("tape") + +var contains = require("../../../lib/util/contains.js") + +tape("test contains function", function(t) { + var tempDoc = document.implementation.createHTMLDocument() + tempDoc.body.innerHTML = "

" + var selectors = ["div"] + var el = tempDoc.body.querySelector("#el") + t.equal(contains(tempDoc, selectors, el), true, "contains() returns true when a selector contains the element") + + selectors = ["span"] + t.equal(contains(tempDoc, selectors, el), false, "contains() returns false when the selectors do not contain the element") + + t.end() +}) -- 2.49.1 From 2d4df39f727af249d8a9023b7328502756304fbc Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Wed, 24 Jan 2018 20:19:02 -0500 Subject: [PATCH 50/92] Remove a redundant call to .blur() --- index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/index.js b/index.js index 37e1187..25cb76e 100644 --- a/index.js +++ b/index.js @@ -181,9 +181,6 @@ Pjax.prototype = { }, document.title, window.location) - // Clear out any focused controls before inserting new page contents. - document.activeElement.blur() - var oldHref = href if (request.responseURL) { if (href !== request.responseURL) { -- 2.49.1 From 05163d9fb503777a5bb4056bba52ad745f4b52c0 Mon Sep 17 00:00:00 2001 From: Robin North Date: Fri, 26 Jan 2018 15:53:07 +0000 Subject: [PATCH 51/92] Stop dispatching extraneous `pjax:complete` events, correct typo --- lib/eval-script.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/eval-script.js b/lib/eval-script.js index 232090d..382e43e 100644 --- a/lib/eval-script.js +++ b/lib/eval-script.js @@ -1,6 +1,4 @@ module.exports = function(el) { - // console.log("going to execute script", el) - var code = (el.text || el.textContent || el.innerHTML || "") var src = (el.src || ""); var parent = el.parentNode || document.querySelector("head") || document.documentElement @@ -17,8 +15,7 @@ module.exports = function(el) { if (src != "") { script.src = src; - script.onload = function() { document.dispatchEvent((new Event("pjax:complete"))); } - script.async = false; // force asynchronous loading of peripheral js + script.async = false; // force synchronous loading of peripheral js } if (code != "") { -- 2.49.1 From 31e1e7e405e5b25431d6794e96a8ff1db5307eff Mon Sep 17 00:00:00 2001 From: Robin North Date: Fri, 26 Jan 2018 15:54:07 +0000 Subject: [PATCH 52/92] Enable test for `document.write` in eval'd scripts --- tests/lib/eval-scripts.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/lib/eval-scripts.js b/tests/lib/eval-scripts.js index 43113b9..183fb76 100644 --- a/tests/lib/eval-scripts.js +++ b/tests/lib/eval-scripts.js @@ -13,10 +13,11 @@ tape("test evalScript method", function(t) { evalScript(script) t.equal(document.body.className, "executed", "script has been properly executed") - // script.innerHTML = "document.write('failure')" - // var bodyText = document.body.text - // evalScript(script) - // t.equal(document.body.text, bodyText, "document.write hasn't been executed") + script.innerHTML = "document.write('failure')" + document.body.text = "document.write hasn't been executed" + var bodyText = document.body.text + evalScript(script) + t.equal(document.body.text, bodyText, "document.write hasn't been executed") t.end() }) -- 2.49.1 From 8718da35c6f6efcff75ea51acb5e02556284ed57 Mon Sep 17 00:00:00 2001 From: Robin North Date: Sun, 28 Jan 2018 14:35:25 +0000 Subject: [PATCH 53/92] Opt in to manual scroll restoration in browsers that support it --- index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.js b/index.js index b6a35d5..a8a00db 100644 --- a/index.js +++ b/index.js @@ -25,6 +25,10 @@ var Pjax = function(options) { parseOptions.apply(this,[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) -- 2.49.1 From 90d26d641cbc76bd120646fa9aff3d16262a9973 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 29 Jan 2018 13:51:00 -0500 Subject: [PATCH 54/92] Remove focus from form elements as well. --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 25cb76e..21e6d6b 100644 --- a/index.js +++ b/index.js @@ -130,7 +130,7 @@ Pjax.prototype = { // Clear out any focused controls before inserting new page contents. // we clear focus on non form elements - if (document.activeElement && !document.activeElement.value && contains(this.options.selectors, document.activeElement)) { + if (document.activeElement && contains(this.options.selectors, document.activeElement)) { try { document.activeElement.blur() } catch (e) { } -- 2.49.1 From 63bc2af226d93138058b260dd579c64d4b29ca73 Mon Sep 17 00:00:00 2001 From: Robin North Date: Mon, 29 Jan 2018 20:06:33 +0000 Subject: [PATCH 55/92] Remove redundant comment --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 3a7e11f..64b933a 100644 --- a/index.js +++ b/index.js @@ -133,7 +133,6 @@ Pjax.prototype = { this.log("load content", tmpEl.documentElement.attributes, tmpEl.documentElement.innerHTML.length) // Clear out any focused controls before inserting new page contents. - // we clear focus on non form elements if (document.activeElement && contains(this.options.selectors, document.activeElement)) { try { document.activeElement.blur() -- 2.49.1 From a72880d205c904b94bca3b9724336ea46a4890df Mon Sep 17 00:00:00 2001 From: BehindTheMath Date: Fri, 2 Feb 2018 09:52:44 -0500 Subject: [PATCH 56/92] Code cleanup (#120) * Lots of code cleanup * Cleanup parse-options tests - Rename objects for clarity and inline unneeded objects - Remove unneeded tests - Use Object.keys().length instead of a custom function - Use typeof === "object" instead of a custom function that checks the prototype tree as well, since we don't expect anything but an object literal. * Remove old switchFallback code * Remove polyfill for Function.prototype.bind * Inline small functions * Add more documentation and tests for options.currentUrlFullReload Closes #17 * Update package.json --- README.md | 240 +++++++++++++---------- index.js | 67 +++---- lib/clone.js | 2 +- lib/eval-script.js | 16 +- lib/events/trigger.js | 6 +- lib/execute-scripts.js | 8 +- lib/foreach-els.js | 2 +- lib/polyfills/Function.prototype.bind.js | 20 -- lib/proto/attach-form.js | 47 ++--- lib/proto/attach-link.js | 10 +- lib/proto/get-elements.js | 3 - lib/proto/log.js | 6 +- lib/proto/parse-dom.js | 7 - lib/proto/parse-options.js | 11 +- lib/proto/refresh.js | 3 - lib/reload.js | 3 - lib/send-request.js | 10 +- lib/switches-selectors.js | 8 +- lib/switches.js | 24 +-- package.json | 9 +- tests/lib/abort-request.js | 4 +- tests/lib/events.js | 7 +- tests/lib/proto/attach-form.js | 9 +- tests/lib/proto/attach-link.js | 9 +- tests/lib/proto/parse-options.js | 92 ++++----- tests/lib/send-request.js | 12 +- 26 files changed, 276 insertions(+), 359 deletions(-) delete mode 100644 lib/polyfills/Function.prototype.bind.js delete mode 100644 lib/proto/get-elements.js delete mode 100644 lib/proto/parse-dom.js delete mode 100644 lib/proto/refresh.js delete mode 100644 lib/reload.js diff --git a/README.md b/README.md index 5bd22e3..54b2665 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ [![Build Status](http://img.shields.io/travis/MoOx/pjax.svg)](https://travis-ci.org/MoOx/pjax). -> Easily enable fast Ajax navigation on any website (using pushState + xhr) +> Easily enable fast AJAX navigation on any website (using pushState() + XHR) Pjax is ~~a jQuery plugin~~ **a standalone JavaScript module** that uses -ajax (XmlHttpRequest) and +AJAX (XmlHttpRequest) and [pushState()](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history) to deliver a fast browsing experience. -_It allow you to completely transform user experience of standard websites -(server side generated or static ones) to make them feel they browse an app. -Especially for user that have low bandwidth connection._ +_It allows you to completely transform the user experience of standard websites +(server-side generated or static ones) to make them feel like they are browsing an app, +especially for users with low bandwidth connection._ -**No more full page reload. No more lots of HTTP request.** +**No more full page reloads. No more multiple HTTP requests.** ## Demo @@ -21,7 +21,7 @@ Especially for user that have low bandwidth connection._ ## Installation -- You can install pjax from **npm**: +- You can install Pjax from **npm**: ```shell npm install pjax ``` @@ -37,45 +37,47 @@ Especially for user that have low bandwidth connection._ ## No dependencies -_There is nothing you need. No jQuery or something._ +_Pjax does not rely on other libraries, like jQuery or similar. It is written entirely in vanilla JS._ ## How Pjax works -Pjax loads page using ajax & updates the browser's current url using pushState without reloading your page's layout or any resources (js, css), giving a fast page load. -_But under the hood, it's just ONE http request with a pushState() call._ -Obviously, for [browsers that don't support pushState()](http://caniuse.com/#search=pushstate) Pjax fully degrades (yeah, it doesn't do anything at all). +Pjax loads pages using AJAX and updates the browser's current URL using `pushState()` without reloading your page's layout or any resources (JS, CSS), giving a fast page load. -It simply works with all permalinks & can update all parts of the page you -want (including html metas, title, navigation state). +_But under the hood, it's just ONE HTTP request with a `pushState()` call._ -- It's not limited to one container, like jQuery-Pjax is, -- It fully support browser history (back & forward buttons), -- It **will** support keyboard browsing (@todo), -- Automatically fallback to classic navigation for externals pages (thanks to Capitain Obvious help), -- Automatically fallback to classic navigation for internals pages that will not have the appropriated DOM tree, +Obviously, for [browsers that don't support `history.pushState()`](http://caniuse.com/#search=pushstate) Pjax gracefully degrades and does not do anything at all. + +It simply works with all permalinks and can update all parts of the page you +want (including HTML metas, title, and navigation state). + +- It's not limited to one container, like jQuery-Pjax is. +- It fully supports browser history (back and forward buttons). +- It supports keyboard browsing. +- Automatically falls back to standard navigation for external pages (thanks to Captain Obvious's help). +- Automatically falls back to standard navigation for internal pages that do not have an appropriate DOM tree. - You can add pretty cool CSS transitions (animations) very easily. -- It's around 3kb (minified & gzipped). +- It's around 4kb (minified and gzipped). ### Under the hood -- It listen to every clicks on links _you want_ (by default all of them), -- When an internal link is clicked, Pjax grabs HTML from your server via ajax, -- Pjax render pages DOM tree (without loading any resources - images, css, js...) -- It check if all defined parts can be replaced: - - if page doesn't suit requirement, classic navigation used, - - if page suits requirement, Pjax does all defined DOM replacements -- Then, it updates the browser's current url using pushState +- It listens to every click on links _you want_ (by default all of them). +- When an internal link is clicked, Pjax grabs HTML from your server via AJAX. +- Pjax renders the page's DOM tree (without loading any resources - images, CSS, JS...). +- It checks that all defined parts can be replaced: + - If the page doesn't meet the requirements, standard navigation is used. + - If the page meets the requirements, Pjax does all defined DOM replacements. +- Then it updates the browser's current URL using `pushState()`. ## Overview -Pjax is fully automatic. You won't need to setup anything on the existing HTML. +Pjax is fully automatic. You don't need to setup anything in the existing HTML. You just need to designate some elements on your page that will be replaced when you navigate your site. Consider the following page. ```html - + @@ -93,7 +95,7 @@ Consider the following page. ``` -We want Pjax to grab the url `/blah` then replace `.my-Content` with whatever it gets back. +We want Pjax to intercept the URL `/blah`, and replace `.my-Content` with the results of the request. Oh and the `