diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 66bf2aaabe7..f36418e3b8b 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -191,6 +191,16 @@ var AbstractField = Widget.extend({ */ activate: function () { }, + /** + * This function should be implemented by widgets that are not able to + * notify their environment when their value changes (maybe because their + * are not aware of the changes). It is called before saving, and should + * call _setValue() to notify the environment if the value changed. + * + * @abstract + */ + commitChanges: function () { + }, /** * this method is used to determine if the field value is set to a meaningful * value. This is useful to determine if a field should be displayed as empty diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index 15b8b3930df..af5d9bcf233 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -702,23 +702,6 @@ var FieldText = DebouncedField.extend({ }, }); -var FieldHtml = InputField.extend({ - supportedFieldTypes: ['html'], - text_to_html: function (text) { - var value = text || ""; - if (value.match(/^\s*$/)) { - value = '


'; - } else { - value = "

"+value.split(//).join("

")+"

"; - value = value.replace(/

<\/p>/g, '').replace('

', '

').replace('

', '

'); - } - return value; - }, - _render: function () { - this.$el.html(this.text_to_html(this.value)); - }, -}); - /** * Displays a handle to modify the sequence. */ @@ -2068,7 +2051,6 @@ return { FieldDomain: FieldDomain, FieldFloat: FieldFloat, FieldFloatTime: FieldFloatTime, - FieldHtml: FieldHtml, FieldInteger: FieldInteger, FieldMonetary: FieldMonetary, FieldPercentPie: FieldPercentPie, diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js index 83d28c1649f..00423d670fb 100644 --- a/addons/web/static/src/js/fields/field_registry.js +++ b/addons/web/static/src/js/fields/field_registry.js @@ -20,7 +20,6 @@ registry .add('abstract', AbstractField) .add('input', basic_fields.InputField) .add('integer', basic_fields.FieldInteger) - .add('html', basic_fields.FieldHtml) .add('boolean', basic_fields.FieldBoolean) .add('date', basic_fields.FieldDate) .add('datetime', basic_fields.FieldDateTime) diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index 7435332ecf4..f97a93e14e8 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -214,6 +214,11 @@ var FormController = BasicController.extend({ saveRecord: function (options) { var self = this; options = options || {}; + + // some field widgets (e.g. 'html') can't detect (all) their changes, so + // we ask them to commit their current value before saving + this.renderer.commitChanges(); + var stayInEdit = 'stayInEdit' in options ? options.stayInEdit : false; var shouldReload = 'reload' in options ? options.reload : true; if (!this.model.isDirty(this.handle) && !this.model.isNew(this.handle)) { diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 23ed3a08ad3..e2ec7b3de70 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -76,6 +76,14 @@ return BasicRenderer.extend({ } }); }, + /** + * Calls 'commitChanges' on all field widgets, so that they can notify the + * environment with their current value (useful for widgets that can't + * detect when their value changes, e.g. field 'html'). + */ + commitChanges: function () { + _.invoke(this.widgets, 'commitChanges'); + }, /** * returns the active tab pages for each notebook * diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js index f7d50e1a6dd..7642588ca26 100644 --- a/addons/web_editor/static/src/js/backend.js +++ b/addons/web_editor/static/src/js/backend.js @@ -2,18 +2,188 @@ odoo.define('web_editor.backend', function (require) { 'use strict'; var AbstractField = require('web.AbstractField'); +var basic_fields = require('web.basic_fields'); var core = require('web.core'); var session = require('web.session'); var field_registry = require('web.field_registry'); +var transcoder = require('web_editor.transcoder'); + +var DebouncedField = basic_fields.DebouncedField; var QWeb = core.qweb; /** - * FieldTextHtml Widget - * Intended for FieldText widgets meant to display HTML content. This - * widget will instantiate an iframe with the editor summernote improved by odoo + * FieldTextHtmlSimple Widget + * Intended to display HTML content. This widget uses the summernote editor + * improved by odoo. + * + * FIXME: this field has a custom handling of the translation feature, which + * hasn't been re-introduced yet (because this feature hasn't been introduced + * yet in the fields in general) */ +var FieldTextHtmlSimple = DebouncedField.extend({ + className: 'oe_form_field oe_form_field_html_text', + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Summernote doesn't notify for all changes (e.g. changing the background + * color). Moreover, we can't detect that this field looses the focus, so + * we can't notify the environment that the value may have changed at that + * moment. So we always send the current value before saving. + * + * @override + */ + commitChanges: function () { + this._setValue(this._getValue()); + }, + /** + * @override + */ + isSet: function () { + return this.value && this.value !== "


" && this.value.match(/\S/); + }, + /** + * Do not re-render this field if it was the origin of the onchange call. + * + * @override + */ + reset: function (record, event) { + this._reset(record, event); + if (!event || event.target !== this) { + if (this.mode === 'edit') { + this.$content.html(this._textToHtml(this.value)); + } else { + this._renderReadonly(); + } + } + return $.when(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @returns {Object} the summernote configuration + */ + _getSummernoteConfig: function () { + var config = { + focus: false, + height: 180, + toolbar: [ + ['style', ['style']], + ['font', ['bold', 'italic', 'underline', 'clear']], + ['fontsize', ['fontsize']], + ['color', ['color']], + ['para', ['ul', 'ol', 'paragraph']], + ['table', ['table']], + ['insert', ['link', 'picture']], + ['history', ['undo', 'redo']] + ], + prettifyHtml: false, + styleWithSpan: false, + inlinemedia: ['p'], + lang: "odoo", + onChange: this._onInput.bind(this), + }; + if (this.getSession().debug) { + config.toolbar.splice(7, 0, ['view', ['codeview']]); + } + return config; + }, + /** + * @override + * @private + */ + _getValue: function () { + if (this.nodeOptions['style-inline']) { + transcoder.class_to_style(this.$content); + transcoder.font_to_img(this.$content); + } + return this.$content.html(); + }, + /** + * @override + * @private + */ + _renderEdit: function () { + this.$textarea = $('