Commit b264ce02d1936312a503f13eedd6eb7ea1a524ac
Exists in
master
and in
1 other branch
Merge pull request #107 from martinciu/notices-paging
notices paging using pjax
Showing
4 changed files
with
285 additions
and
4 deletions
Show diff stats
app/controllers/errs_controller.rb
| @@ -34,6 +34,10 @@ class ErrsController < ApplicationController | @@ -34,6 +34,10 @@ class ErrsController < ApplicationController | ||
| 34 | @notices = @problem.notices.paginate(:page => page, :per_page => 1) | 34 | @notices = @problem.notices.paginate(:page => page, :per_page => 1) |
| 35 | @notice = @notices.first | 35 | @notice = @notices.first |
| 36 | @comment = Comment.new | 36 | @comment = Comment.new |
| 37 | + if request.headers['X-PJAX'] | ||
| 38 | + params["_pjax"] = nil | ||
| 39 | + render :layout => false | ||
| 40 | + end | ||
| 37 | end | 41 | end |
| 38 | 42 | ||
| 39 | def create_issue | 43 | def create_issue |
config/application.rb
| @@ -39,7 +39,7 @@ module Errbit | @@ -39,7 +39,7 @@ module Errbit | ||
| 39 | # config.i18n.default_locale = :de | 39 | # config.i18n.default_locale = :de |
| 40 | 40 | ||
| 41 | # JavaScript files you want as :defaults (application.js is always included). | 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 | # > rails generate - config | 44 | # > rails generate - config |
| 45 | config.generators do |g| | 45 | config.generators do |g| |
public/javascripts/application.js
| @@ -2,6 +2,8 @@ | @@ -2,6 +2,8 @@ | ||
| 2 | 2 | ||
| 3 | $(function() { | 3 | $(function() { |
| 4 | 4 | ||
| 5 | + var currentTab = "summary"; | ||
| 6 | + | ||
| 5 | function init() { | 7 | function init() { |
| 6 | 8 | ||
| 7 | activateTabbedPanels(); | 9 | activateTabbedPanels(); |
| @@ -31,6 +33,17 @@ $(function() { | @@ -31,6 +33,17 @@ $(function() { | ||
| 31 | $('input[type=submit][data-action]').click(function() { | 33 | $('input[type=submit][data-action]').click(function() { |
| 32 | $(this).closest('form').attr('action', $(this).attr('data-action')); | 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 | function activateTabbedPanels() { | 49 | function activateTabbedPanels() { |
| @@ -39,13 +52,13 @@ $(function() { | @@ -39,13 +52,13 @@ $(function() { | ||
| 39 | var panel = $('#'+tab.attr('rel')); | 52 | var panel = $('#'+tab.attr('rel')); |
| 40 | panel.addClass('panel'); | 53 | panel.addClass('panel'); |
| 41 | panel.find('h3').hide(); | 54 | panel.find('h3').hide(); |
| 42 | - }) | 55 | + }); |
| 43 | 56 | ||
| 44 | $('.tab-bar a').click(function(){ | 57 | $('.tab-bar a').click(function(){ |
| 45 | activateTab($(this)); | 58 | activateTab($(this)); |
| 46 | return(false); | 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 | function activateTab(tab) { | 64 | function activateTab(tab) { |
| @@ -65,7 +78,7 @@ $(function() { | @@ -65,7 +78,7 @@ $(function() { | ||
| 65 | var checkbox = $(this).find('input[name="problems[]"]'); | 78 | var checkbox = $(this).find('input[name="problems[]"]'); |
| 66 | checkbox.attr('checked', !checkbox.is(':checked')); | 79 | checkbox.attr('checked', !checkbox.is(':checked')); |
| 67 | } | 80 | } |
| 68 | - }) | 81 | + }); |
| 69 | } | 82 | } |
| 70 | 83 | ||
| 71 | init(); | 84 | init(); |
| @@ -0,0 +1,264 @@ | @@ -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); |