├── .gitignore ├── LICENSE ├── README.md └── jquery.pjax.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Chris Wanstrath 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | Software), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pjax = pushState + ajax 2 | 3 | pjax is a jQuery plugin that uses ajax and pushState to deliver a fast browsing experience with real permalinks, page titles, and a working back button. 4 | 5 | pjax是一个jQuery插件,它通过ajax和pushState技术提供了快速的浏览体验,并且保持有效的地址、网页标题、以及浏览器的前进后退按钮。 6 | 7 | pjax works by fetching HTML from your server via ajax and replacing the content 8 | of a container element on your page with the loaded HTML. It then updates the 9 | current URL in the browser using pushState. This results in faster page 10 | navigation for two reasons: 11 | 12 | pjax的工作是通过ajax从服务端获取HTML,在你的页面中使用加载到的HTML替换指定容器元素中的内容。然后使用pushState技术更新浏览器中的当前地址。使页面更快浏览的原因有以下两点: 13 | 14 | * No page resources (JS, CSS) get re-executed or re-applied; 15 | * 页面不存在资源的重复加载和应用 16 | * If the server is configured for pjax, it can render only partial page 17 | contents and thus avoid the potentially costly full layout render. 18 | 19 | 如果服务器端使用pjax配置,它可以只渲染局部页面内容,从而避免完整布局的渲染。 20 | 21 | ### Status of this project 22 | ### 项目的现状 23 | 24 | jquery-pjax is **largely unmaintained** at this point. It might continue to 25 | receive important bug fixes, but _its feature set is frozen_ and it's unlikely 26 | that it will get new features or enhancements. 27 | 28 | jquery-pjax的维护方向。它可能会继续重要bug的修复,但其功能是确定不变的,不会再实现新的功能和现有功能的扩展。 29 | 30 | ## Installation 31 | ## 安装 32 | 33 | pjax depends on jQuery 1.8 or higher. 34 | pjax 依赖于jQuery 1.8或者更高版本。 35 | 36 | ### 通过npm安装 37 | ### npm 38 | 39 | ```bash 40 | $ npm install jquery-pjax 41 | ``` 42 | 43 | ### standalone script 44 | ### 引入外部文件 45 | 46 | Download and include `jquery.pjax.js` in your web page: 47 | 48 | 下载 `jquery.pjax.js` 插件并且在你的的页面中引用: 49 | 50 | ``` bash 51 | curl -LO https://raw.github.com/defunkt/jquery-pjax/master/jquery.pjax.js 52 | ``` 53 | 54 | ## Usage 55 | ## 使用方法 56 | 57 | ### `$.fn.pjax` 58 | 59 | The simplest and most common use of pjax looks like this: 60 | 61 | pjax最简单常见的使用方法如下所示: 62 | 63 | ``` javascript 64 | $(document).pjax('a', '#pjax-container') 65 | ``` 66 | 67 | This will enable pjax on all links on the page and designate the container as `#pjax-container`. 68 | 69 | 这样可以让页面上所有的a链接都实现pjax加载,并且指定 `#pajx-container`为容器。 70 | 71 | If you are migrating an existing site, you probably don't want to enable pjax 72 | everywhere just yet. Instead of using a global selector like `a`, try annotating 73 | pjaxable links with `data-pjax`, then use `'a[data-pjax]'` as your selector. Or, 74 | try this selector that matches any `` links inside a `
` container: 76 | 77 | 如果你正在迁移现有的网站,你可能不希望在每个地方都使用pjax。那你可以用 `data-pjax` 标明pjax链接,然后使用 `a[data-pjax]` 作为你的选择器来代替使用 `a`的全局选择器。或者,你也可以尝试在 `
` 容器中匹配 `` 链接作为这个选择器。 79 | 80 | ``` javascript 81 | $(document).pjax('[data-pjax] a, a[data-pjax]', '#pjax-container') 82 | ``` 83 | 84 | #### Server-side configuration 85 | #### 服务器配置 86 | 87 | Ideally, your server should detect pjax requests by looking at the special 88 | `X-PJAX` HTTP header, and render only the HTML meant to replace the contents of 89 | the container element (`#pjax-container` in our example) without the rest of 90 | the page layout. Here is an example of how this might be done in Ruby on Rails: 91 | 92 | 理论上,你的服务器应该通过查看特定的 `X-PJAX` HTTP头来检查pjax请求,并且通过加载到的HTML替换容器元素(在我们的例子中是 `#pajx-container`)的内容来渲染,没有其余的页面布局。下面的例子是在Ruby on Rails中的做法: 93 | 94 | ``` ruby 95 | def index 96 | if request.headers['X-PJAX'] 97 | render :layout => false 98 | end 99 | end 100 | ``` 101 | 102 | If you'd like a more automatic solution than pjax for Rails check out [Turbolinks][]. 103 | 104 | 如果你想要了解比上述方法更自动化的方案,请查看[Turbolinks][]。 105 | 106 | [Check if there is a pjax plugin][plugins] for your favorite server framework. 107 | 108 | [看看是不是在这些pjax插件][plugins]中能找到你喜欢的服务端框架匹配的。 109 | 110 | Also check out [RailsCasts #294: Playing with PJAX][railscasts]. 111 | 112 | 也可以看看[RailsCasts #294: Playing with PJAX][railscasts]. 113 | 114 | #### Arguments 115 | #### 参数 116 | 117 | The synopsis for the `$.fn.pjax` function is: 118 | `$.fn.pjax` 方法的概述: 119 | 120 | ``` javascript 121 | $(document).pjax(selector, [container], options) 122 | ``` 123 | 124 | 1. `selector` is a string to be used for click [event delegation][$.fn.on]. 125 | 1. `selector` 是用于click事件的选择器。 126 | 2. `container` is a string selector that uniquely identifies the pjax container. 127 | 2. `container` 是具有唯一标识的pjax容器选择器。 128 | 3. `options` is an object with keys described below. 129 | 3. `options` 是有下列选项的对象。 130 | 131 | ##### pjax options 132 | ##### pjax配置选项 133 | 134 | 选项 | 默认 | 描述 135 | ----|---------|------------ 136 | `timeout` | 650 | ajax超时之后强制刷新整个页面 137 | `push` | true | 使用 [pushState][] 在浏览器中添加历史记录 138 | `replace` | false | 替换URL地址但不添加浏览器历史记录 139 | `maxCacheLength` | 20 | 以前容器内容的最大缓存值 140 | `version` | | 通过字符串或者方法返回当前pjax版本 141 | `scrollTo` | 0 | 浏览器的纵向滚动位置。通过`false`避免改变滚动位置 142 | `type` | `"GET"` | 看[$.ajax][] 143 | `dataType` | `"html"` | 看 [$.ajax][] 144 | `container` | | 要被替换内容元素的CSS选择器 145 | `url` | link.href | 通过一个字符串或者方法返回ajax请求响应的URL 146 | `target` | link | [pjax events](#events)最终的值`relatedTarget` 147 | `fragment` | | 提取ajax响应内容碎片的CSS选择器 148 | 149 | You can change the defaults globally by writing to the `$.pjax.defaults` object: 150 | 151 | 你可以使用 `$.pjax.defaults` 对象在全局改变默认配置: 152 | 153 | ``` javascript 154 | $.pjax.defaults.timeout = 1200 155 | ``` 156 | 157 | ### `$.pjax.click` 158 | 159 | This is a lower level function used by `$.fn.pjax` itself. It allows you to get a little more control over the pjax event handling. 160 | 161 | 将`$.fn.pjax` 作为一个基础的方法使用。它可以让你操作更多的事件行为。 162 | 163 | This example uses the current click context to set an ancestor element as the container: 164 | 165 | 这个列子使用当前的click上下文来设置一个祖先元素作为容器: 166 | 167 | ``` javascript 168 | if ($.support.pjax) { 169 | $(document).on('click', 'a[data-pjax]', function(event) { 170 | var container = $(this).closest('[data-pjax-container]') 171 | var containerSelector = '#' + container.id 172 | $.pjax.click(event, {container: containerSelector}) 173 | }) 174 | } 175 | ``` 176 | 177 | **NOTE** Use the explicit `$.support.pjax` guard. We aren't using `$.fn.pjax` so we should avoid binding this event handler unless the browser is actually going to use pjax. 178 | 179 | **注意** 明确使用 `$.support.pjax` 控制。我们不使用`$.fn.pjax`,我们应该避免绑定这个事件动作,除非浏览器正在使用pjax。 180 | 181 | ### `$.pjax.submit` 182 | 183 | Submits a form via pjax. 184 | 185 | 通过pjax提交表单。 186 | 187 | ``` javascript 188 | $(document).on('submit', 'form[data-pjax]', function(event) { 189 | $.pjax.submit(event, '#pjax-container') 190 | }) 191 | ``` 192 | 193 | ### `$.pjax.reload` 194 | 195 | Initiates a request for the current URL to the server using pjax mechanism and replaces the container with the response. Does not add a browser history entry. 196 | 197 | 使用pjax机制发起一个当前URL的请求到服务器,并且通过响应内容替换容器元素。不添加浏览器历史记录。 198 | 199 | ``` javascript 200 | $.pjax.reload('#pjax-container', options) 201 | ``` 202 | 203 | ### `$.pjax` 204 | 205 | Manual pjax invocation. Used mainly when you want to start a pjax request in a handler that didn't originate from a click. If you can get access to a click `event`, consider `$.pjax.click(event)` instead. 206 | 207 | pjax手动调用。主要用于当不是来自于click事件时,你想发起一个pjax请求处理的情况。如果你能获得一个click `event`。可以考虑通过`$.pjax.click(event)`代替。 208 | 209 | ``` javascript 210 | function applyFilters() { 211 | var url = urlForFilters() 212 | $.pjax({url: url, container: '#pjax-container'}) 213 | } 214 | ``` 215 | 216 | ## Events 217 | ## 事件 218 | 219 | All pjax events except `pjax:click` & `pjax:clicked` are fired from the pjax 220 | container element. 221 | 222 | 除了 `pjax:click` 和 `pjax:clicked`,其他所有pjax事件都是在pjax容器元素上触发的。 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 |
事件关闭参数说明
pjax链接的事件生命周期如下
pjax:click✔︎options从一个链接触发激活到关闭阻止
pjax:beforeSend✔︎xhr, options设置XHR 头
pjax:startxhr, options
pjax:sendxhr, options
pjax:clickedoptions一个pjax链接一个点击之后触发
pjax:beforeReplacecontents, options从服务器加载的内容替换HTML片段之前
pjax:successdata, status, xhr, options从服务器加载的内容替换HTML片段之后
pjax:timeout✔︎xhr, options options.timeout触发之后; 强制刷新,除非被关闭
pjax:error✔︎xhr, textStatus, error, optionsajax出错;强制刷新,除非被关闭
pjax:completexhr, textStatus, options无论结果如果,总在ajax响应后触发
pjax:endxhr, options
浏览器前进后退按钮相关事件
pjax:popstate事件 重定向 特性: "前进"/"后退"
pjax:startnull, options内容替换之前
pjax:beforeReplacecontents, options用缓存内容正确替换HTML片段时
pjax:endnull, options替换内容之后
pjax:callbacknull, options页面脚本加载完成后(admui项目)
334 | 335 | **NOTE** 336 | 使用Admui项目的用户,不要替换其他地方获取的pjax插件。对pjax插件Admui做了一些自己的处理。 337 | 338 | `pjax:send` & `pjax:complete` are a good pair of events to use if you are implementing a 339 | loading indicator. They'll only be triggered if an actual XHR request is made, 340 | not if the content is loaded from cache: 341 | 342 | 如果你正在执行加载操作,`pjax:send`和`pjax:complete`对你来说是很有用的一对事件。它们只有在是实际XHR请求时才会被触发,而不是从缓存中加载内容时: 343 | 344 | ``` javascript 345 | $(document).on('pjax:send', function() { 346 | $('#loading').show() 347 | }) 348 | $(document).on('pjax:complete', function() { 349 | $('#loading').hide() 350 | }) 351 | ``` 352 | 353 | An example of canceling a `pjax:timeout` event would be to disable the fallback 354 | timeout behavior if a spinner is being shown: 355 | 下面是关闭 `pjax:timeout` 事件的例子,将禁用超时后的默认行为: 356 | 357 | ``` javascript 358 | $(document).on('pjax:timeout', function(event) { 359 | // Prevent default timeout redirection behavior 360 | // 阻止默认的超时重定向行为 361 | event.preventDefault() 362 | }) 363 | ``` 364 | 365 | ## Advanced configuration 366 | ## 高级配置 367 | 368 | ### Reinitializing plugins/widget on new page content 369 | ### 在一个新页面中重新预置插件或工具 370 | 371 | The whole point of pjax is that it fetches and inserts new content _without_ 372 | refreshing the page. However, other jQuery plugins or libraries that are set to 373 | react on page loaded event (such as `DOMContentLoaded`) will not pick up on 374 | these changes. Therefore, it's usually a good idea to configure these plugins to 375 | reinitialize in the scope of the updated page content. This can be done like so: 376 | 377 | pjax的特点是它不会刷新页面即可获取并插入新内容。但是,其他jQuery插件或库为页面内容绑定了加载事件(如DOMContentLoaded)的,此时不会在响应。 因此,在更新的页面内容的范围内重新初始化插件是一个通用的方法。 我们可以这样做: 378 | 379 | ``` js 380 | $(document).on('ready pjax:end', function(event) { 381 | $(event.target).initializeMyPlugin() 382 | }) 383 | ``` 384 | 385 | This will make `$.fn.initializeMyPlugin()` be called at the document level on 386 | normal page load, and on the container level after any pjax navigation (either 387 | after clicking on a link or going Back in the browser). 388 | 389 | 390 | 这就可以让`$ .fn.initializeMyPlugin()`在正常页面加载和pjax加载时(点击链接或浏览器前进后退之后)都能被调用。 391 | 392 | ### Response types that force a reload 393 | 394 | ### 强制重载的响应类型 395 | 396 | By default, pjax will force a full reload of the page if it receives one of the 397 | following responses from the server: 398 | 399 | * Page content that includes `` when `fragment` selector wasn't explicitly 400 | configured. Pjax presumes that the server's response hasn't been properly 401 | configured for pjax. If `fragment` pjax option is given, pjax will extract the 402 | content based on that selector. 403 | 404 | * Page content that is blank. Pjax assumes that the server is unable to deliver 405 | proper pjax contents. 406 | 407 | * HTTP response code that is 4xx or 5xx, indicating some server error. 408 | 409 | 默认情况下,如果pjax从服务器收到以下响应之一,则强制重新加载页面: 410 | 411 | * 页面包含``标签,没有明确指定`fragment`选择器时。 Pjax就会推测服务器的响应没有被正确配置为pjax。 如果配置了 `fragment` pjax选项,pjax将根据该选择器提取内容。 412 | 413 | * 页面内容是空白的。 Pjax就会推测服务器无法提供正确的pjax内容。 414 | 415 | * HTTP响应代码为4xx或5xx,表示某些服务器错误。 416 | 417 | ### Affecting the browser URL 418 | 419 | ### 浏览器URL变动 420 | 421 | If the server needs to affect the URL which will appear in the browser URL after 422 | pjax navigation (like HTTP redirects work for normal requests), it can set the 423 | `X-PJAX-URL` header: 424 | 425 | 如果服务器需要影响pjax加载后的浏览器地址栏中显示的URL(例如HTTP重定向适用于正常请求),则可以设置X-PJAX-URL头: 426 | 427 | ``` ruby 428 | def index 429 | request.headers['X-PJAX-URL'] = "http://example.com/hello" 430 | end 431 | ``` 432 | 433 | ### Layout Reloading 434 | ### 重载布局 435 | 436 | Layouts can be forced to do a hard reload when assets or html changes. 437 | 438 | 静态资源或页面改变时,布局将被强制进行重载 439 | 440 | First set the initial layout version in your header with a custom meta tag. 441 | 442 | 首先,用一个自定义的meta标签在你的头部初始化layout版本。 443 | 444 | ``` html 445 | 446 | ``` 447 | 448 | Then from the server side, set the `X-PJAX-Version` header to the same. 449 | 450 | 然后在服务端设置相同的`X-PJAX-Version`头。 451 | 452 | ``` ruby 453 | if request.headers['X-PJAX'] 454 | response.headers['X-PJAX-Version'] = "v123" 455 | end 456 | ``` 457 | 458 | Deploying a deploy, bumping the version constant to force clients to do a full reload the next request getting the new layout and assets. 459 | 460 | 部署实现,版本不同之后会强制整个页面重载,新的请求会获取新的布局和相关资源。 461 | 462 | 463 | [$.fn.on]: http://api.jquery.com/on/ 464 | [$.ajax]: http://api.jquery.com/jQuery.ajax/ 465 | [pushState]: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history#Adding_and_modifying_history_entries 466 | [plugins]: https://gist.github.com/4283721 467 | [turbolinks]: https://github.com/rails/turbolinks 468 | [railscasts]: http://railscasts.com/episodes/294-playing-with-pjax 469 | -------------------------------------------------------------------------------- /jquery.pjax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2016, Admui 3 | * Released under the MIT License 4 | * https://github.com/admui/jquery-pjax 5 | */ 6 | 7 | (function ($) { 8 | 9 | // When called on a container with a selector, fetches the href with 10 | // ajax into the container or with the data-pjax attribute on the link 11 | // itself. 12 | // 13 | // Tries to make sure the back button and ctrl+click work the way 14 | // you'd expect. 15 | // 16 | // Exported as $.fn.pjax 17 | // 18 | // Accepts a jQuery ajax options object that may include these 19 | // pjax specific options: 20 | // 21 | // 22 | // container - Where to stick the response body. Usually a String selector. 23 | // $(container).html(xhr.responseBody) 24 | // (default: current jquery context) 25 | // push - Whether to pushState the URL. Defaults to true (of course). 26 | // replace - Want to use replaceState instead? That's cool. 27 | // 28 | // For convenience the second parameter can be either the container or 29 | // the options object. 30 | // 31 | // Returns the jQuery object 32 | var pjaxWaiting = {}; 33 | 34 | function fnPjax(selector, container, options) { 35 | var context = this; 36 | return this.on('click.pjax', selector, function (event) { 37 | var opts = $.extend({}, optionsFor(container, options)), 38 | dataPjax = $(this).attr('data-pjax'); 39 | if (!opts.container) 40 | if (dataPjax === '') 41 | opts.container = '#admui-pageContent'; 42 | else 43 | opts.container = $(this).attr('data-pjax') || context; 44 | handleClick(event, opts) 45 | }) 46 | } 47 | 48 | // Public: pjax on click handler 49 | // 50 | // Exported as $.pjax.click. 51 | // 52 | // event - "click" jQuery.Event 53 | // options - pjax options 54 | // 55 | // Examples 56 | // 57 | // $(document).on('click', 'a', $.pjax.click) 58 | // // is the same as 59 | // $(document).pjax('a') 60 | // 61 | // $(document).on('click', 'a', function(event) { 62 | // var container = $(this).closest('[data-pjax-container]') 63 | // $.pjax.click(event, container) 64 | // }) 65 | // 66 | // Returns nothing. 67 | function handleClick(event, container, options) { 68 | options = optionsFor(container, options); 69 | var link = event.currentTarget; 70 | 71 | if (link.tagName.toUpperCase() !== 'A') 72 | throw "$.fn.pjax or $.pjax.click requires an anchor element"; 73 | 74 | // Middle click, cmd click, and ctrl click should open 75 | // links in a new tab as normal. 76 | if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) 77 | return; 78 | 79 | // Ignore cross origin links 80 | if (location.protocol !== link.protocol || location.hostname !== link.hostname) 81 | return; 82 | 83 | // Ignore case when a hash is being tacked on the current URL 84 | if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location)) 85 | return; 86 | 87 | // Ignore event with default prevented 88 | if (event.isDefaultPrevented()) 89 | return; 90 | 91 | var defaults = { 92 | url: link.href, 93 | container: $(link).attr('data-pjax'), 94 | target: link 95 | }; 96 | 97 | var opts = $.extend({}, defaults, options); 98 | var clickEvent = $.Event('pjax:click'); 99 | $(link).trigger(clickEvent, [opts]); 100 | 101 | pjaxWaiting.link = opts; 102 | if (pjaxWaiting.script && pjaxWaiting.script.length !== 0) { 103 | event.preventDefault(); 104 | return; 105 | } 106 | 107 | if (!clickEvent.isDefaultPrevented()) { 108 | pjax(opts); 109 | event.preventDefault(); 110 | $(link).trigger('pjax:clicked', [opts]) 111 | } 112 | } 113 | 114 | // Public: pjax on form submit handler 115 | // 116 | // Exported as $.pjax.submit 117 | // 118 | // event - "click" jQuery.Event 119 | // options - pjax options 120 | // 121 | // Examples 122 | // 123 | // $(document).on('submit', 'form', function(event) { 124 | // var container = $(this).closest('[data-pjax-container]') 125 | // $.pjax.submit(event, container) 126 | // }) 127 | // 128 | // Returns nothing. 129 | function handleSubmit(event, container, options) { 130 | options = optionsFor(container, options); 131 | 132 | var form = event.currentTarget; 133 | var $form = $(form); 134 | 135 | if (form.tagName.toUpperCase() !== 'FORM') 136 | throw "$.pjax.submit requires a form element"; 137 | 138 | var defaults = { 139 | type: ($form.attr('method') || 'GET').toUpperCase(), 140 | url: $form.attr('action'), 141 | container: $form.attr('data-pjax'), 142 | target: form 143 | }; 144 | 145 | if (defaults.type !== 'GET' && window.FormData !== undefined) { 146 | defaults.data = new FormData(form); 147 | defaults.processData = false; 148 | defaults.contentType = false; 149 | } else { 150 | // Can't handle file uploads, exit 151 | if ($(form).find(':file').length) { 152 | return; 153 | } 154 | 155 | // Fallback to manually serializing the fields 156 | defaults.data = $(form).serializeArray(); 157 | } 158 | 159 | pjax($.extend({}, defaults, options)); 160 | 161 | event.preventDefault() 162 | } 163 | 164 | // Loads a URL with ajax, puts the response body inside a container, 165 | // then pushState()'s the loaded URL. 166 | // 167 | // Works just like $.ajax in that it accepts a jQuery ajax 168 | // settings object (with keys like url, type, data, etc). 169 | // 170 | // Accepts these extra keys: 171 | // 172 | // container - Where to stick the response body. 173 | // $(container).html(xhr.responseBody) 174 | // push - Whether to pushState the URL. Defaults to true (of course). 175 | // replace - Want to use replaceState instead? That's cool. 176 | // 177 | // Use it just like $.ajax: 178 | // 179 | // var xhr = $.pjax({ url: this.href, container: '#main' }) 180 | // console.log( xhr.readyState ) 181 | // 182 | // Returns whatever $.ajax returns. 183 | function pjax(options) { 184 | options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options); 185 | 186 | if ($.isFunction(options.url)) { 187 | options.url = options.url() 188 | } 189 | 190 | var target = options.target; 191 | 192 | var hash = parseURL(options.url).hash; 193 | 194 | var context = options.context = findContainerFor(options.container); 195 | 196 | // We want the browser to maintain two separate internal caches: one 197 | // for pjax'd partial page loads and one for normal page loads. 198 | // Without adding this secret parameter, some browsers will often 199 | // confuse the two. 200 | if (!options.data) options.data = {}; 201 | if ($.isArray(options.data)) { 202 | options.data.push({name: '_pjax', value: context.selector}) 203 | } else { 204 | options.data._pjax = context.selector 205 | } 206 | 207 | function fire(type, args, props) { 208 | if (!props) props = {}; 209 | props.relatedTarget = target; 210 | var event = $.Event(type, props); 211 | context.trigger(event, args); 212 | return !event.isDefaultPrevented() 213 | } 214 | 215 | var timeoutTimer; 216 | 217 | options.beforeSend = function (xhr, settings) { 218 | // No timeout for non-GET requests 219 | // Its not safe to request the resource again with a fallback method. 220 | if (settings.type !== 'GET') { 221 | settings.timeout = 0 222 | } 223 | 224 | xhr.setRequestHeader('X-PJAX', 'true'); 225 | xhr.setRequestHeader('X-PJAX-Container', context.selector); 226 | 227 | if (!fire('pjax:beforeSend', [xhr, settings])) 228 | return false; 229 | 230 | if (settings.timeout > 0) { 231 | timeoutTimer = setTimeout(function () { 232 | if (fire('pjax:timeout', [xhr, options])) 233 | xhr.abort('timeout') 234 | }, settings.timeout); 235 | 236 | // Clear timeout setting so jquerys internal timeout isn't invoked 237 | settings.timeout = 0 238 | } 239 | 240 | var url = parseURL(settings.url); 241 | if (hash) url.hash = hash; 242 | options.requestUrl = stripInternalParams(url) 243 | }; 244 | 245 | options.complete = function (xhr, textStatus) { 246 | if (timeoutTimer) 247 | clearTimeout(timeoutTimer); 248 | 249 | fire('pjax:complete', [xhr, textStatus, options]); 250 | 251 | fire('pjax:end', [xhr, options]) 252 | }; 253 | 254 | options.error = function (xhr, textStatus, errorThrown) { 255 | var container = extractContainer("", xhr, options); 256 | 257 | var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options]); 258 | if (options.type == 'GET' && textStatus !== 'abort' && allowed) { 259 | locationReplace(container.url) 260 | } 261 | }; 262 | 263 | options.success = function (data, status, xhr) { 264 | var previousState = pjax.state; 265 | 266 | // If $.pjax.defaults.version is a function, invoke it first. 267 | // Otherwise it can be a static string. 268 | var currentVersion = (typeof $.pjax.defaults.version === 'function') ? 269 | $.pjax.defaults.version() : 270 | $.pjax.defaults.version; 271 | 272 | var latestVersion = xhr.getResponseHeader('X-PJAX-Version'); 273 | var container = extractContainer(data, xhr, options); 274 | 275 | var url = parseURL(container.url); 276 | if (hash) { 277 | url.hash = hash; 278 | container.url = url.href 279 | } 280 | 281 | // If there is a layout version mismatch, hard load the new url 282 | if (currentVersion && latestVersion && currentVersion !== latestVersion) { 283 | locationReplace(container.url); 284 | return 285 | } 286 | 287 | // If the new response is missing a body, hard load the page 288 | if (!container.contents) { 289 | locationReplace(container.url); 290 | return 291 | } 292 | 293 | pjax.state = { 294 | id: options.id || uniqueId(), 295 | url: container.url, 296 | title: container.title, 297 | container: context.selector, 298 | fragment: options.fragment, 299 | timeout: options.timeout 300 | }; 301 | 302 | if (options.push || options.replace) { 303 | window.history.replaceState(pjax.state, container.title, container.url) 304 | } 305 | 306 | // Only blur the focus if the focused element is within the container. 307 | var blurFocus = $.contains(options.container, document.activeElement); 308 | 309 | // Clear out any focused controls before inserting new page contents. 310 | if (blurFocus) { 311 | try { 312 | document.activeElement.blur() 313 | } catch (e) { 314 | } 315 | } 316 | 317 | if (container.title) document.title = container.title; 318 | 319 | fire('pjax:beforeReplace', [container.contents, options], { 320 | state: pjax.state, 321 | previousState: previousState 322 | }); 323 | 324 | context.html(container.contents); 325 | 326 | // FF bug: Won't autofocus fields that are inserted via JS. 327 | // This behavior is incorrect. So if theres no current focus, autofocus 328 | // the last field. 329 | // 330 | // http://www.w3.org/html/wg/drafts/html/master/forms.html 331 | var autofocusEl = context.find('input[autofocus], textarea[autofocus]').last()[0]; 332 | if (autofocusEl && document.activeElement !== autofocusEl) { 333 | autofocusEl.focus(); 334 | } 335 | 336 | executeScriptTags(context, container.scripts); 337 | 338 | var scrollTo = options.scrollTo; 339 | 340 | // Ensure browser scrolls to the element referenced by the URL anchor 341 | if (hash) { 342 | var name = decodeURIComponent(hash.slice(1)); 343 | var target = document.getElementById(name) || document.getElementsByName(name)[0]; 344 | if (target) scrollTo = $(target).offset().top 345 | } 346 | 347 | if (typeof scrollTo == 'number') $(window).scrollTop(scrollTo); 348 | 349 | fire('pjax:success', [data, status, xhr, options]) 350 | }; 351 | 352 | 353 | // Initialize pjax.state for the initial page load. Assume we're 354 | // using the container and options of the link we're loading for the 355 | // back button to the initial page. This ensures good back button 356 | // behavior. 357 | if (!pjax.state) { 358 | pjax.state = { 359 | id: uniqueId(), 360 | url: window.location.href, 361 | title: document.title, 362 | container: context.selector, 363 | fragment: options.fragment, 364 | timeout: options.timeout 365 | }; 366 | window.history.replaceState(pjax.state, document.title) 367 | } 368 | 369 | // Cancel the current request if we're already pjaxing 370 | abortXHR(pjax.xhr); 371 | 372 | pjax.options = options; 373 | var xhr = pjax.xhr = $.ajax(options); 374 | 375 | if (xhr.readyState > 0) { 376 | if (options.push && !options.replace) { 377 | // Cache current container element before replacing it 378 | cachePush(pjax.state.id, cloneContents(context)); 379 | 380 | window.history.pushState(null, "", options.requestUrl) 381 | } 382 | 383 | fire('pjax:start', [xhr, options]); 384 | fire('pjax:send', [xhr, options]) 385 | } 386 | 387 | return pjax.xhr 388 | } 389 | 390 | // Public: Reload current page with pjax. 391 | // 392 | // Returns whatever $.pjax returns. 393 | function pjaxReload(container, options) { 394 | var defaults = { 395 | url: window.location.href, 396 | push: false, 397 | replace: true, 398 | scrollTo: false 399 | }; 400 | 401 | return pjax($.extend(defaults, optionsFor(container, options))) 402 | } 403 | 404 | // Internal: Hard replace current state with url. 405 | // 406 | // Work for around WebKit 407 | // https://bugs.webkit.org/show_bug.cgi?id=93506 408 | // 409 | // Returns nothing. 410 | function locationReplace(url) { 411 | window.history.replaceState(null, "", pjax.state.url); 412 | window.location.replace(url) 413 | } 414 | 415 | 416 | var initialPop = true; 417 | var initialURL = window.location.href; 418 | var initialState = window.history.state; 419 | 420 | // Initialize $.pjax.state if possible 421 | // Happens when reloading a page and coming forward from a different 422 | // session history. 423 | if (initialState && initialState.container) { 424 | pjax.state = initialState 425 | } 426 | 427 | // Non-webkit browsers don't fire an initial popstate event 428 | if ('state' in window.history) { 429 | initialPop = false 430 | } 431 | 432 | // popstate handler takes care of the back and forward buttons 433 | // 434 | // You probably shouldn't use pjax on pages with other pushState 435 | // stuff yet. 436 | function onPjaxPopstate(event) { 437 | 438 | // Hitting back or forward should override any pending PJAX request. 439 | if (!initialPop) { 440 | abortXHR(pjax.xhr) 441 | } 442 | 443 | var previousState = pjax.state; 444 | var state = event.state; 445 | var direction; 446 | 447 | if (state && state.container) { 448 | // When coming forward from a separate history session, will get an 449 | // initial pop with a state we are already at. Skip reloading the current 450 | // page. 451 | if (initialPop && initialURL == state.url) return; 452 | 453 | if (previousState) { 454 | // If popping back to the same state, just skip. 455 | // Could be clicking back from hashchange rather than a pushState. 456 | if (previousState.id === state.id) return; 457 | 458 | // Since state IDs always increase, we can deduce the navigation direction 459 | direction = previousState.id < state.id ? 'forward' : 'back' 460 | } 461 | 462 | var cache = cacheMapping[state.id] || []; 463 | var container = $(cache[0] || state.container), contents = cache[1]; 464 | 465 | if (container.length) { 466 | if (previousState) { 467 | // Cache current container before replacement and inform the 468 | // cache which direction the history shifted. 469 | cachePop(direction, previousState.id, cloneContents(container)) 470 | } 471 | 472 | var popstateEvent = $.Event('pjax:popstate', { 473 | state: state, 474 | direction: direction 475 | }); 476 | container.trigger(popstateEvent); 477 | 478 | var options = { 479 | id: state.id, 480 | url: state.url, 481 | container: container, 482 | push: false, 483 | fragment: state.fragment, 484 | timeout: state.timeout, 485 | scrollTo: false 486 | }; 487 | 488 | if (contents) { 489 | container.trigger('pjax:start', [null, options]); 490 | 491 | pjax.state = state; 492 | if (state.title) document.title = state.title; 493 | var beforeReplaceEvent = $.Event('pjax:beforeReplace', { 494 | state: state, 495 | previousState: previousState 496 | }); 497 | container.trigger(beforeReplaceEvent, [contents, options]); 498 | container.html(contents); 499 | 500 | container.trigger('pjax:end', [null, options]) 501 | } else { 502 | pjax(options) 503 | } 504 | 505 | // Force reflow/relayout before the browser tries to restore the 506 | // scroll position. 507 | container[0].offsetHeight 508 | } else { 509 | locationReplace(location.href) 510 | } 511 | } 512 | initialPop = false 513 | } 514 | 515 | // Fallback version of main pjax function for browsers that don't 516 | // support pushState. 517 | // 518 | // Returns nothing since it retriggers a hard form submission. 519 | function fallbackPjax(options) { 520 | var url = $.isFunction(options.url) ? options.url() : options.url, 521 | method = options.type ? options.type.toUpperCase() : 'GET'; 522 | 523 | var form = $('
', { 524 | method: method === 'GET' ? 'GET' : 'POST', 525 | action: url, 526 | style: 'display:none' 527 | }); 528 | 529 | if (method !== 'GET' && method !== 'POST') { 530 | form.append($('', { 531 | type: 'hidden', 532 | name: '_method', 533 | value: method.toLowerCase() 534 | })) 535 | } 536 | 537 | var data = options.data; 538 | if (typeof data === 'string') { 539 | $.each(data.split('&'), function (index, value) { 540 | var pair = value.split('='); 541 | form.append($('', {type: 'hidden', name: pair[0], value: pair[1]})) 542 | }) 543 | } else if ($.isArray(data)) { 544 | $.each(data, function (index, value) { 545 | form.append($('', {type: 'hidden', name: value.name, value: value.value})) 546 | }) 547 | } else if (typeof data === 'object') { 548 | var key; 549 | for (key in data) 550 | form.append($('', {type: 'hidden', name: key, value: data[key]})) 551 | } 552 | 553 | $(document.body).append(form); 554 | form.submit() 555 | } 556 | 557 | // Internal: Abort an XmlHttpRequest if it hasn't been completed, 558 | // also removing its event handlers. 559 | function abortXHR(xhr) { 560 | if (xhr && xhr.readyState < 4) { 561 | xhr.onreadystatechange = $.noop; 562 | xhr.abort() 563 | } 564 | } 565 | 566 | // Internal: Generate unique id for state object. 567 | // 568 | // Use a timestamp instead of a counter since ids should still be 569 | // unique across page loads. 570 | // 571 | // Returns Number. 572 | function uniqueId() { 573 | return (new Date).getTime() 574 | } 575 | 576 | function cloneContents(container) { 577 | var cloned = container.clone(); 578 | // Unmark script tags as already being eval'd so they can get executed again 579 | // when restored from cache. HAXX: Uses jQuery internal method. 580 | cloned.find('script').each(function () { 581 | if (!this.src) jQuery._data(this, 'globalEval', false) 582 | }); 583 | return [container.selector, cloned.contents()] 584 | } 585 | 586 | // Internal: Strip internal query params from parsed URL. 587 | // 588 | // Returns sanitized url.href String. 589 | function stripInternalParams(url) { 590 | url.search = url.search.replace(/([?&])(_pjax|_)=[^&]*/g, ''); 591 | return url.href.replace(/\?($|#)/, '$1') 592 | } 593 | 594 | // Internal: Parse URL components and returns a Locationish object. 595 | // 596 | // url - String URL 597 | // 598 | // Returns HTMLAnchorElement that acts like Location. 599 | function parseURL(url) { 600 | var a = document.createElement('a'); 601 | a.href = url; 602 | return a 603 | } 604 | 605 | // Internal: Return the `href` component of given URL object with the hash 606 | // portion removed. 607 | // 608 | // location - Location or HTMLAnchorElement 609 | // 610 | // Returns String 611 | function stripHash(location) { 612 | return location.href.replace(/#.*/, '') 613 | } 614 | 615 | // Internal: Build options Object for arguments. 616 | // 617 | // For convenience the first parameter can be either the container or 618 | // the options object. 619 | // 620 | // Examples 621 | // 622 | // optionsFor('#container') 623 | // // => {container: '#container'} 624 | // 625 | // optionsFor('#container', {push: true}) 626 | // // => {container: '#container', push: true} 627 | // 628 | // optionsFor({container: '#container', push: true}) 629 | // // => {container: '#container', push: true} 630 | // 631 | // Returns options Object. 632 | function optionsFor(container, options) { 633 | // Both container and options 634 | if (container && options) 635 | options.container = container; 636 | 637 | // First argument is options Object 638 | else if ($.isPlainObject(container)) 639 | options = container; 640 | 641 | // Only container 642 | else 643 | options = {container: container}; 644 | 645 | // Find and validate container 646 | if (options.container) 647 | options.container = findContainerFor(options.container); 648 | 649 | return options 650 | } 651 | 652 | // Internal: Find container element for a variety of inputs. 653 | // 654 | // Because we can't persist elements using the history API, we must be 655 | // able to find a String selector that will consistently find the Element. 656 | // 657 | // container - A selector String, jQuery object, or DOM Element. 658 | // 659 | // Returns a jQuery object whose context is `document` and has a selector. 660 | function findContainerFor(container) { 661 | container = $(container); 662 | 663 | if (!container.length) { 664 | throw "no pjax container for " + container.selector 665 | } else if (container.selector !== '' && container.context === document) { 666 | return container 667 | } else if (container.attr('id')) { 668 | return $('#' + container.attr('id')) 669 | } else { 670 | throw "cant get selector for pjax container!" 671 | } 672 | } 673 | 674 | // Internal: Filter and find all elements matching the selector. 675 | // 676 | // Where $.fn.find only matches descendants, findAll will test all the 677 | // top level elements in the jQuery object as well. 678 | // 679 | // elems - jQuery object of Elements 680 | // selector - String selector to match 681 | // 682 | // Returns a jQuery object. 683 | function findAll(elems, selector) { 684 | return elems.filter(selector).add(elems.find(selector)); 685 | } 686 | 687 | function parseHTML(html) { 688 | return $.parseHTML(html, document, true) 689 | } 690 | 691 | // Internal: Extracts container and metadata from response. 692 | // 693 | // 1. Extracts X-PJAX-URL header if set 694 | // 2. Extracts inline tags 695 | // 3. Builds response Element and extracts fragment if set 696 | // 697 | // data - String response data 698 | // xhr - XHR response 699 | // options - pjax options Object 700 | // 701 | // Returns an Object with url, title, and contents keys. 702 | function extractContainer(data, xhr, options) { 703 | var obj = {}, fullDocument = /<html/i.test(data); 704 | 705 | // Prefer X-PJAX-URL header if it was set, otherwise fallback to 706 | // using the original requested url. 707 | var serverUrl = xhr.getResponseHeader('X-PJAX-URL'); 708 | obj.url = serverUrl ? stripInternalParams(parseURL(serverUrl)) : options.requestUrl; 709 | 710 | // Attempt to parse response html into elements 711 | if (fullDocument) { 712 | var $head = $(parseHTML(data.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0])); 713 | var $body = $(parseHTML(data.match(/<body[^>]*>([\s\S.]*)<\/body>/i)[0])) 714 | } else { 715 | var $head = $body = $(parseHTML(data)) 716 | } 717 | 718 | // If response data is empty, return fast 719 | if ($body.length === 0) 720 | return obj; 721 | 722 | // If there's a <title> tag in the header, use it as 723 | // the page's title. 724 | obj.title = findAll($head, 'title').first().text(); 725 | 726 | if (options.fragment) { 727 | // If they specified a fragment, look for it in the response 728 | // and pull it out. 729 | if (options.fragment === 'body') { 730 | var $fragment = $body 731 | } else { 732 | var $fragment = findAll($body, options.fragment).first() 733 | } 734 | 735 | if ($fragment.length) { 736 | obj.contents = options.fragment === 'body' ? $fragment : $fragment.contents(); 737 | 738 | // If there's no title, look for data-title and title attributes 739 | // on the fragment 740 | if (!obj.title) 741 | obj.title = $fragment.attr('title') || $fragment.data('title') 742 | } 743 | 744 | } else if (!fullDocument) { 745 | obj.contents = $body 746 | } 747 | 748 | // Clean up any <title> tags 749 | if (obj.contents) { 750 | // Remove any parent title elements 751 | obj.contents = obj.contents.not(function () { 752 | return $(this).is('title') 753 | }); 754 | 755 | obj.contents = obj.contents.not(function () { 756 | return $(this).is('meta') 757 | }); 758 | 759 | // Then scrub any titles from their descendants 760 | obj.contents.find('title').remove(); 761 | obj.contents.find('meta').remove(); 762 | 763 | // Gather all script[src] elements 764 | obj.scripts = findAll(obj.contents, 'script[src]').remove(); 765 | obj.contents = obj.contents.not(obj.scripts) 766 | } 767 | 768 | // Trim any whitespace off the title 769 | if (obj.title) obj.title = $.trim(obj.title); 770 | 771 | return obj 772 | } 773 | 774 | // Load an execute scripts using standard script request. 775 | // 776 | // Avoids jQuery's traditional $.getScript which does a XHR request and 777 | // globalEval. 778 | // 779 | // scripts - jQuery object of script Elements 780 | // 781 | // Returns nothing. 782 | function executeScriptTags(e, scripts) { 783 | pjaxWaiting.link = ''; 784 | if (!scripts) return; 785 | 786 | var existingScripts = $('script[src]'); 787 | 788 | pjaxWaiting.script = Array.isArray(scripts) ? scripts : scripts.toArray(); 789 | scriptLoad(existingScripts, scripts); 790 | 791 | var timer = setInterval(function () { 792 | if (pjaxWaiting.script.length === 0) { 793 | clearInterval(timer); 794 | $.Event('pjax:callback'); 795 | $(e).trigger('pjax:callback'); 796 | if (pjaxWaiting.link !== '') { 797 | pjax(pjaxWaiting.link); 798 | } 799 | } 800 | }, 5); 801 | 802 | /*setTimeout(function(){ 803 | if(pjaxWaiting.link !== ''){ 804 | console.log(1) 805 | //pjax(pjaxWaiting.link); 806 | } 807 | },500);*/ 808 | /*if(pjaxWaiting.link !== ''){ 809 | pjaxWaiting.link = ''; 810 | pjax(opts); 811 | }*/ 812 | } 813 | 814 | function scriptLoad(e, scripts) { 815 | var fn = arguments.callee; 816 | if (scripts.length === 0) return; 817 | 818 | $.each(scripts, function (i, n) { 819 | var $item = $(n), 820 | src = $item.attr('src'), 821 | deps = $item.data('deps'); 822 | 823 | var matchedScripts = e.filter(function () { 824 | return this.src === src 825 | }); 826 | if (matchedScripts.length) return; 827 | 828 | if (deps) { 829 | var waitLo = waitLoad(deps, pjaxWaiting.script); 830 | var len = scripts.length; 831 | if (waitLo !== 0) { 832 | setTimeout(function () { 833 | for (var v = 0; v < len; v++) { 834 | if (scripts[0] === '') scripts.splice(0, 1); 835 | } 836 | fn(e, scripts); 837 | }, 5); 838 | return false; 839 | } else { 840 | scripts.splice(i, 1, ''); 841 | } 842 | } else { 843 | scripts.splice(i, 1, ''); 844 | } 845 | 846 | var script = document.createElement('script'); 847 | script.src = src; 848 | 849 | if (script.readyState) { //IE 850 | script.onreadystatechange = function () { 851 | if (script.readyState == "loaded" || script.readyState == "complete") { 852 | script.onreadystatechange = null; 853 | for (var f = 0; f < pjaxWaiting.script.length; f++) { 854 | if (pjaxWaiting.script[f].src === this.src) pjaxWaiting.script.splice(f, 1); 855 | } 856 | } 857 | }; 858 | } else { //Others 859 | script.onload = function () { 860 | for (var f = 0; f < pjaxWaiting.script.length; f++) { 861 | if (pjaxWaiting.script[f].src === this.src) pjaxWaiting.script.splice(f, 1); 862 | } 863 | }; 864 | } 865 | 866 | document.head.appendChild(script); 867 | }); 868 | } 869 | 870 | function waitLoad(deps, wait) { 871 | deps = deps.split(','); 872 | 873 | var unload = []; 874 | 875 | for (var i = 0; i < deps.length; i++) { 876 | for (var n = 0; n < wait.length; n++) { 877 | if (deps[i] === $(wait[n]).data('name')) unload.push('wait'); 878 | } 879 | } 880 | return unload.length; 881 | } 882 | 883 | // Internal: History DOM caching class. 884 | var cacheMapping = {}; 885 | var cacheForwardStack = []; 886 | var cacheBackStack = []; 887 | 888 | // Push previous state id and container contents into the history 889 | // cache. Should be called in conjunction with `pushState` to save the 890 | // previous container contents. 891 | // 892 | // id - State ID Number 893 | // value - DOM Element to cache 894 | // 895 | // Returns nothing. 896 | function cachePush(id, value) { 897 | cacheMapping[id] = value; 898 | cacheBackStack.push(id); 899 | 900 | // Remove all entries in forward history stack after pushing a new page. 901 | trimCacheStack(cacheForwardStack, 0); 902 | 903 | // Trim back history stack to max cache length. 904 | trimCacheStack(cacheBackStack, pjax.defaults.maxCacheLength) 905 | } 906 | 907 | // Shifts cache from directional history cache. Should be 908 | // called on `popstate` with the previous state id and container 909 | // contents. 910 | // 911 | // direction - "forward" or "back" String 912 | // id - State ID Number 913 | // value - DOM Element to cache 914 | // 915 | // Returns nothing. 916 | function cachePop(direction, id, value) { 917 | var pushStack, popStack; 918 | cacheMapping[id] = value; 919 | 920 | if (direction === 'forward') { 921 | pushStack = cacheBackStack; 922 | popStack = cacheForwardStack 923 | } else { 924 | pushStack = cacheForwardStack; 925 | popStack = cacheBackStack 926 | } 927 | 928 | pushStack.push(id); 929 | if (id = popStack.pop()) 930 | delete cacheMapping[id]; 931 | 932 | // Trim whichever stack we just pushed to to max cache length. 933 | trimCacheStack(pushStack, pjax.defaults.maxCacheLength) 934 | } 935 | 936 | // Trim a cache stack (either cacheBackStack or cacheForwardStack) to be no 937 | // longer than the specified length, deleting cached DOM elements as necessary. 938 | // 939 | // stack - Array of state IDs 940 | // length - Maximum length to trim to 941 | // 942 | // Returns nothing. 943 | function trimCacheStack(stack, length) { 944 | while (stack.length > length) 945 | delete cacheMapping[stack.shift()] 946 | } 947 | 948 | // Public: Find version identifier for the initial page load. 949 | // 950 | // Returns String version or undefined. 951 | function findVersion() { 952 | return $('meta').filter(function () { 953 | var name = $(this).attr('http-equiv'); 954 | return name && name.toUpperCase() === 'X-PJAX-VERSION' 955 | }).attr('content') 956 | } 957 | 958 | // Install pjax functions on $.pjax to enable pushState behavior. 959 | // 960 | // Does nothing if already enabled. 961 | // 962 | // Examples 963 | // 964 | // $.pjax.enable() 965 | // 966 | // Returns nothing. 967 | function enable() { 968 | $.fn.pjax = fnPjax; 969 | $.pjax = pjax; 970 | $.pjax.enable = $.noop; 971 | $.pjax.disable = disable; 972 | $.pjax.click = handleClick; 973 | $.pjax.submit = handleSubmit; 974 | $.pjax.reload = pjaxReload; 975 | $.pjax.defaults = { 976 | timeout: 650, 977 | push: true, 978 | replace: false, 979 | type: 'GET', 980 | dataType: 'html', 981 | scrollTo: 0, 982 | maxCacheLength: 20, 983 | version: findVersion 984 | }; 985 | $(window).on('popstate.pjax', onPjaxPopstate) 986 | } 987 | 988 | // Disable pushState behavior. 989 | // 990 | // This is the case when a browser doesn't support pushState. It is 991 | // sometimes useful to disable pushState for debugging on a modern 992 | // browser. 993 | // 994 | // Examples 995 | // 996 | // $.pjax.disable() 997 | // 998 | // Returns nothing. 999 | function disable() { 1000 | $.fn.pjax = function () { 1001 | return this 1002 | }; 1003 | $.pjax = fallbackPjax; 1004 | $.pjax.enable = enable; 1005 | $.pjax.disable = $.noop; 1006 | $.pjax.click = $.noop; 1007 | $.pjax.submit = $.noop; 1008 | $.pjax.reload = function () { 1009 | window.location.reload() 1010 | }; 1011 | 1012 | $(window).off('popstate.pjax', onPjaxPopstate) 1013 | } 1014 | 1015 | 1016 | // Add the state property to jQuery's event object so we can use it in 1017 | // $(window).bind('popstate') 1018 | if ($.inArray('state', $.event.props) < 0) 1019 | $.event.props.push('state'); 1020 | 1021 | // Is pjax supported by this browser? 1022 | $.support.pjax = 1023 | window.history && window.history.pushState && window.history.replaceState && 1024 | // pushState isn't reliable on iOS until 5. 1025 | !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/); 1026 | 1027 | $.support.pjax ? enable() : disable() 1028 | 1029 | })(jQuery); --------------------------------------------------------------------------------