Handle XHR response error (#137)
* Move the XHR callback to a separate file * Trigger an error event if the response cannot be parsed. * Add tests for handle-response.js
This commit was merged in pull request #137.
This commit is contained in:
30
README.md
30
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("<html")) {
|
||||
pjax._handleResponse(responseText, request, href);
|
||||
} else {
|
||||
// handle response here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
##### `elements` (String, default: `"a[href], form[action]"`)
|
||||
@@ -453,7 +481,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/))
|
||||
|
||||
|
||||
63
index.js
63
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() {
|
||||
|
||||
70
lib/proto/handle-response.js
Normal file
70
lib/proto/handle-response.js
Normal file
@@ -0,0 +1,70 @@
|
||||
var clone = require("../util/clone.js")
|
||||
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) {
|
||||
trigger(document, "pjax:complete pjax:error", tempOptions)
|
||||
|
||||
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) {
|
||||
trigger(document, "pjax:error", tempOptions)
|
||||
|
||||
if (!this.options.debug) {
|
||||
if (console && console.error) {
|
||||
console.error("Pjax switch fail: ", e)
|
||||
}
|
||||
return this.latestChance(href)
|
||||
}
|
||||
else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
204
tests/lib/proto/handle-response.js
Normal file
204
tests/lib/proto/handle-response.js
Normal file
@@ -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()
|
||||
})
|
||||
Reference in New Issue
Block a user