From 66ca5405d0cb8116e5cdf5f886b96d321b20477c Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 31 Jul 2015 10:16:18 +1200 Subject: [PATCH] BUG Fix change detection on browser back button --- admin/javascript/LeftAndMain.EditForm.js | 12 +++- admin/javascript/LeftAndMain.js | 86 ++++++++++++++++++++---- 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/admin/javascript/LeftAndMain.EditForm.js b/admin/javascript/LeftAndMain.EditForm.js index 1a2b3e047..79414bfc6 100644 --- a/admin/javascript/LeftAndMain.EditForm.js +++ b/admin/javascript/LeftAndMain.EditForm.js @@ -129,13 +129,23 @@ * Doesn't cancel any unload or form removal events, you'll need to implement this based on the return * value of this message. * + * If changes are confirmed for discard, the 'changed' flag is reset. + * * Returns: * (Boolean) FALSE if the user wants to abort with changes present, TRUE if no changes are detected * or the user wants to discard them. */ confirmUnsavedChanges: function() { this.trigger('beforesubmitform'); - return (this.is('.changed')) ? confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED')) : true; + if(!this.is('.changed')) { + return true; + } + var confirmed = confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED')); + if(confirmed) { + // confirm discard changes + this.removeClass('changed'); + } + return confirmed; }, /** diff --git a/admin/javascript/LeftAndMain.js b/admin/javascript/LeftAndMain.js index 8e424f123..cbaed7a67 100644 --- a/admin/javascript/LeftAndMain.js +++ b/admin/javascript/LeftAndMain.js @@ -253,12 +253,13 @@ jQuery.noConflict(); $('body').removeClass('loading'); $(window).unbind('resize', positionLoadingSpinner); this.restoreTabState(); - this._super(); }, fromWindow: { - onstatechange: function(){ this.handleStateChange(); } + onstatechange: function(e){ + this.handleStateChange(e); + } }, 'onwindowresize': function() { @@ -358,6 +359,34 @@ jQuery.noConflict(); this.find('.cms-preview').redraw(); this.find('.cms-content').redraw(); }, + + /** + * Confirm whether the current user can navigate away from this page + * + * @param {array} selectors Optional list of selectors + * @returns {boolean} True if the navigation can proceed + */ + checkCanNavigate: function(selectors) { + // Check change tracking (can't use events as we need a way to cancel the current state change) + var contentEls = this._findFragments(selectors || ['Content']), + trackedEls = contentEls + .find(':data(changetracker)') + .add(contentEls.filter(':data(changetracker)')), + safe = true; + + if(!trackedEls.length) { + return true; + } + + trackedEls.each(function() { + // See LeftAndMain.EditForm.js + if(!$(this).confirmUnsavedChanges()) { + safe = false; + } + }); + + return safe; + }, /** * Proxy around History.pushState() which handles non-HTML5 fallbacks, @@ -377,18 +406,9 @@ jQuery.noConflict(); if(!title) title = ""; if (!forceReferer) forceReferer = History.getState().url; - // Check change tracking (can't use events as we need a way to cancel the current state change) - var contentEls = this._findFragments(data.pjax ? data.pjax.split(',') : ['Content']); - var trackedEls = contentEls.find(':data(changetracker)').add(contentEls.filter(':data(changetracker)')); - - if(trackedEls.length) { - var abort = false; - - trackedEls.each(function() { - if(!$(this).confirmUnsavedChanges()) abort = true; - }); - - if(abort) return; + // Check for unsaved changes + if(!this.checkCanNavigate(data.pjax ? data.pjax.split(',') : ['Content'])) { + return; } // Save tab selections so we can restore them later @@ -493,6 +513,16 @@ jQuery.noConflict(); return false; }, + + /** + * Last html5 history state + */ + LastState: null, + + /** + * Flag to pause handleStateChange + */ + PauseState: false, /** * Handles ajax loading of new panels through the window.History object. @@ -517,6 +547,10 @@ jQuery.noConflict(); * if the URL is loaded without ajax. */ handleStateChange: function() { + if(this.getPauseState()) { + return; + } + // Don't allow parallel loading to avoid edge cases if(this.getStateChangeXHR()) this.getStateChangeXHR().abort(); @@ -534,6 +568,30 @@ jQuery.noConflict(); return; } + if(!this.checkCanNavigate()) { + // If history is emulated (ie8 or below) disable attempting to restore + if(h.emulated.pushState) { + return; + } + + var lastState = this.getLastState(); + + // Suppress panel loading while resetting state + this.setPauseState(true); + + // Restore best last state + if(lastState) { + h.pushState(lastState.id, lastState.title, lastState.url); + } else { + h.back(); + } + this.setPauseState(false); + + // Abort loading of this panel + return; + } + this.setLastState(state); + // If any of the requested Pjax fragments don't exist in the current view, // fetch the "Content" view instead, which is the "outermost" fragment // that can be reloaded without reloading the whole window.