├── LICENSE ├── README.md └── jquery.pjax.js /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 | 22 | Copyright (c) Andrew Magalich 23 | 24 | Well, i agree with him ↑. 25 | 26 | Copyright (c) Shamray Alexander aka Samurai -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pjax with normal fallback! 2 | ## pushState, hash-navigation, ajax forms, interchangeable url formats and other remarkable features 3 | 4 | .--. 5 | / \ 6 | ## a a 7 | ( '._) 8 | |'-- | 9 | _.\___/_ ___pjax___ 10 | ."\> \Y/|<'. '._.-' 11 | / \ \_\/ / '-' / 12 | | --'\_/|/ | _/ 13 | |___.-' | |`'` 14 | | | | 15 | | / './ 16 | /__./` | | 17 | \ | | 18 | \ | | 19 | ; | | 20 | / | | 21 | jgs |___\_.\_ 22 | `-"--'---' 23 | 24 | ## What is it now? 25 | It'a a fork of defunkt's pjax library: https://github.com/defunkt/jquery-pjax . 26 | That was completely awesome, but my project needed old IEs support. So I modified it a bit. 27 | I hope, you'll enjoy mix of pushState and regular #!/hash navigation. 28 | 29 | Some features and changes: 30 | 31 | * IE7+, FF4+ and all modern browsers (if it works on some not mentioned ancient browser – please let me know `ckaldeg@gmail.com`) 32 | * mix of html5-like navigation and old-school #!/hashes 33 | * i added most of ';' in lines of code for you :]. for some reason, defunkt didn't use them, but it was strange for me 34 | * links from both kinds of browsers are interchangable 35 | 36 | Some bad news: 37 | 38 | * you HAVE to put some settings before using: 39 | 40 | ```js 41 | $.hash = '#!/'; 42 | $.siteurl = 'http://yoursite.com'; 43 | $.container = '#pjaxcontainer'; 44 | ``` 45 | 46 | `$.hash` is a string, which appeares is url, when browser doesn't support pushState. So, by default, url of page changes from http://yoursite.com/this/is/awesome/article to http://yoursite.com/#!/this/is/awesome/article and pjax sends request to server with first url. 47 | 48 | Links are interchangable – so if someone with modern browser gets old-style link http://yoursite.com/#!/page – he would be redirected to http://yoursite.com/page and vice versa. 49 | 50 | ## And what about SEO? 51 | 52 | All we know, most part of AJAX-enabled sites have issues with search engine crawlers – their links are basically not parsable because of `#`. But we can handle it at least with Google! 53 | 54 | Default `$.hash` value is meaningful and hopely would be parsed in future by all major search engine crawlers. All you need – set up some custom routing on your server: if crawler meets link like http://yoursite.com/#!/some/path/on/site, he sends request to http://yoursite.com/?_escaped_fragment_=/some/path/on/site and parses it. 55 | 56 | For more information: 57 | 58 | * http://googlewebmastercentral.blogspot.com/2009/10/proposal-for-making-ajax-crawlable.html 59 | * http://code.google.com/intl/ru-RU/web/ajaxcrawling/docs/specification.html 60 | 61 | 62 | ## What was it? 63 | 64 | Pjax loads HTML from your server into the current page 65 | without a full reload. It's ajax with real permalinks, 66 | page titles, and a working back button that fully degrades. 67 | 68 | Pjax enhances the browsing experience - nothing more. 69 | 70 | You can find a demo on 71 | 72 | 73 | ## three ways to pjax on the client side: 74 | 75 | One. Functionally obtrusive, loading the href with ajax into data-pjax: 76 | 77 | ```html 78 | Explore 79 | ``` 80 | 81 | ```js 82 | $('a[data-pjax]').pjax() 83 | ``` 84 | 85 | 86 | Two. Slightly obtrusive, passing a container and jQuery ajax options: 87 | 88 | ```html 89 | Explore 90 | ``` 91 | 92 | ```js 93 | $('.js-pjax').pjax('#main', { timeout: null, error: function(xhr, err){ 94 | $('.error').text('Something went wrong: ' + err) 95 | }}) 96 | ``` 97 | 98 | 99 | Three. Unobtrusive, showing a 'loading' spinner: 100 | 101 | ```html 102 |
103 | 104 |
105 | Explore 106 | Help 107 |
108 |
109 | ``` 110 | 111 | ```js 112 | $('a').pjax('#main').live('click', function(){ 113 | $(this).showLoader() 114 | }) 115 | ``` 116 | 117 | 118 | ## $(link).pjax( container, options ) 119 | 120 | The `$(link).pjax()` function accepts a container, an options object, 121 | or both. The container MUST be a string selector - this is because we 122 | cannot persist jQuery objects using the History API between page loads. 123 | 124 | The options are the same as jQuery's `$.ajax` options with the 125 | following additions: 126 | 127 | * `container` - The String selector of the container to load the 128 | reponse body. Must be a String. 129 | * `clickedElement` - The element that was clicked to start the pjax call. 130 | * `push` - Whether to pushState the URL. Default: true (of course) 131 | * `replace` - Whether to replaceState the URL. Default: false 132 | * `error` - By default this callback reloads the target page once 133 | `timeout` ms elapses. 134 | * `timeout` - pjax sets this low, <1s. Set this higher if using a 135 | custom error handler. It's ms, so something like 136 | `timeout: 2000` 137 | * `fragment` - A String selector that specifies a sub-element to 138 | be pulled out of the response HTML and inserted 139 | into the `container`. Useful if the server always returns 140 | full HTML pages. 141 | 142 | ## $(form).pjaxform( container, options ) 143 | 144 | Same as `$(link).pjax()` but for forms. For GET forms will change address string 145 | 146 | ## $.pjax( options ) 147 | 148 | You can also just call `$.pjax` directly. It acts much like `$.ajax`, even 149 | returning the same thing and accepting the same options. 150 | 151 | The pjax-specific keys listed in the `$(link).pjax()` section work here 152 | as well. 153 | 154 | This pjax call: 155 | 156 | ```js 157 | $.pjax({ 158 | url: '/authors', 159 | container: '#main' 160 | }) 161 | ``` 162 | 163 | Roughly translates into this ajax call: 164 | 165 | ```js 166 | $.ajax({ 167 | url: '/authors', 168 | dataType: 'html', 169 | beforeSend: function(xhr){ 170 | xhr.setRequestHeader('X-PJAX', 'true') 171 | }, 172 | success: function(data){ 173 | $('#main').html(data) 174 | history.pushState(null, $(data).filter('title').text(), '/authors') 175 | }) 176 | }) 177 | ``` 178 | 179 | 180 | ## pjax on the server side 181 | 182 | You'll want to give pjax requests a 'chrome-less' version of your page. 183 | That is, the page without any layout. 184 | 185 | As you can see in the "ajax call" example above, pjax sets a custom 'X-PJAX' 186 | header to 'true' when it makes an ajax request to make detecting it easy. 187 | 188 | This is for PHP: 189 | 190 | ```php 191 | if (!isset($_SERVER['HTTP_X_PJAX']) 192 | { 193 | // here is regular-kind load 194 | } 195 | else 196 | { 197 | // here you don't print page layout — just the page 198 | } 199 | ``` 200 | 201 | In Rails, check for `request.headers['X-PJAX']`: 202 | 203 | ```ruby 204 | def my_page 205 | if request.headers['X-PJAX'] 206 | render :layout => false 207 | end 208 | end 209 | ``` 210 | 211 | One more Rails example by slayerhabr (http://slayerhabr.habrahabr.ru/) 212 | 213 | ```ruby 214 | class ApplicationController < ActionController::Base 215 | layout Proc.new { |controller| request.headers['X-PJAX'] ? false : 'application' } 216 | end 217 | ``` 218 | 219 | Django: 220 | 221 | Asp.Net MVC3: 222 | 223 | 224 | ## page titles 225 | 226 | Your HTML should also include a `` tag if you want page titles to work. 227 | 228 | 229 | ## events 230 | 231 | pjax will fire four events on the container you've asked it to load your 232 | reponse body into: 233 | 234 | * `start.pjax` - Fired when a pjax ajax request begins. 235 | * `success.pjax` - Fired on pjax ajax request success. 236 | * `complete.pjax` - Fired on pjax ajax request complete, one parameter is jqXHR. 237 | * `error.pjax` - Fired on pjax ajax request fail. 238 | 239 | This allows you to, say, display a loading indicator upon pjaxing: 240 | 241 | ```js 242 | $('a.pjax').pjax('#main') 243 | $('#main') 244 | .bind('start.pjax', function() { $('#loading').show() }) 245 | .bind('success.pjax', function() { $('#loading').hide() }) 246 | .live('complete.pjax', function(event, jqXHR) { }) 247 | .bind('error.pjax', function() { }) 248 | ``` 249 | 250 | Because these events bubble, you can also set them on the body: 251 | 252 | ```js 253 | $('a.pjax').pjax() 254 | $('body') 255 | .bind('start.pjax', function() { $('#loading').show() }) 256 | .bind('end.pjax', function() { $('#loading').hide() }) 257 | ``` 258 | 259 | ## browser support 260 | 261 | Pjax works with browses that support the history.pushState API and old-ones, that don't. For the lasts we use hashes. 262 | 263 | For a history API's table of supported browsers see: <http://caniuse.com/#search=pushstate> 264 | 265 | To check if pjax is supported, use the `$.support.pjax` boolean. 266 | 267 | When history API is not supported, `$('a').pjax()` calls will do use $.ajax to load page and `window.location.hash` to identify itself. On page load without history API script loads page due to hash. 268 | 269 | 270 | ## install it 271 | 272 | Download 273 | 274 | Then, in your HTML: 275 | 276 | ```html 277 | <script src="path/to/js/jquery.pjax.js"></script> 278 | ``` 279 | 280 | Replace `path/to/js` with the path to your JavaScript directory, 281 | e.g. `public/javascripts`. -------------------------------------------------------------------------------- /jquery.pjax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery hashchange event - v1.3 - 7/21/2010 3 | * http://benalman.com/projects/jquery-hashchange-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($,e,b){ 10 | var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7); 11 | function a(j){ 12 | j=j||location.href; 13 | return"#"+j.replace(/^[^#]*#?(.*)$/,"$1") 14 | } 15 | $.fn[c]=function(j){ 16 | return j?this.bind(c,j):this.trigger(c) 17 | }; 18 | 19 | $.fn[c].delay=50; 20 | g[c]=$.extend(g[c],{ 21 | setup:function(){ 22 | if(d){ 23 | return false 24 | } 25 | $(f.start) 26 | }, 27 | teardown:function(){ 28 | if(d){ 29 | return false 30 | } 31 | $(f.stop) 32 | } 33 | }); 34 | f=(function(){ 35 | var j={},p,m=a(),k=function(q){ 36 | return q 37 | },l=k,o=k; 38 | j.start=function(){ 39 | p||n() 40 | }; 41 | 42 | j.stop=function(){ 43 | p&&clearTimeout(p); 44 | p=b 45 | }; 46 | 47 | function n(){ 48 | var r=a(),q=o(m); 49 | if(r!==m){ 50 | l(m=r,q); 51 | $(e).trigger(c) 52 | }else{ 53 | if(q!==m){ 54 | location.href=location.href.replace(/#.*/,"")+q 55 | } 56 | } 57 | p=setTimeout(n,$.fn[c].delay) 58 | } 59 | $.browser.msie&&!d&&(function(){ 60 | var q,r; 61 | j.start=function(){ 62 | if(!q){ 63 | r=$.fn[c].src; 64 | r=r&&r+a(); 65 | q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){ 66 | r||l(a()); 67 | n() 68 | }).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow; 69 | h.onpropertychange=function(){ 70 | try{ 71 | if(event.propertyName==="title"){ 72 | q.document.title=h.title 73 | } 74 | }catch(s){} 75 | } 76 | } 77 | }; 78 | 79 | j.stop=k; 80 | o=function(){ 81 | return a(q.location.href) 82 | }; 83 | 84 | l=function(v,s){ 85 | var u=q.document,t=$.fn[c].domain; 86 | if(v!==s){ 87 | u.title=h.title; 88 | u.open(); 89 | t&&u.write('<script>document.domain="'+t+'"<\/script>'); 90 | u.close(); 91 | q.location.hash=v 92 | } 93 | } 94 | })(); 95 | return j 96 | })() 97 | })(jQuery,this); 98 | 99 | 100 | /* jquery.pjax.js with hash navigation fallback + form pjax + get forms parameters in address 101 | * https://github.com/imsamurai/jquery-pjax 102 | */ 103 | 104 | /* jquery.pjax.js with hash-navigation fallback 105 | * copyright andrew magalich 106 | * https://github.com/ckald/jquery-pjax 107 | */ 108 | 109 | // jquery.pjax.js 110 | // copyright chris wanstrath 111 | // https://github.com/defunkt/jquery-pjax 112 | 113 | (function($){ 114 | if (!$.hash) $.hash = '#!/'; 115 | if (!$.siteurl) $.siteurl = document.location.protocol+'//'+document.location.host; // your site url 116 | if (!$.container) $.container = '#content'; // container SELECTOR to use for hash navigation 117 | 118 | // Is pjax supported by this browser? 119 | $.support.pjax = 120 | window.history && window.history.pushState && window.history.replaceState 121 | // pushState isn't reliable on iOS until 5. 122 | && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/); 123 | 124 | // When called on a link, fetches the href with ajax into the 125 | // container specified as the first parameter or with the data-pjax 126 | // attribute on the link itself. 127 | // 128 | // Tries to make sure the back button and ctrl+click work the way 129 | // you'd expect. 130 | // 131 | // Accepts a jQuery ajax options object that may include these 132 | // pjax specific options: 133 | // 134 | // container - Where to stick the response body. Usually a String selector. 135 | // $(container).html(xhr.responseBody) 136 | // push - Whether to pushState the URL. Defaults to true (of course). 137 | // replace - Want to use replaceState instead? That's cool. 138 | // 139 | // For convenience the first parameter can be either the container or 140 | // the options object. 141 | // 142 | // Returns the jQuery object 143 | $.fn.pjax = function( container, options ) { 144 | if ( options ) 145 | options.container = container; 146 | else 147 | options = $.isPlainObject(container) ? container : { 148 | container:container 149 | } 150 | 151 | // We can't persist $objects using the history API so we must use 152 | // a String selector. Bail if we got anything else. 153 | if ( options.container && typeof options.container !== 'string' ) { 154 | throw "pjax container must be a string selector!"; 155 | return false; 156 | } 157 | 158 | return this.live('click', function(event){ 159 | // Middle click, cmd click, and ctrl click should open 160 | // links in a new tab as normal. 161 | if ( event.which > 1 || event.metaKey ) 162 | return true; 163 | 164 | var defaults = { 165 | url: this.href, 166 | container: $(this).attr('data-pjax'), 167 | clickedElement: $(this), 168 | fragment: null, 169 | isform: false 170 | } 171 | 172 | $.pjax($.extend({}, defaults, options)); 173 | 174 | event.preventDefault(); 175 | }) 176 | } 177 | 178 | // Same as pjax but for forms, also will shows query in address 179 | 180 | $.fn.pjaxform = function( container, options ) { 181 | if ( options ) 182 | options.container = container; 183 | else 184 | options = $.isPlainObject(container) ? container : { 185 | container:container 186 | }; 187 | 188 | // We can't persist $objects using the history API so we must use 189 | // a String selector. Bail if we got anything else. 190 | if ( typeof options.container !== 'string' ) { 191 | throw "pjax container must be a string selector!"; 192 | return false; 193 | } 194 | 195 | return this.live('submit', function(event){ 196 | $(options.container).trigger('form-submit.pjax', $(this)); 197 | if (typeof options.serialize !=='undefined') { 198 | var data = options.serialize(this); 199 | } 200 | else { 201 | var data = $(this).serialize(); 202 | } 203 | options.type = $(this).attr('method'); 204 | 205 | var defaults = { 206 | url: (options.type && options.type.toUpperCase()=='GET')?this.action+'?'+data:this.action, 207 | push: (options.type && options.type.toUpperCase()=='GET')?true:false, 208 | data: data, 209 | isform: true, 210 | container: $(this).attr('data-pjax'), 211 | clickedElement: $(this) 212 | } 213 | 214 | $.pjax($.extend({}, defaults, options)); 215 | 216 | event.preventDefault(); 217 | }) 218 | } 219 | 220 | 221 | // Loads a URL with ajax, puts the response body inside a container, 222 | // then pushState()'s the loaded URL. 223 | // 224 | // Works just like $.ajax in that it accepts a jQuery ajax 225 | // settings object (with keys like url, type, data, etc). 226 | // 227 | // Accepts these extra keys: 228 | // 229 | // container - Where to stick the response body. Must be a String. 230 | // $(container).html(xhr.responseBody) 231 | // push - Whether to pushState the URL. Defaults to true (of course). 232 | // replace - Want to use replaceState instead? That's cool. 233 | // 234 | // Use it just like $.ajax: 235 | // 236 | // var xhr = $.pjax({ url: this.href, container: '#main' }) 237 | // console.log( xhr.readyState ) 238 | // 239 | // Returns whatever $.ajax returns. 240 | var pjax = $.pjax = function( options ) { 241 | var $container = $(options.container), 242 | success = options.success || $.noop, 243 | $settings = []; 244 | 245 | // We don't want to let anyone override our success handler. 246 | delete options.success; 247 | 248 | // We can't persist $objects using the history API so we must use 249 | // a String selector. Bail if we got anything else. 250 | if ( typeof options.container !== 'string' ) 251 | throw "pjax container must be a string selector!"; 252 | 253 | options = $.extend(true, {}, pjax.defaults, options); 254 | 255 | if ( $.isFunction(options.url) ) { 256 | options.url = options.url(); 257 | } 258 | 259 | options.context = $container 260 | 261 | options.success = function(data, textStatus, jqXHR){ 262 | if ( options.fragment ) { 263 | // If they specified a fragment, look for it in the response 264 | // and pull it out. 265 | var $fragment = $(data).find(options.fragment); 266 | if ( $fragment.length ) 267 | data = $fragment.children(); 268 | else 269 | return window.location = options.url; 270 | } else { 271 | // If we got no data or an entire web page, go directly 272 | // to the page and let normal error handling happen. 273 | if ( !$.trim(data) || /<html/i.test(data) ) 274 | return window.location = options.url; 275 | } 276 | 277 | // Make it happen. 278 | this.html(data); 279 | 280 | // If there's a <title> tag in the response, use it as 281 | // the page's title. 282 | var oldTitle = document.title, 283 | title = $.trim( this.find('title').remove().text() ); 284 | if ( title ) document.title = title; 285 | 286 | var state = { 287 | pjax: options.container, 288 | fragment: options.fragment, 289 | timeout: options.timeout 290 | } 291 | 292 | if( $.support.pjax ) 293 | { 294 | // If there are extra params, save the complete URL in the state object 295 | var query = $.param(options.data) 296 | if ( query != "_pjax=true" ) 297 | state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query; 298 | 299 | if ( options.replace ) { 300 | window.history.replaceState(state, document.title, options.url); 301 | } else if ( options.push ) { 302 | // this extra replaceState before first push ensures good back 303 | // button behavior 304 | if ( !pjax.active ) { 305 | window.history.replaceState($.extend({}, state, { 306 | url:null 307 | }), oldTitle); 308 | pjax.active = true; 309 | } 310 | 311 | window.history.pushState(state, document.title, options.url); 312 | } 313 | 314 | // Google Analytics support 315 | if ( (options.replace || options.push) && window._gaq ) 316 | _gaq.push(['_trackPageview']); 317 | 318 | // If the URL has a hash in it, make sure the browser 319 | // knows to navigate to the hash. 320 | var hash = window.location.hash.toString() 321 | if ( hash !== '' ) { 322 | window.location.hash = hash; 323 | } 324 | 325 | } 326 | else { 327 | // change address if it is not form or GET form 328 | if (!options.isform || options.type.toUpperCase()=='GET') { 329 | window.location.hash = "!"+options.url.replace(options.siteurl,""); 330 | $.hash = window.location.hash; 331 | } 332 | } 333 | 334 | // Invoke their success handler if they gave us one. 335 | success.apply(this, arguments); 336 | this.trigger('success.pjax', [data, textStatus, jqXHR, $settings]); 337 | } 338 | 339 | options.beforeSend = function(jqXHR, settings){ 340 | jqXHR.setRequestHeader('X-PJAX', 'true'); 341 | jqXHR.setRequestHeader('X-PJAX-SUPPORT', $.support.pjax?true:false); 342 | jqXHR.setRequestHeader('X-Referer', ($.support.pjax)?window.location.href:window.location.href.replace('/#!', '')); 343 | $settings = settings; 344 | this.trigger('start.pjax', [jqXHR, $settings]); 345 | } 346 | options.error = function(jqXHR, textStatus, errorThrown){ 347 | this.trigger('error.pjax', [jqXHR, textStatus, errorThrown, $settings]); 348 | if ( textStatus !== 'abort' ) 349 | window.location = options.url; 350 | } 351 | options.complete = function(jqXHR, textStatus){ 352 | this.trigger('complete.pjax', [jqXHR, textStatus, $settings]); 353 | } 354 | 355 | // Cancel the current request if we're already pjaxing 356 | var xhr = pjax.xhr; 357 | if ( xhr && xhr.readyState < 4) { 358 | xhr.onreadystatechange = $.noop; 359 | xhr.abort(); 360 | } 361 | 362 | pjax.xhr = $.ajax(options); 363 | $(document).trigger('pjax', [pjax.xhr, options]); 364 | 365 | return pjax.xhr; 366 | } 367 | 368 | 369 | pjax.defaults = { 370 | timeout: 650, 371 | push: true, 372 | replace: false, 373 | url: $.support.pjax ? window.location.href : window.location.hash.substr(2), 374 | // We want the browser to maintain two separate internal caches: one for 375 | // pjax'd partial page loads and one for normal page loads. Without 376 | // adding this secret parameter, some browsers will often confuse the two. 377 | data: { 378 | _pjax: true 379 | }, 380 | cache:false, 381 | type: 'GET', 382 | dataType: 'html', 383 | siteurl : $.siteurl 384 | } 385 | 386 | 387 | // Used to detect initial (useless) popstate. 388 | // If history.state exists, assume browser isn't going to fire initial popstate. 389 | var popped = ('state' in window.history), initialURL = location.href; 390 | 391 | // popstate handler takes care of the back and forward buttons 392 | // 393 | // You probably shouldn't use pjax on pages with other pushState 394 | // stuff yet. 395 | $(window).bind('popstate', function(event){ 396 | // Ignore inital popstate that some browsers fire on page load 397 | var initialPop = !popped && location.href == initialURL; 398 | popped = true; 399 | if ( initialPop ) return; 400 | 401 | var state = event.state; 402 | 403 | if ( state && state.pjax ) { 404 | var container = state.pjax; 405 | if ( $(container+'').length ) 406 | $.pjax({ 407 | url: state.url || location.href, 408 | fragment: state.fragment, 409 | container: container, 410 | push: false, 411 | timeout: state.timeout 412 | }) 413 | else 414 | window.location = location.href; 415 | } 416 | }); 417 | 418 | // Add the state property to jQuery's event object so we can use it in 419 | // $(window).bind('popstate') 420 | if ( $.inArray('state', $.event.props) < 0 ) 421 | $.event.props.push('state'); 422 | 423 | 424 | // While page is loading, we should handle different URL types 425 | var hash = window.location.hash.toString(); 426 | 427 | if( hash.length > 0 ) 428 | { 429 | if( $.support.pjax && hash.match(/^#!\/.*/)) 430 | location = $.siteurl+hash.substr(2); 431 | 432 | } 433 | else if( location.pathname.length > 1 || location.search.length > 1 ) 434 | { 435 | if( !$.support.pjax ) 436 | window.location = $.siteurl+'/#!'+window.location.pathname+window.location.search; 437 | } 438 | 439 | // If there is no pjax support, we should handle hash changes 440 | if( !$.support.pjax ) 441 | { 442 | $(window).hashchange(function(){ 443 | hash = window.location.hash; 444 | 445 | if ( (hash.substr(0,2) == '#!' || hash=='') && hash != $.hash) { 446 | $.ajax({ 447 | type: "GET", 448 | cache:false, 449 | url: $.siteurl+hash.replace('#!',''), 450 | beforeSend : function(jqXHR, settings) { 451 | jqXHR.setRequestHeader('X-PJAX','true'); 452 | jqXHR.setRequestHeader('X-PJAX-SUPPORT', $.support.pjax?true:false); 453 | jqXHR.setRequestHeader('X-Referer', $.hash.replace('#!','')); 454 | $settings = settings; 455 | $($.container).trigger('start.pjax', [jqXHR, $settings]); 456 | }, 457 | success: function(data, textStatus, jqXHR){ 458 | $($.container).html(data); 459 | $($.container).trigger('success.pjax', [data, textStatus, jqXHR, $settings]); 460 | }, 461 | complete: function(jqXHR, textStatus){ 462 | $($.container).trigger('complete.pjax', [jqXHR, textStatus, $settings]); 463 | }, 464 | error: function(jqXHR, textStatus, errorThrown) { 465 | $($.container).trigger('error.pjax', [jqXHR, textStatus, errorThrown, $settings]); 466 | } 467 | }); 468 | 469 | } 470 | }); 471 | $(window).hashchange(); 472 | } 473 | 474 | })(jQuery); 475 | --------------------------------------------------------------------------------