[FIX] web(_editor): field widget html
The field widget html wasn't implemented yet in the new views. A very basic implementation was done in addon web. It only handled the readonly mode so that the field were displayed in studio. This commit removes this basic implementation from web, and re- introduced the old one, converted, in addon web_editor.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 = '<p><br/></p>';
|
||||
} else {
|
||||
value = "<p>"+value.split(/<br\/?>/).join("<br/></p><p>")+"</p>";
|
||||
value = value.replace(/<p><\/p>/g, '').replace('<p><p>', '<p>').replace('<p><p ', '<p ').replace('</p></p>', '</p>');
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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 !== "<p><br/></p>" && 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 = $('<textarea>');
|
||||
this.$textarea.appendTo(this.$el);
|
||||
this.$textarea.summernote(this._getSummernoteConfig());
|
||||
this.$content = this.$('.note-editable:first');
|
||||
this.$content.html(this._textToHtml(this.value));
|
||||
// trigger a mouseup to refresh the editor toolbar
|
||||
this.$content.trigger('mouseup');
|
||||
if (this.nodeOptions['style-inline']) {
|
||||
transcoder.style_to_class(this.$content);
|
||||
}
|
||||
// reset the history (otherwise clicking on undo before editing the
|
||||
// value will empty the editor)
|
||||
var history = this.$content.data('NoteHistory');
|
||||
if (history) {
|
||||
history.reset();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderReadonly: function () {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
if (this.nodeOptions['style-inline']) {
|
||||
var $iframe = $('<iframe class="o_readonly"/>');
|
||||
$iframe.on('load', function () {
|
||||
self.$content = $($iframe.contents()[0]).find("body");
|
||||
self.$content.html(self._textToHtml(self.value));
|
||||
self._resize();
|
||||
});
|
||||
$iframe.appendTo(this.$el);
|
||||
} else {
|
||||
this.$content = $('<div class="o_readonly"/>');
|
||||
this.$content.html(this._textToHtml(this.value));
|
||||
this.$content.appendTo(this.$el);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sets the height of the iframe.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_resize: function () {
|
||||
var height = this.$content[0] ? this.$content[0].scrollHeight : 0;
|
||||
this.$('iframe').css('height', Math.max(30, Math.min(height, 500)) + 'px');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} text
|
||||
* @returns {string} the text converted to html
|
||||
*/
|
||||
_textToHtml: function (text) {
|
||||
var value = text || "";
|
||||
try {
|
||||
$(text)[0].innerHTML; // crashes if text isn't html
|
||||
} catch (e) {
|
||||
if (value.match(/^\s*$/)) {
|
||||
value = '<p><br/></p>';
|
||||
} else {
|
||||
value = "<p>" + value.split(/<br\/?>/).join("<br/></p><p>") + "</p>";
|
||||
value = value
|
||||
.replace(/<p><\/p>/g, '')
|
||||
.replace('<p><p>', '<p>')
|
||||
.replace('<p><p ', '<p ')
|
||||
.replace('</p></p>', '</p>');
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
});
|
||||
|
||||
var FieldTextHtml = AbstractField.extend({
|
||||
template: 'web_editor.FieldTextHtml',
|
||||
supportedFieldTypes: ['html'],
|
||||
@@ -234,11 +404,11 @@ var FieldTextHtml = AbstractField.extend({
|
||||
});
|
||||
|
||||
field_registry
|
||||
.add('html', AbstractField) // TODO
|
||||
.add('html', FieldTextHtmlSimple)
|
||||
.add('html_frame', FieldTextHtml);
|
||||
|
||||
return {
|
||||
FieldTextHtmlSimple: AbstractField,
|
||||
FieldTextHtmlSimple: FieldTextHtmlSimple,
|
||||
FieldTextHtml: FieldTextHtml,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="web_editor.FieldTextHtmlSimple">
|
||||
<div class="oe_form_field oe_form_field_html_text" t-att-style="widget.node.attrs.style">
|
||||
<textarea readonly="readonly"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web_editor.FieldTextHtml">
|
||||
<div class="oe_form_field oe_form_field_html oe_form_embedded_html" t-att-style="widget.attrs.style">
|
||||
<iframe/>
|
||||
|
||||
@@ -15,7 +15,7 @@ QUnit.module('web_editor', {
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "first record",
|
||||
body: "<div>yep</div>"
|
||||
body: "<div class='field_body'>yep</div>",
|
||||
}],
|
||||
onchanges: {},
|
||||
},
|
||||
@@ -24,6 +24,70 @@ QUnit.module('web_editor', {
|
||||
});
|
||||
|
||||
QUnit.test('field html widget', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(3);
|
||||
|
||||
var form = testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mass.mailing',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<field name="body" widget="html" style="height: 100px"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.field_body').text(), 'yep',
|
||||
"should have rendered a div with correct content in readonly");
|
||||
assert.strictEqual(form.$('div[name=body]').attr('style'), 'height: 100px',
|
||||
"should have applied the style correctly");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
|
||||
assert.strictEqual(form.$('.note-editable').html(), '<div class="field_body">yep</div>',
|
||||
"should have rendered the field correctly in edit");
|
||||
|
||||
// summernote invokes handlers after a setTimeout, so we must wait as well
|
||||
// before destroying the widget (otherwise we'll have a crash later on)
|
||||
setTimeout(function () {
|
||||
form.destroy();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
QUnit.test('field html widget (with options inline-style)', function (assert) {
|
||||
var done = assert.async();
|
||||
assert.expect(3);
|
||||
|
||||
var form = testUtils.createView({
|
||||
View: FormView,
|
||||
model: 'mass.mailing',
|
||||
data: this.data,
|
||||
arch: '<form string="Partners">' +
|
||||
'<field name="body" widget="html" style="height: 100px" options="{\'style-inline\': true}"/>' +
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('iframe').length, 1,
|
||||
"should have rendered an iframe without crashing in readonly");
|
||||
assert.strictEqual(form.$('div[name=body]').attr('style'), 'height: 100px',
|
||||
"should have applied the style correctly");
|
||||
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
|
||||
assert.strictEqual(form.$('.note-editable').html(), '<div class="field_body">yep</div>',
|
||||
"should have rendered the field correctly in edit");
|
||||
|
||||
// summernote invokes handlers after a setTimeout, so we must wait as well
|
||||
// before destroying the widget (otherwise we'll have a crash later on)
|
||||
setTimeout(function () {
|
||||
form.destroy();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
QUnit.test('field html_frame widget', function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
var form = testUtils.createView({
|
||||
|
||||
Reference in New Issue
Block a user