From c9e292af6e997022a32bffd667a09ff26b2c414d Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 27 Feb 2018 13:24:19 -0500 Subject: [PATCH 1/6] Move the XHR callback to a separate file --- index.js | 63 ++-------------------------------- lib/proto/handle-response.js | 65 ++++++++++++++++++++++++++++++++++++ lib/send-request.js | 8 ++--- 3 files changed, 72 insertions(+), 64 deletions(-) create mode 100644 lib/proto/handle-response.js diff --git a/index.js b/index.js index 3e8e749..7594c29 100644 --- a/index.js +++ b/index.js @@ -142,6 +142,8 @@ Pjax.prototype = { doRequest: require("./lib/send-request.js"), + handleResponse: require("./lib/proto/handle-response.js"), + loadUrl: function(href, options) { options = typeof options === "object" ? extend({}, this.options, options) : @@ -155,66 +157,7 @@ Pjax.prototype = { trigger(document, "pjax:send", options) // Do the request - this.request = this.doRequest(href, options, function(html, request) { - // Fail if unable to load HTML via AJAX - if (html === false) { - trigger(document, "pjax:complete pjax:error", options) - - return - } - - // push scroll position to history - var currentState = window.history.state || {} - window.history.replaceState({ - url: currentState.url || window.location.href, - title: currentState.title || document.title, - uid: currentState.uid || newUid(), - scrollPos: [document.documentElement.scrollLeft || document.body.scrollLeft, - document.documentElement.scrollTop || document.body.scrollTop] - }, - document.title, window.location) - - var oldHref = href - if (request.responseURL) { - if (href !== request.responseURL) { - href = request.responseURL - } - } - else if (request.getResponseHeader("X-PJAX-URL")) { - href = request.getResponseHeader("X-PJAX-URL") - } - else if (request.getResponseHeader("X-XHR-Redirected-To")) { - href = request.getResponseHeader("X-XHR-Redirected-To") - } - - // Add back the hash if it was removed - var a = document.createElement("a") - a.href = oldHref - var oldHash = a.hash - a.href = href - if (oldHash && !a.hash) { - a.hash = oldHash - href = a.href - } - - this.state.href = href - this.state.options = clone(options) - - try { - this.loadContent(html, options) - } - catch (e) { - if (!this.options.debug) { - if (console && console.error) { - console.error("Pjax switch fail: ", e) - } - return this.latestChance(href) - } - else { - throw e - } - } - }.bind(this)) + this.request = this.doRequest(href, options, this.handleResponse.bind(this)) }, afterAllSwitches: function() { diff --git a/lib/proto/handle-response.js b/lib/proto/handle-response.js new file mode 100644 index 0000000..a4faa80 --- /dev/null +++ b/lib/proto/handle-response.js @@ -0,0 +1,65 @@ +var clone = require("./lib/clone.js") +var newUid = require("./lib/uniqueid.js") +var trigger = require("./lib/events/trigger.js") + +module.export = function(responseText, request, href) { + // Fail if unable to load HTML via AJAX + if (responseText === false) { + trigger(document, "pjax:complete pjax:error", this.options) + + return + } + + // push scroll position to history + var currentState = window.history.state || {} + window.history.replaceState({ + url: currentState.url || window.location.href, + title: currentState.title || document.title, + uid: currentState.uid || newUid(), + scrollPos: [document.documentElement.scrollLeft || document.body.scrollLeft, + document.documentElement.scrollTop || document.body.scrollTop] + }, + document.title, window.location) + + // Check for redirects + var oldHref = href + if (request.responseURL) { + if (href !== request.responseURL) { + href = request.responseURL + } + } + else if (request.getResponseHeader("X-PJAX-URL")) { + href = request.getResponseHeader("X-PJAX-URL") + } + else if (request.getResponseHeader("X-XHR-Redirected-To")) { + href = request.getResponseHeader("X-XHR-Redirected-To") + } + + // Add back the hash if it was removed + var a = document.createElement("a") + a.href = oldHref + var oldHash = a.hash + a.href = href + if (oldHash && !a.hash) { + a.hash = oldHash + href = a.href + } + + this.state.href = href + this.state.options = clone(this.options) + + try { + this.loadContent(responseText, this.options) + } + catch (e) { + if (!this.options.debug) { + if (console && console.error) { + console.error("Pjax switch fail: ", e) + } + return this.latestChance(href) + } + else { + throw e + } + } +} diff --git a/lib/send-request.js b/lib/send-request.js index 9e203aa..37a2fa6 100644 --- a/lib/send-request.js +++ b/lib/send-request.js @@ -13,21 +13,21 @@ module.exports = function(location, options, callback) { request.onreadystatechange = function() { if (request.readyState === 4) { if (request.status === 200) { - callback(request.responseText, request) + callback(request.responseText, request, location) } else { - callback(null, request) + callback(null, request, location) } } } request.onerror = function(e) { console.log(e) - callback(null, request) + callback(null, request, location) } request.ontimeout = function() { - callback(null, request) + callback(null, request, location) } // Prepare the request payload for forms, if available -- 2.49.1 From b0748a2aa511a2b406780b69684922fab49e7ea7 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Tue, 27 Feb 2018 13:29:33 -0500 Subject: [PATCH 2/6] Trigger an error event if the response cannot be parsed. --- README.md | 2 +- lib/proto/handle-response.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38efdb5..9c4bd5a 100644 --- a/README.md +++ b/README.md @@ -453,7 +453,7 @@ All events are fired from the _document_, not the link that was clicked. * `pjax:send` - Fired after the Pjax request begins. * `pjax:complete` - Fired after the Pjax request finishes. * `pjax:success` - Fired after the Pjax request succeeds. -* `pjax:error` - Fired after the Pjax request fails. +* `pjax:error` - Fired after the Pjax request fails. The request object will be passed along as `event.options.request`. `send` and `complete` are a good pair of events to use if you are implementing a loading indicator (eg: [topbar](http://buunguyen.github.io/topbar/)) diff --git a/lib/proto/handle-response.js b/lib/proto/handle-response.js index a4faa80..9b0410c 100644 --- a/lib/proto/handle-response.js +++ b/lib/proto/handle-response.js @@ -5,7 +5,10 @@ var trigger = require("./lib/events/trigger.js") module.export = function(responseText, request, href) { // Fail if unable to load HTML via AJAX if (responseText === false) { - trigger(document, "pjax:complete pjax:error", this.options) + var tempOptions = this.options + tempOptions.request = request + + trigger(document, "pjax:complete pjax:error", tempOptions) return } @@ -52,6 +55,10 @@ module.export = function(responseText, request, href) { this.loadContent(responseText, this.options) } catch (e) { + var tempOptions2 = this.options + tempOptions2.request = request + trigger(document, "pjax:error", tempOptions2) + if (!this.options.debug) { if (console && console.error) { console.error("Pjax switch fail: ", e) -- 2.49.1 From f9b67609b0c897d652d7a942fc86aaa6dd01fe44 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Fri, 9 Mar 2018 10:08:29 -0500 Subject: [PATCH 3/6] Update README --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 9c4bd5a..ec79448 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,34 @@ pjax.loadUrl("/your-url") pjax.loadUrl("/your-other-url", {timeout: 10}) ``` +#### `handleResponse(responseText, request, href)` + +This method takes the raw response, processes the URL, then calls `pjax.loadContent()` to actually load it into the DOM. + +It is passed the following arguments: + +* **responseText** (string): This is the raw response text. This is equivalent to `request.responseText`. +* **request** (XMLHttpRequest): This is the XHR object. +* **href** (string): This is the URL that was passed to `loadUrl()`. + +You can override this if you want to process the data before, or instead of, it being loaded into the DOM. + +For example, if you want to check for a JSON response, you could do the following: + +```js +var pjax = new Pjax(); + +pjax._handleResponse = pjax.handleResponse; + +pjax.handleResponse = function(responseText, request, href) { + if (request.responseText.match(" Date: Fri, 9 Mar 2018 10:20:08 -0500 Subject: [PATCH 4/6] Fix paths and typo --- lib/proto/handle-response.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/proto/handle-response.js b/lib/proto/handle-response.js index 9b0410c..e9e7ce3 100644 --- a/lib/proto/handle-response.js +++ b/lib/proto/handle-response.js @@ -1,8 +1,8 @@ -var clone = require("./lib/clone.js") -var newUid = require("./lib/uniqueid.js") -var trigger = require("./lib/events/trigger.js") +var clone = require("../util/clone.js") +var newUid = require("../uniqueid.js") +var trigger = require("../events/trigger.js") -module.export = function(responseText, request, href) { +module.exports = function(responseText, request, href) { // Fail if unable to load HTML via AJAX if (responseText === false) { var tempOptions = this.options -- 2.49.1 From 1f0e015b3c9f8299486dad290ab57c785dba4ca4 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Sat, 10 Mar 2018 23:21:58 -0500 Subject: [PATCH 5/6] Clone this.options so we don't mutate it --- lib/proto/handle-response.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/proto/handle-response.js b/lib/proto/handle-response.js index e9e7ce3..442da38 100644 --- a/lib/proto/handle-response.js +++ b/lib/proto/handle-response.js @@ -3,11 +3,11 @@ var newUid = require("../uniqueid.js") var trigger = require("../events/trigger.js") module.exports = function(responseText, request, href) { + var tempOptions = clone(this.options); + tempOptions.request = request + // Fail if unable to load HTML via AJAX if (responseText === false) { - var tempOptions = this.options - tempOptions.request = request - trigger(document, "pjax:complete pjax:error", tempOptions) return @@ -55,9 +55,7 @@ module.exports = function(responseText, request, href) { this.loadContent(responseText, this.options) } catch (e) { - var tempOptions2 = this.options - tempOptions2.request = request - trigger(document, "pjax:error", tempOptions2) + trigger(document, "pjax:error", tempOptions) if (!this.options.debug) { if (console && console.error) { -- 2.49.1 From 2ad930f5732922980ca49cbed35f117a1b353b36 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 12 Mar 2018 23:54:59 -0400 Subject: [PATCH 6/6] Add tests for handle-response.js --- tests/lib/proto/handle-response.js | 204 +++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 tests/lib/proto/handle-response.js diff --git a/tests/lib/proto/handle-response.js b/tests/lib/proto/handle-response.js new file mode 100644 index 0000000..1e0bd37 --- /dev/null +++ b/tests/lib/proto/handle-response.js @@ -0,0 +1,204 @@ +var tape = require("tape") + +var handleReponse = require("../../../lib/proto/handle-response") +var noop = require("../../../lib/util/noop") + +var href = "https://example.org/" + +var storeEventHandler +var pjaxErrorEventTriggerTest + +tape("test events triggered when handleResponse(false) is called", function(t) { + t.plan(3) + + var pjax = { + options: { + test: 1 + } + } + + var events = [] + + storeEventHandler = function(e) { + events.push(e.type) + + t.equal(e.test, 1) + } + + document.addEventListener("pjax:complete", storeEventHandler) + document.addEventListener("pjax:error", storeEventHandler) + + handleReponse.bind(pjax)(false, null) + + t.same(events, ["pjax:complete", "pjax:error"], "calling handleResponse(false) triggers 'pjax:complete' and 'pjax:error'") + + t.end() +}) + +tape("test when handleResponse() is called normally", function(t) { + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + getResponseHeader: noop + } + + handleReponse.bind(pjax)("", request, "href") + + delete window.history.state.uid + t.same(window.history.state, { + url: href, + title: "", + scrollPos: [0, 0] + }, "window.history.state is set correctly") + t.equals(pjax.state.href, "href", "this.state.href is set correctly") + t.same(pjax.state.options, pjax.options, "this.state.options is set correctly") + + t.end() +}) + +tape("test when handleResponse() is called normally with request.responseURL", function(t) { + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + responseURL: href + "1", + getResponseHeader: noop + } + + handleReponse.bind(pjax)("", request, "") + + t.equals(pjax.state.href, request.responseURL, "this.state.href is set correctly") + + t.end() +}) + +tape("test when handleResponse() is called normally with X-PJAX-URL", function(t) { + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + getResponseHeader: function(header) { + if (header === "X-PJAX-URL") { + return href + "2" + } + } + } + + handleReponse.bind(pjax)("", request, "") + + t.equals(pjax.state.href, href + "2", "this.state.href is set correctly") + + t.end() +}) + +tape("test when handleResponse() is called normally with X-XHR-Redirected-To", function(t) { + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + getResponseHeader: function(header) { + if (header === "X-XHR-Redirected-To") { + return href + "3" + } + } + } + + handleReponse.bind(pjax)("", request, "") + + t.equals(pjax.state.href, href + "3", "this.state.href is set correctly") + + t.end() +}) + +tape("test when handleResponse() is called normally with a hash", function(t) { + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + responseURL: href + "2", + getResponseHeader: noop + } + + handleReponse.bind(pjax)("", request, href + "1#test") + + t.equals(pjax.state.href, href + "2#test", "this.state.href is set correctly") + + t.end() +}) + +tape("test try...catch for loadContent() when options.debug is true", function(t) { + t.plan(2) + + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + getResponseHeader: noop + } + + pjax.loadContent = function() { + throw new Error() + } + pjax.options.debug = true + + document.removeEventListener("pjax:error", storeEventHandler) + pjaxErrorEventTriggerTest = function() { + t.pass("pjax:error event triggered") + } + document.addEventListener("pjax:error", pjaxErrorEventTriggerTest) + + t.throws(function() { + handleReponse.bind(pjax)("", request, "") + }, Error, "error is rethrown") + + t.end() +}) + +tape("test try...catch for loadContent()", function(t) { + t.plan(2) + + var pjax = { + options: {}, + loadContent: noop, + state: {} + } + + var request = { + getResponseHeader: noop + } + + pjax.loadContent = function() { + throw new Error() + } + pjax.latestChance = function() { + return true + } + pjax.options.debug = false + + document.removeEventListener("pjax:error", pjaxErrorEventTriggerTest) + + t.doesNotThrow(function() { + t.equals(handleReponse.bind(pjax)("", request, ""), true, "this.latestChance() is called") + }, Error, "error is not thrown") + + t.end() +}) -- 2.49.1