你的位置:首页 > Java教程

[Java教程]pjax技术的应用


 

一、什么是PJAX?

现在有一些网站(apicloud,  github)支持这样一种浏览方式,当你点击站内的一个连接的时候,不是传统的跳转到另外一个连接,而是类似ajax的局部刷新改变页面内容,但是与ajax不同的是页面地址(url)改变了,可以使用浏览器的前进和后退功能,这就是使用pjax技术做到的,简单来说,pjax是ajax衍生出来的一个东西,这样说就很容易理解了吧。

 

二、PJAX能做什么?

页面跳转的时候人眼需要对整个页面作重新识别, 刷新部分页面的时候, 只需要重新识别其中一块区域;

同时, 由于刷新部分页面的时候提供了一个loading的提示(如下图), 以及在刷新的时候旧页面还是显示在浏览器中, 用户能够容忍更长的页面加载时间。


三、如何使用PJAX?

1、引入jquery-1.8.3.min,源码可以在网上下载

<script type="text/javascript" src="jquery-1.8.3.min.js"></script>

2、引入jquery.pjax,源码已在下面第“四”条附上

<script type="text/javascript" src="jquery.pjax.js"></script>

3、使用pjax的代码

$(function(){  //这里注意  //live只有jquery - 1.8支持  //如果是jquery - 2.0 以上,请使用on  $('a').live('click',function(){    var url    =  $(this).attr('href');    var target  =  $(this).attr('target');    //如果是javascript:;证明是无需使用pjax跳转页面的链接    //如果是#证明是无需使用pjax跳转页面的链接    //如果target为真,不适用pjax改为原始a链接方法自行处理    if(url!="javascript:;" && url!="#" && !target){      //这里调用加载中的提示      alert('玩命加载中');      //调用pjax的方法      //url是目标链接      //container是获取目标链接内容后填充的位置      $.pjax({        url: url,        container: 'body'      })    }  });});


四、jquery.pjax源码

/*! * Copyright 2012, Chris Wanstrath * Released under the MIT License * https://github.com/defunkt/jquery-pjax */(function($){// When called on a container with a selector, fetches the href with// ajax into the container 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.//// Exported as $.fn.pjax//// 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)//       (default: current jquery context)//   push - Whether to pushState the URL. Defaults to true (of course).//  replace - Want to use replaceState instead? That's cool.//// For convenience the second parameter can be either the container or// the options object.//// Returns the jQuery objectfunction fnPjax(selector, container, options) { var context = this return this.on('click.pjax', selector, function(event) {  var opts = $.extend({}, optionsFor(container, options))  if (!opts.container)   opts.container = $(this).attr('data-pjax') || context  handleClick(event, opts) })}// Public: pjax on click handler//// Exported as $.pjax.click.//// event  - "click" jQuery.Event// options - pjax options//// Examples////  $(document).on('click', 'a', $.pjax.click)//  // is the same as//  $(document).pjax('a')//// $(document).on('click', 'a', function(event) {//  var container = $(this).closest('[data-pjax-container]')//  $.pjax.click(event, container)// })//// Returns nothing.function handleClick(event, container, options) { options = optionsFor(container, options) var link = event.currentTarget if (link.tagName.toUpperCase() !== 'A')  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 || event.ctrlKey || event.shiftKey || event.altKey )  return // Ignore cross origin links if ( location.protocol !== link.protocol || location.hostname !== link.hostname )  return // Ignore case when a hash is being tacked on the current URL if ( link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location) )  return // Ignore event with default prevented if (event.isDefaultPrevented())  return var defaults = {  url: link.href,  container: $(link).attr('data-pjax'),  target: link } var opts = $.extend({}, defaults, options) var clickEvent = $.Event('pjax:click') $(link).trigger(clickEvent, [opts]) if (!clickEvent.isDefaultPrevented()) {  pjax(opts)  event.preventDefault()  $(link).trigger('pjax:clicked', [opts]) }}// Public: pjax on form submit handler//// Exported as $.pjax.submit//// event  - "click" jQuery.Event// options - pjax options//// Examples//// $(document).on('submit', 'form', function(event) {//  var container = $(this).closest('[data-pjax-container]')//  $.pjax.submit(event, container)// })//// Returns nothing.function handleSubmit(event, container, options) { options = optionsFor(container, options) var form = event.currentTarget var $form = $(form) if (form.tagName.toUpperCase() !== 'FORM')  throw "$.pjax.submit requires a form element" var defaults = {  type: ($form.attr('method') || 'GET').toUpperCase(),  url: $form.attr('action'),  container: $form.attr('data-pjax'),  target: form } if (defaults.type !== 'GET' && window.FormData !== undefined) {  defaults.data = new FormData(form);  defaults.processData = false;  defaults.contentType = false; } else {  // Can't handle file uploads, exit  if ($(form).find(':file').length) {   return;  }  // Fallback to manually serializing the fields  defaults.data = $(form).serializeArray(); } pjax($.extend({}, defaults, options)) event.preventDefault()}// 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).//// Accepts these extra keys://// container - Where to stick the response body.//       $(container).html(xhr.responseBody)//   push - Whether to pushState the URL. Defaults to true (of course).//  replace - Want to use replaceState instead? That's cool.//// Use it just like $.ajax:////  var xhr = $.pjax({ url: this.href, container: '#main' })//  console.log( xhr.readyState )//// Returns whatever $.ajax returns.function pjax(options) { options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options) if ($.isFunction(options.url)) {  options.url = options.url() } var target = options.target var hash = parseURL(options.url).hash var context = options.context = findContainerFor(options.container) // We want the browser to maintain two separate internal caches: one // for pjax'd partial page loads and one for normal page loads. // Without adding this secret parameter, some browsers will often // confuse the two. if (!options.data) options.data = {} if ($.isArray(options.data)) {  options.data.push({name: '_pjax', value: context.selector}) } else {  options.data._pjax = context.selector } function fire(type, args, props) {  if (!props) props = {}  props.relatedTarget = target  var event = $.Event(type, props)  context.trigger(event, args)  return !event.isDefaultPrevented() } var timeoutTimer options.beforeSend = function(xhr, settings) {  // No timeout for non-GET requests  // Its not safe to request the resource again with a fallback method.  if (settings.type !== 'GET') {   settings.timeout = 0  }  xhr.setRequestHeader('X-PJAX', 'true')  xhr.setRequestHeader('X-PJAX-Container', context.selector)  if (!fire('pjax:beforeSend', [xhr, settings]))   return false  if (settings.timeout > 0) {   timeoutTimer = setTimeout(function() {    if (fire('pjax:timeout', [xhr, options]))     xhr.abort('timeout')   }, settings.timeout)   // Clear timeout setting so jquerys internal timeout isn't invoked   settings.timeout = 0  }  var url = parseURL(settings.url)  if (hash) url.hash = hash  options.requestUrl = stripInternalParams(url) } options.complete = function(xhr, textStatus) {  if (timeoutTimer)   clearTimeout(timeoutTimer)  fire('pjax:complete', [xhr, textStatus, options])  fire('pjax:end', [xhr, options]) } options.error = function(xhr, textStatus, errorThrown) {  var container = extractContainer("", xhr, options)  var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])  if (options.type == 'GET' && textStatus !== 'abort' && allowed) {   locationReplace(container.url)  } } options.success = function(data, status, xhr) {  var previousState = pjax.state;  // If $.pjax.defaults.version is a function, invoke it first.  // Otherwise it can be a static string.  var currentVersion = (typeof $.pjax.defaults.version === 'function') ?   $.pjax.defaults.version() :   $.pjax.defaults.version  var latestVersion = xhr.getResponseHeader('X-PJAX-Version')  var container = extractContainer(data, xhr, options)  var url = parseURL(container.url)  if (hash) {   url.hash = hash   container.url = url.href  }  // If there is a layout version mismatch, hard load the new url  if (currentVersion && latestVersion && currentVersion !== latestVersion) {   locationReplace(container.url)   return  }  // If the new response is missing a body, hard load the page  if (!container.contents) {   locationReplace(container.url)   return  }  pjax.state = {   id: options.id || uniqueId(),   url: container.url,   title: container.title,   container: context.selector,   fragment: options.fragment,   timeout: options.timeout  }  if (options.push || options.replace) {   window.history.replaceState(pjax.state, container.title, container.url)  }  // Only blur the focus if the focused element is within the container.  var blurFocus = $.contains(options.container, document.activeElement)  // Clear out any focused controls before inserting new page contents.  if (blurFocus) {   try {    document.activeElement.blur()   } catch (e) { }  }  if (container.title) document.title = container.title  fire('pjax:beforeReplace', [container.contents, options], {   state: pjax.state,   previousState: previousState  })  context.html(container.contents)  // FF bug: Won't autofocus fields that are inserted via JS.  // This behavior is incorrect. So if theres no current focus, autofocus  // the last field.  //  // http://www.w3.org/html/wg/drafts/html/master/forms.html  var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]  if (autofocusEl && document.activeElement !== autofocusEl) {   autofocusEl.focus();  }  executeScriptTags(container.scripts)  var scrollTo = options.scrollTo  // Ensure browser scrolls to the element referenced by the URL anchor  if (hash) {   var name = decodeURIComponent(hash.slice(1))   var target = document.getElementById(name) || document.getElementsByName(name)[0]   if (target) scrollTo = $(target).offset().top  }  if (typeof scrollTo == 'number') $(window).scrollTop(scrollTo)  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,   title: document.title,   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 abortXHR(pjax.xhr) pjax.options = options var xhr = pjax.xhr = $.ajax(options) if (xhr.readyState > 0) {  if (options.push && !options.replace) {   // Cache current container element before replacing it   cachePush(pjax.state.id, cloneContents(context))   window.history.pushState(null, "", options.requestUrl)  }  fire('pjax:start', [xhr, options])  fire('pjax:send', [xhr, options]) } return pjax.xhr}// Public: Reload current page with pjax.//// Returns whatever $.pjax returns.function pjaxReload(container, options) { var defaults = {  url: window.location.href,  push: false,  replace: true,  scrollTo: false } return pjax($.extend(defaults, optionsFor(container, options)))}// Internal: Hard replace current state with url.//// Work for around WebKit//  https://bugs.webkit.org/show_bug.cgi?id=93506//// Returns nothing.function locationReplace(url) { window.history.replaceState(null, "", pjax.state.url) window.location.replace(url)}var initialPop = truevar initialURL = window.location.hrefvar initialState = window.history.state// Initialize $.pjax.state if possible// Happens when reloading a page and coming forward from a different// session history.if (initialState && initialState.container) { pjax.state = initialState}// Non-webkit browsers don't fire an initial popstate eventif ('state' in window.history) { initialPop = false}// popstate handler takes care of the back and forward buttons//// You probably shouldn't use pjax on pages with other pushState// stuff yet.function onPjaxPopstate(event) { // Hitting back or forward should override any pending PJAX request. if (!initialPop) {  abortXHR(pjax.xhr) } var previousState = pjax.state var state = event.state var direction if (state && state.container) {  // When coming forward from a separate history session, will get an  // initial pop with a state we are already at. Skip reloading the current  // page.  if (initialPop && initialURL == state.url) return  if (previousState) {   // If popping back to the same state, just skip.   // Could be clicking back from hashchange rather than a pushState.   if (previousState.id === state.id) return   // Since state IDs always increase, we can deduce the navigation direction   direction = previousState.id < state.id ? 'forward' : 'back'  }  var cache = cacheMapping[state.id] || []  var container = $(cache[0] || state.container), contents = cache[1]  if (container.length) {   if (previousState) {    // Cache current container before replacement and inform the    // cache which direction the history shifted.    cachePop(direction, previousState.id, cloneContents(container))   }   var popstateEvent = $.Event('pjax:popstate', {    state: state,    direction: direction   })   container.trigger(popstateEvent)   var options = {    id: state.id,    url: state.url,    container: container,    push: false,    fragment: state.fragment,    timeout: state.timeout,    scrollTo: false   }   if (contents) {    container.trigger('pjax:start', [null, options])    pjax.state = state    if (state.title) document.title = state.title    var beforeReplaceEvent = $.Event('pjax:beforeReplace', {     state: state,     previousState: previousState    })    container.trigger(beforeReplaceEvent, [contents, options])    container.html(contents)    container.trigger('pjax:end', [null, options])   } else {    pjax(options)   }   // Force reflow/relayout before the browser tries to restore the   // scroll position.   container[0].offsetHeight  } else {   locationReplace(location.href)  } } initialPop = false}// Fallback version of main pjax function for browsers that don't// support pushState.//// Returns nothing since it retriggers a hard form submission.function fallbackPjax(options) { var url = $.isFunction(options.url) ? options.url() : options.url,   method = options.type ? options.type.toUpperCase() : 'GET' var form = $('<form>', {  method: method === 'GET' ? 'GET' : 'POST',  action: url,  style: 'display:none' }) if (method !== 'GET' && method !== 'POST') {  form.append($('<input>', {   type: 'hidden',   name: '_method',   value: method.toLowerCase()  })) } var data = options.data if (typeof data === 'string') {  $.each(data.split('&'), function(index, value) {   var pair = value.split('=')   form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))  }) } else if ($.isArray(data)) {  $.each(data, function(index, value) {   form.append($('<input>', {type: 'hidden', name: value.name, value: value.value}))  }) } else if (typeof data === 'object') {  var key  for (key in data)   form.append($('<input>', {type: 'hidden', name: key, value: data[key]})) } $(document.body).append(form) form.submit()}// Internal: Abort an // also removing its event handlers.function abortXHR(xhr) { if ( xhr && xhr.readyState < 4) {  xhr.onreadystatechange = $.noop  xhr.abort() }}// 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()}function cloneContents(container) { var cloned = container.clone() // Unmark script tags as already being eval'd so they can get executed again // when restored from cache. HAXX: Uses jQuery internal method. cloned.find('script').each(function(){  if (!this.src) jQuery._data(this, 'globalEval', false) }) return [container.selector, cloned.contents()]}// Internal: Strip internal query params from parsed URL.//// Returns sanitized url.href String.function stripInternalParams(url) { url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, '') return url.href.replace(/\?($|#)/, '$1')}// 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: Return the `href` component of given URL object with the hash// portion removed.//// location - Location or HTMLAnchorElement//// Returns Stringfunction stripHash(location) { return location.href.replace(/#.*/, '')}// Internal: Build options Object for arguments.//// For convenience the first parameter can be either the container or// the options object.//// Examples////  optionsFor('#container')//  // => {container: '#container'}////  optionsFor('#container', {push: true})//  // => {container: '#container', push: true}////  optionsFor({container: '#container', push: true})//  // => {container: '#container', push: true}//// Returns options Object.function optionsFor(container, options) { // Both container and options if ( container && options )  options.container = container // First argument is options Object else if ( $.isPlainObject(container) )  options = container // Only container else  options = {container: container} // Find and validate container if (options.container)  options.container = findContainerFor(options.container) return options}// Internal: Find container element for a variety of inputs.//// Because we can't persist elements using the history API, we must be// able to find a String selector that will consistently find the Element.//// container - A selector String, jQuery object, or DOM Element.//// Returns a jQuery object whose context is `document` and has a selector.function findContainerFor(container) { container = $(container) if ( !container.length ) {  throw "no pjax container for " + container.selector } else if ( container.selector !== '' && container.context === document ) {  return container } else if ( container.attr('id') ) {  return $('#' + container.attr('id')) } 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) { return elems.filter(selector).add(elems.find(selector));}function parseHTML(html) { return $.parseHTML(html, document, true)}// 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 = {}, fullDocument = /<html/i.test(data) // Prefer X-PJAX-URL header if it was set, otherwise fallback to // using the original requested url. var serverUrl = xhr.getResponseHeader('X-PJAX-URL') obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl // Attempt to parse response html into elements if (fullDocument) {  var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0]))  var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) } else {  var $head = $body = $(parseHTML(data)) } // If response data is empty, return fast if ($body.length === 0)  return obj // If there's a <title> tag in the header, use it as // the page's title. obj.title = findAll($head, 'title').last().text() if (options.fragment) {  // If they specified a fragment, look for it in the response  // and pull it out.  if (options.fragment === 'body') {   var $fragment = $body  } else {   var $fragment = findAll($body, options.fragment).first()  }  if ($fragment.length) {   obj.contents = options.fragment === 'body' ? $fragment : $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 (!fullDocument) {  obj.contents = $body } // Clean up any <title> tags if (obj.contents) {  // Remove any parent title elements  obj.contents = obj.contents.not(function() { return $(this).is('title') })  // Then scrub any titles from their descendants  obj.contents.find('title').remove()  // Gather all script[src] elements  obj.scripts = findAll(obj.contents, 'script[src]').remove()  obj.contents = obj.contents.not(obj.scripts) } // Trim any whitespace off the title if (obj.title) obj.title = $.trim(obj.title) return obj}// Load an execute scripts using standard script request.//// Avoids jQuery's traditional $.getScript which does a XHR request and// globalEval.//// scripts - jQuery object of script Elements//// Returns nothing.function executeScriptTags(scripts) { if (!scripts) return var existingScripts = $('script[src]') scripts.each(function() {  var src = this.src  var matchedScripts = existingScripts.filter(function() {   return this.src === src  })  if (matchedScripts.length) return  var script = document.createElement('script')  var type = $(this).attr('type')  if (type) script.type = type  script.src = $(this).attr('src')  document.head.appendChild(script) })}// Internal: History DOM caching class.var cacheMapping   = {}var cacheForwardStack = []var cacheBackStack  = []// 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.function cachePush(id, value) { cacheMapping[id] = value cacheBackStack.push(id) // Remove all entries in forward history stack after pushing a new page. trimCacheStack(cacheForwardStack, 0) // Trim back history stack to max cache length. trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength)}// Shifts cache from directional history cache. Should be// called on `popstate` with the previous state id and container// contents.//// direction - "forward" or "back" String// id    - State ID Number// value   - DOM Element to cache//// Returns nothing.function cachePop(direction, id, value) { var pushStack, popStack cacheMapping[id] = value if (direction === 'forward') {  pushStack = cacheBackStack  popStack = cacheForwardStack } else {  pushStack = cacheForwardStack  popStack = cacheBackStack } pushStack.push(id) if (id = popStack.pop())  delete cacheMapping[id] // Trim whichever stack we just pushed to to max cache length. trimCacheStack(pushStack, pjax.defaults.maxCacheLength)}// Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no// longer than the specified length, deleting cached DOM elements as necessary.//// stack - Array of state IDs// length - Maximum length to trim to//// Returns nothing.function trimCacheStack(stack, length) { while (stack.length > length)  delete cacheMapping[stack.shift()]}// Public: Find version identifier for the initial page load.//// Returns String version or undefined.function findVersion() { return $('meta').filter(function() {  var name = $(this).attr('http-equiv')  return name && name.toUpperCase() === 'X-PJAX-VERSION' }).attr('content')}// Install pjax functions on $.pjax to enable pushState behavior.//// Does nothing if already enabled.//// Examples////   $.pjax.enable()//// Returns nothing.function enable() { $.fn.pjax = fnPjax $.pjax = pjax $.pjax.enable = $.noop $.pjax.disable = disable $.pjax.click = handleClick $.pjax.submit = handleSubmit $.pjax.reload = pjaxReload $.pjax.defaults = {  timeout: 650,  push: true,  replace: false,  type: 'GET',  dataType: 'html',  scrollTo: 0,  maxCacheLength: 20,  version: findVersion } $(window).on('popstate.pjax', onPjaxPopstate)}// Disable pushState behavior.//// This is the case when a browser doesn't support pushState. It is// sometimes useful to disable pushState for debugging on a modern// browser.//// Examples////   $.pjax.disable()//// Returns nothing.function disable() { $.fn.pjax = function() { return this } $.pjax = fallbackPjax $.pjax.enable = enable $.pjax.disable = $.noop $.pjax.click = $.noop $.pjax.submit = $.noop $.pjax.reload = function() { window.location.reload() } $(window).off('popstate.pjax', onPjaxPopstate)}// Add the state property to jQuery's event object so we can use it in// $(window).bind('popstate')if ( $.inArray('state', $.event.props) < 0 ) $.event.props.push('state')// Is pjax supported by this browser?$.support.pjax = window.history && window.history.pushState && window.history.replaceState && // pushState isn't reliable on iOS until 5. !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)$.support.pjax ? enable() : disable()})(jQuery);