Commit e17caf089de82c5ffadb0e15b7ed67ea3a36896f
1 parent
b9c9ce3b
Exists in
master
and in
1 other branch
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); |