pjax_rails 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,6 +9,11 @@ module Pjax
9
9
  before_filter :set_pjax_url, :if => :pjax_request?
10
10
  end
11
11
 
12
+ class Error < StandardError; end
13
+ class Unsupported < Error; end
14
+
15
+ rescue_from Pjax::Unsupported, :with => :pjax_unsupported
16
+
12
17
  protected
13
18
  def pjax_request?
14
19
  env['HTTP_X_PJAX'].present?
@@ -23,9 +28,28 @@ module Pjax
23
28
  request.headers['X-PJAX-Container']
24
29
  end
25
30
 
31
+ def pjax_unsupported
32
+ head :not_acceptable
33
+ end
34
+
35
+ # Call in a before_filter or in an action to disable pjax on an action.
36
+ #
37
+ # Examples
38
+ #
39
+ # before_filter :prevent_pjax!
40
+ #
41
+ # def login
42
+ # prevent_pjax!
43
+ # # ...
44
+ # end
45
+ #
46
+ def prevent_pjax!
47
+ raise PjaxUnsupported if pjax_request?
48
+ end
49
+
26
50
  def strip_pjax_param
27
51
  params.delete(:_pjax)
28
- request.env['QUERY_STRING'].sub!(/_pjax=[^&]+&?/, '')
52
+ request.env['QUERY_STRING'] = request.env['QUERY_STRING'].sub(/_pjax=[^&]+&?/, '')
29
53
 
30
54
  request.env.delete('rack.request.query_string')
31
55
  request.env.delete('rack.request.query_hash')
@@ -23,7 +23,7 @@
23
23
  // the options object.
24
24
  //
25
25
  // Returns the jQuery object
26
- $.fn.pjax = function( container, options ) {
26
+ function fnPjax(container, options) {
27
27
  return this.live('click.pjax', function(event){
28
28
  handleClick(event, container, options)
29
29
  })
@@ -82,12 +82,11 @@ function handleClick(event, container, options) {
82
82
  fragment: null
83
83
  }
84
84
 
85
- $.pjax($.extend({}, defaults, options))
85
+ pjax($.extend({}, defaults, options))
86
86
 
87
87
  event.preventDefault()
88
88
  }
89
89
 
90
-
91
90
  // Loads a URL with ajax, puts the response body inside a container,
92
91
  // then pushState()'s the loaded URL.
93
92
  //
@@ -107,7 +106,7 @@ function handleClick(event, container, options) {
107
106
  // console.log( xhr.readyState )
108
107
  //
109
108
  // Returns whatever $.ajax returns.
110
- var pjax = $.pjax = function( options ) {
109
+ function pjax(options) {
111
110
  options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options)
112
111
 
113
112
  if ($.isFunction(options.url)) {
@@ -146,6 +145,12 @@ var pjax = $.pjax = function( options ) {
146
145
  var timeoutTimer
147
146
 
148
147
  options.beforeSend = function(xhr, settings) {
148
+ // No timeout for non-GET requests
149
+ // Its not safe to request the resource again with a fallback method.
150
+ if (settings.type !== 'GET') {
151
+ settings.timeout = 0
152
+ }
153
+
149
154
  if (settings.timeout > 0) {
150
155
  timeoutTimer = setTimeout(function() {
151
156
  if (fire('pjax:timeout', [xhr, options]))
@@ -302,6 +307,121 @@ var pjax = $.pjax = function( options ) {
302
307
  return pjax.xhr
303
308
  }
304
309
 
310
+ // Public: Reload current page with pjax.
311
+ //
312
+ // Returns whatever $.pjax returns.
313
+ function pjaxReload(container, options) {
314
+ var defaults = {
315
+ url: window.location.href,
316
+ push: false,
317
+ replace: true,
318
+ scrollTo: false
319
+ }
320
+
321
+ return pjax($.extend(defaults, optionsFor(container, options)))
322
+ }
323
+
324
+ // popstate handler takes care of the back and forward buttons
325
+ //
326
+ // You probably shouldn't use pjax on pages with other pushState
327
+ // stuff yet.
328
+ function onPjaxPopstate(event) {
329
+ var state = event.state
330
+
331
+ if (state && state.container) {
332
+ var container = $(state.container)
333
+ if (container.length) {
334
+ var contents = cacheMapping[state.id]
335
+
336
+ if (pjax.state) {
337
+ // Since state ids always increase, we can deduce the history
338
+ // direction from the previous state.
339
+ var direction = pjax.state.id < state.id ? 'forward' : 'back'
340
+
341
+ // Cache current container before replacement and inform the
342
+ // cache which direction the history shifted.
343
+ cachePop(direction, pjax.state.id, container.clone().contents())
344
+ }
345
+
346
+ var popstateEvent = $.Event('pjax:popstate', {
347
+ state: state,
348
+ direction: direction
349
+ })
350
+ container.trigger(popstateEvent)
351
+
352
+ var options = {
353
+ id: state.id,
354
+ url: state.url,
355
+ container: container,
356
+ push: false,
357
+ fragment: state.fragment,
358
+ timeout: state.timeout,
359
+ scrollTo: false
360
+ }
361
+
362
+ if (contents) {
363
+ // pjax event is deprecated
364
+ $(document).trigger('pjax', [null, options])
365
+ container.trigger('pjax:start', [null, options])
366
+ // end.pjax event is deprecated
367
+ container.trigger('start.pjax', [null, options])
368
+
369
+ if (state.title) document.title = state.title
370
+ container.html(contents)
371
+ pjax.state = state
372
+
373
+ container.trigger('pjax:end', [null, options])
374
+ // end.pjax event is deprecated
375
+ container.trigger('end.pjax', [null, options])
376
+ } else {
377
+ pjax(options)
378
+ }
379
+
380
+ // Force reflow/relayout before the browser tries to restore the
381
+ // scroll position.
382
+ container[0].offsetHeight
383
+ } else {
384
+ window.location = location.href
385
+ }
386
+ }
387
+ }
388
+
389
+ // Fallback version of main pjax function for browsers that don't
390
+ // support pushState.
391
+ //
392
+ // Returns nothing since it retriggers a hard form submission.
393
+ function fallbackPjax(options) {
394
+ var url = $.isFunction(options.url) ? options.url() : options.url,
395
+ method = options.type ? options.type.toUpperCase() : 'GET'
396
+
397
+ var form = $('<form>', {
398
+ method: method === 'GET' ? 'GET' : 'POST',
399
+ action: url,
400
+ style: 'display:none'
401
+ })
402
+
403
+ if (method !== 'GET' && method !== 'POST') {
404
+ form.append($('<input>', {
405
+ type: 'hidden',
406
+ name: '_method',
407
+ value: method.toLowerCase()
408
+ }))
409
+ }
410
+
411
+ var data = options.data
412
+ if (typeof data === 'string') {
413
+ $.each(data.split('&'), function(index, value) {
414
+ var pair = value.split('=')
415
+ form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
416
+ })
417
+ } else if (typeof data === 'object') {
418
+ for (key in data)
419
+ form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
420
+ }
421
+
422
+ $(document.body).append(form)
423
+ form.submit()
424
+ }
305
425
 
306
426
  // Internal: Generate unique id for state object.
307
427
  //
@@ -476,35 +596,11 @@ function extractContainer(data, xhr, options) {
476
596
  return obj
477
597
  }
478
598
 
479
- // Public: Reload current page with pjax.
480
- //
481
- // Returns whatever $.pjax returns.
482
- pjax.reload = function(container, options) {
483
- var defaults = {
484
- url: window.location.href,
485
- push: false,
486
- replace: true,
487
- scrollTo: false
488
- }
489
-
490
- return $.pjax($.extend(defaults, optionsFor(container, options)))
491
- }
492
-
493
-
494
- pjax.defaults = {
495
- timeout: 650,
496
- push: true,
497
- replace: false,
498
- type: 'GET',
499
- dataType: 'html',
500
- scrollTo: 0,
501
- maxCacheLength: 20
502
- }
503
-
504
599
  // Internal: History DOM caching class.
505
600
  var cacheMapping = {}
506
601
  var cacheForwardStack = []
507
602
  var cacheBackStack = []
603
+
508
604
  // Push previous state id and container contents into the history
509
605
  // cache. Should be called in conjunction with `pushState` to save the
510
606
  // previous container contents.
@@ -526,6 +622,7 @@ function cachePush(id, value) {
526
622
  while (cacheBackStack.length > pjax.defaults.maxCacheLength)
527
623
  delete cacheMapping[cacheBackStack.shift()]
528
624
  }
625
+
529
626
  // Shifts cache from directional history cache. Should be
530
627
  // called on `popstate` with the previous state id and container
531
628
  // contents.
@@ -552,75 +649,54 @@ function cachePop(direction, id, value) {
552
649
  delete cacheMapping[id]
553
650
  }
554
651
 
555
-
556
- // Export $.pjax.click
557
- pjax.click = handleClick
558
-
559
-
560
- // popstate handler takes care of the back and forward buttons
652
+ // Install pjax functions on $.pjax to enable pushState behavior.
561
653
  //
562
- // You probably shouldn't use pjax on pages with other pushState
563
- // stuff yet.
564
- $(window).bind('popstate', function(event){
565
- var state = event.state
566
-
567
- if (state && state.container) {
568
- var container = $(state.container)
569
- if (container.length) {
570
- var contents = cacheMapping[state.id]
571
-
572
- if (pjax.state) {
573
- // Since state ids always increase, we can deduce the history
574
- // direction from the previous state.
575
- var direction = pjax.state.id < state.id ? 'forward' : 'back'
576
-
577
- // Cache current container before replacement and inform the
578
- // cache which direction the history shifted.
579
- cachePop(direction, pjax.state.id, container.clone().contents())
580
- }
581
-
582
- var popstateEvent = $.Event('pjax:popstate', {
583
- state: state,
584
- direction: direction
585
- })
586
- container.trigger(popstateEvent)
587
-
588
- var options = {
589
- id: state.id,
590
- url: state.url,
591
- container: container,
592
- push: false,
593
- fragment: state.fragment,
594
- timeout: state.timeout,
595
- scrollTo: false
596
- }
597
-
598
- if (contents) {
599
- // pjax event is deprecated
600
- $(document).trigger('pjax', [null, options])
601
- container.trigger('pjax:start', [null, options])
602
- // end.pjax event is deprecated
603
- container.trigger('start.pjax', [null, options])
604
-
605
- if (state.title) document.title = state.title
606
- container.html(contents)
607
- pjax.state = state
608
-
609
- container.trigger('pjax:end', [null, options])
610
- // end.pjax event is deprecated
611
- container.trigger('end.pjax', [null, options])
612
- } else {
613
- $.pjax(options)
614
- }
615
-
616
- // Force reflow/relayout before the browser tries to restore the
617
- // scroll position.
618
- container[0].offsetHeight
619
- } else {
620
- window.location = location.href
621
- }
654
+ // Does nothing if already enabled.
655
+ //
656
+ // Examples
657
+ //
658
+ // $.pjax.enable()
659
+ //
660
+ // Returns nothing.
661
+ function enable() {
662
+ $.fn.pjax = fnPjax
663
+ $.pjax = pjax
664
+ $.pjax.enable = $.noop
665
+ $.pjax.disable = disable
666
+ $.pjax.click = handleClick
667
+ $.pjax.reload = pjaxReload
668
+ $.pjax.defaults = {
669
+ timeout: 650,
670
+ push: true,
671
+ replace: false,
672
+ type: 'GET',
673
+ dataType: 'html',
674
+ scrollTo: 0,
675
+ maxCacheLength: 20
622
676
  }
623
- })
677
+ $(window).bind('popstate.pjax', onPjaxPopstate)
678
+ }
679
+
680
+ // Disable pushState behavior.
681
+ //
682
+ // This is the case when a browser doesn't support pushState. It is
683
+ // sometimes useful to disable pushState for debugging on a modern
684
+ // browser.
685
+ //
686
+ // Examples
687
+ //
688
+ // $.pjax.disable()
689
+ //
690
+ // Returns nothing.
691
+ function disable() {
692
+ $.fn.pjax = function() { return this }
693
+ $.pjax = fallbackPjax
694
+ $.pjax.enable = enable
695
+ $.pjax.disable = $.noop
696
+ $.pjax.click = $.noop
697
+ $.pjax.reload = window.location.reload
698
+ $(window).unbind('popstate.pjax', onPjaxPopstate)
699
+ }
624
700
 
625
701
 
626
702
  // Add the state property to jQuery's event object so we can use it in
@@ -628,50 +704,12 @@ $(window).bind('popstate', function(event){
628
704
  if ( $.inArray('state', $.event.props) < 0 )
629
705
  $.event.props.push('state')
630
706
 
631
-
632
- // Is pjax supported by this browser?
707
+ // Is pjax supported by this browser?
633
708
  $.support.pjax =
634
- window.history && window.history.pushState && window.history.replaceState
709
+ window.history && window.history.pushState && window.history.replaceState &&
635
710
  // pushState isn't reliable on iOS until 5.
636
- && !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
637
-
638
- // Fall back to normalcy for older browsers.
639
- if ( !$.support.pjax ) {
640
- $.pjax = function( options ) {
641
- var url = $.isFunction(options.url) ? options.url() : options.url,
642
- method = options.type ? options.type.toUpperCase() : 'GET'
643
-
644
- var form = $('<form>', {
645
- method: method === 'GET' ? 'GET' : 'POST',
646
- action: url,
647
- style: 'display:none'
648
- })
649
-
650
- if (method !== 'GET' && method !== 'POST') {
651
- form.append($('<input>', {
652
- type: 'hidden',
653
- name: '_method',
654
- value: method.toLowerCase()
655
- }))
656
- }
657
-
658
- var data = options.data
659
- if (typeof data === 'string') {
660
- $.each(data.split('&'), function(index, value) {
661
- var pair = value.split('=')
662
- form.append($('<input>', {type: 'hidden', name: pair[0], value: pair[1]}))
663
- })
664
- } else if (typeof data === 'object') {
665
- for (key in data)
666
- form.append($('<input>', {type: 'hidden', name: key, value: data[key]}))
667
- }
711
+ !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]|WebApps\/.+CFNetwork)/)
668
712
 
669
- $(document.body).append(form)
670
- form.submit()
671
- }
672
- $.pjax.click = $.noop
673
- $.pjax.reload = window.location.reload
674
- $.fn.pjax = function() { return this }
675
- }
713
+ $.support.pjax ? enable() : disable()
676
714
 
677
715
  })(jQuery);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pjax_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-09 00:00:00.000000000 Z
12
+ date: 2012-08-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: jquery-rails