Add custom events to replace ajax callbacks #96

Merged
merged 5 commits into from Feb 22, 2012
View
@@ -7,8 +7,8 @@ install: gem install sinatra
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- - ruby ./test/app.rb &
- - sleep 3
+ - ruby ./test/app.rb 2>/dev/null &
+ - sleep 2
script: phantomjs ./test/run-qunit.js "http://localhost:4567/"
View
@@ -45,16 +45,18 @@ $('a[data-pjax]').pjax()
```
-Two. Slightly obtrusive, passing a container and jQuery ajax options:
+Two. Slightly obtrusive, passing a container and binding an error handler:
```html
<a href='/explore' class='js-pjax'>Explore</a>
```
```js
-$('.js-pjax').pjax('#main', { timeout: null, error: function(xhr, err){
+$('.js-pjax').pjax('#main')
+
+$('#main').live('pjax:error', function(e, xhr, err) {
$('.error').text('Something went wrong: ' + err)
-}})
+})
```
@@ -91,8 +93,6 @@ following additions:
* `clickedElement` - The element that was clicked to start the pjax call.
* `push` - Whether to pushState the URL. Default: true (of course)
* `replace` - Whether to replaceState the URL. Default: false
-* `error` - By default this callback reloads the target page once
- `timeout` ms elapses.
* `timeout` - pjax sets this low, <1s. Set this higher if using a
custom error handler. It's ms, so something like
`timeout: 2000`
@@ -195,19 +195,35 @@ This allows you to, say, display a loading indicator upon pjaxing:
```js
$('a.pjax').pjax('#main')
$('#main')
- .bind('pjax:start', function() { $('#loading').show() })
- .bind('pjax:end', function() { $('#loading').hide() })
+ .on('pjax:start', function() { $('#loading').show() })
+ .on('pjax:end', function() { $('#loading').hide() })
```
-Because these events bubble, you can also set them on the body:
+Because these events bubble, you can also set them on the document:
```js
$('a.pjax').pjax()
-$('body')
- .bind('pjax:start', function() { $('#loading').show() })
- .bind('pjax:end', function() { $('#loading').hide() })
+$(document)
+ .on('pjax:start', function() { $('#loading').show() })
+ .on('pjax:end', function() { $('#loading').hide() })
```
+In addition, custom events are added to complement `$.ajax`'s
+callbacks.
+
+* `pjax:beforeSend` - Fired before the pjax request begins. Returning
+ false will abort the request.
+* `pjax:complete` - Fired after the pjax request finishes.
+* `pjax:success` - Fired after the pjax request succeeds.
+* `pjax:error` - Fired after the pjax request fails. Returning
+ false will prevent the the fallback redirect.
+* `pjax:timeout` - Fired if after timeout is reached. Returning
+ false will disable the fallback and will wait
+ indefinitely until the response returns.
+
+**CAUTION** Callback handlers passed to `$.pjax` **cannot** be persisted
+across full page reloads. Its recommended you use custom events instead.
+
## browser support
pjax only works with browsers that support the history.pushState API.
View
@@ -102,21 +102,83 @@ function handleClick(event, container, options) {
//
// Returns whatever $.ajax returns.
var pjax = $.pjax = function( options ) {
- var $container = findContainerFor(options.container),
- success = options.success || $.noop
-
- // We don't want to let anyone override our success handler.
- delete options.success
-
- options = $.extend(true, {}, pjax.defaults, options)
+ options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
if ( $.isFunction(options.url) ) {
options.url = options.url()
}
- options.context = $container
+ // DEPRECATED: Save references to original event callbacks. However,
+ // listening for custom pjax:* events is prefered.
+ var oldBeforeSend = options.beforeSend,
+ oldComplete = options.complete,
+ oldSuccess = options.success,
+ oldError = options.error
+
+ options.context = findContainerFor(options.container)
+
+ var timeoutTimer
+
+ options.beforeSend = function(xhr, settings) {
+ var context = this
+
+ if (settings.timeout > 0) {
+ timeoutTimer = setTimeout(function() {
+ var event = $.Event('pjax:timeout')
+ context.trigger(event, [xhr, options])
+ if (event.result !== false)
+ xhr.abort('timeout')
+ }, settings.timeout)
+
+ // Clear timeout setting so jquerys internal timeout isn't invoked
+ settings.timeout = 0
+ }
+
+ xhr.setRequestHeader('X-PJAX', 'true')
+
+ var result
+
+ // DEPRECATED: Invoke original `beforeSend` handler
+ if (oldBeforeSend) {
+ result = oldBeforeSend.apply(this, arguments)
+ if (result === false) return false
+ }
+
+ var event = $.Event('pjax:beforeSend')
+ this.trigger(event, [xhr, settings])
+ result = event.result
+ if (result === false) return false
+
+ this.trigger('pjax:start', [xhr, options])
+ // start.pjax is deprecated
+ this.trigger('start.pjax', [xhr, options])
+ }
+
+ options.complete = function(xhr, textStatus) {
+ if (timeoutTimer)
+ clearTimeout(timeoutTimer)
+
+ // DEPRECATED: Invoke original `complete` handler
+ if (oldComplete) oldComplete.apply(this, arguments)
+
+ this.trigger('pjax:complete', [xhr, textStatus, options])
- options.success = function(data){
+ this.trigger('pjax:end', [xhr, options])
+ // end.pjax is deprecated
+ this.trigger('end.pjax', [xhr, options])
+ }
+
+ options.error = function(xhr, textStatus, errorThrown) {
+ // DEPRECATED: Invoke original `error` handler
+ if (oldError) oldError.apply(this, arguments)
+
+ var event = $.Event('pjax:error')
+ this.trigger(event, [xhr, textStatus, errorThrown, options])
+ if (textStatus !== 'abort' && event.result !== false)
+ window.location = options.url
+ }
+
+ options.success = function(data, status, xhr) {
var title, oldTitle = document.title
if ( options.fragment ) {
@@ -134,10 +196,10 @@ var pjax = $.pjax = function( options ) {
return window.location = options.url
}
} else {
- // If we got no data or an entire web page, go directly
- // to the page and let normal error handling happen.
- if ( !$.trim(data) || /<html/i.test(data) )
- return window.location = options.url
+ // If we got no data or an entire web page, go directly
+ // to the page and let normal error handling happen.
+ if ( !$.trim(data) || /<html/i.test(data) )
+ return window.location = options.url
this.html(data)
@@ -149,7 +211,7 @@ var pjax = $.pjax = function( options ) {
if ( title ) document.title = $.trim(title)
var state = {
- pjax: $container.selector,
+ pjax: this.selector,
fragment: options.fragment,
timeout: options.timeout
}
@@ -184,10 +246,13 @@ var pjax = $.pjax = function( options ) {
window.location.href = hash
}
- // Invoke their success handler if they gave us one.
- success.apply(this, arguments)
+ // DEPRECATED: Invoke original `success` handler
+ if (oldSuccess) oldSuccess.apply(this, arguments)
+
+ this.trigger('pjax:success', [data, status, xhr, options])
}
+
// Cancel the current request if we're already pjaxing
var xhr = pjax.xhr
if ( xhr && xhr.readyState < 4) {
@@ -263,8 +328,6 @@ function findContainerFor(container) {
}
-var timeoutTimer = null
-
pjax.defaults = {
timeout: 650,
push: true,
@@ -274,39 +337,7 @@ pjax.defaults = {
// adding this secret parameter, some browsers will often confuse the two.
data: { _pjax: true },
type: 'GET',
- dataType: 'html',
- beforeSend: function(xhr, settings){
- var context = this
-
- if (settings.async && settings.timeout > 0) {
- timeoutTimer = setTimeout(function() {
- var event = $.Event('pjax:timeout')
- context.trigger(event, [xhr, pjax.options])
- if (event.result !== false)
- xhr.abort('timeout')
- }, settings.timeout)
-
- // Clear timeout setting so jquerys internal timeout isn't invoked
- settings.timeout = 0
- }
-
- this.trigger('pjax:start', [xhr, pjax.options])
- // start.pjax is deprecated
- this.trigger('start.pjax', [xhr, pjax.options])
- xhr.setRequestHeader('X-PJAX', 'true')
- },
- error: function(xhr, textStatus, errorThrown){
- if ( textStatus !== 'abort' )
- window.location = pjax.options.url
- },
- complete: function(xhr){
- if (timeoutTimer)
- clearTimeout(timeoutTimer)
-
- this.trigger('pjax:end', [xhr, pjax.options])
- // end.pjax is deprecated
- this.trigger('end.pjax', [xhr, pjax.options])
- }
+ dataType: 'html'
}
// Export $.pjax.click
View
@@ -29,8 +29,14 @@ page.open(phantom.args[0], function(status) {
})
}, function() {
var failures = page.evaluate(function() {
- var el = document.getElementById('qunit-testresult')
+ var el = document.getElementById('qunit-testresult'),
+ fails = document.getElementsByClassName('fail')
+
+ for (var i = 0; i < fails.length; i++)
+ console.log(fails[i].innerText)
+
console.log(el.innerText)
+
return parseInt(el.getElementsByClassName('failed')[0].innerHTML)
})
phantom.exit(failures > 0 ? 1 : 0)
Oops, something went wrong. Retry.