./vendor/assets/javascripts/jquery.pjax.js in rails-pjax-0.0.1 vs ./vendor/assets/javascripts/jquery.pjax.js in rails-pjax-0.0.2
- old
+ new
@@ -22,12 +22,12 @@
// For convenience the first parameter can be either the container or
// the options object.
//
// Returns the jQuery object
$.fn.pjax = function( container, options ) {
- return this.live('click.pjax', function(event){
- handleClick(event, container, options)
+ return this.live('click', function(event){
+ return handleClick(event, container, options)
})
}
// Public: pjax on click handler
//
@@ -51,15 +51,11 @@
function handleClick(event, container, options) {
options = optionsFor(container, options)
var link = event.currentTarget
- // If current target isnt a link, try to find the first A descendant
if (link.tagName.toUpperCase() !== 'A')
- link = $(link).find('a')[0]
-
- if (!link)
throw "$.fn.pjax or $.pjax.click requires an anchor element"
// Middle click, cmd click, and ctrl click should open
// links in a new tab as normal.
if ( event.which > 1 || event.metaKey )
@@ -83,13 +79,37 @@
}
$.pjax($.extend({}, defaults, options))
event.preventDefault()
+ return false
}
+// Internal: Strips _pjax param from url
+//
+// url - String
+//
+// Returns String.
+function stripPjaxParam(url) {
+ return url
+ .replace(/\?_pjax=[^&]+&?/, '?')
+ .replace(/_pjax=[^&]+&?/, '')
+ .replace(/[\?&]$/, '')
+}
+// Internal: Parse URL components and returns a Locationish object.
+//
+// url - String URL
+//
+// Returns HTMLAnchorElement that acts like Location.
+function parseURL(url) {
+ var a = document.createElement('a')
+ a.href = url
+ return a
+}
+
+
// Loads a URL with ajax, puts the response body inside a container,
// then pushState()'s the loaded URL.
//
// Works just like $.ajax in that it accepts a jQuery ajax
// settings object (with keys like url, type, data, etc).
@@ -117,11 +137,12 @@
var target = options.target
// DEPRECATED: use options.target
if (!target && options.clickedElement) target = options.clickedElement[0]
- var hash = parseURL(options.url).hash
+ var url = options.url
+ var hash = parseURL(url).hash
// DEPRECATED: Save references to original event callbacks. However,
// listening for custom pjax:* events is prefered.
var oldBeforeSend = options.beforeSend,
oldComplete = options.complete,
@@ -144,10 +165,12 @@
}
var timeoutTimer
options.beforeSend = function(xhr, settings) {
+ url = stripPjaxParam(settings.url)
+
if (settings.timeout > 0) {
timeoutTimer = setTimeout(function() {
if (fire('pjax:timeout', [xhr, options]))
xhr.abort('timeout')
}, settings.timeout)
@@ -167,22 +190,13 @@
if (result === false) return false
}
if (!fire('pjax:beforeSend', [xhr, settings])) return false
- if (options.push && !options.replace) {
- // Cache current container element before replacing it
- containerCache.push(pjax.state.id, context.clone(true, true).contents())
-
- window.history.pushState(null, "", options.url)
- }
-
fire('pjax:start', [xhr, options])
// start.pjax is deprecated
fire('start.pjax', [xhr, options])
-
- fire('pjax:send', [xhr, settings])
}
options.complete = function(xhr, textStatus) {
if (timeoutTimer)
clearTimeout(timeoutTimer)
@@ -196,47 +210,77 @@
// end.pjax is deprecated
fire('end.pjax', [xhr, options])
}
options.error = function(xhr, textStatus, errorThrown) {
- var container = extractContainer("", xhr, options)
+ var respUrl = xhr.getResponseHeader('X-PJAX-URL')
+ if (respUrl) url = stripPjaxParam(respUrl)
// DEPRECATED: Invoke original `error` handler
if (oldError) oldError.apply(this, arguments)
var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
if (textStatus !== 'abort' && allowed)
- window.location = container.url
+ window.location = url
}
options.success = function(data, status, xhr) {
- var container = extractContainer(data, xhr, options)
+ var respUrl = xhr.getResponseHeader('X-PJAX-URL')
+ if (respUrl) url = stripPjaxParam(respUrl)
- if (!container.contents) {
- window.location = container.url
- return
+ var title, oldTitle = document.title
+
+ if ( options.fragment ) {
+ // If they specified a fragment, look for it in the response
+ // and pull it out.
+ var html = $('<html>').html(data)
+ var $fragment = html.find(options.fragment)
+ if ( $fragment.length ) {
+ this.html($fragment.contents())
+
+ // If there's a <title> tag in the response, use it as
+ // the page's title. Otherwise, look for data-title and title attributes.
+ title = html.find('title').text() || $fragment.attr('title') || $fragment.data('title')
+ } else {
+ return window.location = 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 = url
+
+ this.html(data)
+
+ // If there's a <title> tag in the response, use it as
+ // the page's title.
+ title = this.find('title').remove().text()
}
- pjax.state = {
- id: options.id || uniqueId(),
- url: container.url,
- container: context.selector,
+ if ( title ) document.title = $.trim(title)
+
+ var state = {
+ url: url,
+ pjax: this.selector,
fragment: options.fragment,
timeout: options.timeout
}
- if (options.push || options.replace) {
- window.history.replaceState(pjax.state, container.title, container.url)
+ if ( options.replace ) {
+ pjax.active = true
+ window.history.replaceState(state, document.title, url)
+ } else if ( options.push ) {
+ // this extra replaceState before first push ensures good back
+ // button behavior
+ if ( !pjax.active ) {
+ window.history.replaceState($.extend({}, state, {url:null}), oldTitle)
+ pjax.active = true
+ }
+
+ window.history.pushState(state, document.title, url)
}
- if (container.title) document.title = container.title
- context.html(container.contents)
-
- // Scroll to top by default
- if (typeof options.scrollTo === 'number')
- $(window).scrollTop(options.scrollTo)
-
// Google Analytics support
if ( (options.replace || options.push) && window._gaq )
_gaq.push(['_trackPageview'])
// If the URL has a hash in it, make sure the browser
@@ -250,75 +294,25 @@
fire('pjax:success', [data, status, xhr, options])
}
- // Initialize pjax.state for the initial page load. Assume we're
- // using the container and options of the link we're loading for the
- // back button to the initial page. This ensures good back button
- // behavior.
- if (!pjax.state) {
- pjax.state = {
- id: uniqueId(),
- url: window.location.href,
- container: context.selector,
- fragment: options.fragment,
- timeout: options.timeout
- }
- window.history.replaceState(pjax.state, document.title)
- }
-
// Cancel the current request if we're already pjaxing
var xhr = pjax.xhr
if ( xhr && xhr.readyState < 4) {
xhr.onreadystatechange = $.noop
xhr.abort()
}
pjax.options = options
pjax.xhr = $.ajax(options)
-
- // pjax event is deprecated
$(document).trigger('pjax', [pjax.xhr, options])
return pjax.xhr
}
-// Internal: Generate unique id for state object.
-//
-// Use a timestamp instead of a counter since ids should still be
-// unique across page loads.
-//
-// Returns Number.
-function uniqueId() {
- return (new Date).getTime()
-}
-
-// Internal: Strips _pjax param from url
-//
-// url - String
-//
-// Returns String.
-function stripPjaxParam(url) {
- return url
- .replace(/\?_pjax=[^&]+&?/, '?')
- .replace(/_pjax=[^&]+&?/, '')
- .replace(/[\?&]$/, '')
-}
-
-// Internal: Parse URL components and returns a Locationish object.
-//
-// url - String URL
-//
-// Returns HTMLAnchorElement that acts like Location.
-function parseURL(url) {
- var a = document.createElement('a')
- a.href = url
- return a
-}
-
// Internal: Build options Object for arguments.
//
// For convenience the first parameter can be either the container or
// the options object.
//
@@ -374,185 +368,19 @@
} else {
throw "cant get selector for pjax container!"
}
}
-// Internal: Filter and find all elements matching the selector.
-//
-// Where $.fn.find only matches descendants, findAll will test all the
-// top level elements in the jQuery object as well.
-//
-// elems - jQuery object of Elements
-// selector - String selector to match
-//
-// Returns a jQuery object.
-function findAll(elems, selector) {
- var results = $()
- elems.each(function() {
- if ($(this).is(selector))
- results = results.add(this)
- results = results.add(selector, this)
- })
- return results
-}
-// Internal: Extracts container and metadata from response.
-//
-// 1. Extracts X-PJAX-URL header if set
-// 2. Extracts inline <title> tags
-// 3. Builds response Element and extracts fragment if set
-//
-// data - String response data
-// xhr - XHR response
-// options - pjax options Object
-//
-// Returns an Object with url, title, and contents keys.
-function extractContainer(data, xhr, options) {
- var obj = {}
-
- // Prefer X-PJAX-URL header if it was set, otherwise fallback to
- // using the original requested url.
- obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.url)
-
- // Attempt to parse response html into elements
- var $data = $(data)
-
- // If response data is empty, return fast
- if ($data.length === 0)
- return obj
-
- // If there's a <title> tag in the response, use it as
- // the page's title.
- obj.title = findAll($data, 'title').last().text()
-
- if (options.fragment) {
- // If they specified a fragment, look for it in the response
- // and pull it out.
- var $fragment = findAll($data, options.fragment).first()
-
- if ($fragment.length) {
- obj.contents = $fragment.contents()
-
- // If there's no title, look for data-title and title attributes
- // on the fragment
- if (!obj.title)
- obj.title = $fragment.attr('title') || $fragment.data('title')
- }
-
- } else if (!/<html/i.test(data)) {
- obj.contents = $data
- }
-
- // Clean up any <title> tags
- if (obj.contents) {
- // Remove any parent title elements
- obj.contents = obj.contents.not('title')
-
- // Then scrub any titles from their descendents
- obj.contents.find('title').remove()
- }
-
- // Trim any whitespace off the title
- if (obj.title) obj.title = $.trim(obj.title)
-
- return obj
-}
-
-// Public: Reload current page with pjax.
-//
-// Returns whatever $.pjax returns.
-pjax.reload = function(container, options) {
- var defaults = {
- url: window.location.href,
- push: false,
- replace: true,
- scrollTo: false
- }
-
- return $.pjax($.extend(defaults, optionsFor(container, options)))
-}
-
-
pjax.defaults = {
timeout: 650,
push: true,
replace: false,
type: 'GET',
- dataType: 'html',
- scrollTo: 0,
- maxCacheLength: 20
+ dataType: 'html'
}
-// Internal: History DOM caching class.
-function Cache() {
- this.mapping = {}
- this.forwardStack = []
- this.backStack = []
-}
-// Push previous state id and container contents into the history
-// cache. Should be called in conjunction with `pushState` to save the
-// previous container contents.
-//
-// id - State ID Number
-// value - DOM Element to cache
-//
-// Returns nothing.
-Cache.prototype.push = function(id, value) {
- this.mapping[id] = value
- this.backStack.push(id)
-
- // Remove all entires in forward history stack after pushing
- // a new page.
- while (this.forwardStack.length)
- delete this.mapping[this.forwardStack.shift()]
-
- // Trim back history stack to max cache length.
- while (this.backStack.length > pjax.defaults.maxCacheLength)
- delete this.mapping[this.backStack.shift()]
-}
-// Retrieve cached DOM Element for state id.
-//
-// id - State ID Number
-//
-// Returns DOM Element(s) or undefined if cache miss.
-Cache.prototype.get = function(id) {
- return this.mapping[id]
-}
-// Shifts cache from forward history cache to back stack. Should be
-// called on `popstate` with the previous state id and container
-// contents.
-//
-// id - State ID Number
-// value - DOM Element to cache
-//
-// Returns nothing.
-Cache.prototype.forward = function(id, value) {
- this.mapping[id] = value
- this.backStack.push(id)
-
- if (id = this.forwardStack.pop())
- delete this.mapping[id]
-}
-// Shifts cache from back history cache to forward stack. Should be
-// called on `popstate` with the previous state id and container
-// contents.
-//
-// id - State ID Number
-// value - DOM Element to cache
-//
-// Returns nothing.
-Cache.prototype.back = function(id, value) {
- this.mapping[id] = value
- this.forwardStack.push(id)
-
- if (id = this.backStack.pop())
- delete this.mapping[id]
-}
-
-var containerCache = new Cache
-
-
// Export $.pjax.click
pjax.click = handleClick
// Used to detect initial (useless) popstate.
@@ -570,58 +398,22 @@
popped = true
if ( initialPop ) return
var state = event.state
- if (state && state.container) {
- var container = $(state.container)
- if (container.length) {
- var contents = containerCache.get(state.id)
-
- if (pjax.state) {
- // Since state ids always increase, we can deduce the history
- // direction from the previous state.
- var direction = pjax.state.id < state.id ? 'forward' : 'back'
-
- // Cache current container before replacement and inform the
- // cache which direction the history shifted.
- containerCache[direction](pjax.state.id, container.clone(true, true).contents())
- }
-
- var options = {
- id: state.id,
- url: state.url,
+ if ( state && state.pjax ) {
+ var container = state.pjax
+ if ( $(container+'').length )
+ $.pjax({
+ url: state.url || location.href,
+ fragment: state.fragment,
container: container,
push: false,
- fragment: state.fragment,
- timeout: state.timeout,
- scrollTo: false
- }
-
- if (contents) {
- // pjax event is deprecated
- $(document).trigger('pjax', [null, options])
- container.trigger('pjax:start', [null, options])
- // end.pjax event is deprecated
- container.trigger('start.pjax', [null, options])
-
- container.html(contents)
- pjax.state = state
-
- container.trigger('pjax:end', [null, options])
- // end.pjax event is deprecated
- container.trigger('end.pjax', [null, options])
- } else {
- $.pjax(options)
- }
-
- // Force reflow/relayout before the browser tries to restore the
- // scroll position.
- container[0].offsetHeight
- } else {
+ timeout: state.timeout
+ })
+ else
window.location = location.href
- }
}
})
// Add the state property to jQuery's event object so we can use it in
@@ -670,10 +462,9 @@
$(document.body).append(form)
form.submit()
}
$.pjax.click = $.noop
- $.pjax.reload = window.location.reload
$.fn.pjax = function() { return this }
}
})(jQuery);