rails-pjax 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+