├── 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 |
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:
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 |
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=$('').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('