diff --git a/api/js/etemplate/et2_core_arrayMgr.js b/api/js/etemplate/et2_core_arrayMgr.js index 9abdeaec6a..5064c48ff7 100644 --- a/api/js/etemplate/et2_core_arrayMgr.js +++ b/api/js/etemplate/et2_core_arrayMgr.js @@ -426,7 +426,8 @@ var et2_readonlysArrayMgr = (function(){ "use strict"; return et2_arrayMgr.exten // If the attribute is set, return that if (typeof _attr != "undefined" && _attr !== null) { - return et2_evalBool(_attr); + // Accept 'editable', but otherwise boolean + return this.expandName(_attr) === 'editable' ? 'editable' : et2_evalBool(_attr); } // Otherwise take into accounf whether the parent is readonly diff --git a/api/js/etemplate/et2_core_editableWidget.js b/api/js/etemplate/et2_core_editableWidget.js new file mode 100644 index 0000000000..ddc8d27e72 --- /dev/null +++ b/api/js/etemplate/et2_core_editableWidget.js @@ -0,0 +1,158 @@ +/** + * EGroupware eTemplate2 - JS Widget base class + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011 + * @version $Id$ + */ + +/*egw:uses + et2_core_inputWidget; +*/ + +/** + * et2_editableWidget derives from et2_inputWidget and adds the ability to start + * readonly, then turn editable on double-click. If we decide to do this with + * more widgets, it should just be merged with et2_inputWidget. + * + * @augments et2_inputWidget + */ +var et2_editableWidget = (function(){ "use strict"; return et2_inputWidget.extend( +{ + attributes: { + readonly: { + name: "readonly", + type: "string", // | boolean + default: false, + description: "If set to 'editable' will start readonly, double clicking will make it editable and clicking out will save" + }, + save_callback: { + name: "save_callback", + type: "string", + default: et2_no_init, + description: "Ajax callback to save changed value when readonly is 'editable'. If not provided, a regular submit is done." + }, + save_callback_params: { + name: "readonly", + type: "string", + default: et2_no_init, + description: "Additional parameters passed to save_callback" + } + }, + + /** + * Constructor + * + * @memberOf et2_inputWidget + */ + init: function(_parent, _attrs) { + // 'Editable' really should be boolean for everything else to work + if(_attrs.readonly && typeof _attrs.readonly === 'string') + { + _attrs.readonly = true; + this._toggle_readonly = true; + } + this._super.apply(this, arguments); + }, + + destroy: function() { + var node = this.getInputNode(); + if (node) + { + jQuery(node).off('.et2_editableWidget'); + } + + this._super.apply(this, arguments); + }, + + /** + * Load the validation errors from the server + * + * @param {object} _attrs + */ + transformAttributes: function(_attrs) { + this._super.apply(this, arguments); + + }, + + attachToDOM: function() { + this._super.apply(this,arguments); + var node = this.getDOMNode(); + if (node && this._toggle_readonly) + { + jQuery(node) + .off('.et2_editableWidget') + .on("dblclick.et2_editableWidget", this, function(e) { + e.data.dblclick.call(e.data, this); + }) + .addClass('et2_clickable et2_editable'); + } + + }, + + detatchFromDOM: function() { + this._super.apply(this,arguments); + }, + + /** + * Handle double click + * + * Turn widget editable + * + * @param {DOMNode} _node + */ + dblclick: function (_node) { + // Turn off readonly + this.set_readonly(false); + + jQuery('body').on("click.et2_editableWidget", this, function(e) { + // Make sure click comes from body, not a popup + if(jQuery.contains(this, e.target)) + { + jQuery(this).off("click.et2_editableWidget"); + e.data.focusout.call(e.data, this); + } + }); + }, + + /** + * User clicked somewhere else, save and turn back to readonly + * + * @param {DOMNode} _node Body node + * @returns {et2_core_editableWidgetet2_editableWidget.et2_core_editableWidgetAnonym$0@call;getInstanceManager@call;submit} + */ + focusout: function (_node) + { + var value = this.get_value(); + var oldValue = this._oldValue; + + // Change back to readonly + this.set_readonly(true); + + // No change, do nothing + if(value == oldValue) return; + + // Submit + if(this.options.save_callback) + { + var params = [value]; + if(this.options.save_callback_params) + { + params = params.concat(this.options.save_callback_params.split(',')); + } + + egw.json(this.options.save_callback, params, function() { + }, this, true, this).sendRequest(); + } + else + { + return this.getInstanceManager().submit(); + } + } + +});}).call(this); + diff --git a/api/js/etemplate/et2_core_widget.js b/api/js/etemplate/et2_core_widget.js index 39af60fc38..148bd02cea 100644 --- a/api/js/etemplate/et2_core_widget.js +++ b/api/js/etemplate/et2_core_widget.js @@ -718,7 +718,7 @@ var et2_widget = (function(){ "use strict"; return ClassWithAttributes.extend( // constructor if it is available var constructor = typeof et2_registry[_nodeName] == "undefined" ? et2_placeholder : et2_registry[_nodeName]; - if (readonly && typeof et2_registry[_nodeName + "_ro"] != "undefined") + if (readonly === true && typeof et2_registry[_nodeName + "_ro"] != "undefined") { constructor = et2_registry[_nodeName + "_ro"]; } diff --git a/api/js/etemplate/et2_widget_htmlarea.js b/api/js/etemplate/et2_widget_htmlarea.js index 7e4d781224..0f59943c26 100644 --- a/api/js/etemplate/et2_widget_htmlarea.js +++ b/api/js/etemplate/et2_widget_htmlarea.js @@ -13,13 +13,13 @@ /*egw:uses jsapi.jsapi; // Needed for egw_seperateJavaScript /api/js/tinymce/tinymce.min.js; - et2_core_baseWidget; + et2_core_editableWidget; */ /** * @augments et2_inputWidget */ -var et2_htmlarea = (function(){ "use strict"; return et2_inputWidget.extend([et2_IResizeable], +var et2_htmlarea = (function(){ "use strict"; return et2_editableWidget.extend([et2_IResizeable], { attributes: { 'mode': { @@ -87,7 +87,7 @@ var et2_htmlarea = (function(){ "use strict"; return et2_inputWidget.extend([et2 this._super.apply(this, arguments); this.editor = null; // TinyMce editor instance this.supportedWidgetClasses = []; // Allow no child widgets - this.htmlNode = jQuery(document.createElement("textarea")) + this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea")) .css('height', this.options.height) .addClass('et2_textbox_ro'); this.setDOMNode(this.htmlNode[0]); @@ -99,7 +99,12 @@ var et2_htmlarea = (function(){ "use strict"; return et2_inputWidget.extend([et2 */ doLoadingFinished: function() { this._super.apply(this, arguments); - if(this.mode == 'ascii' || this.editor != null) return; + + this.init_editor(); + }, + + init_editor: function() { + if(this.mode == 'ascii' || this.editor != null || this.options.readonly) return; var imageUpload = ''; var self = this; if (this.options.imageUpload && this.options.imageUpload[0] !== '/' && this.options.imageUpload.substr(0, 4) != 'http') @@ -192,6 +197,34 @@ var et2_htmlarea = (function(){ "use strict"; return et2_inputWidget.extend([et2 } }, + set_readonly: function(_value) + { + if(this.options.readonly === _value) return; + var value = this.get_value(); + this.options.readonly = _value; + if(this.options.readonly) + { + this.editor.remove(); + this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea")) + .css('height', this.options.height) + .addClass('et2_textbox_ro'); + this.editor = null; + this.setDOMNode(this.htmlNode[0]); + this.set_value(value); + } + else + { + if(!this.editor) + { + this.htmlNode = jQuery(document.createElement("textarea")) + .css('height', this.options.height) + .val(value); + this.setDOMNode(this.htmlNode[0]); + this.init_editor(); + } + } + }, + /** * Callback function runs when the filepicker in image dialog is clicked * @@ -315,13 +348,22 @@ var et2_htmlarea = (function(){ "use strict"; return et2_inputWidget.extend([et2 } else { - this.htmlNode.val(_value); - this.value = _value; + if(this.options.readonly) + { + this.htmlNode.empty().append(_value); + } + else + { + this.htmlNode.val(_value); + } } + this.value = _value; }, getValue: function() { - return this.editor ? this.editor.getContent() : this.htmlNode.val(); + return this.editor ? this.editor.getContent() : ( + this.options.readonly ? this.value : this.htmlNode.val() + ); }, /**