rails-pjax 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ PJAX for Rails 3.1+
2
+ ===================
3
+
4
+ Integrate Chris Wanstrath's [PJAX](https://github.com/defunkt/jquery-pjax) into Rails 3.1+ via the asset pipeline.
5
+
6
+ To activate, add this to your app/assets/javascripts/application.js (or whatever bundle you use):
7
+
8
+ //=require pjax
9
+
10
+ All links that match `$('a:not([data-remote]):not([data-behavior]):not([data-skip-pjax])')` will then use PJAX.
11
+
12
+ The PJAX container has to be marked with data-pjax-container attribute, so for example:
13
+
14
+ <body>
15
+ <div>
16
+ <!-- This will not be touched on PJAX updates -->
17
+ <%= Time.now %>
18
+ </div>
19
+
20
+ <div data-pjax-container>
21
+ <!-- PJAX updates will go here -->
22
+ <%= content_tag :h3, 'My site' %>
23
+ <%= link_to 'About me', about_me_path %>
24
+ <!-- The following link will not be pjax'd -->
25
+ <%= link_to 'Google', 'http://google.com', 'data-skip-pjax' => true %>
26
+ </div>
27
+ </body>
28
+
29
+
30
+ FIXME: Currently the layout is hardcoded to "application". Need to delegate that to the specific layout of the controller.
31
+
32
+ Examples for redirect_to
33
+ -----------------------------
34
+
35
+ class ProjectsController < ApplicationController
36
+ before_filter :set_project, except: [ :index, :create ]
37
+
38
+ def index
39
+ @projects = current_user.projects
40
+ end
41
+
42
+ def show
43
+ end
44
+
45
+ def create
46
+ @project = Project.create params[:project]
47
+ redirect_to :show, @project
48
+ end
49
+
50
+ def update
51
+ @project.update_attributes params[:project]
52
+ redirect_to :show, @project
53
+ end
54
+
55
+ def destroy
56
+ @project.destroy
57
+
58
+ index # set the objects needed for rendering index
59
+ redirect_to :index
60
+ end
61
+
62
+ private
63
+ def set_project
64
+ @project = current_user.projects.find params[:id].to_i
65
+ end
66
+ end
@@ -0,0 +1,8 @@
1
+ // DEPRECATED: This default activation selector is too domain specific
2
+ // and can not be easily customized.
3
+ //
4
+ // This file will be removed in 0.3.
5
+
6
+ $('a:not([data-remote]):not([data-behavior]):not([data-skip-pjax])')
7
+ .not('.results a')
8
+ .pjax('[data-pjax-container]');
@@ -0,0 +1,18 @@
1
+ // DEPRECATED: Move these events into your application if you want to
2
+ // continue using them.
3
+ //
4
+ // This file will be removed in 0.3.
5
+
6
+ $(document).ready(function() {
7
+ $(document).trigger('pageChanged');
8
+ $(document).trigger('pageUpdated');
9
+ });
10
+
11
+ $(document).bind('pjax:end', function() {
12
+ $(document).trigger('pageChanged');
13
+ $(document).trigger('pageUpdated');
14
+ });
15
+
16
+ $(document).bind('ajaxComplete', function() {
17
+ $(document).trigger('pageUpdated');
18
+ });
@@ -0,0 +1,5 @@
1
+ // DEPRECATED: Require "jquery.pjax" instead.
2
+ //
3
+ //= require jquery.pjax
4
+ //= require ./pjax/page_triggers
5
+ //= require ./pjax/enable_pjax
data/lib/pjax.rb ADDED
@@ -0,0 +1,41 @@
1
+ module Pjax
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ layout proc { |c| pjax_request? ? pjax_layout : 'application' }
6
+ helper_method :pjax_request?
7
+
8
+ before_filter :strip_pjax_param, :if => :pjax_request?
9
+ before_filter :set_pjax_url, :if => :pjax_request?
10
+ end
11
+
12
+ protected
13
+ def pjax_request?
14
+ env['HTTP_X_PJAX'].present?
15
+ end
16
+
17
+ def pjax_layout
18
+ false
19
+ end
20
+
21
+ def pjax_container
22
+ return unless pjax_request?
23
+ request.headers['X-PJAX-Container']
24
+ end
25
+
26
+ def strip_pjax_param
27
+ params.delete(:_pjax)
28
+ request.env['QUERY_STRING'].sub!(/_pjax=[^&]+&?/, '')
29
+
30
+ request.env.delete('rack.request.query_string')
31
+ request.env.delete('rack.request.query_hash')
32
+ request.env.delete('action_dispatch.request.query_parameters')
33
+
34
+ request.instance_variable_set('@original_fullpath', nil)
35
+ request.instance_variable_set('@fullpath', nil)
36
+ end
37
+
38
+ def set_pjax_url
39
+ response.headers['X-PJAX-URL'] = request.url
40
+ end
41
+ end
data/lib/pjax_rails.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'pjax'
2
+
3
+ module PjaxRails
4
+ class Engine < ::Rails::Engine
5
+ initializer "pjax_rails.add_controller" do
6
+ config.to_prepare { ApplicationController.send :include, Pjax }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rails-pjax'
3
+ s.version = '0.0.1'
4
+ s.author = 'Helioid (original David Heinemeier Hansson and PJAX by Chris Wanstrath)'
5
+ s.email = 'info@helioid.com'
6
+ s.summary = 'PJAX integration for Rails 3.1+'
7
+ s.homepage = 'http://www.helioid.com/'
8
+
9
+ s.add_dependency 'jquery-rails'
10
+
11
+ s.files = Dir["#{File.dirname(__FILE__)}/**/*"]
12
+ end
@@ -0,0 +1,679 @@
1
+ // jquery.pjax.js
2
+ // copyright chris wanstrath
3
+ // https://github.com/defunkt/jquery-pjax
4
+
5
+ (function($){
6
+
7
+ // When called on a link, fetches the href with ajax into the
8
+ // container specified as the first parameter or with the data-pjax
9
+ // attribute on the link itself.
10
+ //
11
+ // Tries to make sure the back button and ctrl+click work the way
12
+ // you'd expect.
13
+ //
14
+ // Accepts a jQuery ajax options object that may include these
15
+ // pjax specific options:
16
+ //
17
+ // container - Where to stick the response body. Usually a String selector.
18
+ // $(container).html(xhr.responseBody)
19
+ // push - Whether to pushState the URL. Defaults to true (of course).
20
+ // replace - Want to use replaceState instead? That's cool.
21
+ //
22
+ // For convenience the first parameter can be either the container or
23
+ // the options object.
24
+ //
25
+ // Returns the jQuery object
26
+ $.fn.pjax = function( container, options ) {
27
+ return this.live('click.pjax', function(event){
28
+ handleClick(event, container, options)
29
+ })
30
+ }
31
+
32
+ // Public: pjax on click handler
33
+ //
34
+ // Exported as $.pjax.click.
35
+ //
36
+ // event - "click" jQuery.Event
37
+ // options - pjax options
38
+ //
39
+ // Examples
40
+ //
41
+ // $('a').live('click', $.pjax.click)
42
+ // // is the same as
43
+ // $('a').pjax()
44
+ //
45
+ // $(document).on('click', 'a', function(event) {
46
+ // var container = $(this).closest('[data-pjax-container]')
47
+ // return $.pjax.click(event, container)
48
+ // })
49
+ //
50
+ // Returns false if pjax runs, otherwise nothing.
51
+ function handleClick(event, container, options) {
52
+ options = optionsFor(container, options)
53
+
54
+ var link = event.currentTarget
55
+
56
+ // If current target isnt a link, try to find the first A descendant
57
+ if (link.tagName.toUpperCase() !== 'A')
58
+ link = $(link).find('a')[0]
59
+
60
+ if (!link)
61
+ throw "$.fn.pjax or $.pjax.click requires an anchor element"
62
+
63
+ // Middle click, cmd click, and ctrl click should open
64
+ // links in a new tab as normal.
65
+ if ( event.which > 1 || event.metaKey )
66
+ return
67
+
68
+ // Ignore cross origin links
69
+ if ( location.protocol !== link.protocol || location.host !== link.host )
70
+ return
71
+
72
+ // Ignore anchors on the same page
73
+ if ( link.hash && link.href.replace(link.hash, '') ===
74
+ location.href.replace(location.hash, '') )
75
+ return
76
+
77
+ var defaults = {
78
+ url: link.href,
79
+ container: $(link).attr('data-pjax'),
80
+ target: link,
81
+ clickedElement: $(link), // DEPRECATED: use target
82
+ fragment: null
83
+ }
84
+
85
+ $.pjax($.extend({}, defaults, options))
86
+
87
+ event.preventDefault()
88
+ }
89
+
90
+
91
+ // Loads a URL with ajax, puts the response body inside a container,
92
+ // then pushState()'s the loaded URL.
93
+ //
94
+ // Works just like $.ajax in that it accepts a jQuery ajax
95
+ // settings object (with keys like url, type, data, etc).
96
+ //
97
+ // Accepts these extra keys:
98
+ //
99
+ // container - Where to stick the response body.
100
+ // $(container).html(xhr.responseBody)
101
+ // push - Whether to pushState the URL. Defaults to true (of course).
102
+ // replace - Want to use replaceState instead? That's cool.
103
+ //
104
+ // Use it just like $.ajax:
105
+ //
106
+ // var xhr = $.pjax({ url: this.href, container: '#main' })
107
+ // console.log( xhr.readyState )
108
+ //
109
+ // Returns whatever $.ajax returns.
110
+ var pjax = $.pjax = function( options ) {
111
+ options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
112
+
113
+ if ($.isFunction(options.url)) {
114
+ options.url = options.url()
115
+ }
116
+
117
+ var target = options.target
118
+
119
+ // DEPRECATED: use options.target
120
+ if (!target && options.clickedElement) target = options.clickedElement[0]
121
+
122
+ var hash = parseURL(options.url).hash
123
+
124
+ // DEPRECATED: Save references to original event callbacks. However,
125
+ // listening for custom pjax:* events is prefered.
126
+ var oldBeforeSend = options.beforeSend,
127
+ oldComplete = options.complete,
128
+ oldSuccess = options.success,
129
+ oldError = options.error
130
+
131
+ var context = options.context = findContainerFor(options.container)
132
+
133
+ // We want the browser to maintain two separate internal caches: one
134
+ // for pjax'd partial page loads and one for normal page loads.
135
+ // Without adding this secret parameter, some browsers will often
136
+ // confuse the two.
137
+ if (!options.data) options.data = {}
138
+ options.data._pjax = context.selector
139
+
140
+ function fire(type, args) {
141
+ var event = $.Event(type, { relatedTarget: target })
142
+ context.trigger(event, args)
143
+ return !event.isDefaultPrevented()
144
+ }
145
+
146
+ var timeoutTimer
147
+
148
+ options.beforeSend = function(xhr, settings) {
149
+ if (settings.timeout > 0) {
150
+ timeoutTimer = setTimeout(function() {
151
+ if (fire('pjax:timeout', [xhr, options]))
152
+ xhr.abort('timeout')
153
+ }, settings.timeout)
154
+
155
+ // Clear timeout setting so jquerys internal timeout isn't invoked
156
+ settings.timeout = 0
157
+ }
158
+
159
+ xhr.setRequestHeader('X-PJAX', 'true')
160
+ xhr.setRequestHeader('X-PJAX-Container', context.selector)
161
+
162
+ var result
163
+
164
+ // DEPRECATED: Invoke original `beforeSend` handler
165
+ if (oldBeforeSend) {
166
+ result = oldBeforeSend.apply(this, arguments)
167
+ if (result === false) return false
168
+ }
169
+
170
+ if (!fire('pjax:beforeSend', [xhr, settings])) return false
171
+
172
+ if (options.push && !options.replace) {
173
+ // Cache current container element before replacing it
174
+ containerCache.push(pjax.state.id, context.clone(true, true).contents())
175
+
176
+ window.history.pushState(null, "", options.url)
177
+ }
178
+
179
+ fire('pjax:start', [xhr, options])
180
+ // start.pjax is deprecated
181
+ fire('start.pjax', [xhr, options])
182
+
183
+ fire('pjax:send', [xhr, settings])
184
+ }
185
+
186
+ options.complete = function(xhr, textStatus) {
187
+ if (timeoutTimer)
188
+ clearTimeout(timeoutTimer)
189
+
190
+ // DEPRECATED: Invoke original `complete` handler
191
+ if (oldComplete) oldComplete.apply(this, arguments)
192
+
193
+ fire('pjax:complete', [xhr, textStatus, options])
194
+
195
+ fire('pjax:end', [xhr, options])
196
+ // end.pjax is deprecated
197
+ fire('end.pjax', [xhr, options])
198
+ }
199
+
200
+ options.error = function(xhr, textStatus, errorThrown) {
201
+ var container = extractContainer("", xhr, options)
202
+
203
+ // DEPRECATED: Invoke original `error` handler
204
+ if (oldError) oldError.apply(this, arguments)
205
+
206
+ var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
207
+ if (textStatus !== 'abort' && allowed)
208
+ window.location = container.url
209
+ }
210
+
211
+ options.success = function(data, status, xhr) {
212
+ var container = extractContainer(data, xhr, options)
213
+
214
+ if (!container.contents) {
215
+ window.location = container.url
216
+ return
217
+ }
218
+
219
+ pjax.state = {
220
+ id: options.id || uniqueId(),
221
+ url: container.url,
222
+ container: context.selector,
223
+ fragment: options.fragment,
224
+ timeout: options.timeout
225
+ }
226
+
227
+ if (options.push || options.replace) {
228
+ window.history.replaceState(pjax.state, container.title, container.url)
229
+ }
230
+
231
+ if (container.title) document.title = container.title
232
+ context.html(container.contents)
233
+
234
+ // Scroll to top by default
235
+ if (typeof options.scrollTo === 'number')
236
+ $(window).scrollTop(options.scrollTo)
237
+
238
+ // Google Analytics support
239
+ if ( (options.replace || options.push) && window._gaq )
240
+ _gaq.push(['_trackPageview'])
241
+
242
+ // If the URL has a hash in it, make sure the browser
243
+ // knows to navigate to the hash.
244
+ if ( hash !== '' ) {
245
+ window.location.href = hash
246
+ }
247
+
248
+ // DEPRECATED: Invoke original `success` handler
249
+ if (oldSuccess) oldSuccess.apply(this, arguments)
250
+
251
+ fire('pjax:success', [data, status, xhr, options])
252
+ }
253
+
254
+
255
+ // Initialize pjax.state for the initial page load. Assume we're
256
+ // using the container and options of the link we're loading for the
257
+ // back button to the initial page. This ensures good back button
258
+ // behavior.
259
+ if (!pjax.state) {
260
+ pjax.state = {
261
+ id: uniqueId(),
262
+ url: window.location.href,
263
+ container: context.selector,
264
+ fragment: options.fragment,
265
+ timeout: options.timeout
266
+ }
267
+ window.history.replaceState(pjax.state, document.title)
268
+ }
269
+
270
+ // Cancel the current request if we're already pjaxing
271
+ var xhr = pjax.xhr
272
+ if ( xhr && xhr.readyState < 4) {
273
+ xhr.onreadystatechange = $.noop
274
+ xhr.abort()
275
+ }
276
+
277
+ pjax.options = options
278
+ pjax.xhr = $.ajax(options)
279
+
280
+ // pjax event is deprecated
281
+ $(document).trigger('pjax', [pjax.xhr, options])
282
+
283
+ return pjax.xhr
284
+ }
285
+
286
+
287
+ // Internal: Generate unique id for state object.
288
+ //
289
+ // Use a timestamp instead of a counter since ids should still be
290
+ // unique across page loads.
291
+ //
292
+ // Returns Number.
293
+ function uniqueId() {
294
+ return (new Date).getTime()
295
+ }
296
+
297
+ // Internal: Strips _pjax param from url
298
+ //
299
+ // url - String
300
+ //
301
+ // Returns String.
302
+ function stripPjaxParam(url) {
303
+ return url
304
+ .replace(/\?_pjax=[^&]+&?/, '?')
305
+ .replace(/_pjax=[^&]+&?/, '')
306
+ .replace(/[\?&]$/, '')
307
+ }
308
+
309
+ // Internal: Parse URL components and returns a Locationish object.
310
+ //
311
+ // url - String URL
312
+ //
313
+ // Returns HTMLAnchorElement that acts like Location.
314
+ function parseURL(url) {
315
+ var a = document.createElement('a')
316
+ a.href = url
317
+ return a
318
+ }
319
+
320
+ // Internal: Build options Object for arguments.
321
+ //
322
+ // For convenience the first parameter can be either the container or
323
+ // the options object.
324
+ //
325
+ // Examples
326
+ //
327
+ // optionsFor('#container')
328
+ // // => {container: '#container'}
329
+ //
330
+ // optionsFor('#container', {push: true})
331
+ // // => {container: '#container', push: true}
332
+ //
333
+ // optionsFor({container: '#container', push: true})
334
+ // // => {container: '#container', push: true}
335
+ //
336
+ // Returns options Object.
337
+ function optionsFor(container, options) {
338
+ // Both container and options
339
+ if ( container && options )
340
+ options.container = container
341
+
342
+ // First argument is options Object
343
+ else if ( $.isPlainObject(container) )
344
+ options = container
345
+
346
+ // Only container
347
+ else
348
+ options = {container: container}
349
+
350
+ // Find and validate container
351
+ if (options.container)
352
+ options.container = findContainerFor(options.container)
353
+
354
+ return options
355
+ }
356
+
357
+ // Internal: Find container element for a variety of inputs.
358
+ //
359
+ // Because we can't persist elements using the history API, we must be
360
+ // able to find a String selector that will consistently find the Element.
361
+ //
362
+ // container - A selector String, jQuery object, or DOM Element.
363
+ //
364
+ // Returns a jQuery object whose context is `document` and has a selector.
365
+ function findContainerFor(container) {
366
+ container = $(container)
367
+
368
+ if ( !container.length ) {
369
+ throw "no pjax container for " + container.selector
370
+ } else if ( container.selector !== '' && container.context === document ) {
371
+ return container
372
+ } else if ( container.attr('id') ) {
373
+ return $('#' + container.attr('id'))
374
+ } else {
375
+ throw "cant get selector for pjax container!"
376
+ }
377
+ }
378
+
379
+ // Internal: Filter and find all elements matching the selector.
380
+ //
381
+ // Where $.fn.find only matches descendants, findAll will test all the
382
+ // top level elements in the jQuery object as well.
383
+ //
384
+ // elems - jQuery object of Elements
385
+ // selector - String selector to match
386
+ //
387
+ // Returns a jQuery object.
388
+ function findAll(elems, selector) {
389
+ var results = $()
390
+ elems.each(function() {
391
+ if ($(this).is(selector))
392
+ results = results.add(this)
393
+ results = results.add(selector, this)
394
+ })
395
+ return results
396
+ }
397
+
398
+ // Internal: Extracts container and metadata from response.
399
+ //
400
+ // 1. Extracts X-PJAX-URL header if set
401
+ // 2. Extracts inline <title> tags
402
+ // 3. Builds response Element and extracts fragment if set
403
+ //
404
+ // data - String response data
405
+ // xhr - XHR response
406
+ // options - pjax options Object
407
+ //
408
+ // Returns an Object with url, title, and contents keys.
409
+ function extractContainer(data, xhr, options) {
410
+ var obj = {}
411
+
412
+ // Prefer X-PJAX-URL header if it was set, otherwise fallback to
413
+ // using the original requested url.
414
+ obj.url = stripPjaxParam(xhr.getResponseHeader('X-PJAX-URL') || options.url)
415
+
416
+ // Attempt to parse response html into elements
417
+ var $data = $(data)
418
+
419
+ // If response data is empty, return fast
420
+ if ($data.length === 0)
421
+ return obj
422
+
423
+ // If there's a <title> tag in the response, use it as
424
+ // the page's title.
425
+ obj.title = findAll($data, 'title').last().text()
426
+
427
+ if (options.fragment) {
428
+ // If they specified a fragment, look for it in the response
429
+ // and pull it out.
430
+ var $fragment = findAll($data, options.fragment).first()
431
+
432
+ if ($fragment.length) {
433
+ obj.contents = $fragment.contents()
434
+
435
+ // If there's no title, look for data-title and title attributes
436
+ // on the fragment
437
+ if (!obj.title)
438
+ obj.title = $fragment.attr('title') || $fragment.data('title')
439
+ }
440
+
441
+ } else if (!/<html/i.test(data)) {
442
+ obj.contents = $data
443
+ }
444
+
445
+ // Clean up any <title> tags
446
+ if (obj.contents) {
447
+ // Remove any parent title elements
448
+ obj.contents = obj.contents.not('title')
449
+
450
+ // Then scrub any titles from their descendents
451
+ obj.contents.find('title').remove()
452
+ }
453
+
454
+ // Trim any whitespace off the title
455
+ if (obj.title) obj.title = $.trim(obj.title)
456
+
457
+ return obj
458
+ }
459
+
460
+ // Public: Reload current page with pjax.
461
+ //
462
+ // Returns whatever $.pjax returns.
463
+ pjax.reload = function(container, options) {
464
+ var defaults = {
465
+ url: window.location.href,
466
+ push: false,
467
+ replace: true,
468
+ scrollTo: false
469
+ }
470
+
471
+ return $.pjax($.extend(defaults, optionsFor(container, options)))
472
+ }
473
+
474
+
475
+ pjax.defaults = {
476
+ timeout: 650,
477
+ push: true,
478
+ replace: false,
479
+ type: 'GET',
480
+ dataType: 'html',
481
+ scrollTo: 0,
482
+ maxCacheLength: 20
483
+ }
484
+
485
+ // Internal: History DOM caching class.
486
+ function Cache() {
487
+ this.mapping = {}
488
+ this.forwardStack = []
489
+ this.backStack = []
490
+ }
491
+ // Push previous state id and container contents into the history
492
+ // cache. Should be called in conjunction with `pushState` to save the
493
+ // previous container contents.
494
+ //
495
+ // id - State ID Number
496
+ // value - DOM Element to cache
497
+ //
498
+ // Returns nothing.
499
+ Cache.prototype.push = function(id, value) {
500
+ this.mapping[id] = value
501
+ this.backStack.push(id)
502
+
503
+ // Remove all entires in forward history stack after pushing
504
+ // a new page.
505
+ while (this.forwardStack.length)
506
+ delete this.mapping[this.forwardStack.shift()]
507
+
508
+ // Trim back history stack to max cache length.
509
+ while (this.backStack.length > pjax.defaults.maxCacheLength)
510
+ delete this.mapping[this.backStack.shift()]
511
+ }
512
+ // Retrieve cached DOM Element for state id.
513
+ //
514
+ // id - State ID Number
515
+ //
516
+ // Returns DOM Element(s) or undefined if cache miss.
517
+ Cache.prototype.get = function(id) {
518
+ return this.mapping[id]
519
+ }
520
+ // Shifts cache from forward history cache to back stack. Should be
521
+ // called on `popstate` with the previous state id and container
522
+ // contents.
523
+ //
524
+ // id - State ID Number
525
+ // value - DOM Element to cache
526
+ //
527
+ // Returns nothing.
528
+ Cache.prototype.forward = function(id, value) {
529
+ this.mapping[id] = value
530
+ this.backStack.push(id)
531
+
532
+ if (id = this.forwardStack.pop())
533
+ delete this.mapping[id]
534
+ }
535
+ // Shifts cache from back history cache to forward stack. Should be
536
+ // called on `popstate` with the previous state id and container
537
+ // contents.
538
+ //
539
+ // id - State ID Number
540
+ // value - DOM Element to cache
541
+ //
542
+ // Returns nothing.
543
+ Cache.prototype.back = function(id, value) {
544
+ this.mapping[id] = value
545
+ this.forwardStack.push(id)
546
+
547
+ if (id = this.backStack.pop())
548
+ delete this.mapping[id]
549
+ }
550
+
551
+ var containerCache = new Cache
552
+
553
+
554
+ // Export $.pjax.click
555
+ pjax.click = handleClick
556
+
557
+
558
+ // Used to detect initial (useless) popstate.
559
+ // If history.state exists, assume browser isn't going to fire initial popstate.
560
+ var popped = ('state' in window.history), initialURL = location.href
561
+
562
+
563
+ // popstate handler takes care of the back and forward buttons
564
+ //
565
+ // You probably shouldn't use pjax on pages with other pushState
566
+ // stuff yet.
567
+ $(window).bind('popstate', function(event){
568
+ // Ignore inital popstate that some browsers fire on page load
569
+ var initialPop = !popped && location.href == initialURL
570
+ popped = true
571
+ if ( initialPop ) return
572
+
573
+ var state = event.state
574
+
575
+ if (state && state.container) {
576
+ var container = $(state.container)
577
+ if (container.length) {
578
+ var contents = containerCache.get(state.id)
579
+
580
+ if (pjax.state) {
581
+ // Since state ids always increase, we can deduce the history
582
+ // direction from the previous state.
583
+ var direction = pjax.state.id < state.id ? 'forward' : 'back'
584
+
585
+ // Cache current container before replacement and inform the
586
+ // cache which direction the history shifted.
587
+ containerCache[direction](pjax.state.id, container.clone(true, true).contents())
588
+ }
589
+
590
+ var options = {
591
+ id: state.id,
592
+ url: state.url,
593
+ container: container,
594
+ push: false,
595
+ fragment: state.fragment,
596
+ timeout: state.timeout,
597
+ scrollTo: false
598
+ }
599
+
600
+ if (contents) {
601
+ // pjax event is deprecated
602
+ $(document).trigger('pjax', [null, options])
603
+ container.trigger('pjax:start', [null, options])
604
+ // end.pjax event is deprecated
605
+ container.trigger('start.pjax', [null, options])
606
+
607
+ container.html(contents)
608
+ pjax.state = state
609
+
610
+ container.trigger('pjax:end', [null, options])
611
+ // end.pjax event is deprecated
612
+ container.trigger('end.pjax', [null, options])
613
+ } else {
614
+ $.pjax(options)
615
+ }
616
+
617
+ // Force reflow/relayout before the browser tries to restore the
618
+ // scroll position.
619
+ container[0].offsetHeight
620
+ } else {
621
+ window.location = location.href
622
+ }
623
+ }
624
+ })
625
+
626
+
627
+ // Add the state property to jQuery's event object so we can use it in
628
+ // $(window).bind('popstate')
629
+ if ( $.inArray('state', $.event.props) < 0 )
630
+ $.event.props.push('state')
631
+
632
+
633
+ // Is pjax supported by this browser?
634
+ $.support.pjax =
635
+ window.history && window.history.pushState && window.history.replaceState
636
+ // pushState isn't reliable on iOS until 5.
637
+ && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
638
+
639
+
640
+ // Fall back to normalcy for older browsers.
641
+ if ( !$.support.pjax ) {
642
+ $.pjax = function( options ) {
643
+ var url = $.isFunction(options.url) ? options.url() : options.url,
644
+ method = options.type ? options.type.toUpperCase() : 'GET'
645
+
646
+ var form = $('<form>', {
647
+ method: method === 'GET' ? 'GET' : 'POST',
648
+ action: url,
649
+ style: 'display:none'
650
+ })
651
+
652
+ if (method !== 'GET' && method !== 'POST') {
653
+ form.append($('<input>', {
654
+ type: 'hidden',
655
+ name: '_method',
656
+ value: method.toLowerCase()
657
+ }))
658
+ }
659
+
660
+ var data = options.data
661
+ if (typeof data === 'string') {
662
+ $.each(data.split('&'), function(index, value) {
663
+ var pair = value.split('=')
664
+ form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
665
+ })
666
+ } else if (typeof data === 'object') {
667
+ for (key in data)
668
+ form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
669
+ }
670
+
671
+ $(document.body).append(form)
672
+ form.submit()
673
+ }
674
+ $.pjax.click = $.noop
675
+ $.pjax.reload = window.location.reload
676
+ $.fn.pjax = function() { return this }
677
+ }
678
+
679
+ })(jQuery);
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-pjax
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Helioid (original David Heinemeier Hansson and PJAX by Chris Wanstrath)
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-05-01 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: jquery-rails
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description:
35
+ email: info@helioid.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - ./pjax_rails.gemspec
44
+ - ./README.md
45
+ - ./vendor/assets/javascripts/jquery.pjax.js
46
+ - ./lib/pjax_rails.rb
47
+ - ./lib/pjax.rb
48
+ - ./lib/assets/javascripts/pjax/page_triggers.js
49
+ - ./lib/assets/javascripts/pjax/enable_pjax.js
50
+ - ./lib/assets/javascripts/pjax.js
51
+ homepage: http://www.helioid.com/
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.8.15
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: PJAX integration for Rails 3.1+
84
+ test_files: []
85
+