BUGFIX Updated jquery.changetracker behaviour in LeftAndMain javascript to properly respond to window.onbeforeunload events

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@92688 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2009-11-21 03:14:14 +00:00
parent 55ed72d2aa
commit f267dac02c
3 changed files with 66 additions and 41 deletions

View File

@ -22,42 +22,47 @@
ChangeTrackerOptions: {}, ChangeTrackerOptions: {},
onmatch: function() { onmatch: function() {
var self = this;
this._setupChangeTracker(); this._setupChangeTracker();
// Can't bind this through jQuery
window.onbeforeunload = function(e) {return self._checkChangeTracker(false);};
$._super(); $._super();
}, },
_setupChangeTracker: function() { _setupChangeTracker: function() {
var self = this;
// Don't bind any events here, as we dont replace the // Don't bind any events here, as we dont replace the
// full <form> tag by any ajax updates they won't automatically reapply // full <form> tag by any ajax updates they won't automatically reapply
this.changetracker(this.ChangeTrackerOptions()); this.changetracker(this.ChangeTrackerOptions());
},
var autoSaveOnUnload = function(e) {
// @todo TinyMCE coupling /**
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave(); * Checks the jquery.changetracker plugin status for this form.
if(self.is('.changed') && confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED'))) { * Usually bound to window.onbeforeunload.
// unloads can't be prevented, but we can delay it with a synchronous ajax request *
self.ajaxSubmit( * @param {boolean} doConfirm
self.find(':submit[name=action_save]'), * @return Either a string with a confirmation message, or the result of a confirm() dialog,
null, * based on the doConfirm parameter.
{async: false} */
); _checkChangeTracker: function(doConfirm) {
} var self = this;
};
// @todo TinyMCE coupling
// use custom IE 'onbeforeunload' event, as it destroys the DOM if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
// before going into 'unload' if(self.is('.changed')) {
if(typeof window.onbeforeunload != 'undefined') { var msg = ss.i18n._t('LeftAndMain.CONFIRMUNSAVED');
window.onbeforeunload = autoSaveOnUnload; // returned string will trigger a confirm() dialog,
} else { // but only if the method is triggered by an event
$(window).unload(autoSaveOnUnload); return (doConfirm) ? confirm(msg) : msg;
} }
}, },
/** /**
* Suppress submission unless it is handled through ajaxSubmit() * Suppress submission unless it is handled through ajaxSubmit().
*
* @param {Event} e
*/ */
onsubmit: function(e) { onsubmit: function(e) {
return false; return false;
@ -66,15 +71,17 @@
/** /**
* @param {DOMElement} button The pressed button (optional) * @param {DOMElement} button The pressed button (optional)
* @param {Function} callback Called in complete() handler of jQuery.ajax() * @param {Function} callback Called in complete() handler of jQuery.ajax()
* @param {Object} ajaxOptions Object literal to merge into $.ajax() call
* @param {boolean} loadResponse Render response through _loadResponse() (Default: true)
*/ */
ajaxSubmit: function(button, callback, ajaxOptions) { ajaxSubmit: function(button, callback, ajaxOptions, loadResponse) {
var self = this;
// look for save button // look for save button
if(!button) button = this.find('.Actions :submit[name=action_save]'); if(!button) button = this.find('.Actions :submit[name=action_save]');
// default to first button if none given - simulates browser behaviour // default to first button if none given - simulates browser behaviour
if(!button) button = this.find('.Actions :submit:first'); if(!button) button = this.find('.Actions :submit:first');
var self = this;
this.trigger('ajaxsubmit', {button: button}); this.trigger('ajaxsubmit', {button: button});
// set button to "submitting" state // set button to "submitting" state
@ -97,17 +104,22 @@
var formData = this.serializeArray(); var formData = this.serializeArray();
// add button action // add button action
formData.push({name: $(button).attr('name'), value:'1'}); formData.push({name: $(button).attr('name'), value:'1'});
$.ajax($.extend({ jQuery.ajax(jQuery.extend({
url: this.attr('action'), url: this.attr('action'),
data: formData, data: formData,
type: 'POST', type: 'POST',
complete: function(xmlhttp, status) { complete: function(xmlhttp, status) {
$(button).removeClass('loading'); $(button).removeClass('loading');
// TODO This should be using the plugin API
self.removeClass('changed');
if(callback) callback(xmlhttp, status); if(callback) callback(xmlhttp, status);
// pass along original form data to enable old/new comparisons // pass along original form data to enable old/new comparisons
self._loadResponse(xmlhttp.responseText, status, xmlhttp, formData); if(loadResponse !== false) {
self._loadResponse(xmlhttp.responseText, status, xmlhttp, formData);
}
}, },
dataType: 'html' dataType: 'html'
}, ajaxOptions)); }, ajaxOptions));
@ -131,19 +143,28 @@
}, },
/** /**
* @param String url * @param {String} url
* @param Function callback (Optional) * @param {Function} callback (Optional)
* @param {ajaxOptions} Object literal merged into the jQuery.ajax() call (Optional)
*/ */
load: function(url, callback) { load: function(url, callback, ajaxOptions) {
var self = this; var self = this;
$.ajax({
// Alert when unsaved changes are present
if(!this._checkChangeTracker(true)) return false;
return jQuery.ajax(jQuery.extend({
url: url, url: url,
complete: function(xmlhttp, status) { complete: function(xmlhttp, status) {
self._loadResponse(xmlhttp.responseText, status, xmlhttp); // TODO This should be using the plugin API
self.removeClass('changed');
if(callback) callback.apply(self, arguments); if(callback) callback.apply(self, arguments);
self._loadResponse(xmlhttp.responseText, status, xmlhttp);
}, },
dataType: 'html' dataType: 'html'
}); }, ajaxOptions));
}, },
/** /**
@ -206,6 +227,8 @@
} else { } else {
this.removeForm(); this.removeForm();
} }
this._setupChangeTracker();
// Optionally get the form attributes from embedded fields, see Form->formHtmlContent() // Optionally get the form attributes from embedded fields, see Form->formHtmlContent()
for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) { for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) {
@ -245,7 +268,8 @@
$('#Form_EditForm .Actions :submit').concrete('ss', function($){ $('#Form_EditForm .Actions :submit').concrete('ss', function($){
return/** @lends ss.Form_EditForm.Actions.submit */{ return/** @lends ss.Form_EditForm.Actions.submit */{
onmatch: function() { onmatch: function() {
var self = this; var self = this;
// TODO Fix once concrete library is updated // TODO Fix once concrete library is updated
this.bind('click', function(e) {return self.clickFake(e);}); this.bind('click', function(e) {return self.clickFake(e);});
}, },

View File

@ -202,7 +202,7 @@ TreeNodeAPI.prototype = {
if(this.getElementsByTagName('a')[0].href) { if(this.getElementsByTagName('a')[0].href) {
_AJAX_LOADING = true; _AJAX_LOADING = true;
if($('sitetree').notify('SelectionChanged', this)) { if($('sitetree').notify('SelectionChanged', this)) {
jQuery('#Form_EditForm').concrete('ss').ajaxSubmit(null, this.getPageFromServer.bind(this)); this.getPageFromServer();
} }
} }
}, },
@ -210,9 +210,7 @@ TreeNodeAPI.prototype = {
getPageFromServer : function() { getPageFromServer : function() {
var self = this; var self = this;
this.addNodeClass('loading'); var xmlhttp = jQuery('#Form_EditForm').concrete('ss').load(
jQuery('#Form_EditForm').concrete('ss').load(
jQuery(this).find('a').attr('href'), jQuery(this).find('a').attr('href'),
function(response) { function(response) {
self.removeNodeClass('loading'); self.removeNodeClass('loading');
@ -223,6 +221,9 @@ TreeNodeAPI.prototype = {
} }
} }
); );
if(xmlhttp) this.addNodeClass('loading');
_AJAX_LOADING = false; _AJAX_LOADING = false;
}, },
ajaxExpansion : function() { ajaxExpansion : function() {

View File

@ -24,6 +24,6 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
'ModelAdmin.DELETED': "Deleted", 'ModelAdmin.DELETED': "Deleted",
'ModelAdmin.VALIDATIONERROR': "Validation Error", 'ModelAdmin.VALIDATIONERROR': "Validation Error",
'LeftAndMain.PAGEWASDELETED': "This page was deleted. To edit a page, select it from the left.", 'LeftAndMain.PAGEWASDELETED': "This page was deleted. To edit a page, select it from the left.",
'LeftAndMain.CONFIRMUNSAVED': "You have unsaved changes. Are you sure you want to navigate away from this page?\n\nPress OK to save your changes automatically." 'LeftAndMain.CONFIRMUNSAVED': "Are you sure you want to navigate away from this page?\n\nWARNING: Your changes have not been saved.\n\nPress OK to continue, or Cancel to stay on the current page."
}); });
} }