Commit e17caf089de82c5ffadb0e15b7ed67ea3a36896f

Authored by Marcin Ciunelis
1 parent b9c9ce3b
Exists in master and in 1 other branch production

notices paging using pjax

app/controllers/errs_controller.rb
... ... @@ -34,6 +34,10 @@ class ErrsController < ApplicationController
34 34 @notices = @problem.notices.paginate(:page => page, :per_page => 1)
35 35 @notice = @notices.first
36 36 @comment = Comment.new
  37 + if request.headers['X-PJAX']
  38 + params["_pjax"] = nil
  39 + render :layout => false
  40 + end
37 41 end
38 42  
39 43 def create_issue
... ...
config/application.rb
... ... @@ -39,7 +39,7 @@ module Errbit
39 39 # config.i18n.default_locale = :de
40 40  
41 41 # JavaScript files you want as :defaults (application.js is always included).
42   - config.action_view.javascript_expansions[:defaults] = %w(jquery underscore-1.1.6 rails form)
  42 + config.action_view.javascript_expansions[:defaults] = %w(jquery underscore-1.1.6 rails form jquery.pjax)
43 43  
44 44 # > rails generate - config
45 45 config.generators do |g|
... ...
public/javascripts/application.js
... ... @@ -2,6 +2,8 @@
2 2  
3 3 $(function() {
4 4  
  5 + var currentTab = "summary";
  6 +
5 7 function init() {
6 8  
7 9 activateTabbedPanels();
... ... @@ -31,6 +33,17 @@ $(function() {
31 33 $('input[type=submit][data-action]').click(function() {
32 34 $(this).closest('form').attr('action', $(this).attr('data-action'));
33 35 });
  36 +
  37 + $('.notice-pagination').each(function() {
  38 + $('.notice-pagination a').pjax('#content', { timeout: 2000});
  39 + $('#content').bind('pjax:start', function() {
  40 + currentTab = $('.tab-bar ul li a.button.active').attr('rel');
  41 + });
  42 +
  43 + $('#content').bind('pjax:end', function() {
  44 + activateTabbedPanels();
  45 + });
  46 + });
34 47 }
35 48  
36 49 function activateTabbedPanels() {
... ... @@ -39,13 +52,13 @@ $(function() {
39 52 var panel = $('#'+tab.attr('rel'));
40 53 panel.addClass('panel');
41 54 panel.find('h3').hide();
42   - })
  55 + });
43 56  
44 57 $('.tab-bar a').click(function(){
45 58 activateTab($(this));
46 59 return(false);
47 60 });
48   - activateTab($('.tab-bar a').first());
  61 + activateTab($('.tab-bar ul li a.button[rel=' + currentTab + ']'));
49 62 }
50 63  
51 64 function activateTab(tab) {
... ... @@ -65,7 +78,7 @@ $(function() {
65 78 var checkbox = $(this).find('input[name="problems[]"]');
66 79 checkbox.attr('checked', !checkbox.is(':checked'));
67 80 }
68   - })
  81 + });
69 82 }
70 83  
71 84 init();
... ...
public/javascripts/jquery.pjax.js 0 → 100644
... ... @@ -0,0 +1,264 @@
  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 + if ( options )
  28 + options.container = container
  29 + else
  30 + options = $.isPlainObject(container) ? container : {container:container}
  31 +
  32 + // We can't persist $objects using the history API so we must use
  33 + // a String selector. Bail if we got anything else.
  34 + if ( options.container && typeof options.container !== 'string' ) {
  35 + throw "pjax container must be a string selector!"
  36 + return false
  37 + }
  38 +
  39 + return this.live('click', function(event){
  40 + // Middle click, cmd click, and ctrl click should open
  41 + // links in a new tab as normal.
  42 + if ( event.which > 1 || event.metaKey )
  43 + return true
  44 +
  45 + var defaults = {
  46 + url: this.href,
  47 + container: $(this).attr('data-pjax'),
  48 + clickedElement: $(this),
  49 + fragment: null
  50 + }
  51 +
  52 + $.pjax($.extend({}, defaults, options))
  53 +
  54 + event.preventDefault()
  55 + })
  56 +}
  57 +
  58 +
  59 +// Loads a URL with ajax, puts the response body inside a container,
  60 +// then pushState()'s the loaded URL.
  61 +//
  62 +// Works just like $.ajax in that it accepts a jQuery ajax
  63 +// settings object (with keys like url, type, data, etc).
  64 +//
  65 +// Accepts these extra keys:
  66 +//
  67 +// container - Where to stick the response body. Must be a String.
  68 +// $(container).html(xhr.responseBody)
  69 +// push - Whether to pushState the URL. Defaults to true (of course).
  70 +// replace - Want to use replaceState instead? That's cool.
  71 +//
  72 +// Use it just like $.ajax:
  73 +//
  74 +// var xhr = $.pjax({ url: this.href, container: '#main' })
  75 +// console.log( xhr.readyState )
  76 +//
  77 +// Returns whatever $.ajax returns.
  78 +var pjax = $.pjax = function( options ) {
  79 + var $container = $(options.container),
  80 + success = options.success || $.noop
  81 +
  82 + // We don't want to let anyone override our success handler.
  83 + delete options.success
  84 +
  85 + // We can't persist $objects using the history API so we must use
  86 + // a String selector. Bail if we got anything else.
  87 + if ( typeof options.container !== 'string' )
  88 + throw "pjax container must be a string selector!"
  89 +
  90 + options = $.extend(true, {}, pjax.defaults, options)
  91 +
  92 + if ( $.isFunction(options.url) ) {
  93 + options.url = options.url()
  94 + }
  95 +
  96 + options.context = $container
  97 +
  98 + options.success = function(data){
  99 + if ( options.fragment ) {
  100 + // If they specified a fragment, look for it in the response
  101 + // and pull it out.
  102 + var $fragment = $(data).find(options.fragment)
  103 + if ( $fragment.length )
  104 + data = $fragment.children()
  105 + else
  106 + return window.location = options.url
  107 + } else {
  108 + // If we got no data or an entire web page, go directly
  109 + // to the page and let normal error handling happen.
  110 + if ( !$.trim(data) || /<html/i.test(data) )
  111 + return window.location = options.url
  112 + }
  113 +
  114 + // Make it happen.
  115 + this.html(data)
  116 +
  117 + // If there's a <title> tag in the response, use it as
  118 + // the page's title.
  119 + var oldTitle = document.title,
  120 + title = $.trim( this.find('title').remove().text() )
  121 + if ( title ) document.title = title
  122 +
  123 + // No <title>? Fragment? Look for data-title and title attributes.
  124 + if ( !title && options.fragment ) {
  125 + title = $fragment.attr('title') || $fragment.data('title')
  126 + }
  127 +
  128 + var state = {
  129 + pjax: options.container,
  130 + fragment: options.fragment,
  131 + timeout: options.timeout
  132 + }
  133 +
  134 + // If there are extra params, save the complete URL in the state object
  135 + var query = $.param(options.data)
  136 + if ( query != "_pjax=true" )
  137 + state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query
  138 +
  139 + if ( options.replace ) {
  140 + window.history.replaceState(state, document.title, options.url)
  141 + } else if ( options.push ) {
  142 + // this extra replaceState before first push ensures good back
  143 + // button behavior
  144 + if ( !pjax.active ) {
  145 + window.history.replaceState($.extend({}, state, {url:null}), oldTitle)
  146 + pjax.active = true
  147 + }
  148 +
  149 + window.history.pushState(state, document.title, options.url)
  150 + }
  151 +
  152 + // Google Analytics support
  153 + if ( (options.replace || options.push) && window._gaq )
  154 + _gaq.push(['_trackPageview'])
  155 +
  156 + // If the URL has a hash in it, make sure the browser
  157 + // knows to navigate to the hash.
  158 + var hash = window.location.hash.toString()
  159 + if ( hash !== '' ) {
  160 + window.location.href = hash
  161 + }
  162 +
  163 + // Invoke their success handler if they gave us one.
  164 + success.apply(this, arguments)
  165 + }
  166 +
  167 + // Cancel the current request if we're already pjaxing
  168 + var xhr = pjax.xhr
  169 + if ( xhr && xhr.readyState < 4) {
  170 + xhr.onreadystatechange = $.noop
  171 + xhr.abort()
  172 + }
  173 +
  174 + pjax.options = options
  175 + pjax.xhr = $.ajax(options)
  176 + $(document).trigger('pjax', [pjax.xhr, options])
  177 +
  178 + return pjax.xhr
  179 +}
  180 +
  181 +
  182 +pjax.defaults = {
  183 + timeout: 650,
  184 + push: true,
  185 + replace: false,
  186 + // We want the browser to maintain two separate internal caches: one for
  187 + // pjax'd partial page loads and one for normal page loads. Without
  188 + // adding this secret parameter, some browsers will often confuse the two.
  189 + data: { _pjax: true },
  190 + type: 'GET',
  191 + dataType: 'html',
  192 + beforeSend: function(xhr){
  193 + this.trigger('pjax:start', [xhr, pjax.options])
  194 + // start.pjax is deprecated
  195 + this.trigger('start.pjax', [xhr, pjax.options])
  196 + xhr.setRequestHeader('X-PJAX', 'true')
  197 + },
  198 + error: function(xhr, textStatus, errorThrown){
  199 + if ( textStatus !== 'abort' )
  200 + window.location = pjax.options.url
  201 + },
  202 + complete: function(xhr){
  203 + this.trigger('pjax:end', [xhr, pjax.options])
  204 + // end.pjax is deprecated
  205 + this.trigger('end.pjax', [xhr, pjax.options])
  206 + }
  207 +}
  208 +
  209 +
  210 +// Used to detect initial (useless) popstate.
  211 +// If history.state exists, assume browser isn't going to fire initial popstate.
  212 +var popped = ('state' in window.history), initialURL = location.href
  213 +
  214 +
  215 +// popstate handler takes care of the back and forward buttons
  216 +//
  217 +// You probably shouldn't use pjax on pages with other pushState
  218 +// stuff yet.
  219 +$(window).bind('popstate', function(event){
  220 + // Ignore inital popstate that some browsers fire on page load
  221 + var initialPop = !popped && location.href == initialURL
  222 + popped = true
  223 + if ( initialPop ) return
  224 +
  225 + var state = event.state
  226 +
  227 + if ( state && state.pjax ) {
  228 + var container = state.pjax
  229 + if ( $(container+'').length )
  230 + $.pjax({
  231 + url: state.url || location.href,
  232 + fragment: state.fragment,
  233 + container: container,
  234 + push: false,
  235 + timeout: state.timeout
  236 + })
  237 + else
  238 + window.location = location.href
  239 + }
  240 +})
  241 +
  242 +
  243 +// Add the state property to jQuery's event object so we can use it in
  244 +// $(window).bind('popstate')
  245 +if ( $.inArray('state', $.event.props) < 0 )
  246 + $.event.props.push('state')
  247 +
  248 +
  249 +// Is pjax supported by this browser?
  250 +$.support.pjax =
  251 + window.history && window.history.pushState && window.history.replaceState
  252 + // pushState isn't reliable on iOS yet.
  253 + && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/)
  254 +
  255 +
  256 +// Fall back to normalcy for older browsers.
  257 +if ( !$.support.pjax ) {
  258 + $.pjax = function( options ) {
  259 + window.location = $.isFunction(options.url) ? options.url() : options.url
  260 + }
  261 + $.fn.pjax = function() { return this }
  262 +}
  263 +
  264 +})(jQuery);
... ...