Skip to content

Commit

Permalink
pjax
Browse files Browse the repository at this point in the history
  • Loading branch information
defunkt committed Feb 26, 2011
0 parents commit 3efcc3c
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 0 deletions.
20 changes: 20 additions & 0 deletions LICENSE
@@ -0,0 +1,20 @@
Copyright (c) Chris Wanstrath

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Software), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
117 changes: 117 additions & 0 deletions README
@@ -0,0 +1,117 @@
## pushState + ajax = pjax

.--.
/ \
## a a
( '._)
|'-- |
_.\___/_ ___pjax___
."\> \Y/|<'. '._.-'
/ \ \_\/ / '-' /
| --'\_/|/ | _/
|___.-' | |`'`
| | |
| / './
/__./` | |
\ | |
\ | |
; | |
/ | |
jgs |___\_.\_
`-"--'---'


## what is it?

pjax loads content from your server into an existing page
without reloading that entire page while ensuring permalinks,
titles, and the back button work as expected. It enhances the
browsing experience of your users - nothing more.


## three ways to pjax on the client side:

1. Functionally obtrusive, loading the href with ajax into data-pjax:

<a href='/explore' data-pjax='#main'>Explore</a>

$('a[data-pjax]').pjax()


2. Slightly obtrusive, passing a container and jQuery ajax options:

<a href='/explore' class='js-pjax'>Explore</a>

$('a.js-pjax').pjax('#main', { error: function(){
$('.error').text('Something went wrong!')
})


3. Unobtrusive, using the pjax 'loading' callback:

<div id='main'>
<div class='loader' style='display:none'><img src='spin.gif'></div>
<div class='tabs'>
<a href='/explore'>Explore</a>
<a href='/help'>Help</a>
</div>
</div>

$('a').pjax('#main', { loading: function(){
this == the target container == #main
$(this).find('.loader').show()
})


## $(link).pjax( container, options )

The $(link).pjax() function accepts a container, an options object,
or both. The options are the same as jQuery's ajax options with the
following additions:

container - The selector of the container to load the reponse body into, or
the container itself.
push - Whether to pushState the URL. Defaults to true (of course).
loading - A callback to fire after it's been too many ms and
you want to ease the user's pain with a loading indicator.
You can also bind to the 'loading.pjax' event on container.


## $.pjax( options )

You can also just call $.pjax directly. It acts much like $.ajax, even
returning the same thing and accepting the same options.

The pjax-specific keys listed in the $(link).pjax() section work here
as well.

This pjax call:

$.pjax({
url: '/authors',
container: '#main'
})

Roughly translates into this ajax call:

$.ajax({
url: '/authors?pjax=true',
dataType: 'html',
success: function(data){
$('#main').html( data )
history.pushState( null, $(data).filter('title').text(), '/authors' )
})
})


## install it

$ cd path/to/js
$ curl -O https://github.com/defunkt/jquery-pjax/raw/master/jquery.pjax.js

Then, in your HTML:

<script src="path/to/js/jquery.pjax.js"></script>

Replace 'path/to/js' with the path to your JavaScript directory,
e.g. 'public/javascripts'.
142 changes: 142 additions & 0 deletions jquery.pjax.js
@@ -0,0 +1,142 @@
// When called on a link, fetches the href with ajax into the
// container specified as the first parameter or with the data-pjax
// attribute on the link itself.
//
// Tries to make sure the back button and ctrl+click work the way
// you'd expect.
//
// Accepts a jQuery ajax options object that may include these
// pjax specific options:
//
// container - Where to stick the response body. Usually a String selector.
// $(container).html(xhr.responseBody)
// loading - A callback to fire after it's been too many ms and
// you want to ease the user's pain with a loading indicator.
// You can also bind to the 'loading.pjax' event on container.
// push - Whether to pushState the URL. Defaults to true (of course).
//
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
jQuery.fn.pjax = function( container, options ) {
var $ = jQuery

if ( options )
options.container = container
else
options = container

$(this).live('click', function(){
// Middle click, cmd click, and ctrl click should open
// links in a new tab as normal.
if ( event.which == 2 || event.metaKey )
return true

var defaults = {
url: this.href,
container: $(this).attr('data-pjax')
}

$.pjax( $.extend({}, defaults, options) )

return false
})
}


// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL. Also ensures the back button
// works the way you'd expect.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
//
// Accepts these extra keys:
//
// container - Where to stick the response body.
// $(container).html(xhr.responseBody)
// loading - A callback to fire after it's been too many ms and
// you want to ease the user's pain with a loading indicator.
// You can also bind to the 'loading.pjax' event on container.
// push - Whether to pushState the URL. Defaults to true (of course).
//
// Use it just like $.ajax:
//
// var xhr = $.pjax({ url: this.href, container: '#main' })
// console.log( xhr.readyState )
//
// Returns whatever $.ajax returns.
jQuery.pjax = function( options ) {
// Helper
var $ = jQuery

var defaults = {
data: { pjax: true },
type: 'GET',
dataType: 'html',
error: function(){ window.location = options.url },
success: function(data){
// 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 there's a <title> tag in the response, use it as
// the page's title.
var title = $.trim( $(data).filter('title').remove().text() )
if ( title ) document.title = title

// Make it happen.
$(options.container).html( data )

// If they didn't explicitly disable `push`, call pushState()
if ( options.push !== false ) {
window.history.pushState( { pjax: options.container },
document.title, options.url )
}

// Invoke their success handler if they gave us one.
success.apply( this, arguments )
}
}

// We don't want to let anyone override our success handler.
var success = options.success || $.noop
delete options.success

options = $.extend( true, {}, defaults, options )
var xhr = $.ajax( options )

// If we haven't found what we're looking for after a buncha ms
// you might want to show a 'Loading...' indicator.
setTimeout(function(){
if ( xhr.readyState == 4 ) return
$(options.container).trigger('loading.pjax')
if ( options.loading ) options.loading.call( options.container )
}, 350)

$(document).trigger('pjax', xhr, options)
return xhr
}

// Bind our popstate handler which takes care of the back and
// forward buttons, but only once we've called pjax()
jQuery(document).one('pjax', function(){
jQuery(window).bind('popstate', function(event){
var state = event.state
if ( state && state.pjax )
jQuery.pjax({ url: location.href, container: state.pjax, push: false })
else
window.location = location.href
})
})

// Add the state property to jQuery's event object so we can use it in
// $(window).bind('popstate')
jQuery.event.props.push('state')

// Fall back to normalcy for older browsers.
if ( !window.history || !window.history.pushState ) {
jQuery.pjax = jQuery.fn.pjax = $.noop
}

0 comments on commit 3efcc3c

Please sign in to comment.