From 6fb509a021ba1846efcf2561bdbce0cf2c4b0501 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 29 Jan 2018 23:20:00 -0500 Subject: [PATCH 01/13] Code cleanup --- index.js | 7 +++---- lib/clone.js | 2 +- lib/eval-script.js | 6 +++--- lib/proto/attach-form.js | 4 ++-- lib/proto/attach-link.js | 2 +- lib/proto/parse-options.js | 1 + lib/send-request.js | 2 +- lib/switches.js | 2 +- tests/lib/abort-request.js | 4 ++-- tests/lib/proto/attach-form.js | 7 +++---- tests/lib/proto/attach-link.js | 7 +++---- tests/lib/send-request.js | 8 ++++---- 12 files changed, 25 insertions(+), 27 deletions(-) diff --git a/index.js b/index.js index 64b933a..61bf465 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ var Pjax = function(options) { } var parseOptions = require("./lib/proto/parse-options.js"); - parseOptions.apply(this,[options]) + parseOptions.call(this,options) this.log("Pjax options", this.options) if (this.options.scrollRestoration && "scrollRestoration" in history) { @@ -168,7 +168,7 @@ Pjax.prototype = { 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) + trigger(document, "pjax:complete pjax:error", options) return } @@ -218,8 +218,7 @@ Pjax.prototype = { if (console && console.error) { console.error("Pjax switch fail: ", e) } - this.latestChance(href) - return + return this.latestChance(href) } else { throw e diff --git a/lib/clone.js b/lib/clone.js index a6b9ae6..674baa3 100644 --- a/lib/clone.js +++ b/lib/clone.js @@ -1,5 +1,5 @@ module.exports = function(obj) { - if (null === obj || "object" != typeof obj) { + if (null === obj || "object" !== typeof obj) { return obj } var copy = obj.constructor() diff --git a/lib/eval-script.js b/lib/eval-script.js index 382e43e..3e9f516 100644 --- a/lib/eval-script.js +++ b/lib/eval-script.js @@ -13,12 +13,12 @@ module.exports = function(el) { script.type = "text/javascript" - if (src != "") { + if (src !== "") { script.src = src; script.async = false; // force synchronous loading of peripheral js } - if (code != "") { + if (code !== "") { try { script.appendChild(document.createTextNode(code)) } @@ -31,7 +31,7 @@ module.exports = function(el) { // execute parent.appendChild(script); // avoid pollution only in head or body tags - if (["head","body"].indexOf(parent.tagName.toLowerCase()) > 0) { + if (["head", "body"].indexOf(parent.tagName.toLowerCase()) > 0) { parent.removeChild(script) } diff --git a/lib/proto/attach-form.js b/lib/proto/attach-form.js index f6775a2..5ec846e 100644 --- a/lib/proto/attach-form.js +++ b/lib/proto/attach-form.js @@ -13,7 +13,7 @@ var formAction = function(el, event) { // Initialize requestOptions options.requestOptions = { requestUrl: el.getAttribute("action") || window.location.href, - requestMethod: el.getAttribute("method") || "GET", + requestMethod: el.getAttribute("method") || "GET" } // create a testable virtual link of the form action @@ -93,7 +93,7 @@ module.exports = function(el) { } - if (event.keyCode == 13) { + 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 69945bc..b0af05c 100644 --- a/lib/proto/attach-link.js +++ b/lib/proto/attach-link.js @@ -91,7 +91,7 @@ module.exports = function(el) { return } - if (event.keyCode == 13) { + if (event.keyCode === 13) { linkAction.call(that, el, event) } }.bind(this)) diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index 358b8ff..6c35d0d 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -3,6 +3,7 @@ var defaultSwitches = require("../switches") module.exports = function(options) { + options = options || {} options.elements = options.elements || "a[href], form[action]" options.selectors = options.selectors || ["title", ".js-Pjax"] options.switches = options.switches || {} diff --git a/lib/send-request.js b/lib/send-request.js index 9a4e5b8..a474e05 100644 --- a/lib/send-request.js +++ b/lib/send-request.js @@ -35,7 +35,7 @@ module.exports = function(location, options, callback) { request.setRequestHeader("X-PJAX", "true") // Add the request payload if available - if (options.requestPayloadString != undefined && options.requestPayloadString != "") { + if (options.requestPayloadString !== undefined && options.requestPayloadString !== "") { // Send the proper header information along with the request request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); } diff --git a/lib/switches.js b/lib/switches.js index 405d6bf..51daf6b 100644 --- a/lib/switches.js +++ b/lib/switches.js @@ -40,7 +40,7 @@ module.exports = { var animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend" var animatedElsNumber = 0 var sexyAnimationEnd = function(e) { - if (e.target != e.currentTarget) { + if (e.target !== e.currentTarget) { // end triggered by an animation on a child return } diff --git a/tests/lib/abort-request.js b/tests/lib/abort-request.js index 8fa257a..7661de0 100644 --- a/tests/lib/abort-request.js +++ b/tests/lib/abort-request.js @@ -18,8 +18,8 @@ if (!("responseURL" in XMLHttpRequest.prototype)) { tape("test aborting xhr request", function(t) { var requestCacheBust = sendRequest.bind({ options: { - cacheBust: true, - }, + cacheBust: true + } }) t.test("- pending request is aborted", function(t) { diff --git a/tests/lib/proto/attach-form.js b/tests/lib/proto/attach-form.js index ab89543..86ecdbc 100644 --- a/tests/lib/proto/attach-form.js +++ b/tests/lib/proto/attach-form.js @@ -80,12 +80,11 @@ tape("test attach form preventDefaulted events", function(t) { tape("test options are not modified by attachForm", function(t) { var form = document.createElement("form") var options = {foo: "bar"} - var loadUrl = () => {} + var loadUrl = function() {} - attachForm.call({options, loadUrl}, form) + attachForm.call({options: options, loadUrl: loadUrl}, form) - var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search - form.action = internalUri + form.action = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search form.method = "GET" trigger(form, "submit") diff --git a/tests/lib/proto/attach-link.js b/tests/lib/proto/attach-link.js index fbaae4e..3a5141c 100644 --- a/tests/lib/proto/attach-link.js +++ b/tests/lib/proto/attach-link.js @@ -79,12 +79,11 @@ tape("test attach link preventDefaulted events", function(t) { tape("test options are not modified by attachLink", function(t) { var a = document.createElement("a") var options = {foo: "bar"} - var loadUrl = () => {}; + var loadUrl = function() {}; - attachLink.call({options, loadUrl}, a) + attachLink.call({options: options, loadUrl: loadUrl}, a) - var internalUri = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search - a.href = internalUri + a.href = window.location.protocol + "//" + window.location.host + window.location.pathname + window.location.search trigger(a, "click") diff --git a/tests/lib/send-request.js b/tests/lib/send-request.js index f33b28c..cbc3b36 100644 --- a/tests/lib/send-request.js +++ b/tests/lib/send-request.js @@ -20,8 +20,8 @@ tape("test xhr request", function(t) { t.test("- request is made, gets a result, and is cache-busted", function(t) { var requestCacheBust = sendRequest.bind({ options: { - cacheBust: true, - }, + cacheBust: true + } }); var r = requestCacheBust(url, {}, function(result) { t.equal(r.responseURL.indexOf("?"), url.length, "XHR URL is cache-busted when configured to be") @@ -38,8 +38,8 @@ tape("test xhr request", function(t) { t.test("- request is not cache-busted when configured not to be", function(t) { var requestNoCacheBust = sendRequest.bind({ options: { - cacheBust: false, - }, + cacheBust: false + } }); var r = requestNoCacheBust(url, {}, function() { t.equal(r.responseURL, url, "XHR URL is left untouched") -- 2.49.1 From 7ca1d4c2c8be443a8bd8a979fce0ed296dc4af68 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 29 Jan 2018 23:24:22 -0500 Subject: [PATCH 02/13] 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. --- tests/lib/proto/parse-options.js | 79 +++++++++++--------------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/tests/lib/proto/parse-options.js b/tests/lib/proto/parse-options.js index 3c4a4b3..f4914db 100644 --- a/tests/lib/proto/parse-options.js +++ b/tests/lib/proto/parse-options.js @@ -2,72 +2,47 @@ var tape = require("tape") 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; - })() - ) - ); - } - - function enumerableKeys(_obj) { - var c = 0; - for (var n in _obj) { - n = n; - c++; - } - return c; - } - t.test("- default options", function(t) { - var body1 = {}; - var options1 = {}; - parseOptions.apply(body1, [options1]); + var pjax = {}; + parseOptions.call(pjax, {}); - 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.equal(pjax.options.elements, "a[href], form[action]"); + t.equal(pjax.options.selectors.length, 2, "selectors length"); + t.equal(pjax.options.selectors[0], "title"); + t.equal(pjax.options.selectors[1], ".js-Pjax"); + + t.equal(typeof pjax.options.switches, "object"); + t.equal(Object.keys(pjax.options.switches).length, 2);// head and body + + t.equal(typeof pjax.options.switchesOptions, "object"); + t.equal(Object.keys(pjax.options.switchesOptions).length, 0); + + t.equal(pjax.options.history, true); + t.equal(typeof pjax.options.analytics, "function"); + t.equal(pjax.options.scrollTo, 0); + t.equal(pjax.options.scrollRestoration, true); + t.equal(pjax.options.cacheBust, true); + t.equal(pjax.options.debug, false); t.end(); }); // 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]); + var pjax = {}; + parseOptions.call(pjax, {analytics: "some string"}); - t.deepEqual(typeof body2.options.analytics, "function"); + t.deepEqual(typeof pjax.options.analytics, "function"); t.end(); }); + // verify that the value false for scrollTo is not squashed t.test("- scrollTo remains false", function(t) { - var body3 = {}; - var options3 = {scrollTo: false}; - parseOptions.apply(body3, [options3]); + var pjax = {}; + parseOptions.call(pjax, {scrollTo: false}); - t.deepEqual(body3.options.scrollTo, false); + t.deepEqual(pjax.options.scrollTo, false); t.end(); }); + t.end() }) -- 2.49.1 From 70f63837021d0e14d93bad9e1fc8866c39e55775 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 29 Jan 2018 23:53:27 -0500 Subject: [PATCH 03/13] Clean up README --- README.md | 167 +++++++++++++++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 5bd22e3..debf522 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 @@ -37,45 +37,45 @@ Especially for user that have low bandwidth connection._ ## No dependencies -_There is nothing you need. No jQuery or something._ +_There is nothing you need. No jQuery or anything else._ ## 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 & 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 gracefully degrades and does not do anything at all. It simply works with all permalinks & can update all parts of the page you -want (including html metas, title, navigation state). +want (including HTML metas, title, and navigation state). -- 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, +- It's not limited to one container, like jQuery-Pjax is. +- It fully supports browser history (back & forward buttons). +- It supports keyboard browsing. +- Automatically falls back to classic navigation for external pages (thanks to Capitain Obvious's help). +- Automatically falls back to classic 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 & 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, classic navigation is used. + - if 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 +93,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 `