Files
server/usr/share/psa-horde/js/inplaceeditor.js
2026-01-07 20:52:11 +01:00

461 lines
16 KiB
JavaScript

/**
* inplaceeditor.js - A javascript library which implements ajax inplace
* editing.
* Requires prototype.js v1.6.0.2+ and scriptaculous v1.8.0+ (effects.js) if
* using the default callback functions.
*
* Adapted from script.aculo.us controls.js v1.8.3
* (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
* (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
* (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
*
* Contributors:
* Richard Livsey
* Rahul Bhargava
* Rob Wills
*
* The original script was freely distributable under the terms of an
* MIT-style license.
*
* @copyright 2010-2015 Horde LLC
* @license LGPL-2.1 (http://www.horde.org/licenses/lgpl21)
*/
InPlaceEditor = Class.create(
{
/**
* Constructor
*/
initialize: function(element, url, options)
{
// Default Options/Callbacks
var defaults =
{
ajaxOptions: { },
autoRows: 3, // Use when multi-line w/ rows == 1
cancelControl: 'link', // 'link'|'button'|false
cancelText: 'cancel',
cancelClassName: 'button',
clickToEditText: 'Click to edit',
emptyClassName: 'inplaceeditor-empty',
externalControl: null, // id|elt
externalControlOnly: false,
fieldPostCreation: 'activate', // 'activate'|'focus'|false
formClassName: 'inplaceeditor-form',
formId: null, // id|elt
highlightColor: '#ffff99',
highlightEndColor: '#ffffff',
hoverClassName: '',
htmlResponse: true,
loadingClassName: 'inplaceeditor-loading',
loadingText: 'Loading...',
okControl: 'button', // 'link'|'button'|false
okText: 'ok',
okClassName: 'button',
paramName: 'value',
rows: 1, // If 1 and multi-line, uses autoRows
savingClassName: 'inplaceeditor-saving',
savingText: 'Saving...',
size: 0,
stripLoadedTextTags: false,
submitOnBlur: false,
width: null,
autoWidth: false,
/** Default Callbacks **/
callback: function(form)
{
return Form.serialize(form);
},
onComplete: function(ipe)
{
new Effect.Highlight(element, { startcolor: this.options.highlightColor, keepBackgroundImage: true });
},
onEnterEditMode: Prototype.emptyFunction,
onEnterHover: function(ipe)
{
ipe.element.style.backgroundColor = ipe.options.highlightColor;
if (ipe._effect) {
ipe._effect.cancel();
}
},
onFailure: function(transport, ipe) {
alert('Error communication with the server: ' + transport.responseText.stripTags());
},
/**
* Takes the IPE and its generated form, after editor, before controls.
*/
onFormCustomization: Prototype.emptyFunction,
onLeaveEditMode: Prototype.emptyFunction,
onLeaveHover: function(ipe)
{
ipe._effect = new Effect.Highlight(ipe.element, {
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
restorecolor: ipe._originalBackground, keepBackgroundImage: true
});
}
};
this.url = url;
this.element = element = $(element);
this._controls = { };
Object.extend(defaults, options || { });
this.options = defaults;
if (!this.options.formId && this.element.id) {
this.options.formId = this.element.id + '-inplaceeditor';
if ($(this.options.formId)) {
this.options.formId = '';
}
}
if (this.options.externalControl) {
this.options.externalControl = $(this.options.externalControl);
}
if (!this.options.externalControl) {
this.options.externalControlOnly = false;
}
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
this.element.title = this.options.clickToEditText;
this._boundCancelHandler = this.handleFormCancellation.bind(this);
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
this._boundWrapperHandler = this.wrapUp.bind(this);
this.registerListeners();
this.checkEmpty();
},
checkEmpty: function() {
if (this.element.innerHTML === 0) {
emptyNode = new Element('span', {className: this.options.emptyClassName}).update(this.options.emptyText);
this.element.appendChild(emptyNode);
}
},
keyHandler: function(e)
{
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
if (e.keyCode == Event.KEY_ESC) {
this.handleFormCancellation(e);
} else if (e.keyCode == Event.KEY_RETURN) {
this.handleFormSubmission(e);
}
},
createControl: function(mode, handler, extraClasses)
{
var control = this.options[mode + 'Control'];
var text = this.options[mode + 'Text'];
if (control == 'button') {
var btn = new Element('input', { type: 'submit', value: text, className: this.options[mode + 'ClassName'] });
if (mode == 'cancel') {
btn.observe('click', this._boundCancelHandler);
}
this._form.appendChild(btn);
this._controls[mode] = btn;
} else if (control == 'link') {
var link = new Element('a', { href: '#', className: this.options[mode + 'ClassName'] });
link.observe('click', 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler);
link.appendChild(document.createTextNode(text));
if (extraClasses) {
link.addClassName(extraClasses);
}
this._form.appendChild(link);
this._controls[mode] = link;
}
},
createEditField: function()
{
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()), fld, w;
if (this.options.rows <= 1 && !/\r|\n/.test(this.getText())) {
fld = new Element('input', { type: 'text' });
var size = this.options.size || this.options.cols || 0;
if (size > 0) {
fld.size = size;
}
} else {
fld = new Element('textarea', { rows: (this.options.rows <= 1 ? this.options.autoRows : this.options.rows),
cols: this.options.cols || 40 });
}
fld.name = this.options.paramName;
fld.value = text; // No HTML breaks conversion anymore
fld.className = 'editor_field';
if (this.options.width) {
w = this.options.width + 'px';
} else if (this.options.autoWidth) {
w = this.element.up().getWidth() + 'px';
}
fld.setStyle({ width: w });
if (this.options.submitOnBlur) {
fld.observe('blur', this._boundSubmitHandler);
}
this._controls.editor = fld;
if (this.options.loadTextURL) {
this.loadExternalText();
}
this._form.appendChild(this._controls.editor);
},
createForm: function()
{
var ipe = this;
function addText(mode, condition)
{
var text = ipe.options['text' + mode + 'Controls'];
if (!text || condition === false) return;
ipe._form.appendChild(text);
}
this._form = new Element('form', { id: this.options.formId, className: this.options.formClassName });
this._form.observe('submit', this._boundSubmitHandler);
this.createEditField();
if (this._controls.editor.tagName.toLowerCase() == 'textarea') {
this._form.appendChild(new Element('br'));
}
if (this.options.onFormCustomization) {
this.options.onFormCustomization(this, this._form);
}
addText('Before', this.options.okControl || this.options.cancelControl);
this.createControl('ok', this._boundSubmitHandler);
addText('Between', this.options.okControl && this.options.cancelControl);
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
addText('After', this.options.okControl || this.options.cancelControl);
},
destroy: function()
{
if (this._oldInnerHTML) {
this.element.innerHTML = this._oldInnerHTML;
}
this.leaveEditMode();
this.unregisterListeners();
},
clickHandler: function(e)
{
if (this._saving || this._editing) {
return;
}
this._editing = true;
this.triggerCallback('onEnterEditMode');
if (this.options.externalControl) {
this.options.externalControl.hide();
}
this.element.hide();
this.createForm();
this.element.parentNode.insertBefore(this._form, this.element);
if (!this.options.loadTextURL) {
this.postProcessEditField();
}
if (e) {
Event.stop(e);
}
},
mouseoverHandler: function(e)
{
if (this.options.hoverClassName) {
this.element.addClassName(this.options.hoverClassName);
}
if (this._saving) {
return;
}
this.triggerCallback('onEnterHover');
},
getText: function()
{
$(this.element).select('.' + this.options.emptyClassName).each(function(child) {
this.element.removeChild(child);
}.bind(this));
return this.element.innerHTML.unescapeHTML();
},
handleAJAXFailure: function(transport)
{
this.triggerCallback('onFailure', transport);
if (this._oldInnerHTML) {
this.element.innerHTML = this._oldInnerHTML;
this._oldInnerHTML = null;
}
},
handleFormCancellation: function(e)
{
this.wrapUp();
if (e) {
Event.stop(e);
}
},
handleFormSubmission: function(e)
{
var form = this._form;
var value = $F(this._controls.editor);
this.prepareSubmission();
var params = this.options.callback(form, value) || '';
if (Object.isString(params)) {
params = params.toQueryParams();
}
params.editorId = this.element.id;
if (this.options.htmlResponse) {
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Updater({ success: this.element }, this.url, options);
} else {
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.url, options);
}
if (e) {
Event.stop(e);
}
},
leaveEditMode: function()
{
this.element.removeClassName(this.options.savingClassName);
this.removeForm();
this.mouseoutHandler();
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
if (this.options.externalControl) {
this.options.externalControl.show();
}
this._saving = false;
this._editing = false;
this._oldInnerHTML = null;
this.triggerCallback('onLeaveEditMode');
},
mouseoutHandler: function(e)
{
if (this.options.hoverClassName) {
this.element.removeClassName(this.options.hoverClassName);
}
if (this._saving) {
return;
}
this.triggerCallback('onLeaveHover');
},
loadExternalText: function()
{
this._form.addClassName(this.options.loadingClassName);
this._controls.editor.disabled = true;
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._form.removeClassName(this.options.loadingClassName);
var text = transport.responseJSON;
if (this.options.stripLoadedTextTags) {
text = text.stripTags();
}
this._controls.editor.value = text;
this._controls.editor.disabled = false;
this.postProcessEditField();
}.bind(this),
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.options.loadTextURL, options);
},
postProcessEditField: function()
{
var fpc = this.options.fieldPostCreation;
if (fpc) {
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
}
},
prepareSubmission: function()
{
this._saving = true;
this.removeForm();
this.mouseoutHandler();
this.showSaving();
},
registerListeners: function()
{
var listeners = {
click: 'clickHandler',
keydown: 'keyHandler',
mouseover: 'mouseoverHandler',
mouseout: 'mouseoutHandler'
};
this._listeners = { };
var listener;
$H(listeners).each(function(pair) {
listener = this[pair.value].bind(this);
this._listeners[pair.key] = listener;
if (!this.options.externalControlOnly) {
this.element.observe(pair.key, listener);
}
if (this.options.externalControl) {
this.options.externalControl.observe(pair.key, listener);
}
}.bind(this));
},
removeForm: function()
{
if (!this._form) return;
this._form.remove();
this._form = null;
this._controls = { };
},
showSaving: function()
{
this._oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
this.element.addClassName(this.options.savingClassName);
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
},
triggerCallback: function(cbName, arg)
{
if ('function' == typeof this.options[cbName]) {
this.options[cbName](this, arg);
}
},
unregisterListeners: function()
{
$H(this._listeners).each(function(pair) {
if (!this.options.externalControlOnly) {
this.element.stopObserving(pair.key, pair.value);
}
if (this.options.externalControl) {
this.options.externalControl.stopObserving(pair.key, pair.value);
}
}.bind(this));
},
wrapUp: function(transport) {
this.leaveEditMode();
this.triggerCallback('onComplete', transport);
}
});