├── LICENSE ├── README.md └── src ├── jquery.pjax.js ├── kissy.pjax.js └── qwrap.pjax.js /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2011-2012 TJ Welefen Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [WARNING] 该项目已经停止维护,愿意继续维护的同学请联系 welefen@gmail.com,可以把项目转给你。 3 | 4 | ## 介绍 5 | 6 | pushState是一个可以操作history的api,该api的介绍和使用请见这里:http://www.welefen.com/use-ajax-and-pushstate.html 7 | 8 | 目前已经有http://github.com/, http://plus.google.com, http://www.welefen.com 等网站已经使用。 9 | 10 | pjax是对ajax + pushState的封装,让你可以很方便的使用pushState技术。 11 | 12 | 同时支持了缓存和本地存储,下次访问的时候直接读取本地数据,无需在次访问。 13 | 14 | 并且展现方式支持动画技术,可以使用系统自带的动画方式,也可以自定义动画展现方式。 15 | 16 | 17 | 18 | ## 如何使用 19 | 20 | ### jquery版 21 | 将jquery.pjax.js部署到你的页面中,将需要使用pjax的a链接进行绑定(不能绑定外域的url),如: 22 | 23 | 24 | ```js 25 | $.pjax({ 26 | selector: 'a', 27 | container: '#container', //内容替换的容器 28 | show: 'fade', //展现的动画,支持默认和fade, 可以自定义动画方式,这里为自定义的function即可。 29 | cache: true, //是否使用缓存 30 | storage: true, //是否使用本地存储 31 | titleSuffix: '', //标题后缀 32 | filter: function(){}, 33 | callback: function(){} 34 | }) 35 | ``` 36 | **注意:若设置 storage 为 true,为避免多次请求页面后导致本地 localStorage 容量不足而触发异常,请在页面加载完成后加载以下 JavaScript 代码清除已过期的记录。** 37 | 38 | ```javascript 39 | if (!!window.localStorage) { 40 | for (var key in localStorage) { 41 | try { 42 | if ((key.split("_") || [""])[0] === "pjax") { 43 | var item = localStorage.getItem(key); 44 | if (item) { 45 | item = JSON.parse(item); 46 | if ((parseInt(item.time) + 600 * 1000) <= new Date * 1) { 47 | localStorage.removeItem(key) 48 | } 49 | } 50 | } 51 | } catch (e) { } 52 | } 53 | } 54 | ``` 55 | 56 | 57 | 58 | ### qwrap版 59 | 60 | qwrap版需要在页面引入qwrap和对应的ajax组件。 61 | 62 | qwrap见: https://github.com/jkisjk/qwrap 63 | 64 | 对应的ajax组件见: https://github.com/jkisjk/qwrap/tree/master/resource/js/wagang/ajax 65 | 66 | 或者你直接引用我打包好的: http://www.welefen.com/wp-content/themes/gplus/js/qwrap.js 由于我的空间速度不咋地,建议你另存为。 67 | 68 | 69 | ```js 70 | QW.pjax( 71 | selector: 'a', 72 | container: '#container', 73 | cache: true, 74 | storage: true, 75 | titleSuffix: '', 76 | filter: function(){}, 77 | callback: function(){} 78 | }) 79 | ``` 80 | ### kissy版 81 | 82 | kissy版需要在页面引入kissy。 83 | 84 | kissy见: http://docs.kissyui.com/ 85 | 86 | ```js 87 | KISSY.pjax( 88 | selector: 'a', 89 | container: '#container', 90 | cache: true, 91 | storage: true, 92 | titleSuffix: '', 93 | filter: function(){}, 94 | callback: function(){} 95 | }) 96 | ``` 97 | 98 | 由于kissy核心没有引用sizzle, 只支持一些简单的selector, 所以selector参数的值最好只为a, 对于一些不使用pjax的链接,可以通过filter函数参数进行过滤,具体的使用方法见下面的参数说明。 99 | 100 | 101 | ## 参数及含义 102 | 103 | === options.selector 104 | 105 | 给哪些selector绑定pjax事件,一般的为:"a", 如果要去掉一些外连的URL, 这里的selector可以为: "a[href^='http://www.welefen.com']" 106 | 107 | ,表示域名是www.welefen.com下才有pjax事件(也就是站内)。 108 | 109 | === options.container 110 | 111 | 内容变换容器,是指哪个容器里的内容发生的变换,如: '#content', 112 | 113 | === options.cache 114 | 115 | 缓存pjax的内容,对于更新不频繁的页面来说,缓存pjax内容可以减少HTTP请求数 116 | 117 | options.cache的值是缓存时间,单位为秒,默认为: 24*3600(一天) 118 | 119 | === options.storage 120 | 121 | 是否使用本地存储进行内容的缓存,使用本地存储缓存的话即使关闭浏览器后,下次访问如果缓存时间有效的话会直接读取缓存的内容,避免重新请求了。 122 | 123 | === options.titleSuffix 124 | 125 | 标题后缀。 126 | 127 | 对于pjax显示标题,首先会从返回内容里查找,如果没有的话,会取当前a标签的title值,并可以指定统一的后缀 128 | 129 | === options.filter 130 | 131 | 过滤函数,虽然options.selector可以写个比较复杂的选择器,但有时候还要过滤一些URL,如:后台的URL。 132 | 133 | 这时候就可以使用options.filter函数进行过滤了。如: 134 | 135 | ```js 136 | { 137 | filter: function(href){ 138 | //对于wordpress后台的URL和wp-content里的URL不使用pjax 139 | if(href.indexOf('/wp-admin') || href.indexOf('/wp-content')){ 140 | return true; 141 | } 142 | } 143 | } 144 | ``` 145 | 对于要过滤掉的URL, 需要返回值为true。 146 | 147 | === options.callback 148 | 149 | 回调函数,这个函数不同于pjax.start和pjax.end(这2个事件下面描述)事件。 150 | 151 | 该函数会在每个阶段都会执行,即使pjax发生error的时候,并且会传递一个参数标明当前的状态,如: 152 | 153 | ```js 154 | { 155 | callback: function(status){ 156 | var type = status.type; 157 | switch(type){ 158 | case 'success': ;break; //正常 159 | case 'cache':;break; //读取缓存 160 | case 'error': ;break; //发生异常 161 | case 'hash': ;break; //只是hash变化 162 | } 163 | } 164 | } 165 | ``` 166 | 167 | ## 事件(events) 168 | 169 | 一般情况下使用ajax来获取数据的时候,我们都希望有个loading的效果,pjax本身不提供这个功能,但提供了2个相关的事件。 170 | 171 | 如果需要这样的功能,可以在事件里实现这种功能。 172 | 173 | * pjax.start 在pjax ajax发送request之前调用 174 | 175 | * pjax.end 在phax ajax结束时调用 176 | 177 | 这样你可以在pjax.start事件里显示loading效果,在pjax.end事件里隐藏loading了。如: 178 | 179 | ```js 180 | $('#container').bind('pjax.start', function(){ 181 | $('#loading').show(); 182 | }) 183 | $('#container').bind('pjax.end', function(){ 184 | $('#loading').hide(); 185 | }) 186 | ``` 187 | 188 | ## 浏览器支持 189 | 190 | 提供了history.pushState接口的浏览器才支持这个功能 191 | 192 | 如果浏览器不支持这个功能而调用pjax方法的话,实际上什么都没做,还是使用默认的链接响应机制 193 | 194 | ## 后端需要做的 195 | 196 | 类似于ajax, 异步请求的时候后端不能将公用的内容也返回。 197 | 198 | 所以需要一个判断是否pjax请求的接口。如:php可以借鉴下面的实现 199 | 200 | ``` 201 | function is_pjax(){ 202 | return array_key_exists('HTTP_X_PJAX', $_SERVER) && $_SERVER['HTTP_X_PJAX']; 203 | } 204 | ``` 205 | 206 | ## 其他 207 | 208 | 实际上该类的封装借鉴于 209 | 210 | 对其增加了缓存、本地存储和动画等功能,并且将一些参数进行了优化。 211 | 212 | -------------------------------------------------------------------------------- /src/jquery.pjax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pjax(ajax + history.pushState) for jquery 3 | * 4 | * by welefen 5 | */ 6 | (function ($) { 7 | var Util = { 8 | support: { 9 | pjax: window.history && window.history.pushState && window.history.replaceState && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/), 10 | storage: !!window.localStorage 11 | }, 12 | toInt: function (obj) { 13 | return parseInt(obj); 14 | }, 15 | stack: {}, 16 | getTime: function () { 17 | return new Date * 1; 18 | }, 19 | // 获取URL不带hash的部分,切去掉pjax=true部分 20 | getRealUrl: function (url) { 21 | url = (url || '').replace(/\#.*?$/, ''); 22 | url = url.replace('?pjax=true&', '?').replace('?pjax=true', '').replace('&pjax=true', ''); 23 | return url; 24 | }, 25 | // 获取url的hash部分 26 | getUrlHash: function (url) { 27 | return url.replace(/^[^\#]*(?:\#(.*?))?$/, '$1'); 28 | }, 29 | // 获取本地存储的key 30 | getLocalKey: function (src) { 31 | return 'pjax_' + encodeURIComponent(src); 32 | }, 33 | // 清除所有的cache 34 | removeAllCache: function () { 35 | if (!Util.support.storage) 36 | return; 37 | for (var name in localStorage) { 38 | if ((name.split('_') || [''])[0] === 'pjax') { 39 | delete localStorage[name]; 40 | } 41 | } 42 | }, 43 | // 获取cache 44 | getCache: function (src, time, flag) { 45 | var item, vkey, tval; 46 | time = Util.toInt(time); 47 | if (src in Util.stack) { 48 | item = Util.stack[src], ctime = Util.getTime(); 49 | if ((item.time + time * 1000) > ctime) { 50 | return item; 51 | } else { 52 | delete Util.stack[src]; 53 | } 54 | } else if (flag && Util.support.storage) { // 从localStorage里查询 55 | vkey = Util.getLocalKey(src); 56 | item = localStorage.getItem(vkey); 57 | if (item) { 58 | item = JSON.parse(item); 59 | tval = Util.toInt(item.time); 60 | if ((tval + time * 1000) > Util.getTime()) { 61 | return { 62 | data: item.data, 63 | title: item.title 64 | }; 65 | } else { 66 | localStorage.removeItem(vkey); 67 | } 68 | } 69 | } 70 | return null; 71 | }, 72 | // 设置cache 73 | setCache: function (src, data, title, flag) { 74 | var time = Util.getTime(), key; 75 | Util.stack[src] = { 76 | data: data, 77 | title: title, 78 | time: time 79 | }; 80 | if (flag && Util.support.storage) { 81 | key = Util.getLocalKey(src); 82 | try { 83 | localStorage.setItem(key, JSON.stringify({ 84 | data: data, 85 | title: title, 86 | time: time 87 | })); 88 | } catch (e) { 89 | console.warn("Failed to execute 'setItem' on 'Storage': Setting the value of '" + key + "' exceeded the quota."); 90 | } 91 | } 92 | }, 93 | // 清除cache 94 | removeCache: function (src) { 95 | try { 96 | src = Util.getRealUrl(src || location.href); 97 | delete Util.stack[src]; 98 | if (Util.support.storage) { 99 | localStorage.removeItem(Util.getLocalKey(src)); 100 | } 101 | } catch (e) { 102 | } 103 | } 104 | }; 105 | // pjax 106 | var pjax = function (options) { 107 | options = $.extend({ 108 | selector: '', 109 | container: '', 110 | callback: function () { }, 111 | filter: function () { } 112 | }, options); 113 | if (!options.container || !options.selector) { 114 | throw new Error('selector & container options must be set'); 115 | } 116 | $('body').delegate(options.selector, 'click', function (event) { 117 | if (event.which > 1 || event.metaKey) { 118 | return true; 119 | } 120 | var $this = $(this), href = $this.attr('href'); 121 | // 过滤 122 | if (typeof options.filter === 'function') { 123 | if (options.filter.call(this, href, this) === true) { 124 | return true; 125 | } 126 | } 127 | if (href === location.href) { 128 | return true; 129 | } 130 | // 只是hash不同 131 | if (Util.getRealUrl(href) == Util.getRealUrl(location.href)) { 132 | var hash = Util.getUrlHash(href); 133 | if (hash) { 134 | location.hash = hash; 135 | options.callback && options.callback.call(this, { 136 | type: 'hash' 137 | }); 138 | } 139 | return true; 140 | } 141 | event.preventDefault(); 142 | options = $.extend(true, options, { 143 | url: href, 144 | element: this, 145 | push: true 146 | }); 147 | // 发起请求 148 | pjax.request(options); 149 | }); 150 | }; 151 | pjax.xhr = null; 152 | pjax.options = {}; 153 | pjax.state = {}; 154 | 155 | // 默认选项 156 | pjax.defaultOptions = { 157 | timeout: 4000, 158 | element: null, 159 | cache: 24 * 3600, // 缓存时间, 0为不缓存, 单位为秒 160 | storage: true, // 是否使用localstorage将数据保存到本地 161 | url: '', // 链接地址 162 | push: true, // true is push, false is replace, null for do nothing 163 | show: '', // 展示的动画 164 | title: '', // 标题 165 | titleSuffix: '',// 标题后缀 166 | type: 'GET', 167 | data: { 168 | pjax: true 169 | }, 170 | dataType: 'html', 171 | callback: null, // 回调函数 172 | // for jquery 173 | beforeSend: function (xhr) { 174 | $(pjax.options.container).trigger('pjax.start', [xhr, pjax.options]); 175 | xhr && xhr.setRequestHeader('X-PJAX', true); 176 | }, 177 | error: function () { 178 | pjax.options.callback && pjax.options.callback.call(pjax.options.element, { 179 | type: 'error' 180 | }); 181 | location.href = pjax.options.url; 182 | }, 183 | complete: function (xhr) { 184 | $(pjax.options.container).trigger('pjax.end', [xhr, pjax.options]); 185 | } 186 | }; 187 | // 展现动画 188 | pjax.showFx = { 189 | "_default": function (data, callback, isCached) { 190 | this.html(data); 191 | callback && callback.call(this, data, isCached); 192 | }, 193 | fade: function (data, callback, isCached) { 194 | var $this = this; 195 | if (isCached) { 196 | $this.html(data); 197 | callback && callback.call($this, data, isCached); 198 | } else { 199 | this.fadeTo(200, .5, function () { 200 | $this.html(data).fadeTo(200, 1, function () { 201 | callback && callback.call($this, data, isCached); 202 | }); 203 | }); 204 | } 205 | } 206 | } 207 | // 展现函数 208 | pjax.showFn = function (showType, container, data, fn, isCached) { 209 | var fx = null; 210 | if (typeof showType === 'function') { 211 | fx = showType; 212 | } else { 213 | if (!(showType in pjax.showFx)) { 214 | showType = "_default"; 215 | } 216 | fx = pjax.showFx[showType]; 217 | } 218 | fx && fx.call(container, data, function () { 219 | var hash = location.hash; 220 | if (hash != '') { 221 | location.href = hash; 222 | //for FF 223 | if (/Firefox/.test(navigator.userAgent)) { 224 | history.replaceState($.extend({}, pjax.state, { 225 | url: null 226 | }), document.title); 227 | } 228 | } else { 229 | window.scrollTo(0, 0); 230 | } 231 | fn && fn.call(this, data, isCached); 232 | }, isCached); 233 | } 234 | // success callback 235 | pjax.success = function (data, isCached) { 236 | // isCached default is success 237 | if (isCached !== true) { 238 | isCached = false; 239 | } 240 | //accept Whole html 241 | if (pjax.html) { 242 | data = $(data).find(pjax.html).html(); 243 | } 244 | if ((data || '').indexOf('(.*?)<\/title>/); 257 | if (matches) { 258 | title = matches[1]; 259 | } 260 | if (title) { 261 | if (title.indexOf(pjax.options.titleSuffix) == -1) { 262 | title += pjax.options.titleSuffix; 263 | } 264 | } 265 | document.title = title; 266 | pjax.state = { 267 | container: pjax.options.container, 268 | timeout: pjax.options.timeout, 269 | cache: pjax.options.cache, 270 | storage: pjax.options.storage, 271 | show: pjax.options.show, 272 | title: title, 273 | url: pjax.options.oldUrl 274 | }; 275 | var query = $.param(pjax.options.data); 276 | if (query != "") { 277 | pjax.state.url = pjax.options.url + (/\?/.test(pjax.options.url) ? "&" : "?") + query; 278 | } 279 | if (pjax.options.push) { 280 | if (!pjax.active) { 281 | history.replaceState($.extend({}, pjax.state, { 282 | url: null 283 | }), document.title); 284 | pjax.active = true; 285 | } 286 | history.pushState(pjax.state, document.title, pjax.options.oldUrl); 287 | } else if (pjax.options.push === false) { 288 | history.replaceState(pjax.state, document.title, pjax.options.oldUrl); 289 | } 290 | pjax.options.showFn && pjax.options.showFn(data, function () { 291 | pjax.options.callback && pjax.options.callback.call(pjax.options.element, { 292 | type: isCached ? 'cache' : 'success' 293 | }); 294 | }, isCached); 295 | // 设置cache 296 | if (pjax.options.cache && !isCached) { 297 | Util.setCache(pjax.options.url, data, title, pjax.options.storage); 298 | } 299 | }; 300 | 301 | // 发送请求 302 | pjax.request = function (options) { 303 | if (options.hasOwnProperty('data')) { 304 | pjax.defaultOptions.data = options.data; 305 | } 306 | options = $.extend(true, pjax.defaultOptions, options); 307 | var cache, container = $(options.container); 308 | options.oldUrl = options.url; 309 | options.url = Util.getRealUrl(options.url); 310 | if ($(options.element).length) { 311 | cache = Util.toInt($(options.element).attr('data-pjax-cache')); 312 | if (cache) { 313 | options.cache = cache; 314 | } 315 | } 316 | if (options.cache === true) { 317 | options.cache = 24 * 3600; 318 | } 319 | options.cache = Util.toInt(options.cache); 320 | // 如果将缓存时间设为0,则将之前的缓存也清除 321 | if (options.cache === 0) { 322 | Util.removeAllCache(); 323 | } 324 | // 展现函数 325 | if (!options.showFn) { 326 | options.showFn = function (data, fn, isCached) { 327 | pjax.showFn(options.show, container, data, fn, isCached); 328 | }; 329 | } 330 | pjax.options = options; 331 | pjax.options.success = pjax.success; 332 | if (options.cache && (cache = Util.getCache(options.url, options.cache, options.storage))) { 333 | options.beforeSend(); 334 | options.title = cache.title; 335 | pjax.success(cache.data, true); 336 | options.complete(); 337 | return true; 338 | } 339 | if (pjax.xhr && pjax.xhr.readyState < 4) { 340 | pjax.xhr.onreadystatechange = $.noop; 341 | pjax.xhr.abort(); 342 | } 343 | pjax.xhr = $.ajax(pjax.options); 344 | }; 345 | 346 | // popstate event 347 | var popped = ('state' in window.history), initialURL = location.href; 348 | $(window).bind('popstate', function (event) { 349 | var initialPop = !popped && location.href == initialURL; 350 | popped = true; 351 | if (initialPop) return; 352 | var state = event.state; 353 | if (state && state.container) { 354 | if ($(state.container).length) { 355 | var data = { 356 | url: state.url, 357 | container: state.container, 358 | push: null, 359 | timeout: state.timeout, 360 | cache: state.cache, 361 | storage: state.storage, 362 | title: state.title, 363 | element: null 364 | }; 365 | pjax.request(data); 366 | } else { 367 | window.location = location.href; 368 | } 369 | } 370 | }); 371 | 372 | // not support 373 | if (!Util.support.pjax) { 374 | pjax = function () { 375 | return true; 376 | }; 377 | pjax.request = function (options) { 378 | if (options && options.url) { 379 | location.href = options.url; 380 | } 381 | }; 382 | } 383 | // pjax bind to $ 384 | $.pjax = pjax; 385 | $.pjax.util = Util; 386 | 387 | // extra 388 | if ($.inArray('state', $.event.props) < 0) { 389 | $.event.props.push('state') 390 | } 391 | 392 | })(jQuery); 393 | -------------------------------------------------------------------------------- /src/kissy.pjax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pjax(ajax + history.pushState) for kissy 3 | * 4 | * by Junyi 5 | */ 6 | KISSY.add('pjax', function (S, DOM, Node, Event, Ajax) { 7 | var Util = { 8 | support : { 9 | pjax : window.history && window.history.pushState && window.history.replaceState && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/), 10 | storage : !!window.localStorage 11 | }, 12 | toInt : function(obj) { 13 | return parseInt(obj); 14 | }, 15 | stack : {}, 16 | getTime : function() { 17 | return new Date * 1; 18 | }, 19 | // 获取URL不带hash的部分,且去掉pjax=true部分 20 | getRealUrl : function(url) { 21 | url = (url || '').replace(/\#.*?$/, ''); 22 | url = url.replace('?pjax=true&', '?').replace('?pjax=true', '').replace('&pjax=true', ''); 23 | return url; 24 | }, 25 | // 获取url的hash部分 26 | getUrlHash : function(url) { 27 | return url.replace(/^[^\#]*(?:\#(.*?))?$/, '$1'); 28 | }, 29 | // 获取本地存储的key 30 | getLocalKey : function(src) { 31 | var s = 'pjax_' + encodeURIComponent(src); 32 | return { 33 | data : s + '_data', 34 | time : s + '_time', 35 | title : s + '_title' 36 | }; 37 | }, 38 | // 清除所有的cache 39 | removeAllCache : function() { 40 | if (!Util.support.storage) 41 | return; 42 | for ( var name in localStorage) { 43 | if ((name.split('_') || [ '' ])[0] === 'pjax') { 44 | delete localStorage[name]; 45 | } 46 | } 47 | }, 48 | // 获取cache 49 | getCache : function(src, time, flag) { 50 | var item, vkey, tkey, tval; 51 | time = Util.toInt(time); 52 | if (src in Util.stack) { 53 | item = Util.stack[src], ctime = Util.getTime(); 54 | if ((item.time + time * 1000) > ctime) { 55 | return item; 56 | } else { 57 | delete Util.stack[src]; 58 | } 59 | } else if (flag && Util.support.storage) { // 从localStorage里查询 60 | var l = Util.getLocalKey(src); 61 | vkey = l.data; 62 | tkey = l.time; 63 | item = localStorage.getItem(vkey); 64 | if (item) { 65 | tval = Util.toInt(localStorage.getItem(tkey)); 66 | if ((tval + time * 1000) > Util.getTime()) { 67 | return { 68 | data : item, 69 | title : localStorage.getItem(l.title) 70 | }; 71 | } else { 72 | localStorage.removeItem(vkey); 73 | localStorage.removeItem(tkey); 74 | localStorage.removeItem(l.title); 75 | } 76 | } 77 | } 78 | return null; 79 | }, 80 | // 设置cache 81 | setCache : function(src, data, title, flag) { 82 | var time = Util.getTime(), key; 83 | Util.stack[src] = { 84 | data : data, 85 | title : title, 86 | time : time 87 | }; 88 | if (flag && Util.support.storage) { 89 | key = Util.getLocalKey(src); 90 | localStorage.setItem(key.data, data); 91 | localStorage.setItem(key.time, time); 92 | localStorage.setItem(key.title, title); 93 | } 94 | }, 95 | // 清除cache 96 | removeCache : function(src) { 97 | src = Util.getRealUrl(src || location.href); 98 | delete Util.stack[src]; 99 | if (Util.support.storage) { 100 | var key = Util.getLocalKey(src); 101 | localStorage.removeItem(key.data); 102 | localStorage.removeItem(key.time); 103 | localStorage.removeItem(key.title); 104 | } 105 | } 106 | }; 107 | 108 | // pjax 109 | var pjax = function(options) { 110 | options = S.merge({ 111 | selector : '', 112 | container : '', 113 | callback : function() {}, 114 | filter : function() {} 115 | }, options); 116 | if (!options.container || !options.selector) { 117 | throw new Error('selector & container options must be set'); 118 | } 119 | Event.delegate(document, 'click', options.selector, function(ev) { 120 | if (ev.which > 1 || ev.metaKey) { 121 | return true; 122 | } 123 | var target = ev.target, href = target['href']; 124 | // 过滤 125 | if (typeof options.filter === 'function') { 126 | if (options.filter.call(target, href, target) === true){ 127 | return true; 128 | } 129 | } 130 | if (href === location.href) { 131 | return true; 132 | } 133 | // 只是hash不同 134 | if (Util.getRealUrl(href) == Util.getRealUrl(location.href)) { 135 | var hash = Util.getUrlHash(href); 136 | if (hash) { 137 | location.hash = hash; 138 | options.callback && options.callback.call(target, { 139 | type : 'hash' 140 | }); 141 | } 142 | return true; 143 | } 144 | ev.preventDefault(); 145 | options = S.merge(options, { 146 | url : href, 147 | element : target 148 | }); 149 | // 发起请求 150 | pjax.request(options); 151 | }); 152 | }; 153 | pjax.xhr = null; 154 | pjax.options = {}; 155 | pjax.state = {}; 156 | 157 | // 默认选项 158 | pjax.defaultOptions = { 159 | timeout : 2000, 160 | element : null, 161 | cache : 24 * 3600, // 缓存时间, 0为不缓存, 单位为秒 162 | storage : true, // 是否使用localstorage将数据保存到本地 163 | url : '', // 链接地址 164 | push : true, // true is push, false is replace, null for do nothing 165 | show : '', // 展示的动画 166 | title : '', // 标题 167 | titleSuffix : '',// 标题后缀 168 | type : 'GET', 169 | data : { 170 | pjax : true 171 | }, 172 | dataType : 'html', 173 | callback : null, // 回调函数 174 | headers : { 175 | 'X-PJAX' : true 176 | }, 177 | error : function() { 178 | pjax.options.callback && pjax.options.callback.call(pjax.options.element, { 179 | type : 'error' 180 | }); 181 | location.href = pjax.options.url; 182 | }, 183 | complete : function(xhr) { 184 | Node.all(pjax.options.container).fire('pjax.end', [ xhr, pjax.options ]); 185 | } 186 | }; 187 | // 展现动画 188 | pjax.showFx = { 189 | "_default" : function(data, callback, isCached) { 190 | this.html(data); 191 | callback && callback.call(this, data, isCached); 192 | } 193 | } 194 | // 展现函数 195 | pjax.showFn = function(showType, container, data, fn, isCached) { 196 | var fx = null; 197 | if (typeof showType === 'function') { 198 | fx = showType; 199 | } else { 200 | if (!(showType in pjax.showFx)) { 201 | showType = "_default"; 202 | } 203 | fx = pjax.showFx[showType]; 204 | } 205 | fx && fx.call(container, data, function() { 206 | var hash = location.hash; 207 | if (hash != '') { 208 | location.href = hash; 209 | //for FF 210 | if (/Firefox/.test(navigator.userAgent)) { 211 | history.replaceState(S.merge({}, pjax.state, { 212 | url : null 213 | }), document.title); 214 | } 215 | } else { 216 | window.scrollTo(0, 0); 217 | } 218 | fn && fn.call(this, data, isCached); 219 | }, isCached); 220 | } 221 | // success callback 222 | pjax.success = function(data, isCached) { 223 | // isCached default is success 224 | if (isCached !== true) { 225 | isCached = false; 226 | } 227 | if ((data || '').indexOf('(.*?)<\/title>/); 237 | if (matches) { 238 | title = matches[1]; 239 | } 240 | if (!title && pjax.options.element) { 241 | el = Node.one(pjax.options.element); 242 | title = el.attr('title') || el.text(); 243 | } 244 | } 245 | if (title) { 246 | if (title.indexOf(pjax.options.titleSuffix) == -1) { 247 | title += pjax.options.titleSuffix; 248 | } 249 | document.title = title; 250 | } 251 | pjax.state = { 252 | container : pjax.options.container, 253 | timeout : pjax.options.timeout, 254 | cache : pjax.options.cache, 255 | storage : pjax.options.storage, 256 | show : pjax.options.show, 257 | title : title, 258 | url : pjax.options.oldUrl 259 | }; 260 | var query = S.param(pjax.options.data); 261 | if (query != "") { 262 | pjax.state.url = pjax.options.url + (/\?/.test(pjax.options.url) ? "&" : "?") + query; 263 | } 264 | if (pjax.options.push) { 265 | if (!pjax.active) { 266 | history.replaceState(S.merge({}, pjax.state, { 267 | url : null 268 | }), document.title); 269 | pjax.active = true; 270 | } 271 | history.pushState(pjax.state, document.title, pjax.options.oldUrl); 272 | } else if (pjax.options.push === false) { 273 | history.replaceState(pjax.state, document.title, pjax.options.oldUrl); 274 | } 275 | pjax.options.showFn && pjax.options.showFn(data, function() { 276 | pjax.options.callback && pjax.options.callback.call(pjax.options.element, { 277 | type : isCached ? 'cache' : 'success' 278 | }); 279 | }, isCached); 280 | // 设置cache 281 | if (pjax.options.cache && !isCached) { 282 | Util.setCache(pjax.options.url, data, title, pjax.options.storage); 283 | } 284 | }; 285 | // 发送请求 286 | pjax.request = function(options) { 287 | options = S.merge(pjax.defaultOptions, options); 288 | var cache, container = Node.one(options.container); 289 | options.oldUrl = options.url; 290 | options.url = Util.getRealUrl(options.url); 291 | 292 | if(Node.one(options.element)){ 293 | cache = Util.toInt(Node.one(options.element).attr('data-pjax-cache')); 294 | if (cache) { 295 | options.cache = cache; 296 | } 297 | } 298 | if (options.cache === true) { 299 | options.cache = 24 * 3600; 300 | } 301 | options.cache = Util.toInt(options.cache); 302 | // 如果将缓存时间设为0,则将之前的缓存也清除 303 | if (options.cache === 0) { 304 | Util.removeAllCache(); 305 | } 306 | // 展现函数 307 | if (!options.showFn) { 308 | options.showFn = function(data, fn, isCached) { 309 | pjax.showFn(options.show, container, data, fn, isCached); 310 | }; 311 | } 312 | pjax.options = options; 313 | pjax.options.success = pjax.success; 314 | 315 | // 尝试从缓存里读取 316 | if (options.cache && (cache = Util.getCache(options.url, options.cache, options.storage))) { 317 | options.title = cache.title; 318 | pjax.success(cache.data, true); 319 | options.complete(); 320 | return true; 321 | } 322 | if (pjax.xhr && pjax.xhr.readyState < 4) { 323 | pjax.xhr.abort(); 324 | } 325 | pjax.xhr = Ajax(pjax.options); 326 | }; 327 | 328 | // popstate event 329 | var popped = ('state' in window.history), initialURL = location.href; 330 | Event.on(window, 'popstate', function(event) { 331 | var initialPop = !popped && location.href == initialURL; 332 | popped = true; 333 | if (initialPop) return; 334 | var state = history.state; 335 | if (state && state.container) { 336 | if (Node.one(state.container).length) { 337 | var data = { 338 | url : state.url || location.href, 339 | container : state.container, 340 | push : null, 341 | timeout : state.timeout, 342 | cache : state.cache, 343 | storage : state.storage 344 | }; 345 | pjax.request(data); 346 | } else { 347 | window.location = location.href; 348 | } 349 | } 350 | }); 351 | 352 | // not support 353 | if (!Util.support.pjax) { 354 | pjax = function() { 355 | return true; 356 | }; 357 | pjax.request = function(options) { 358 | if (options && options.url) { 359 | location.href = options.url; 360 | } 361 | }; 362 | } 363 | pjax.util = Util; 364 | 365 | S.pjax = pjax; 366 | return pjax; 367 | },{ 368 | requires: ['dom', 'node', 'event', 'ajax'] 369 | }); 370 | -------------------------------------------------------------------------------- /src/qwrap.pjax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pjax(ajax + history.pushState) for qwrap 3 | * 4 | * by welefen 5 | */ 6 | (function() { 7 | var mix = function(){ 8 | var target = arguments[0] ,i = 1, len = arguments.length; 9 | for(;i ctime) { 63 | return item; 64 | } else { 65 | delete Util.stack[src]; 66 | } 67 | } else if (flag && Util.support.storage) { // 从localStorage里查询 68 | var l = Util.getLocalKey(src); 69 | vkey = l.data; 70 | tkey = l.time; 71 | item = localStorage.getItem(vkey); 72 | if (item) { 73 | tval = Util.toInt(localStorage.getItem(tkey)); 74 | if ((tval + time * 1000) > Util.getTime()) { 75 | return { 76 | data : item, 77 | title : localStorage.getItem(l.title) 78 | }; 79 | } else { 80 | localStorage.removeItem(vkey); 81 | localStorage.removeItem(tkey); 82 | localStorage.removeItem(l.title); 83 | } 84 | } 85 | } 86 | return null; 87 | }, 88 | // 设置cache 89 | setCache : function(src, data, title, flag) { 90 | var time = Util.getTime(), key; 91 | Util.stack[src] = { 92 | data : data, 93 | title : title, 94 | time : time 95 | }; 96 | if (flag && Util.support.storage) { 97 | key = Util.getLocalKey(src); 98 | localStorage.setItem(key.data, data); 99 | localStorage.setItem(key.time, time); 100 | localStorage.setItem(key.title, title); 101 | } 102 | }, 103 | // 清除cache 104 | removeCache : function(src) { 105 | src = Util.getRealUrl(src || location.href); 106 | delete Util.stack[src]; 107 | if (Util.support.storage) { 108 | var key = Util.getLocalKey(src); 109 | localStorage.removeItem(key.data); 110 | localStorage.removeItem(key.time); 111 | localStorage.removeItem(key.title); 112 | } 113 | } 114 | }; 115 | // pjax 116 | var pjax = function(options) { 117 | options = mix({}, { 118 | selector : '', 119 | container : '', 120 | callback : function() {}, 121 | filter : function() {} 122 | }, options); 123 | if (!options.container || !options.selector) { 124 | throw new Error('selector & container options must be set'); 125 | } 126 | W('body').delegate(options.selector, 'click', function(event) { 127 | if (event.which > 1 || event.metaKey) { 128 | return true; 129 | } 130 | var $this = W(this), href = $this.attr('href'); 131 | // 过滤 132 | if (typeof options.filter === 'function') { 133 | if (options.filter.call(this, href, this) === true){ 134 | return true; 135 | } 136 | } 137 | if (href === location.href) { 138 | return true; 139 | } 140 | // 只是hash不同 141 | if (Util.getRealUrl(href) == Util.getRealUrl(location.href)) { 142 | var hash = Util.getUrlHash(href); 143 | if (hash) { 144 | location.hash = hash; 145 | options.callback && options.callback.call(this, { 146 | type : 'hash' 147 | }); 148 | } 149 | return true; 150 | } 151 | event.preventDefault(); 152 | options = mix({}, options, { 153 | url : href, 154 | element : this 155 | }); 156 | // 发起请求 157 | pjax.request(options); 158 | }); 159 | }; 160 | pjax.xhr = null; 161 | pjax.options = {}; 162 | pjax.state = {}; 163 | 164 | // 默认选项 165 | pjax.defaultOptions = { 166 | timeout : 2000, 167 | element : null, 168 | cache : 24 * 3600, // 缓存时间, 0为不缓存, 单位为秒 169 | storage : true, // 是否使用localstorage将数据保存到本地 170 | url : '', // 链接地址 171 | push : true, // true is push, false is replace, null for do nothing 172 | show : '', // 展示的动画 173 | title : '', // 标题 174 | titleSuffix : '',// 标题后缀 175 | type : 'GET', 176 | data : { 177 | pjax : true 178 | }, 179 | dataType : 'html', 180 | callback : null, // 回调函数 181 | requestHeaders:{ 182 | 'X-PJAX': true 183 | }, 184 | onerror : function() { 185 | pjax.options.callback && pjax.options.callback.call(pjax.options.element, { 186 | type : 'error' 187 | }); 188 | location.href = pjax.options.url; 189 | }, 190 | oncomplete : function(xhr) { 191 | W(pjax.options.container).fire('pjax.end', [ xhr, pjax.options ]); 192 | } 193 | }; 194 | // 展现动画 195 | pjax.showFx = { 196 | "_default" : function(data, callback, isCached) { 197 | this.html(data); 198 | callback && callback.call(this, data, isCached); 199 | } 200 | } 201 | // 展现函数 202 | pjax.showFn = function(showType, container, data, fn, isCached) { 203 | var fx = null; 204 | if (typeof showType === 'function') { 205 | fx = showType; 206 | } else { 207 | if (!(showType in pjax.showFx)) { 208 | showType = "_default"; 209 | } 210 | fx = pjax.showFx[showType]; 211 | } 212 | fx && fx.call(container, data, function() { 213 | var hash = location.hash; 214 | if (hash != '') { 215 | location.href = hash; 216 | //for FF 217 | if(/Firefox/.test(navigator.userAgent)){ 218 | history.replaceState($.extend({}, pjax.state, { 219 | url : null 220 | }), document.title); 221 | } 222 | } else { 223 | window.scrollTo(0, 0); 224 | } 225 | fn && fn.call(this, data, isCached); 226 | }, isCached); 227 | } 228 | // success callback 229 | pjax.onsucceed = function(data, isCached) { 230 | // isCached default is success 231 | if (isCached !== true) { 232 | isCached = false; 233 | } 234 | if(typeof data ==='object'){ 235 | data = this.requester.responseText; 236 | } 237 | if ((data || '').indexOf('(.*?)<\/title>/); 247 | if (matches) { 248 | title = matches[1].decode4Html(); 249 | } 250 | if (!title && pjax.options.element) { 251 | el = W(pjax.options.element); 252 | title = el.attr('title') || el.html(); 253 | } 254 | } 255 | if (title) { 256 | if (title.indexOf(pjax.options.titleSuffix) == -1) { 257 | title += pjax.options.titleSuffix; 258 | } 259 | document.title = title; 260 | } 261 | pjax.state = { 262 | container : pjax.options.container, 263 | timeout : pjax.options.timeout, 264 | cache : pjax.options.cache, 265 | storage : pjax.options.storage, 266 | show : pjax.options.show, 267 | title : title, 268 | url : pjax.options.oldUrl 269 | }; 270 | var query = Object.encodeURIJson(pjax.options.data); 271 | if (query != "") { 272 | pjax.state.url = pjax.options.url + (/\?/.test(pjax.options.url) ? "&" : "?") + query; 273 | } 274 | if (pjax.options.push) { 275 | if (!pjax.active) { 276 | history.replaceState(mix({}, pjax.state, { 277 | url : null 278 | }), document.title); 279 | pjax.active = true; 280 | } 281 | history.pushState(pjax.state, document.title, pjax.options.oldUrl); 282 | } else if (pjax.options.push === false) { 283 | history.replaceState(pjax.state, document.title, pjax.options.oldUrl); 284 | } 285 | pjax.options.showFn && pjax.options.showFn(data, function() { 286 | pjax.options.callback && pjax.options.callback.call(pjax.options.element,{ 287 | type : isCached? 'cache' : 'success' 288 | }); 289 | }, isCached); 290 | // 设置cache 291 | if (pjax.options.cache && ! isCached) { 292 | Util.setCache(pjax.options.url, data, title, pjax.options.storage); 293 | } 294 | }; 295 | 296 | // 发送请求 297 | pjax.request = function(options) { 298 | options = mix({}, pjax.defaultOptions, options); 299 | var cache, container = W(options.container); 300 | options.oldUrl = options.url; 301 | options.url = Util.getRealUrl(options.url); 302 | if(W(options.element)){ 303 | cache = Util.toInt(W(options.element).attr('data-pjax-cache')); 304 | if (cache) { 305 | options.cache = cache; 306 | } 307 | } 308 | if (options.cache === true) { 309 | options.cache = 24 * 3600; 310 | } 311 | options.cache = Util.toInt(options.cache); 312 | // 如果将缓存时间设为0,则将之前的缓存也清除 313 | if (options.cache === 0) { 314 | Util.removeAllCache(); 315 | } 316 | // 展现函数 317 | if (!options.showFn) { 318 | options.showFn = function(data, fn, isCached) { 319 | pjax.showFn(options.show, container, data, fn, isCached); 320 | }; 321 | } 322 | pjax.options = options; 323 | pjax.options.onsucceed = pjax.onsucceed; 324 | if (options.cache && (cache = Util.getCache(options.url, options.cache, options.storage))) { 325 | container.fire('pjax.start'); 326 | options.title = cache.title; 327 | pjax.onsucceed(cache.data, true); 328 | options.oncomplete(); 329 | return true; 330 | } 331 | if (pjax.xhr && pjax.xhr.cancel) { 332 | pjax.xhr.cancel(); 333 | } 334 | pjax.xhr = new QW.Ajax(pjax.options); 335 | container.fire('pjax.start'); 336 | pjax.xhr.send(pjax.options.url, 'get', pjax.options.data); 337 | }; 338 | 339 | // popstate event 340 | var popped = ('state' in window.history), initialURL = location.href; 341 | W(window).on('popstate', function(event) { 342 | var initialPop = !popped && location.href == initialURL; 343 | popped = true; 344 | if (initialPop) return; 345 | var state = event.state; 346 | if (state && state.container) { 347 | if (W(state.container).length) { 348 | var data = { 349 | url : state.url || location.href, 350 | container : state.container, 351 | push : null, 352 | timeout : state.timeout, 353 | cache : state.cache, 354 | storage : state.storage 355 | }; 356 | pjax.request(data); 357 | } else { 358 | window.location = location.href; 359 | } 360 | } 361 | }); 362 | 363 | // not support 364 | if (!Util.support.pjax) { 365 | pjax = function() { 366 | return true; 367 | }; 368 | pjax.request = function(options) { 369 | if (options && options.url) { 370 | location.href = options.url; 371 | } 372 | }; 373 | } 374 | pjax.util = Util; 375 | QW.provide('pjax', pjax); 376 | 377 | })(); 378 | --------------------------------------------------------------------------------