diff --git a/app/controllers/errs_controller.rb b/app/controllers/errs_controller.rb index 618db0e..b941d93 100644 --- a/app/controllers/errs_controller.rb +++ b/app/controllers/errs_controller.rb @@ -34,6 +34,10 @@ class ErrsController < ApplicationController @notices = @problem.notices.paginate(:page => page, :per_page => 1) @notice = @notices.first @comment = Comment.new + if request.headers['X-PJAX'] + params["_pjax"] = nil + render :layout => false + end end def create_issue diff --git a/config/application.rb b/config/application.rb index a7656b5..d2270c2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -39,7 +39,7 @@ module Errbit # config.i18n.default_locale = :de # JavaScript files you want as :defaults (application.js is always included). - config.action_view.javascript_expansions[:defaults] = %w(jquery underscore-1.1.6 rails form) + config.action_view.javascript_expansions[:defaults] = %w(jquery underscore-1.1.6 rails form jquery.pjax) # > rails generate - config config.generators do |g| diff --git a/public/javascripts/application.js b/public/javascripts/application.js index fe792f2..e3bc0db 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -2,6 +2,8 @@ $(function() { + var currentTab = "summary"; + function init() { activateTabbedPanels(); @@ -31,6 +33,17 @@ $(function() { $('input[type=submit][data-action]').click(function() { $(this).closest('form').attr('action', $(this).attr('data-action')); }); + + $('.notice-pagination').each(function() { + $('.notice-pagination a').pjax('#content', { timeout: 2000}); + $('#content').bind('pjax:start', function() { + currentTab = $('.tab-bar ul li a.button.active').attr('rel'); + }); + + $('#content').bind('pjax:end', function() { + activateTabbedPanels(); + }); + }); } function activateTabbedPanels() { @@ -39,13 +52,13 @@ $(function() { var panel = $('#'+tab.attr('rel')); panel.addClass('panel'); panel.find('h3').hide(); - }) + }); $('.tab-bar a').click(function(){ activateTab($(this)); return(false); }); - activateTab($('.tab-bar a').first()); + activateTab($('.tab-bar ul li a.button[rel=' + currentTab + ']')); } function activateTab(tab) { @@ -65,7 +78,7 @@ $(function() { var checkbox = $(this).find('input[name="problems[]"]'); checkbox.attr('checked', !checkbox.is(':checked')); } - }) + }); } init(); diff --git a/public/javascripts/jquery.pjax.js b/public/javascripts/jquery.pjax.js new file mode 100644 index 0000000..6d2533a --- /dev/null +++ b/public/javascripts/jquery.pjax.js @@ -0,0 +1,264 @@ +// jquery.pjax.js +// copyright chris wanstrath +// https://github.com/defunkt/jquery-pjax + +(function($){ + +// When called on a link, fetches the href with ajax into the +// container specified as the first parameter or with the data-pjax +// attribute on the link itself. +// +// Tries to make sure the back button and ctrl+click work the way +// you'd expect. +// +// Accepts a jQuery ajax options object that may include these +// pjax specific options: +// +// container - Where to stick the response body. Usually a String selector. +// $(container).html(xhr.responseBody) +// push - Whether to pushState the URL. Defaults to true (of course). +// replace - Want to use replaceState instead? That's cool. +// +// For convenience the first parameter can be either the container or +// the options object. +// +// Returns the jQuery object +$.fn.pjax = function( container, options ) { + if ( options ) + options.container = container + else + options = $.isPlainObject(container) ? container : {container:container} + + // We can't persist $objects using the history API so we must use + // a String selector. Bail if we got anything else. + if ( options.container && typeof options.container !== 'string' ) { + throw "pjax container must be a string selector!" + return false + } + + return this.live('click', function(event){ + // Middle click, cmd click, and ctrl click should open + // links in a new tab as normal. + if ( event.which > 1 || event.metaKey ) + return true + + var defaults = { + url: this.href, + container: $(this).attr('data-pjax'), + clickedElement: $(this), + fragment: null + } + + $.pjax($.extend({}, defaults, options)) + + event.preventDefault() + }) +} + + +// Loads a URL with ajax, puts the response body inside a container, +// then pushState()'s the loaded URL. +// +// Works just like $.ajax in that it accepts a jQuery ajax +// settings object (with keys like url, type, data, etc). +// +// Accepts these extra keys: +// +// container - Where to stick the response body. Must be a String. +// $(container).html(xhr.responseBody) +// push - Whether to pushState the URL. Defaults to true (of course). +// replace - Want to use replaceState instead? That's cool. +// +// Use it just like $.ajax: +// +// var xhr = $.pjax({ url: this.href, container: '#main' }) +// console.log( xhr.readyState ) +// +// Returns whatever $.ajax returns. +var pjax = $.pjax = function( options ) { + var $container = $(options.container), + success = options.success || $.noop + + // We don't want to let anyone override our success handler. + delete options.success + + // We can't persist $objects using the history API so we must use + // a String selector. Bail if we got anything else. + if ( typeof options.container !== 'string' ) + throw "pjax container must be a string selector!" + + options = $.extend(true, {}, pjax.defaults, options) + + if ( $.isFunction(options.url) ) { + options.url = options.url() + } + + options.context = $container + + options.success = function(data){ + if ( options.fragment ) { + // If they specified a fragment, look for it in the response + // and pull it out. + var $fragment = $(data).find(options.fragment) + if ( $fragment.length ) + data = $fragment.children() + else + return window.location = options.url + } else { + // If we got no data or an entire web page, go directly + // to the page and let normal error handling happen. + if ( !$.trim(data) || / tag in the response, use it as + // the page's title. + var oldTitle = document.title, + title = $.trim( this.find('title').remove().text() ) + if ( title ) document.title = title + + // No ? Fragment? Look for data-title and title attributes. + if ( !title && options.fragment ) { + title = $fragment.attr('title') || $fragment.data('title') + } + + var state = { + pjax: options.container, + fragment: options.fragment, + timeout: options.timeout + } + + // If there are extra params, save the complete URL in the state object + var query = $.param(options.data) + if ( query != "_pjax=true" ) + state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query + + if ( options.replace ) { + window.history.replaceState(state, document.title, options.url) + } else if ( options.push ) { + // this extra replaceState before first push ensures good back + // button behavior + if ( !pjax.active ) { + window.history.replaceState($.extend({}, state, {url:null}), oldTitle) + pjax.active = true + } + + window.history.pushState(state, document.title, options.url) + } + + // Google Analytics support + if ( (options.replace || options.push) && window._gaq ) + _gaq.push(['_trackPageview']) + + // If the URL has a hash in it, make sure the browser + // knows to navigate to the hash. + var hash = window.location.hash.toString() + if ( hash !== '' ) { + window.location.href = hash + } + + // Invoke their success handler if they gave us one. + success.apply(this, arguments) + } + + // Cancel the current request if we're already pjaxing + var xhr = pjax.xhr + if ( xhr && xhr.readyState < 4) { + xhr.onreadystatechange = $.noop + xhr.abort() + } + + pjax.options = options + pjax.xhr = $.ajax(options) + $(document).trigger('pjax', [pjax.xhr, options]) + + return pjax.xhr +} + + +pjax.defaults = { + timeout: 650, + push: true, + replace: false, + // We want the browser to maintain two separate internal caches: one for + // pjax'd partial page loads and one for normal page loads. Without + // adding this secret parameter, some browsers will often confuse the two. + data: { _pjax: true }, + type: 'GET', + dataType: 'html', + beforeSend: function(xhr){ + this.trigger('pjax:start', [xhr, pjax.options]) + // start.pjax is deprecated + this.trigger('start.pjax', [xhr, pjax.options]) + xhr.setRequestHeader('X-PJAX', 'true') + }, + error: function(xhr, textStatus, errorThrown){ + if ( textStatus !== 'abort' ) + window.location = pjax.options.url + }, + complete: function(xhr){ + this.trigger('pjax:end', [xhr, pjax.options]) + // end.pjax is deprecated + this.trigger('end.pjax', [xhr, pjax.options]) + } +} + + +// Used to detect initial (useless) popstate. +// If history.state exists, assume browser isn't going to fire initial popstate. +var popped = ('state' in window.history), initialURL = location.href + + +// popstate handler takes care of the back and forward buttons +// +// You probably shouldn't use pjax on pages with other pushState +// stuff yet. +$(window).bind('popstate', function(event){ + // Ignore inital popstate that some browsers fire on page load + var initialPop = !popped && location.href == initialURL + popped = true + if ( initialPop ) return + + var state = event.state + + if ( state && state.pjax ) { + var container = state.pjax + if ( $(container+'').length ) + $.pjax({ + url: state.url || location.href, + fragment: state.fragment, + container: container, + push: false, + timeout: state.timeout + }) + else + window.location = location.href + } +}) + + +// Add the state property to jQuery's event object so we can use it in +// $(window).bind('popstate') +if ( $.inArray('state', $.event.props) < 0 ) + $.event.props.push('state') + + +// Is pjax supported by this browser? +$.support.pjax = + window.history && window.history.pushState && window.history.replaceState + // pushState isn't reliable on iOS yet. + && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/) + + +// Fall back to normalcy for older browsers. +if ( !$.support.pjax ) { + $.pjax = function( options ) { + window.location = $.isFunction(options.url) ? options.url() : options.url + } + $.fn.pjax = function() { return this } +} + +})(jQuery); -- libgit2 0.21.2