Merge pull request #4458 from tractorcow/pulls/3.2/fix-change-detection-back

BUG Fix change detection on browser back button
This commit is contained in:
Sam Minnée 2015-08-03 13:52:05 +12:00
commit 8425a2b5a3
2 changed files with 83 additions and 15 deletions

View File

@ -129,13 +129,23 @@
* Doesn't cancel any unload or form removal events, you'll need to implement this based on the return * Doesn't cancel any unload or form removal events, you'll need to implement this based on the return
* value of this message. * value of this message.
* *
* If changes are confirmed for discard, the 'changed' flag is reset.
*
* Returns: * Returns:
* (Boolean) FALSE if the user wants to abort with changes present, TRUE if no changes are detected * (Boolean) FALSE if the user wants to abort with changes present, TRUE if no changes are detected
* or the user wants to discard them. * or the user wants to discard them.
*/ */
confirmUnsavedChanges: function() { confirmUnsavedChanges: function() {
this.trigger('beforesubmitform'); 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;
}, },
/** /**

View File

@ -253,12 +253,13 @@ jQuery.noConflict();
$('body').removeClass('loading'); $('body').removeClass('loading');
$(window).unbind('resize', positionLoadingSpinner); $(window).unbind('resize', positionLoadingSpinner);
this.restoreTabState(); this.restoreTabState();
this._super(); this._super();
}, },
fromWindow: { fromWindow: {
onstatechange: function(){ this.handleStateChange(); } onstatechange: function(e){
this.handleStateChange(e);
}
}, },
'onwindowresize': function() { 'onwindowresize': function() {
@ -359,6 +360,34 @@ jQuery.noConflict();
this.find('.cms-content').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, * Proxy around History.pushState() which handles non-HTML5 fallbacks,
* as well as global change tracking. Change tracking needs to be synchronous rather than event/callback * as well as global change tracking. Change tracking needs to be synchronous rather than event/callback
@ -377,18 +406,9 @@ jQuery.noConflict();
if(!title) title = ""; if(!title) title = "";
if (!forceReferer) forceReferer = History.getState().url; if (!forceReferer) forceReferer = History.getState().url;
// Check change tracking (can't use events as we need a way to cancel the current state change) // Check for unsaved changes
var contentEls = this._findFragments(data.pjax ? data.pjax.split(',') : ['Content']); if(!this.checkCanNavigate(data.pjax ? data.pjax.split(',') : ['Content'])) {
var trackedEls = contentEls.find(':data(changetracker)').add(contentEls.filter(':data(changetracker)')); return;
if(trackedEls.length) {
var abort = false;
trackedEls.each(function() {
if(!$(this).confirmUnsavedChanges()) abort = true;
});
if(abort) return;
} }
// Save tab selections so we can restore them later // Save tab selections so we can restore them later
@ -494,6 +514,16 @@ jQuery.noConflict();
return false; 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. * Handles ajax loading of new panels through the window.History object.
* To trigger loading, pass a new URL to window.History.pushState(). * To trigger loading, pass a new URL to window.History.pushState().
@ -517,6 +547,10 @@ jQuery.noConflict();
* if the URL is loaded without ajax. * if the URL is loaded without ajax.
*/ */
handleStateChange: function() { handleStateChange: function() {
if(this.getPauseState()) {
return;
}
// Don't allow parallel loading to avoid edge cases // Don't allow parallel loading to avoid edge cases
if(this.getStateChangeXHR()) this.getStateChangeXHR().abort(); if(this.getStateChangeXHR()) this.getStateChangeXHR().abort();
@ -534,6 +568,30 @@ jQuery.noConflict();
return; 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, // If any of the requested Pjax fragments don't exist in the current view,
// fetch the "Content" view instead, which is the "outermost" fragment // fetch the "Content" view instead, which is the "outermost" fragment
// that can be reloaded without reloading the whole window. // that can be reloaded without reloading the whole window.