2009-11-21 03:38:20 +01:00
|
|
|
(function($) {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @class Base edit form, provides ajaxified saving
|
|
|
|
* and reloading itself through the ajax return values.
|
|
|
|
* Takes care of resizing tabsets within the layout container.
|
|
|
|
* @name ss.Form_EditForm
|
2009-11-21 04:14:08 +01:00
|
|
|
* @require jquery.changetracker
|
2009-11-21 04:14:17 +01:00
|
|
|
*
|
|
|
|
* <h3>Events</h3>
|
|
|
|
* - ajaxsubmit: Form is about to be submitted through ajax
|
|
|
|
* - validate: Contains validation result
|
|
|
|
* - removeform: A form is about to be removed from the DOM
|
|
|
|
* - load: Form is about to be loaded through ajax
|
2009-11-21 03:38:20 +01:00
|
|
|
*/
|
|
|
|
$('#Form_EditForm').concrete('ss',function($){
|
|
|
|
return/** @lends ss.Form_EditForm */{
|
2009-11-21 04:14:17 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
2009-11-21 04:14:22 +01:00
|
|
|
* @type String HTML text to show when no form content is chosen.
|
|
|
|
* Will show inside the <form> tag.
|
2009-11-21 03:38:31 +01:00
|
|
|
*/
|
2009-11-21 04:14:22 +01:00
|
|
|
PlaceholderHtml: '',
|
2009-11-21 03:38:46 +01:00
|
|
|
|
2009-11-21 04:14:08 +01:00
|
|
|
/**
|
|
|
|
* @type Object
|
|
|
|
*/
|
|
|
|
ChangeTrackerOptions: {},
|
|
|
|
|
2009-11-21 03:38:46 +01:00
|
|
|
onmatch: function() {
|
2009-11-21 04:14:14 +01:00
|
|
|
var self = this;
|
|
|
|
|
2009-11-21 04:14:08 +01:00
|
|
|
this._setupChangeTracker();
|
2009-11-21 04:15:23 +01:00
|
|
|
|
|
|
|
this.bind('submit', function(e) {
|
|
|
|
return self._submit(e);
|
|
|
|
});
|
2009-11-21 04:14:22 +01:00
|
|
|
|
2009-11-21 04:14:14 +01:00
|
|
|
// Can't bind this through jQuery
|
|
|
|
window.onbeforeunload = function(e) {return self._checkChangeTracker(false);};
|
|
|
|
|
2009-11-21 04:15:23 +01:00
|
|
|
this._super();
|
2009-11-21 04:14:08 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
_setupChangeTracker: function() {
|
2009-11-21 03:38:46 +01:00
|
|
|
// Don't bind any events here, as we dont replace the
|
|
|
|
// full <form> tag by any ajax updates they won't automatically reapply
|
2009-11-21 04:14:08 +01:00
|
|
|
this.changetracker(this.ChangeTrackerOptions());
|
2009-11-21 04:14:14 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks the jquery.changetracker plugin status for this form.
|
|
|
|
* Usually bound to window.onbeforeunload.
|
|
|
|
*
|
2009-11-21 04:14:38 +01:00
|
|
|
* @param {boolean} isUnloadEvent
|
2009-11-21 04:14:14 +01:00
|
|
|
* @return Either a string with a confirmation message, or the result of a confirm() dialog,
|
2009-11-21 04:14:38 +01:00
|
|
|
* based on the isUnloadEvent parameter.
|
2009-11-21 04:14:14 +01:00
|
|
|
*/
|
2009-11-21 04:14:38 +01:00
|
|
|
_checkChangeTracker: function(isUnloadEvent) {
|
2009-11-21 04:14:14 +01:00
|
|
|
var self = this;
|
|
|
|
|
|
|
|
// @todo TinyMCE coupling
|
|
|
|
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
|
2009-11-21 04:14:17 +01:00
|
|
|
|
|
|
|
// check for form changes
|
2009-11-21 04:14:14 +01:00
|
|
|
if(self.is('.changed')) {
|
|
|
|
// returned string will trigger a confirm() dialog,
|
|
|
|
// but only if the method is triggered by an event
|
2009-11-21 04:14:38 +01:00
|
|
|
if(isUnloadEvent) {
|
|
|
|
return confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED'));
|
|
|
|
} else {
|
|
|
|
return ss.i18n._t('LeftAndMain.CONFIRMUNSAVEDSHORT');
|
|
|
|
}
|
2009-11-21 04:14:08 +01:00
|
|
|
}
|
2009-11-21 03:38:46 +01:00
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
2009-11-21 04:14:14 +01:00
|
|
|
* Suppress submission unless it is handled through ajaxSubmit().
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2009-11-21 03:38:31 +01:00
|
|
|
*/
|
2009-11-21 04:15:23 +01:00
|
|
|
_submit: function(e) {
|
2009-11-21 04:17:55 +01:00
|
|
|
this.ajaxSubmit();
|
2009-11-21 03:38:31 +01:00
|
|
|
return false;
|
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
|
|
|
* @param {DOMElement} button The pressed button (optional)
|
2009-11-21 04:14:05 +01:00
|
|
|
* @param {Function} callback Called in complete() handler of jQuery.ajax()
|
2009-11-21 04:14:14 +01:00
|
|
|
* @param {Object} ajaxOptions Object literal to merge into $.ajax() call
|
|
|
|
* @param {boolean} loadResponse Render response through _loadResponse() (Default: true)
|
2009-11-21 03:38:31 +01:00
|
|
|
*/
|
2009-11-21 04:14:14 +01:00
|
|
|
ajaxSubmit: function(button, callback, ajaxOptions, loadResponse) {
|
2009-11-21 04:17:55 +01:00
|
|
|
var self = this;
|
2009-11-21 04:14:14 +01:00
|
|
|
|
2009-11-21 04:14:05 +01:00
|
|
|
// look for save button
|
|
|
|
if(!button) button = this.find('.Actions :submit[name=action_save]');
|
2009-11-21 03:38:31 +01:00
|
|
|
// default to first button if none given - simulates browser behaviour
|
2009-11-21 04:14:05 +01:00
|
|
|
if(!button) button = this.find('.Actions :submit:first');
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
this.trigger('ajaxsubmit', {button: button});
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
// set button to "submitting" state
|
|
|
|
$(button).addClass('loading');
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
// @todo TinyMCE coupling
|
|
|
|
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
// validate if required
|
|
|
|
if(!this.validate()) {
|
|
|
|
// TODO Automatically switch to the tab/position of the first error
|
|
|
|
statusMessage("Validation failed.", "bad");
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
$(button).removeClass('loading');
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
return false;
|
|
|
|
}
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
// get all data from the form
|
2009-11-21 03:39:06 +01:00
|
|
|
var formData = this.serializeArray();
|
2009-11-21 03:38:31 +01:00
|
|
|
// add button action
|
2009-11-21 03:39:06 +01:00
|
|
|
formData.push({name: $(button).attr('name'), value:'1'});
|
2009-11-21 04:14:14 +01:00
|
|
|
jQuery.ajax(jQuery.extend({
|
2009-11-21 03:39:06 +01:00
|
|
|
url: this.attr('action'),
|
|
|
|
data: formData,
|
|
|
|
type: 'POST',
|
|
|
|
complete: function(xmlhttp, status) {
|
2009-11-21 03:38:31 +01:00
|
|
|
$(button).removeClass('loading');
|
2009-11-21 04:14:05 +01:00
|
|
|
|
2009-11-21 04:14:14 +01:00
|
|
|
// TODO This should be using the plugin API
|
|
|
|
self.removeClass('changed');
|
|
|
|
|
2009-11-21 04:14:05 +01:00
|
|
|
if(callback) callback(xmlhttp, status);
|
|
|
|
|
2009-11-21 03:39:06 +01:00
|
|
|
// pass along original form data to enable old/new comparisons
|
2009-11-21 04:14:14 +01:00
|
|
|
if(loadResponse !== false) {
|
|
|
|
self._loadResponse(xmlhttp.responseText, status, xmlhttp, formData);
|
|
|
|
}
|
2009-11-21 03:38:31 +01:00
|
|
|
},
|
2009-11-21 03:39:06 +01:00
|
|
|
dataType: 'html'
|
2009-11-21 04:14:05 +01:00
|
|
|
}, ajaxOptions));
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
return false;
|
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
|
|
|
* Hook in (optional) validation routines.
|
|
|
|
* Currently clientside validation is not supported out of the box in the CMS.
|
|
|
|
*
|
|
|
|
* @todo Placeholder implementation
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
validate: function() {
|
|
|
|
var isValid = true;
|
|
|
|
this.trigger('validate', {isValid: isValid});
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
return isValid;
|
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
2009-11-21 04:14:14 +01:00
|
|
|
* @param {String} url
|
2009-11-21 04:15:04 +01:00
|
|
|
* @param {Function} callback (Optional) Called after the form content as been loaded
|
2009-11-21 04:14:14 +01:00
|
|
|
* @param {ajaxOptions} Object literal merged into the jQuery.ajax() call (Optional)
|
2009-11-21 03:38:31 +01:00
|
|
|
*/
|
2009-11-21 04:15:23 +01:00
|
|
|
loadForm: function(url, callback, ajaxOptions) {
|
2009-11-21 04:15:04 +01:00
|
|
|
var self = this;
|
|
|
|
|
2009-11-21 04:14:17 +01:00
|
|
|
// Alert when unsaved changes are present
|
|
|
|
if(this._checkChangeTracker(true) == false) return false;
|
2009-11-21 04:14:14 +01:00
|
|
|
|
2009-11-21 04:17:04 +01:00
|
|
|
// hide existing form - shown again through _loadResponse()
|
|
|
|
this.addClass('loading');
|
2009-11-21 04:17:36 +01:00
|
|
|
|
|
|
|
this.trigger('load', {url: url});
|
2009-11-21 04:17:25 +01:00
|
|
|
|
|
|
|
this.cleanup();
|
2009-11-21 04:14:17 +01:00
|
|
|
|
2009-11-21 04:14:14 +01:00
|
|
|
return jQuery.ajax(jQuery.extend({
|
2009-11-21 03:39:06 +01:00
|
|
|
url: url,
|
|
|
|
complete: function(xmlhttp, status) {
|
2009-11-21 04:14:14 +01:00
|
|
|
// TODO This should be using the plugin API
|
|
|
|
self.removeClass('changed');
|
2009-11-21 04:15:23 +01:00
|
|
|
|
2009-11-21 04:14:14 +01:00
|
|
|
self._loadResponse(xmlhttp.responseText, status, xmlhttp);
|
2009-11-21 04:17:04 +01:00
|
|
|
|
|
|
|
self.removeClass('loading');
|
2009-11-21 04:15:23 +01:00
|
|
|
|
2009-11-21 04:15:04 +01:00
|
|
|
if(callback) callback.apply(self, arguments);
|
2009-11-21 03:38:31 +01:00
|
|
|
},
|
2009-11-21 03:39:06 +01:00
|
|
|
dataType: 'html'
|
2009-11-21 04:14:14 +01:00
|
|
|
}, ajaxOptions));
|
2009-11-21 03:38:31 +01:00
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
|
|
|
* Remove everying inside the <form> tag
|
|
|
|
* with a custom HTML fragment. Useful e.g. for deleting a page in the CMS.
|
2009-11-21 04:14:17 +01:00
|
|
|
* Checks for unsaved changes before removing the form
|
2009-11-21 03:38:31 +01:00
|
|
|
*
|
2009-11-21 04:14:17 +01:00
|
|
|
* @param {String} placeholderHtml Short note why the form has been removed, displayed in <p> tags.
|
2009-11-21 03:38:46 +01:00
|
|
|
* Falls back to the default RemoveText() option (Optional)
|
2009-11-21 03:38:31 +01:00
|
|
|
*/
|
2009-11-21 04:14:17 +01:00
|
|
|
removeForm: function(placeholderHtml) {
|
|
|
|
if(!placeholderHtml) placeholderHtml = this.PlaceholderHtml();
|
|
|
|
|
|
|
|
// Alert when unsaved changes are present
|
|
|
|
if(this._checkChangeTracker(true) == false) return false;
|
|
|
|
|
|
|
|
this.trigger('removeform');
|
|
|
|
|
|
|
|
this.html(placeholderHtml);
|
|
|
|
|
|
|
|
// TODO This should be using the plugin API
|
|
|
|
this.removeClass('changed');
|
2009-11-21 03:38:31 +01:00
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
|
|
|
* Remove all the currently active TinyMCE editors.
|
|
|
|
* Note: Everything that calls this externally has an inappropriate coupling to TinyMCE.
|
|
|
|
*/
|
|
|
|
cleanup: function() {
|
|
|
|
if((typeof tinymce != 'undefined') && tinymce.EditorManager) {
|
|
|
|
var id;
|
|
|
|
for(id in tinymce.EditorManager.editors) {
|
|
|
|
tinymce.EditorManager.editors[id].remove();
|
|
|
|
}
|
|
|
|
tinymce.EditorManager.editors = {};
|
2009-11-21 03:38:20 +01:00
|
|
|
}
|
2009-11-21 03:38:31 +01:00
|
|
|
},
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:38:31 +01:00
|
|
|
/**
|
2009-11-21 03:39:06 +01:00
|
|
|
* @param {String} data Either HTML for straight insertion, or eval'ed JavaScript.
|
2009-11-21 03:38:31 +01:00
|
|
|
* If passed as HTML, it is assumed that everying inside the <form> tag is replaced,
|
|
|
|
* but the old <form> tag itself stays intact.
|
2009-11-21 03:39:06 +01:00
|
|
|
* @param {String} status
|
|
|
|
* @param {XMLHTTPRequest} xmlhttp
|
|
|
|
* @param {Array} origData The original submitted data, useful to do comparisons of changed
|
|
|
|
* values in new form output, e.g. to detect a URLSegment being changed on the serverside.
|
|
|
|
* Array in jQuery serializeArray() notation.
|
2009-11-21 03:38:31 +01:00
|
|
|
*/
|
2009-11-21 03:39:06 +01:00
|
|
|
_loadResponse: function(data, status, xmlhttp, origData) {
|
|
|
|
if(status == 'success') {
|
|
|
|
this.cleanup();
|
|
|
|
|
|
|
|
var html = data;
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:39:06 +01:00
|
|
|
// Rewrite # links
|
|
|
|
html = html.replace(/(<a[^>]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#');
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:39:06 +01:00
|
|
|
// Rewrite iframe links (for IE)
|
|
|
|
html = html.replace(/(<iframe[^>]*src=")([^"]+)("[^>]*>)/g, '$1' + $('base').attr('href') + '$2$3');
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:39:06 +01:00
|
|
|
// Prepare iframes for removal, otherwise we get loading bugs
|
|
|
|
this.find('iframe').each(function() {
|
|
|
|
this.contentWindow.location.href = 'about:blank';
|
2009-11-21 04:18:04 +01:00
|
|
|
$(this).remove();
|
2009-11-21 03:39:06 +01:00
|
|
|
});
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:39:06 +01:00
|
|
|
// update form content
|
|
|
|
if(html) {
|
|
|
|
this.html(html);
|
|
|
|
} else {
|
|
|
|
this.removeForm();
|
2009-11-21 03:38:48 +01:00
|
|
|
}
|
2009-11-21 04:14:14 +01:00
|
|
|
|
2009-11-21 04:17:08 +01:00
|
|
|
// @todo Coupling to avoid FOUC (concrete applies to late)
|
|
|
|
this.find('.ss-tabset').tabs();
|
|
|
|
|
2009-11-21 04:14:14 +01:00
|
|
|
this._setupChangeTracker();
|
2009-11-21 03:39:06 +01:00
|
|
|
|
|
|
|
// Optionally get the form attributes from embedded fields, see Form->formHtmlContent()
|
|
|
|
for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) {
|
|
|
|
var el = this.find(':input[name='+ '_form_' + overrideAttr + ']');
|
|
|
|
if(el) {
|
|
|
|
this.attr(overrideAttr, el.val());
|
|
|
|
el.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Behaviour.apply(); // refreshes ComplexTableField
|
|
|
|
|
|
|
|
// focus input on first form element
|
|
|
|
this.find(':input:visible:first').focus();
|
|
|
|
|
|
|
|
this.trigger('loadnewpage', {data: data, origData: origData});
|
2009-11-21 03:38:48 +01:00
|
|
|
}
|
2009-11-21 03:38:20 +01:00
|
|
|
|
2009-11-21 03:39:06 +01:00
|
|
|
// set status message based on response
|
|
|
|
var _statusMessage = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText;
|
2009-11-21 03:38:31 +01:00
|
|
|
if(this.hasClass('validationerror')) {
|
2009-11-21 03:39:06 +01:00
|
|
|
// TODO validation shouldnt need a special case
|
2009-11-21 03:38:31 +01:00
|
|
|
statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad');
|
|
|
|
} else {
|
2009-11-21 03:39:06 +01:00
|
|
|
statusMessage(_statusMessage, (xmlhttp.status >= 400) ? 'bad' : 'good');
|
2009-11-21 03:38:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
2009-11-21 03:38:20 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @class All buttons in the right CMS form go through here by default.
|
|
|
|
* We need this onclick overloading because we can't get to the
|
|
|
|
* clicked button from a form.onsubmit event.
|
|
|
|
* @name ss.Form_EditForm.Actions.submit
|
|
|
|
*/
|
|
|
|
$('#Form_EditForm .Actions :submit').concrete('ss', function($){
|
|
|
|
return/** @lends ss.Form_EditForm.Actions.submit */{
|
2009-11-21 04:17:57 +01:00
|
|
|
onmatch: function() {
|
|
|
|
this.bind('click', this._onclick);
|
|
|
|
|
|
|
|
this._super();
|
|
|
|
},
|
|
|
|
_onclick: function() {
|
|
|
|
jQuery('#Form_EditForm').concrete('ss').ajaxSubmit(this);
|
2009-11-21 03:38:33 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
2009-11-21 04:14:40 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @class Add tinymce to HtmlEditorFields within the CMS.
|
|
|
|
* @name ss.Form_EditForm.textarea.htmleditor
|
|
|
|
*/
|
|
|
|
$('#Form_EditForm textarea.htmleditor').concrete('ss', function($){
|
|
|
|
return/** @lends ss.Form_EditForm.Actions.submit */{
|
|
|
|
onmatch : function() {
|
|
|
|
tinyMCE.execCommand("mceAddControl", true, this.attr('id'));
|
|
|
|
this.isChanged = function() {
|
|
|
|
return tinyMCE.getInstanceById(this.attr('id')).isDirty();
|
|
|
|
}
|
|
|
|
this.resetChanged = function() {
|
|
|
|
var inst = tinyMCE.getInstanceById(this.attr('id'));
|
|
|
|
if (inst) inst.startContent = tinymce.trim(inst.getContent({format : 'raw', no_events : 1}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
2009-11-21 03:38:20 +01:00
|
|
|
}(jQuery));
|