API CHANGE Removed ChangeTracker javascript class, superseded by jquery.changetracker

API CHANGE Removed autoSave() javascript logic in CMS, superseded by custom code in LeftAndMain.EditForm.js
ENHANCEMENT Using jquery.changetracker in LeftAndMain.EditForm.js, and overhauled window.unload logic to automatically trigger ajax saving after a user confirmation
MINOR Changed unload confirmation text in CMS logic to adapt to new behaviour

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@92685 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2009-11-21 03:14:08 +00:00
parent 5f8a164d21
commit 5aef49a0a7
8 changed files with 44 additions and 185 deletions

View File

@ -214,6 +214,7 @@ class LeftAndMain extends Controller {
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-livequery/jquery.livequery.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-cookie/jquery.cookie.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery-ondemand/jquery.ondemand.js');
Requirements::javascript(CMS_DIR . '/javascript/jquery-changetracker/lib/jquery.changetracker.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang');
@ -227,7 +228,6 @@ class LeftAndMain extends Controller {
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain_left.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain_right.js');
Requirements::javascript(CMS_DIR . '/javascript/LeftAndMain.EditForm.js');
Requirements::javascript(CMS_DIR . '/javascript/SideTabs.js');

View File

@ -124,6 +124,7 @@ var ss_MainLayout;
*/
$('#Form_EditForm').concrete('ss', function($){
return/** @lends ss.EditForm */{
/*
onmatch: function() {
// Alert the user on change of page-type - this might have implications
// on the available form fields etc.
@ -132,7 +133,10 @@ var ss_MainLayout;
alert('The page type will be updated after the page is saved');
}
);
$._super();
}
*/
};
});

View File

@ -436,11 +436,11 @@ TreeContextMenu = {
},
'Duplicate page and children' : function(treeNode) {
// First save the page silently (without confirmation) and then duplicate the page.
autoSave(false, treeNode.duplicatePageWithChildren.bind(treeNode));
jQuery('#Form_EditForm').concrete('ss').ajaxSubmit(null, treeNode.duplicatePageWithChildren.bind(treeNode));
},
'Duplicate just this page' : function(treeNode) {
// First save the page silently (without confirmation) and then duplicate the page.
autoSave(false, treeNode.duplicatePage.bind(treeNode));
jQuery('#Form_EditForm').concrete('ss').ajaxSubmit(null, treeNode.duplicatePageWithChildren.bind(treeNode));
},
'Sort sub-pages' : function(treeNode) {
var children = treeNode.treeNodeHolder().childTreeNodes();

View File

@ -5,6 +5,7 @@
* and reloading itself through the ajax return values.
* Takes care of resizing tabsets within the layout container.
* @name ss.Form_EditForm
* @require jquery.changetracker
*/
$('#Form_EditForm').concrete('ss',function($){
return/** @lends ss.Form_EditForm */{
@ -15,11 +16,44 @@
*/
RemoveText: 'Removed',
/**
* @type Object
*/
ChangeTrackerOptions: {},
onmatch: function() {
this._setupChangeTracker();
$._super();
},
_setupChangeTracker: function() {
var self = this;
// Don't bind any events here, as we dont replace the
// full <form> tag by any ajax updates they won't automatically reapply
this.changetracker(this.ChangeTrackerOptions());
_super();
var autoSaveOnUnload = function(e) {
// @todo TinyMCE coupling
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
if(self.is('.changed') && confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED'))) {
// unloads can't be prevented, but we can delay it with a synchronous ajax request
self.ajaxSubmit(
self.find(':submit[name=action_save]'),
null,
{async: false}
);
}
};
// use custom IE 'onbeforeunload' event, as it destroys the DOM
// before going into 'unload'
if(typeof window.onbeforeunload != 'undefined') {
window.onbeforeunload = autoSaveOnUnload;
} else {
$(window).unload(autoSaveOnUnload);
}
},
/**

View File

@ -293,25 +293,6 @@
var _AJAX_LOADING = false;
Behaviour.register({
'#MainMenu li' : {
onclick : function(event) {
LeftAndMain_window_unload(); // Confirm if there are unsaved changes
window.location.href = this.getElementsByTagName('a')[0].href;
Event.stop(event);
}
}
});
LeftAndMain_window_unload = function() {
window.exiting = true; // this is used by prototype
if(typeof autoSave == 'function') {
return autoSave(true);
}
}
// Event.observe(window, 'beforeunload', LeftAndMain_window_unload);
@ -498,105 +479,6 @@ StatusTitle.prototype = {
}
*/
/**
* ChangeTracker is a class that can be applied to forms to support change tracking on forms.
*/
ChangeTracker = Class.create();
ChangeTracker.prototype = {
initialize: function() {
this.resetElements();
},
/**
* Reset all the 'changed field' data.
*/
resetElements: function(debug) {
var elements = Form.getElements(this);
var i, element;
for(i=0;element=elements[i];i++) {
// Initialise each element
if(element.resetChanged) {
element.resetChanged();
} else {
element.originalSerialized = Form.Element.serialize(element);
}
}
},
field_changed: function() {
// Something a value will go from 'undefined' to ''. Ignore such changes
if((this.originalSerialized+'') == 'undefined') return Form.Element.serialize(this) ? true : false;
else return this.originalSerialized != Form.Element.serialize(this);
},
/**
* Returns true if something in the form has been changed
*/
isChanged: function() {
var elements = Form.getElements(this);
var i, element;
for(i=0;element=elements[i];i++) {
// NOTE: TinyMCE coupling
// Ignore mce-generated elements
if(element.className.substr(0,3) == 'mce') continue;
if(!element.isChanged) element.isChanged = this.field_changed;
if(!this.changeDetection_fieldsToIgnore[element.name] && element.isChanged()) {
//console.log('Changed:'+ element.id + '(' + this.originalSerialized +')->('+Form.Element.serialize(element)+')' );
//console.log(element)
return true;
}
}
return false;
},
changeDetection_fieldsToIgnore : {
'Sort' : true
},
/**
* Serialize only the fields to change.
* You can specify the names of fields that must be included as arguments
*/
serializeChangedFields: function() {
var elements = Form.getElements(this);
var queryComponent, queryComponents = new Array();
var i, element;
var forceFields = {};
if(arguments) {for(var i=0;i<arguments.length;i++) forceFields[arguments[i]] = true;}
for(i=0;element=elements[i];i++) {
if(!element.name.match(/^action_(.+)$/i)) // For dropdown those 'action_xxx' fields.
{ if(!element.isChanged) element.isChanged = this.field_changed;
if(forceFields[element.name] || (element.isChanged()) || element.name.match(/\[.*\]/g) ) {
queryComponent = Form.Element.serialize(element);
if (queryComponent)
queryComponents.push(queryComponent);
} else {
// Used by the Sapphire code to preserve the form field value
if( element.name.match( '/\]$/' ) )
queryComponents.push(element.name.substring( 0, element.name.length - 1 ) + '_unchanged' + ']=1' );
else
queryComponents.push(element.name + '_unchanged=1');
}
}
}
//alert(queryComponents.join('&'));
return queryComponents.join('&');
},
/**
* Serialize all the fields on the page
*/
serializeAllFields: function() {
return Form.serializeWithoutButtons(this);
}
}
function hideLoading() {
if($('Loading')) $('Loading').style.display = 'none';
Element.removeClassName(document.body, 'stillLoading');

View File

@ -202,7 +202,7 @@ TreeNodeAPI.prototype = {
if(this.getElementsByTagName('a')[0].href) {
_AJAX_LOADING = true;
if($('sitetree').notify('SelectionChanged', this)) {
autoSave(true, this.getPageFromServer.bind(this));
jQuery('#Form_EditForm').concrete('ss').ajaxSubmit(null, this.getPageFromServer.bind(this));
}
}
},

View File

@ -1,61 +0,0 @@
/**
* Handle auto-saving. Detects if changes have been made, and if so save everything on the page.
* If confirmation is true it will ask for confirmation.
*/
function autoSave(confirmation, callAfter) {
// Note: TinyMCE coupling
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
var __forms = []
if($('Form_EditForm')) __forms.push($('Form_EditForm'));
if($('Form_SubForm')) __forms.push($('Form_SubForm'));
if($('Form_MemberForm')) __forms.push($('Form_MemberForm'));
var __somethingHasChanged = false;
var __callAfter = callAfter;
__forms.each(function(form) {
if(form.isChanged && form.isChanged()) {
__somethingHasChanged = true;
}
});
if(__somethingHasChanged) {
// Note: discard and cancel options are no longer used since switching to confirm dialog.
// save is still used if confirmation = false
var options = {
save: function() {
statusMessage(ss.i18n._t('CMSMAIN.SAVING'), '', true);
var i;
for(i=0;i<__forms.length;i++) {
if(__forms[i].isChanged && __forms[i].isChanged()) {
if(i == 0) __forms[i].save(true, __callAfter);
else __forms[i].save(true);
}
}
},
discard: function() {
__forms.each(function(form) { form.resetElements(false); });
if(__callAfter) __callAfter();
},
cancel: function() {
}
}
if(confirmation ) {
if(confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED')))
{
// OK was pressed, call function for what was clicked on
if(__callAfter) __callAfter();
} else {
// Cancel was pressed, stay on the current page
return false;
}
} else {
options.save();
}
} else {
if(__callAfter) __callAfter();
}
}

View File

@ -24,6 +24,6 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
'ModelAdmin.DELETED': "Deleted",
'ModelAdmin.VALIDATIONERROR': "Validation Error",
'LeftAndMain.PAGEWASDELETED': "This page was deleted. To edit a page, select it from the left.",
'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."
'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."
});
}