rails-pjax 0.0.1 → 0.0.2

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 CHANGED
@@ -29,7 +29,7 @@ The PJAX container has to be marked with data-pjax-container attribute, so for e
29
29
 
30
30
  FIXME: Currently the layout is hardcoded to "application". Need to delegate that to the specific layout of the controller.
31
31
 
32
- Examples for redirect_to
32
+ Examples for redirect_pjax_to
33
33
  -----------------------------
34
34
 
35
35
  class ProjectsController < ApplicationController
@@ -44,19 +44,19 @@ Examples for redirect_to
44
44
 
45
45
  def create
46
46
  @project = Project.create params[:project]
47
- redirect_to :show, @project
47
+ redirect_pjax_to :show, @project
48
48
  end
49
49
 
50
50
  def update
51
51
  @project.update_attributes params[:project]
52
- redirect_to :show, @project
52
+ redirect_pjax_to :show, @project
53
53
  end
54
54
 
55
55
  def destroy
56
56
  @project.destroy
57
57
 
58
58
  index # set the objects needed for rendering index
59
- redirect_to :index
59
+ redirect_pjax_to :index
60
60
  end
61
61
 
62
62
  private
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rails-pjax'
3
- s.version = '0.0.1'
3
+ s.version = '0.0.2'
4
4
  s.author = 'Helioid (original David Heinemeier Hansson and PJAX by Chris Wanstrath)'
5
5
  s.email = 'info@helioid.com'
6
6
  s.summary = 'PJAX integration for Rails 3.1+'
Binary file
@@ -24,8 +24,8 @@
24
24
  //
25
25
  // Returns the jQuery object
26
26
  $.fn.pjax = function( container, options ) {
27
- return this.live('click.pjax', function(event){
28
- handleClick(event, container, options)
27
+ return this.live('click', function(event){
28
+ return handleClick(event, container, options)
29
29
  })
30
30
  }
31
31
 
@@ -53,11 +53,7 @@ function handleClick(event, container, options) {
53
53
 
54
54
  var link = event.currentTarget
55
55
 
56
- // If current target isnt a link, try to find the first A descendant
57
56
  if (link.tagName.toUpperCase() !== 'A')
58
- link = $(link).find('a')[0]
59
-
60
- if (!link)
61
57
  throw "$.fn.pjax or $.pjax.click requires an anchor element"
62
58
 
63
59
  // Middle click, cmd click, and ctrl click should open
@@ -85,6 +81,30 @@ function handleClick(event, container, options) {
85
81
  $.pjax($.extend({}, defaults, options))
86
82
 
87
83
  event.preventDefault()
84
+ return false
85
+ }
86
+
87
+ // Internal: Strips _pjax param from url
88
+ //
89
+ // url - String
90
+ //
91
+ // Returns String.
92
+ function stripPjaxParam(url) {
93
+ return url
94
+ .replace(/\?_pjax=[^&]+&?/, '?')
95
+ .replace(/_pjax=[^&]+&?/, '')
96
+ .replace(/[\?&]$/, '')
97
+ }
98
+
99
+ // Internal: Parse URL components and returns a Locationish object.
100
+ //
101
+ // url - String URL
102
+ //
103
+ // Returns HTMLAnchorElement that acts like Location.
104
+ function parseURL(url) {
105
+ var a = document.createElement('a')
106
+ a.href = url
107
+ return a
88
108
  }
89
109
 
90
110
 
@@ -119,7 +139,8 @@ var pjax = $.pjax = function( options ) {
119
139
  // DEPRECATED: use options.target
120
140
  if (!target && options.clickedElement) target = options.clickedElement[0]
121
141
 
122
- var hash = parseURL(options.url).hash
142
+ var url = options.url
143
+ var hash = parseURL(url).hash
123
144
 
124
145
  // DEPRECATED: Save references to original event callbacks. However,
125
146
  // listening for custom pjax:* events is prefered.
@@ -146,6 +167,8 @@ var pjax = $.pjax = function( options ) {
146
167
  var timeoutTimer
147
168
 
148
169
  options.beforeSend = function(xhr, settings) {
170
+ url = stripPjaxParam(settings.url)
171
+
149
172
  if (settings.timeout > 0) {
150
173
  timeoutTimer = setTimeout(function() {
151
174
  if (fire('pjax:timeout', [xhr, options]))
@@ -169,18 +192,9 @@ var pjax = $.pjax = function( options ) {
169
192
 
170
193
  if (!fire('pjax:beforeSend', [xhr, settings])) return false
171
194
 
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
195
  fire('pjax:start', [xhr, options])
180
196
  // start.pjax is deprecated
181
197
  fire('start.pjax', [xhr, options])
182
-
183
- fire('pjax:send', [xhr, settings])
184
198
  }
185
199
 
186
200
  options.complete = function(xhr, textStatus) {
@@ -198,42 +212,72 @@ var pjax = $.pjax = function( options ) {
198
212
  }
199
213
 
200
214
  options.error = function(xhr, textStatus, errorThrown) {
201
- var container = extractContainer("", xhr, options)
215
+ var respUrl = xhr.getResponseHeader('X-PJAX-URL')
216
+ if (respUrl) url = stripPjaxParam(respUrl)
202
217
 
203
218
  // DEPRECATED: Invoke original `error` handler
204
219
  if (oldError) oldError.apply(this, arguments)
205
220
 
206
221
  var allowed = fire('pjax:error', [xhr, textStatus, errorThrown, options])
207
222
  if (textStatus !== 'abort' && allowed)
208
- window.location = container.url
223
+ window.location = url
209
224
  }
210
225
 
211
226
  options.success = function(data, status, xhr) {
212
- var container = extractContainer(data, xhr, options)
227
+ var respUrl = xhr.getResponseHeader('X-PJAX-URL')
228
+ if (respUrl) url = stripPjaxParam(respUrl)
229
+
230
+ var title, oldTitle = document.title
231
+
232
+ if ( options.fragment ) {
233
+ // If they specified a fragment, look for it in the response
234
+ // and pull it out.
235
+ var html = $('<html>').html(data)
236
+ var $fragment = html.find(options.fragment)
237
+ if ( $fragment.length ) {
238
+ this.html($fragment.contents())
239
+
240
+ // If there's a <title> tag in the response, use it as
241
+ // the page's title. Otherwise, look for data-title and title attributes.
242
+ title = html.find('title').text() || $fragment.attr('title') || $fragment.data('title')
243
+ } else {
244
+ return window.location = url
245
+ }
246
+ } else {
247
+ // If we got no data or an entire web page, go directly
248
+ // to the page and let normal error handling happen.
249
+ if ( !$.trim(data) || /<html/i.test(data) )
250
+ return window.location = url
251
+
252
+ this.html(data)
213
253
 
214
- if (!container.contents) {
215
- window.location = container.url
216
- return
254
+ // If there's a <title> tag in the response, use it as
255
+ // the page's title.
256
+ title = this.find('title').remove().text()
217
257
  }
218
258
 
219
- pjax.state = {
220
- id: options.id || uniqueId(),
221
- url: container.url,
222
- container: context.selector,
259
+ if ( title ) document.title = $.trim(title)
260
+
261
+ var state = {
262
+ url: url,
263
+ pjax: this.selector,
223
264
  fragment: options.fragment,
224
265
  timeout: options.timeout
225
266
  }
226
267
 
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)
268
+ if ( options.replace ) {
269
+ pjax.active = true
270
+ window.history.replaceState(state, document.title, url)
271
+ } else if ( options.push ) {
272
+ // this extra replaceState before first push ensures good back
273
+ // button behavior
274
+ if ( !pjax.active ) {
275
+ window.history.replaceState($.extend({}, state, {url:null}), oldTitle)
276
+ pjax.active = true
277
+ }
233
278
 
234
- // Scroll to top by default
235
- if (typeof options.scrollTo === 'number')
236
- $(window).scrollTop(options.scrollTo)
279
+ window.history.pushState(state, document.title, url)
280
+ }
237
281
 
238
282
  // Google Analytics support
239
283
  if ( (options.replace || options.push) && window._gaq )
@@ -252,21 +296,6 @@ var pjax = $.pjax = function( options ) {
252
296
  }
253
297
 
254
298
 
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
299
  // Cancel the current request if we're already pjaxing
271
300
  var xhr = pjax.xhr
272
301
  if ( xhr && xhr.readyState < 4) {
@@ -276,47 +305,12 @@ var pjax = $.pjax = function( options ) {
276
305
 
277
306
  pjax.options = options
278
307
  pjax.xhr = $.ajax(options)
279
-
280
- // pjax event is deprecated
281
308
  $(document).trigger('pjax', [pjax.xhr, options])
282
309
 
283
310
  return pjax.xhr
284
311
  }
285
312
 
286
313
 
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
314
  // Internal: Build options Object for arguments.
321
315
  //
322
316
  // For convenience the first parameter can be either the container or
@@ -376,181 +370,15 @@ function findContainerFor(container) {
376
370
  }
377
371
  }
378
372
 
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
373
 
475
374
  pjax.defaults = {
476
375
  timeout: 650,
477
376
  push: true,
478
377
  replace: false,
479
378
  type: 'GET',
480
- dataType: 'html',
481
- scrollTo: 0,
482
- maxCacheLength: 20
379
+ dataType: 'html'
483
380
  }
484
381
 
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
382
  // Export $.pjax.click
555
383
  pjax.click = handleClick
556
384
 
@@ -572,54 +400,18 @@ $(window).bind('popstate', function(event){
572
400
 
573
401
  var state = event.state
574
402
 
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,
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,
593
409
  container: container,
594
410
  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 {
411
+ timeout: state.timeout
412
+ })
413
+ else
621
414
  window.location = location.href
622
- }
623
415
  }
624
416
  })
625
417
 
@@ -672,7 +464,6 @@ if ( !$.support.pjax ) {
672
464
  form.submit()
673
465
  }
674
466
  $.pjax.click = $.noop
675
- $.pjax.reload = window.location.reload
676
467
  $.fn.pjax = function() { return this }
677
468
  }
678
469
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-pjax
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Helioid (original David Heinemeier Hansson and PJAX by Chris Wanstrath)
@@ -42,6 +42,7 @@ extra_rdoc_files: []
42
42
  files:
43
43
  - ./pjax_rails.gemspec
44
44
  - ./README.md
45
+ - ./rails-pjax-0.0.1.gem
45
46
  - ./vendor/assets/javascripts/jquery.pjax.js
46
47
  - ./lib/pjax_rails.rb
47
48
  - ./lib/pjax.rb