summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorfja-odoo <fja@odoo.com>2020-02-04 17:31:40 +0000
committerqsm-odoo <qsm@odoo.com>2020-02-05 16:13:25 +0000
commit617eba941785aa680edb826e14b0009637d60dee (patch)
tree8a4828c7287e1da320453e6436c86e105d78ef33 /addons
parentf434e02f3d965cae754f0c6a0134a56f3f77021b (diff)
[IMP] website_form, *: entirely review s_website_form snippet
*: web_editor, website_crm, website_form_project, website_hr_recruitment, website_sale The form builder is now using the left panel option system. Many new options are introduced to build better forms more quickly. The tests are now running with the new website_form implementation. Part of: https://github.com/odoo/odoo/pull/42189 task-2092425 closes odoo/odoo#42189 Related: odoo/enterprise#7365 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js4
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_snippets.scss2
-rw-r--r--addons/website_crm/static/src/js/website_crm_editor.js32
-rw-r--r--addons/website_crm/static/src/xml/website_crm.xml55
-rw-r--r--addons/website_crm/static/tests/tours/website_crm.js2
-rw-r--r--addons/website_crm/views/website_crm_templates.xml121
-rw-r--r--addons/website_form/static/src/js/website_form_editor_registry.js31
-rw-r--r--addons/website_form/static/src/scss/wysiwyg_snippets.scss23
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/000.js132
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/000.scss94
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/001.scss41
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/options.js1830
-rw-r--r--addons/website_form/static/src/xml/website_form.xml58
-rw-r--r--addons/website_form/static/src/xml/website_form_editor.xml216
-rw-r--r--addons/website_form/static/tests/tours/website_form_editor.js758
-rw-r--r--addons/website_form/views/assets.xml2
-rw-r--r--addons/website_form/views/snippets/s_website_form.xml152
-rw-r--r--addons/website_form/views/website_form_templates.xml138
-rw-r--r--addons/website_form_project/static/src/js/website_form_project_editor.js17
-rw-r--r--addons/website_form_project/static/src/xml/website_form_project.xml31
-rw-r--r--addons/website_hr_recruitment/static/src/js/website_hr_recruitment_editor.js26
-rw-r--r--addons/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml47
-rw-r--r--addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js2
-rw-r--r--addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml125
-rw-r--r--addons/website_sale/data/data.xml4
-rw-r--r--addons/website_sale/static/src/js/website_sale_form_editor.js21
-rw-r--r--addons/website_sale/static/src/xml/website_sale_form.xml39
-rw-r--r--addons/website_sale/views/templates.xml65
28 files changed, 2323 insertions, 1745 deletions
diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js
index 02f994fdeb2..abfcc6526e8 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -696,6 +696,10 @@ const SelectUserValueWidget = UserValueWidget.extend({
this.trigger_up('user_value_widget_opening');
}
this.menuTogglerEl.classList.toggle('active');
+ const activeButton = this._userValueWidgets.find(widget => widget.isActive());
+ if (activeButton) {
+ this.menuEl.scrollTop = activeButton.el.offsetTop - (this.menuEl.offsetHeight / 2);
+ }
},
});
diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
index 5d610465f92..ed5ada70830 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -396,6 +396,8 @@ body.editor_enable.editor_has_snippets {
z-index: $zindex-dropdown;
border: 1px solid var(--o-we-color);
background-color: var(--o-we-bg-color-dark);
+ max-height: 400px;
+ overflow-y: auto;
}
}
diff --git a/addons/website_crm/static/src/js/website_crm_editor.js b/addons/website_crm/static/src/js/website_crm_editor.js
index 44762e58c38..a0492d0eee5 100644
--- a/addons/website_crm/static/src/js/website_crm_editor.js
+++ b/addons/website_crm/static/src/js/website_crm_editor.js
@@ -7,8 +7,36 @@ var FormEditorRegistry = require('website_form.form_editor_registry');
var _t = core._t;
FormEditorRegistry.add('create_lead', {
- defaultTemplateName: 'website_crm.default_crm_form',
- defaultTemplatePath: '/website_crm/static/src/xml/website_crm.xml',
+ formFields: [{
+ type: 'char',
+ required: true,
+ name: 'contact_name',
+ string: 'Your Name',
+ }, {
+ type: 'char',
+ name: 'phone',
+ string: 'Phone Number',
+ }, {
+ type: 'email',
+ required: true,
+ name: 'email_from',
+ string: 'Your Email',
+ }, {
+ type: 'char',
+ required: true,
+ name: 'partner_name',
+ string: 'Your Company',
+ }, {
+ type: 'char',
+ modelRequired: true,
+ name: 'name',
+ string: 'Subject',
+ }, {
+ type: 'text',
+ required: true,
+ name: 'description',
+ string: 'Your Question',
+ }],
fields: [{
name: 'team_id',
type: 'many2one',
diff --git a/addons/website_crm/static/src/xml/website_crm.xml b/addons/website_crm/static/src/xml/website_crm.xml
deleted file mode 100644
index 71356a5176f..00000000000
--- a/addons/website_crm/static/src/xml/website_crm.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates>
-
- <t t-name="website_crm.default_crm_form">
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="contact_name">Your Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="contact_name" required=""/>
- </div>
- </div>
- <div class="form-group row form-field">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="phone">Phone Number</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="phone" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email_from">Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="email_from" required="" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="partner_name">Your Company</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="partner_name" required="" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="name">Subject</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="name" required="" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="description">Your Question</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <textarea class="form-control o_website_form_input" name="description" required=""></textarea>
- </div>
- </div>
- </t>
-
-</templates>
diff --git a/addons/website_crm/static/tests/tours/website_crm.js b/addons/website_crm/static/tests/tours/website_crm.js
index 2923cb1ab50..9b615a56f8f 100644
--- a/addons/website_crm/static/tests/tours/website_crm.js
+++ b/addons/website_crm/static/tests/tours/website_crm.js
@@ -32,7 +32,7 @@ odoo.define('website_crm.tour', function(require) {
run: "text ### TOUR DATA ###"
}, {
content: "Send the form",
- trigger: ".o_website_form_send"
+ trigger: ".s_website_form_send"
}, {
content: "Check we were redirected to the success page",
trigger: "#wrap:has(h1:contains('Thanks')):has(div.alert-success)"
diff --git a/addons/website_crm/views/website_crm_templates.xml b/addons/website_crm/views/website_crm_templates.xml
index a62eae17c89..2366a67a114 100644
--- a/addons/website_crm/views/website_crm_templates.xml
+++ b/addons/website_crm/views/website_crm_templates.xml
@@ -1,53 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="contactus_form" name="Contact Form (Opportunity)" inherit_id="website_form.contactus_form" customize_show="True">
- <xpath expr="//form[@id='contactus_form']" position="replace">
- <div t-ignore="true">
- <form action="/website_form/" method="post" data-model_name="crm.lead" data-success_page="/contactus-thank-you" class="s_website_form container-fluid mt32" enctype="multipart/form-data" data-editable-form="false">
- <div class="form-group row form-field o_website_form_required_custom">
- <label class="col-lg-3 col-md-4 col-form-label" for="contact_name">Your Name</label>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="contact_name" t-att-value="request.params.get('contact_name', '')" required=""/>
- </div>
- </div>
- <div class="form-group row form-field">
- <label class="col-lg-3 col-md-4 col-form-label" for="phone">Phone Number</label>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="phone" t-att-value="request.params.get('phone', '')" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <label class="col-lg-3 col-md-4 col-form-label" for="email_from">Email</label>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="email_from" required="" t-att-value="request.params.get('email_from', '')" />
- </div>
- </div>
- <div class="form-group row form-field">
- <label class="col-lg-3 col-md-4 col-form-label" for="partner_name">Your Company</label>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="partner_name" t-att-value="request.params.get('partner_name', '')" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required">
- <label class="col-lg-3 col-md-4 col-form-label" for="name">Subject</label>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="name" required="" t-att-value="request.params.get('name', '')" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <label class="col-lg-3 col-md-4 col-form-label" for="description">Your Question</label>
- <div class="col-lg-7 col-md-8">
- <textarea class="form-control o_website_form_input" name="description" required=""><t t-esc="request.params.get('description', '')"/></textarea>
- </div>
- </div>
- <div class="form-group row">
- <div class="offset-lg-3 offset-md-4 col-md-8 col-lg-7">
- <a href="#" role="button" class="btn btn-primary btn-lg o_website_form_send">Send</a>
- <span id="o_website_form_result"></span>
+ <xpath expr="//span[@data-for='contactus_form']" position="attributes">
+ <attribute name="t-att-data-values">{
+ 'contact_name': request.params.get('contact_name', ''),
+ 'phone': request.params.get('phone', ''),
+ 'email_from': request.params.get('email_from', ''),
+ 'partner_name': request.params.get('partner_name', ''),
+ 'name': request.params.get('name', ''),
+ 'description': request.params.get('description', ''),
+ }</attribute>
+ </xpath>
+ <xpath expr="//section[@id='contactus_section']" position="replace">
+ <section class="s_website_form" data-vcss="001">
+ <div class="container">
+ <form id="contactus_form" action="/website_form/" method="post" enctype="multipart/form-data" data-model_name="crm.lead" data-success-mode="redirect" data-success-page="/contactus-thank-you" class="o_mark_required" data-mark="*" data-editable-form="false">
+ <div class="s_website_form_rows row s_col_no_bgcolor">
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="opportunity1">Your Name<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="opportunity1" type="text" class="form-control s_website_form_input" name="contact_name" required=""/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="opportunity2">Phone Number</label>
+ <div class="col-sm">
+ <input id="opportunity2" type="text" class="form-control s_website_form_input" name="phone" />
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="opportunity3">Email<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="opportunity3" type="text" class="form-control s_website_form_input" name="email_from" required="" />
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="opportunity4">Your Company</label>
+ <div class="col-sm">
+ <input id="opportunity4" type="text" class="form-control s_website_form_input" name="partner_name" />
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_model_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="opportunity5">Subject</label>
+ <div class="col-sm">
+ <input id="opportunity5" type="text" class="form-control s_website_form_input" name="name" required="" />
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="text">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="opportunity6">Your Question<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <textarea id="opportunity6" class="form-control s_website_form_input" name="description" required=""></textarea>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_submit">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <div style="width: 200px" class="col-sm-auto s_website_form_label"/>
+ <div class="col-sm">
+ <a href="#" role="button" class="btn btn-primary btn-lg s_website_form_send">Submit</a>
+ <span id="s_website_form_result"></span>
+ </div>
+ </div>
+ </div>
</div>
- </div>
- </form>
- </div>
+ </form>
+ </div>
+ </section>
</xpath>
</template>
diff --git a/addons/website_form/static/src/js/website_form_editor_registry.js b/addons/website_form/static/src/js/website_form_editor_registry.js
index f15cb9bd89e..93b183a9be8 100644
--- a/addons/website_form/static/src/js/website_form_editor_registry.js
+++ b/addons/website_form/static/src/js/website_form_editor_registry.js
@@ -16,8 +16,35 @@ var FormEditorRegistry = require('website_form.form_editor_registry');
var _t = core._t;
FormEditorRegistry.add('send_mail', {
- defaultTemplateName: 'website_form.default_contactus_form',
- defaultTemplatePath: '/website_form/static/src/xml/website_form.xml',
+ formFields: [{
+ type: 'char',
+ custom: true,
+ required: true,
+ name: 'Your Name',
+ }, {
+ type: 'char',
+ custom: true,
+ name: 'Phone Number',
+ }, {
+ type: 'email',
+ modelRequired: true,
+ name: 'email_from',
+ string: 'Your Email',
+ }, {
+ type: 'char',
+ custom: true,
+ name: 'Your Company',
+ }, {
+ type: 'char',
+ modelRequired: true,
+ name: 'subject',
+ string: 'Subject',
+ }, {
+ type: 'text',
+ custom: true,
+ required: true,
+ name: 'Your Question',
+ }],
fields: [{
name: 'email_to',
type: 'char',
diff --git a/addons/website_form/static/src/scss/wysiwyg_snippets.scss b/addons/website_form/static/src/scss/wysiwyg_snippets.scss
new file mode 100644
index 00000000000..009579f1819
--- /dev/null
+++ b/addons/website_form/static/src/scss/wysiwyg_snippets.scss
@@ -0,0 +1,23 @@
+#oe_snippets > .o_we_customize_panel we-customizeblock-option {
+ we-list {
+ .oe_we_table_wraper {
+ max-height: 200px;
+ overflow-y: auto;
+ table {
+ table-layout: fixed;
+ width: 100%;
+ input {
+ background-color: var(--o-we-bg-color-dark);
+ color: var(--o-we-color);
+ border: none;
+ width: 100%;
+ }
+ td {
+ &:first-child, &:last-child {
+ width: 28px;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/addons/website_form/static/src/snippets/s_website_form/000.js b/addons/website_form/static/src/snippets/s_website_form/000.js
index bd97b94ddcf..7669d98b808 100644
--- a/addons/website_form/static/src/snippets/s_website_form/000.js
+++ b/addons/website_form/static/src/snippets/s_website_form/000.js
@@ -1,5 +1,5 @@
-odoo.define('website_form.animation', function (require) {
-'use strict';
+odoo.define('website_form.s_website_form', function (require) {
+ 'use strict';
var core = require('web.core');
var time = require('web.time');
@@ -9,35 +9,44 @@ odoo.define('website_form.animation', function (require) {
var _t = core._t;
var qweb = core.qweb;
- publicWidget.registry.form_builder_send = publicWidget.Widget.extend({
- selector: '.s_website_form',
+ publicWidget.registry.s_website_form = publicWidget.Widget.extend({
+ selector: '.s_website_form form, form.s_website_form', // !compatibility
+ xmlDependencies: ['/website_form/static/src/xml/website_form.xml'],
+ events: {
+ 'click .s_website_form_send, .o_website_form_send': 'send', // !compatibility
+ },
+
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super(...arguments);
+ this.__started = new Promise(resolve => this.__startResolve = resolve);
+ },
start: function () {
var self = this;
- this.templates_loaded = ajax.loadXML('/website_form/static/src/xml/website_form.xml', qweb);
- this.$target.find('.o_website_form_send').on('click',function (e) {self.send(e);});
-
// Initialize datetimepickers
var datepickers_options = {
- minDate: moment({ y: 1900 }),
- maxDate: moment({ y: 9999, M: 11, d: 31 }),
+ minDate: moment({y: 1900}),
+ maxDate: moment({y: 9999, M: 11, d: 31}),
calendarWeeks: true,
- icons : {
+ icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
next: 'fa fa-chevron-right',
previous: 'fa fa-chevron-left',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
- },
- locale : moment.locale(),
- format : time.getLangDatetimeFormat(),
+ },
+ locale: moment.locale(),
+ format: time.getLangDatetimeFormat(),
};
- this.$target.find('.o_website_form_datetime').datetimepicker(datepickers_options);
+ this.$target.find('.s_website_form_datetime, .o_website_form_datetime').datetimepicker(datepickers_options); // !compatibility
// Adapt options to date-only pickers
datepickers_options.format = time.getLangDateFormat();
- this.$target.find('.o_website_form_date').datetimepicker(datepickers_options);
+ this.$target.find('.s_website_form_date, .o_website_form_date').datetimepicker(datepickers_options); // !compatibility
// Display form values from tag having data-for attribute
// It's necessary to handle field values generated on server-side
@@ -56,21 +65,35 @@ odoo.define('website_form.animation', function (require) {
});
}
- return this._super.apply(this, arguments);
+ return this._super(...arguments).then(() => this.__startResolve());
},
destroy: function () {
this._super.apply(this, arguments);
this.$target.find('button').off('click');
+
+ // Empty imputs
+ this.$target.find('input[type="text"], input[type="email"], input[type="number"], textarea').val('');
+
+ // Remove saving of the error colors
+ this.$target.find('.o_has_error').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
+
+ // Remove the status message
+ this.$target.find('#s_website_form_result, #o_website_form_result').empty(); // !compatibility
+
+ // Remove the success message and display the form
+ this.$target.removeClass('d-none');
+ this.$target.parent().find('.s_website_form_end_message').addClass('d-none');
},
send: function (e) {
- e.preventDefault(); // Prevent the default submit behavior
- this.$target.find('.o_website_form_send').off().addClass('disabled'); // Prevent users from crazy clicking
+ e.preventDefault(); // Prevent the default submit behavior
+ // Prevent users from crazy clicking
+ this.$target.find('.s_website_form_send, .o_website_form_send').addClass('disabled'); // !compatibility
var self = this;
- self.$target.find('#o_website_form_result').empty();
+ self.$target.find('#s_website_form_result, #o_website_form_result').empty(); // !compatibility
if (!self.check_error_fields({})) {
self.update_status('invalid');
return false;
@@ -110,8 +133,10 @@ odoo.define('website_form.animation', function (require) {
});
// Post form and handle result
- ajax.post(this.$target.attr('action') + (this.$target.data('force_action')||this.$target.data('model_name')), form_values)
+ ajax.post(this.$target.attr('action') + (this.$target.data('force_action') || this.$target.data('model_name')), form_values)
.then(function (result_data) {
+ // Restore send button behavior
+ self.$target.find('.s_website_form_send, .o_website_form_send').removeClass('disabled'); // !compatibility
result_data = JSON.parse(result_data);
if (!result_data.id) {
// Failure, the server didn't return the created record ID
@@ -122,19 +147,30 @@ odoo.define('website_form.animation', function (require) {
}
} else {
// Success, redirect or update status
- var success_page = self.$target.attr('data-success_page');
- if (success_page) {
- $(window.location).attr('href', success_page);
+ let successMode = self.$target[0].dataset.successMode;
+ let successPage = self.$target[0].dataset.successPage;
+ if (!successMode) {
+ successPage = self.$target.attr('data-success_page'); // Compatibility
+ successMode = successPage ? 'redirect' : 'nothing';
}
- else {
- self.update_status('success');
+ switch (successMode) {
+ case 'redirect':
+ $(window.location).attr('href', successPage);
+ break;
+ case 'message':
+ self.$target[0].classList.add('d-none');
+ self.$target[0].parentElement.querySelector('.s_website_form_end_message').classList.remove('d-none');
+ break;
+ default:
+ self.update_status('success');
+ break;
}
// Reset the form
self.$target[0].reset();
}
})
- .guardedCatch(function (){
+ .guardedCatch(function () {
self.update_status('error');
});
},
@@ -143,12 +179,12 @@ odoo.define('website_form.animation', function (require) {
var self = this;
var form_valid = true;
// Loop on all fields
- this.$target.find('.form-field').each(function (k, field){
+ this.$target.find('.form-field, .s_website_form_field').each(function (k, field) { // !compatibility
var $field = $(field);
var field_name = $field.find('.col-form-label').attr('for');
// Validate inputs for this field
- var inputs = $field.find('.o_website_form_input:not(#editable_select)');
+ var inputs = $field.find('.s_website_form_input, .o_website_form_input').not('#editable_select'); // !compatibility
var invalid_inputs = inputs.toArray().filter(function (input, k, inputs) {
// Special check for multiple required checkbox for same
// field as it seems checkValidity forces every required
@@ -159,17 +195,17 @@ odoo.define('website_form.animation', function (require) {
// Considering we are currently processing a single
// field, we can assume that all checkboxes in the
// inputs variable have the same name
- var checkboxes = _.filter(inputs, function (input){
+ var checkboxes = _.filter(inputs, function (input) {
return input.required && input.type === 'checkbox';
});
- return !_.any(checkboxes, function (checkbox) { return checkbox.checked; });
+ return !_.any(checkboxes, checkbox => checkbox.checked);
// Special cases for dates and datetimes
- } else if ($(input).hasClass('o_website_form_date')) {
+ } else if ($(input).hasClass('s_website_form_date') || $(input).hasClass('o_website_form_date')) { // !compatibility
if (!self.is_datetime_valid(input.value, 'date')) {
return true;
}
- } else if ($(input).hasClass('o_website_form_datetime')) {
+ } else if ($(input).hasClass('s_website_form_datetime') || $(input).hasClass('o_website_form_datetime')) { // !compatibility
if (!self.is_datetime_valid(input.value, 'datetime')) {
return true;
}
@@ -179,9 +215,9 @@ odoo.define('website_form.animation', function (require) {
// Update field color if invalid or erroneous
$field.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
- if (invalid_inputs.length || error_fields[field_name]){
- $field.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid')
- if (_.isString(error_fields[field_name])){
+ if (invalid_inputs.length || error_fields[field_name]) {
+ $field.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
+ if (_.isString(error_fields[field_name])) {
$field.popover({content: error_fields[field_name], trigger: 'hover', container: 'body', placement: 'top'});
// update error message and show it.
$field.data("bs.popover").options.content = error_fields[field_name];
@@ -210,32 +246,36 @@ odoo.define('website_form.animation', function (require) {
parse_date: function (value, type_of_date, value_if_empty) {
var date_pattern = time.getLangDateFormat(),
time_pattern = time.getLangTimeFormat();
- var date_pattern_wo_zero = date_pattern.replace('MM','M').replace('DD','D'),
- time_pattern_wo_zero = time_pattern.replace('HH','H').replace('mm','m').replace('ss','s');
+ var date_pattern_wo_zero = date_pattern.replace('MM', 'M').replace('DD', 'D'),
+ time_pattern_wo_zero = time_pattern.replace('HH', 'H').replace('mm', 'm').replace('ss', 's');
switch (type_of_date) {
case 'datetime':
var datetime = moment(value, [date_pattern + ' ' + time_pattern, date_pattern_wo_zero + ' ' + time_pattern_wo_zero], true);
- if (datetime.isValid())
+ if (datetime.isValid()) {
return time.datetime_to_str(datetime.toDate());
+ }
throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value));
case 'date':
var date = moment(value, [date_pattern, date_pattern_wo_zero], true);
- if (date.isValid())
+ if (date.isValid()) {
return time.date_to_str(date.toDate());
+ }
throw new Error(_.str.sprintf(_t("'%s' is not a correct date"), value));
}
return value;
},
update_status: function (status) {
- var self = this;
- if (status !== 'success') { // Restore send button behavior if result is an error
- this.$target.find('.o_website_form_send').on('click',function (e) {self.send(e);}).removeClass('disabled');
+ if (status !== 'success') { // Restore send button behavior if result is an error
+ this.$target.find('.s_website_form_send, .o_website_form_send').removeClass('disabled'); // !compatibility
}
- var $result = this.$('#o_website_form_result');
- this.templates_loaded.then(function () {
- $result.replaceWith(qweb.render("website_form.status_" + status));
- });
+ var $result = this.$('#s_website_form_result, #o_website_form_result'); // !compatibility
+
+ // Note: we still need to wait that the widget is properly started
+ // before any qweb rendering which depends on xmlDependencies
+ // because the event handlers are binded before the call to
+ // willStart for public widgets...
+ this.__started.then(() => $result.replaceWith(qweb.render(`website_form.status_${status}`)));
},
});
});
diff --git a/addons/website_form/static/src/snippets/s_website_form/000.scss b/addons/website_form/static/src/snippets/s_website_form/000.scss
index 936edad2715..0ba43a0c6e4 100644
--- a/addons/website_form/static/src/snippets/s_website_form/000.scss
+++ b/addons/website_form/static/src/snippets/s_website_form/000.scss
@@ -1,57 +1,61 @@
-// Radio buttons and checkboxes flex layout
-.o_website_form_flex {
- display: flex;
- flex-wrap: wrap;
-
- &.o_website_form_flex_fw > .o_website_form_flex_item {
- flex-basis: 100%;
+.editor_enable .s_website_form:not([data-vcss]) {
+ // Select inputs do not trigger the default browser behavior
+ // Since we use a custom editable element
+ .form-field select {
+ pointer-events: none;
}
- &:not(.o_website_form_flex_fw) > .o_website_form_flex_item {
- // col-lg-4
- flex-basis: 33%;
-
- // col-md-6
- @include media-breakpoint-down(md) {
- flex-basis: 50%;
- }
- // col-12
- @include media-breakpoint-down(sm) {
- flex-basis: 100%;
- }
+ .o_website_form_field_hidden {
+ display: flex;
+ opacity: 0.5;
}
-}
-// Hidden field is only partially hidden in editor
-.o_website_form_field_hidden {
- display: none;
+ // Quickfix to display the editable select as a single big field
+ #editable_select.form-control {
+ height: 100%;
+ }
}
-.editor_enable .o_website_form_field_hidden {
- display: flex;
- opacity: 0.5;
-}
+.s_website_form:not([data-vcss]) {
+ // Radio buttons and checkboxes flex layout
+ .o_website_form_flex {
+ display: flex;
+ flex-wrap: wrap;
-// Select inputs do not trigger the default browser behavior
-// Since we use a custom editable element
-.editor_enable .s_website_form .form-field select {
- pointer-events: none;
-}
+ &.o_website_form_flex_fw > .o_website_form_flex_item {
+ flex-basis: 100%;
+ }
+ &:not(.o_website_form_flex_fw) > .o_website_form_flex_item {
+ // col-lg-4
+ flex-basis: 33%;
+
+ // col-md-6
+ @include media-breakpoint-down(md) {
+ flex-basis: 50%;
+ }
+
+ // col-12
+ @include media-breakpoint-down(sm) {
+ flex-basis: 100%;
+ }
+ }
+ }
-// Required fields have a star which is not part of the field label
-.o_website_form_required, .o_website_form_required_custom {
- .col-form-label:after {
- content: ' *';
+ // Hidden field is only partially hidden in editor
+ .o_website_form_field_hidden {
+ display: none;
}
-}
-// Quickfix to display the editable select as a single big field
-#editable_select.form-control {
- height: 100%;
-}
+ // Required fields have a star which is not part of the field label
+ .o_website_form_required, .o_website_form_required_custom {
+ .col-form-label:after {
+ content: ' *';
+ }
+ }
-// Fix for firefox browse button which is too big for Bootstrap form-field
-// http://stackoverflow.com/questions/22049739/fix-for-firefox-file-input-using-bootstrap-3-1
-.form-field input[type=file].form-control {
- height: 100%;
+ // Fix for firefox browse button which is too big for Bootstrap form-field
+ // http://stackoverflow.com/questions/22049739/fix-for-firefox-file-input-using-bootstrap-3-1
+ .form-field input[type=file].form-control {
+ height: 100%;
+ }
}
diff --git a/addons/website_form/static/src/snippets/s_website_form/001.scss b/addons/website_form/static/src/snippets/s_website_form/001.scss
new file mode 100644
index 00000000000..561ac208543
--- /dev/null
+++ b/addons/website_form/static/src/snippets/s_website_form/001.scss
@@ -0,0 +1,41 @@
+.editor_enable .s_website_form[data-vcss="001"] {
+ // Hidden field is only partially hidden in editor
+ .s_website_form_field_hidden {
+ display: block;
+ opacity: 0.5;
+ }
+ // Select inputs do not trigger the default browser behavior
+ // Since we use a custom editable element
+ .s_website_form_field select {
+ pointer-events: none;
+ }
+ // Display the editable select as a single big field
+ #editable_select.form-control {
+ height: auto;
+ }
+}
+
+.s_website_form[data-vcss="001"] {
+ .s_website_form_label {
+ @include media-breakpoint-down(xs) {
+ width: auto !important;
+ }
+ }
+
+ .s_website_form_field_hidden {
+ display: none;
+ }
+
+ span.s_website_form_mark {
+ font-size: 0.85em;
+ font-weight: 400;
+ }
+
+ // The snippet editor uses padding and not margin.
+ // This will include bootstrap margin in the dragable y axes
+ .s_website_form_rows > .form-group {
+ margin-bottom: 0;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ }
+}
diff --git a/addons/website_form/static/src/snippets/s_website_form/options.js b/addons/website_form/static/src/snippets/s_website_form/options.js
index d659f51dd06..9a69b3efcda 100644
--- a/addons/website_form/static/src/snippets/s_website_form/options.js
+++ b/addons/website_form/static/src/snippets/s_website_form/options.js
@@ -1,635 +1,1269 @@
odoo.define('website_form_editor', function (require) {
- 'use strict';
-
- /**
- * @todo this should be entirely refactored
- */
-
- var ajax = require('web.ajax');
- var core = require('web.core');
- var Dialog = require('web.Dialog');
- var FormEditorRegistry = require('website_form.form_editor_registry');
- var options = require('web_editor.snippets.options');
- var wUtils = require('website.utils');
- var Wysiwyg = require('web_editor.wysiwyg');
-
- var qweb = core.qweb;
- var _t = core._t;
-
- var FormEditorDialog = Dialog.extend({
- /**
- * @constructor
- */
- init: function (parent, options) {
- this._super(parent, _.extend({
- buttons: [{
- text: _t('Save'),
- classes: 'btn-primary',
- close: true,
- click: this._onSaveModal.bind(this),
- }, {
- text: _t('Cancel'),
- close: true
- }],
- }, options));
-
- this.opened(() => {
- this.$modal.find('.o_website_form_input, .modal-footer .btn').first().focus();
- });
- },
-
- //----------------------------------------------------------------------
- // Handlers
- //----------------------------------------------------------------------
-
- /**
- * @private
- */
- _onSaveModal: function () {
- if (this.$el[0].checkValidity()) {
- this.trigger_up('save');
- } else {
- _.each(this.$el.find('.o_website_form_input'), function (input) {
- var $field = $(input).closest('.form-field');
- $field.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
- if (!input.checkValidity()) {
- $field.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
- }
- });
- }
- },
- });
-
- options.registry['website_form_editor'] = options.Class.extend({
- xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
-
- start: function () {
- this.$target.addClass('o_fake_not_editable').attr('contentEditable', false);
- this.$target.find('label:not(:has(span)), label span, .o_form_heading').addClass('o_fake_editable').attr('contentEditable', true);
- return this._super.apply(this, arguments);
- },
-
- // Return the fields promise if we already issued a model
- // fields fetch request, or issue said request.
- fields: function () {
- return this.fields_promise || this.fetch_model_fields();
- },
-
- fetch_model_fields: function () {
- return this._rpc({
- model: "ir.model",
- method: "get_authorized_fields",
- args: [this.$target.closest('form').attr('data-model_name')],
- }).then(function (fields) {
- // The get_fields function doesn't return the name
- // in the field dict since it uses it has the key
- _.each(fields, function (field, field_name) {
- field.name = field_name;
- });
- return fields;
- });
- },
-
- // Choose a model modal
- website_form_model_modal: function (previewMode, widgetValue, params) {
- var self = this;
- this._rpc({
- model: "ir.model",
- method: "search_read",
+'use strict';
+
+const core = require('web.core');
+const FormEditorRegistry = require('website_form.form_editor_registry');
+const options = require('web_editor.snippets.options');
+
+const qweb = core.qweb;
+const _t = core._t;
+
+const FormEditor = options.Class.extend({
+ xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * Returns a promise which is resolved once the records of the field
+ * have been retrieved.
+ *
+ * @private
+ * @param {Object} field
+ * @returns {Promise<Object>}
+ */
+ _fetchFieldRecords: async function (field) {
+ // Convert the required boolean to a value directly usable
+ // in qweb js to avoid duplicating this in the templates
+ field.required = field.required ? 1 : null;
+
+ // Fetch possible values for relation fields
+ if (!field.records && field.relation && field.relation !== 'ir.attachment') {
+ field.records = await this._rpc({
+ model: field.relation,
+ method: 'search_read',
args: [
- [['website_form_access', '=', true]],
- ['id', 'model', 'name', 'website_form_label', 'website_form_key']
+ field.domain,
+ ['display_name']
],
- }).then(function (models) {
- self.models = models;
- var selectedModel = self.$target.attr('data-model_name') || 'mail.mail';
- // Models selection input
- var modelSelection = qweb.render("website_form.field_many2one", {
- field: {
- name: 'model_selection',
- string: 'Action',
- required: true,
- records: _.map(models, function (m) {
- return {
- id: m.id,
- display_name: m.website_form_label || m.name,
- selected: (m.model === selectedModel) ? 1 : null,
- };
- }),
- }
- });
+ });
+ }
+ return field.records;
+ },
+ /**
+ * Returns a field object
+ *
+ * @private
+ * @param {string} type the type of the field
+ * @param {string} name The name of the field used also as label
+ * @returns {Object}
+ */
+ _getCustomField: function (type, name) {
+ return {
+ name: name,
+ string: name,
+ custom: true,
+ type: type,
+ // Default values for x2many fields
+ records: [{
+ id: _t('Option 1'),
+ display_name: _t('Option 1'),
+ }, {
+ id: _t('Option 2'),
+ display_name: _t('Option 2'),
+ }, {
+ id: _t('Option 3'),
+ display_name: _t('Option 3'),
+ }],
+ // Default values for selection fields
+ selection: [[
+ _t('Option 1'),
+ _t('Option 1'),
+ ], [
+ _t('Option 2'),
+ _t('Option 2'),
+ ], [
+ _t('Option 3'),
+ _t('Option 3'),
+ ]],
+ };
+ },
+ /**
+ * Returns the default formatInfos of a field.
+ *
+ * @private
+ * @returns {Object}
+ */
+ _getDefaultFormat: function () {
+ const formOptions = this.el.closest('.o_we_customize_panel');
+ return {
+ labelWidth: formOptions && formOptions.querySelector('we-customizeblock-options [data-select-style] input').value + 'px',
+ labelPosition: 'left',
+ multiPosition: 'horizontal',
+ requiredMark: this._isRequiredMark(),
+ optionalMark: this._isOptionalMark(),
+ mark: this._getMark(),
+ };
+ },
+ /**
+ * @private
+ * @returns {string}
+ */
+ _getMark: function () {
+ return this.$target[0].dataset.mark;
+ },
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _isOptionalMark: function () {
+ return this.$target[0].classList.contains('o_mark_optional');
+ },
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _isRequiredMark: function () {
+ return this.$target[0].classList.contains('o_mark_required');
- // Success page input
- var successPage = qweb.render("website_form.field_char", {
- field: {
- name: 'success_page',
- string: 'Thank You Page',
- value: self.$target.attr('data-success_page')
- }
- });
+ },
+ /**
+ * @private
+ * @param {Object} field
+ * @returns {Promise<HTMLElement>}
+ */
+ _renderField: function (field) {
+ field.id = Math.random().toString(36).substring(2, 15); // Big unique ID
+ const template = document.createElement('template');
+ template.innerHTML = qweb.render("website_form.field_" + field.type, {field: field}).trim();
+ return template.content.firstElementChild;
+ },
+});
- var save = function () {
- var successPage = this.$el.find("[name='success_page']").val();
- self.init_form();
- self.$target.attr('data-success_page', successPage);
-
- this.$el.find('.o_form_parameter_custom').each(function () {
- var $field = $(this).find('.o_website_form_input');
- var value = $field.val();
- var fieldName = $field.attr('name');
- self.$target.find('.form-group:has("[name=' + fieldName + ']")').remove();
- if (value) {
- var $hiddenField = $(qweb.render('website_form.field_char', {
- field: {
- name: fieldName,
- value: value,
- }
- })).addClass('d-none');
- self.$target.find('.form-group:has(".o_website_form_send")').before($hiddenField);
- }
- });
- };
+const FieldEditor = FormEditor.extend({
+ /**
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this.formEl = this.$target[0].closest('form');
+ },
- var cancel = function () {
- if (!self.$target.attr('data-model_name')) {
- self.$target.remove();
- }
- };
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
- var $content = $('<form role="form">' + modelSelection + successPage + '</form>');
- var dialog = new FormEditorDialog(self, {
- title: 'Form Parameters',
- size: 'medium',
- $content: $content,
- }).open();
- dialog.on('closed', this, cancel);
- dialog.on('save', this, ev => {
- ev.stopPropagation();
- save.call(dialog);
- });
+ /**
+ * Returns the target as a field Object
+ *
+ * @private
+ * @returns {Object}
+ */
+ _getActiveField: function () {
+ let field;
+ if (this._isFieldCustom()) {
+ const name = this.$target[0].querySelector('.s_website_form_label').firstChild.textContent.trim();
+ field = this._getCustomField(this.$target[0].dataset.type, name);
+ } else {
+ field = this.fields[this._getFieldName()];
+ }
+ this._setActiveProperties(field);
+ return field;
+ },
+ /**
+ * Returns the format object of a field containing
+ * the position, labelWidth and bootstrap col class
+ *
+ * @private
+ * @returns {Object}
+ */
+ _getFieldFormat: function () {
+ let requiredMark, optionalMark;
+ const mark = this.$target[0].querySelector('span.s_website_form_mark');
+ if (mark) {
+ requiredMark = this._isFieldRequired();
+ optionalMark = !requiredMark;
+ }
+ const multipleInput = this._getMultipleInputs();
+ const format = {
+ labelPosition: this._getLabelPosition(),
+ labelWidth: this.$target[0].querySelector('.s_website_form_label').style.width,
+ multiPosition: multipleInput && multipleInput.dataset.display || 'horizontal',
+ col: [...this.$target[0].classList].filter(el => el.match(/^col-/g)).join(' '),
+ requiredMark: requiredMark,
+ optionalMark: optionalMark,
+ mark: mark && mark.textContent,
+ };
+ return format;
+ },
+ /**
+ * Returns the name of the field
+ *
+ * @private
+ * @returns {string}
+ */
+ _getFieldName: function () {
+ const multipleName = this.$target[0].querySelector('.s_website_form_multiple');
+ return multipleName ? multipleName.dataset.name : this.$target[0].querySelector('.s_website_form_input').name;
+ },
+ /**
+ * Returns the type of the field, can be used for both custom and existing fields
+ *
+ * @private
+ * @returns {string}
+ */
+ _getFieldType: function () {
+ return this.$target[0].dataset.type;
+ },
+ /**
+ * @private
+ * @returns {string}
+ */
+ _getLabelPosition: function () {
+ const label = this.$target[0].querySelector('.s_website_form_label');
+ if (this.$target[0].querySelector('.row:not(.s_website_form_multiple)')) {
+ return label.classList.contains('text-right') ? 'right' : 'left';
+ } else {
+ return label.classList.contains('d-none') ? 'none' : 'top';
+ }
+ },
+ /**
+ * Returns the multiple checkbox/radio element if it exist else null
+ *
+ * @private
+ * @returns {HTMLElement}
+ */
+ _getMultipleInputs: function () {
+ return this.$target[0].querySelector('.s_website_form_multiple');
+ },
+ /**
+ * @private
+ * @returns {string}
+ */
+ _getPlaceholder: function () {
+ const input = this._getPlaceholderInput();
+ return input ? input.placeholder : '';
+ },
+ /**
+ * Returns the field's input if it is placeholder compatible, else null
+ *
+ * @private
+ * @returns {HTMLElement}
+ */
+ _getPlaceholderInput: function () {
+ return this.$target[0].querySelector('input[type="text"], input[type="email"], input[type="number"] , textarea');
+ },
+ /**
+ * Returns true if the field is a custom field, false if it is an existing field
+ *
+ * @private
+ * @returns {boolean}
+ */
+ _isFieldCustom: function () {
+ return !!this.$target[0].classList.contains('s_website_form_custom');
+ },
+ /**
+ * Returns true if the field is required by the model or by the user.
+ *
+ * @private
+ * @returns {boolean}
+ */
+ _isFieldRequired: function () {
+ const classList = this.$target[0].classList;
+ return classList.contains('s_website_form_required') || classList.contains('s_website_form_model_required');
+ },
+ /**
+ * Set the active field properties on the field Object
+ *
+ * @param {Object} field Field to complete with the active field info
+ */
+ _setActiveProperties(field) {
+ field.placeholder = this._getPlaceholder();
+ field.required = this._isFieldRequired();
+ field.hidden = this.$target[0].classList.contains('s_website_form_field_hidden');
+ field.formatInfo = this._getFieldFormat();
+ },
+ /**
+ * Set the placeholder on the current field if the input allow it
+ *
+ * @private
+ * @param {string} value
+ */
+ _setPlaceholder: function (value) {
+ const input = this._getPlaceholderInput();
+ if (input) {
+ input.placeholder = value;
+ }
+ },
+});
+
+options.registry.WebsiteFormEditor = FormEditor.extend({
+ events: _.extend({}, options.Class.prototype.events || {}, {
+ 'click .toggle-edit-message': '_onToggleEndMessageClick',
+ }),
+
+ /**
+ * @override
+ */
+ willStart: async function () {
+ const _super = this._super.bind(this);
+
+ // Hide change form parameters option for forms
+ // e.g. User should not be enable to change existing job application form
+ // to opportunity form in 'Apply job' page.
+ this.modelCantChange = this.$target.attr('hide-change-model') !== undefined;
+ if (this.modelCantChange) {
+ return _super(...arguments);
+ }
+
+ // Get list of website_form compatible models.
+ this.models = await this._rpc({
+ model: "ir.model",
+ method: "search_read",
+ args: [
+ [['website_form_access', '=', true]],
+ ['id', 'model', 'name', 'website_form_label', 'website_form_key']
+ ],
+ });
- wUtils.autocompleteWithPages(self, $content.find("input[name='success_page']"));
- self.originSuccessPage = $content.find("input[name='success_page']").val();
- self.originFormID = $content.find("[name='model_selection']").val();
- self._renderParameterFields($content);
+ const targetModelName = this.$target[0].dataset.model_name || 'mail.mail';
+ this.activeForm = _.findWhere(this.models, {model: targetModelName});
+ // Create the Form Action select
+ this.selectActionEl = document.createElement('we-select');
+ this.selectActionEl.setAttribute('string', 'Action');
+ this.selectActionEl.dataset.noPreview = 'true';
+ this.models.forEach(el => {
+ const option = document.createElement('we-button');
+ option.textContent = el.website_form_label;
+ option.dataset.selectAction = el.id;
+ this.selectActionEl.append(option);
+ });
- $content.find("[name='model_selection']").on('change', function () {
- self._renderParameterFields($content);
+ return _super(...arguments);
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ const proms = [this._super(...arguments)];
+ // Disable text edition
+ this.$target.attr('contentEditable', false);
+ // Make button editable
+ this.$target.find('.s_website_form_send').parent().attr('contentEditable', true);
+ // Get potential message
+ this.$message = this.$target.parent().find('.s_website_form_end_message');
+ this.showEndMessage = false;
+ // If the form has no model it means a new snippet has been dropped.
+ // Apply the default model selected in willStart on it.
+ if (!this.$target[0].dataset.model_name) {
+ proms.push(this._applyFormModel());
+ }
+ return Promise.all(proms);
+ },
+ /**
+ * @override
+ */
+ cleanForSave: function () {
+ const model = this.$target[0].dataset.model_name;
+ // because apparently this can be called on the wrong widget and
+ // we may not have a model, or fields...
+ if (model) {
+ // we may be re-whitelisting already whitelisted fields. Doesn't
+ // really matter.
+ const fields = [...this.$target[0].querySelectorAll('.s_website_form_field:not(.s_website_form_custom) input')].map(el => el.name);
+ if (fields.length) {
+ // ideally we'd only do this if saving the form
+ // succeeds... but no idea how to do that
+ this._rpc({
+ model: 'ir.model.fields',
+ method: 'formbuilder_whitelist',
+ args: [model, _.uniq(fields)],
});
- });
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- * @returns {Promise}
- */
- _renderParameterFields: function ($modal) {
- var self = this;
- var $successPage = $modal.find("[name='success_page']");
- $modal.find('.o_form_parameter_custom').remove();
- var id = $modal.find("[name='model_selection']").val();
- this.activeForm = _.findWhere(this.models, {id: parseInt(id)});
- var formKey = this.activeForm.website_form_key;
- if (!formKey) {
- return Promise.resolve();
}
- var proms = [];
- var formInfo = FormEditorRegistry.get(formKey);
+ }
+ if (this.$message.length) {
+ this.$target.removeClass('d-none');
+ this.$message.addClass("d-none");
+ }
+ },
+ /**
+ * @override
+ */
+ updateUI: async function () {
+ // If we want to rerender the xml we need to avoid the updateUI
+ // as they are asynchronous and the ui might try to update while
+ // we are building the UserValueWidgets.
+ if (this.rerender) {
+ this.rerender = false;
+ await this._rerenderXML();
+ return;
+ }
+ await this._super.apply(this, arguments);
+ // End Message UI
+ this.updateUIEndMessage();
+ },
+ /**
+ * @see this.updateUI
+ */
+ updateUIEndMessage: function () {
+ this.$target.toggleClass("d-none", this.showEndMessage);
+ this.$message.toggleClass("d-none", !this.showEndMessage);
+ },
+ /**
+ * @override
+ */
+ notify: async function (name, data) {
+ await this._super(...arguments);
+ if (name === 'field_mark') {
+ this._setLabelsMark();
+ } else if (name === 'add_field') {
+ const field = this._getCustomField('char', 'Custom Text');
+ field.formatInfo = data.formatInfo;
+ field.formatInfo.requiredMark = this._isRequiredMark();
+ field.formatInfo.optionalMark = this._isOptionalMark();
+ field.formatInfo.mark = this._getMark();
+ const htmlField = this._renderField(field);
+ data.$target.after(htmlField);
+ this.trigger_up('activate_snippet', {
+ $snippet: $(htmlField),
+ });
+ }
+ },
- if (this.originFormID === id) {
- $successPage.val(this.originSuccessPage || formInfo.successPage || '/contactus-thank-you');
- } else {
- $successPage.val(formInfo.successPage || '/contactus-thank-you');
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Select the value of a field (hidden) that will be used on the model as a preset.
+ * ie: The Job you apply for if the form is on that job's page.
+ */
+ addActionField: function (previewMode, value, params) {
+ let fieldName = params.fieldName;
+ if (params.isSelect === 'true') {
+ value = parseInt(value);
+ }
+ this.$target.find(`.s_website_form_field.d-none:has(input[name="${params.fieldName}"])`).remove();
+ if (value) {
+ const hiddenField = qweb.render('website_form.field_hidden', {
+ field: {
+ name: fieldName,
+ value: value,
+ },
+ });
+ this.$target.find('.s_website_form_submit').before(hiddenField);
+ }
+ },
+ /**
+ * Changes the onSuccess event.
+ */
+ onSuccess: function (previewMode, value, params) {
+ this.$target[0].dataset.successMode = value;
+ if (value === 'message') {
+ if (!this.$message.length) {
+ this.$message = $(qweb.render('website_form.s_website_form_end_message'));
}
+ this.$target.after(this.$message);
+ } else {
+ this.$message.remove();
+ }
+ },
+ /**
+ * Select the model to create with the form.
+ */
+ selectAction: async function (previewMode, value, params) {
+ if (this.modelCantChange) {
+ return;
+ }
+ await this._applyFormModel(parseInt(value));
+ this.rerender = true;
+ },
+ /**
+ * @override
+ */
+ selectClass: function (previewMode, value, params) {
+ this._super(...arguments);
+ if (params.name === 'field_mark_select') {
+ this._setLabelsMark();
+ }
+ },
+ /**
+ * Set the mark string on the form
+ */
+ setMark: function (previewMode, value, params) {
+ this.$target[0].dataset.mark = value.trim();
+ this._setLabelsMark();
+ },
- if (formInfo.fields && formInfo.fields.length) {
- _.each(formInfo.fields, function (field) {
- var value = self.$target.find('[name="' + field.name + '"]').val();
- proms.push(self.render_field(field).then(function ($field) {
- $field.addClass('o_form_parameter_custom');
- // Remove content editable (Added by render_field)
- $field.find('label').removeAttr('contenteditable');
- // Set tooltip on label
- $field.find('label').attr('title', field.title);
- // Set value
- $field.find('.o_website_form_input').val(value);
- $modal.append($field);
- }));
- });
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _computeWidgetState: function (methodName, params) {
+ switch (methodName) {
+ case 'selectAction':
+ return this.activeForm.id;
+ case 'addActionField': {
+ const value = this.$target.find(`.s_website_form_field.d-none input[name="${params.fieldName}"]`).val();
+ if (value) {
+ return value;
+ } else {
+ return params.isSelect ? '0' : '';
+ }
}
- return Promise.all(proms);
- },
-
- // Choose a field modal
- website_form_field_modal: function (previewMode, widgetValue, params) {
- var self = this;
-
- this.fields().then(function (fields) {
- // Make a nice array to render the select input
- var fields_array = _.map(fields, function (v, k) { return {id: k, name: v.name, display_name: v.string}; });
- // Filter the fields to remove the ones already in the form
- var fields_in_form = _.map(self.$target.find('.col-form-label'), function (label) { return label.getAttribute('for'); });
- var available_fields = _.filter(fields_array, function (field) { return !_.contains(fields_in_form, field.name); });
- // Render the select input
- var fieldSelection = qweb.render("website_form.field_many2one", {
- field: {
- name: 'field_selection',
- string: 'Field',
- records: _.sortBy(available_fields, 'display_name')
- }
- });
+ case 'onSuccess':
+ return this.$target[0].dataset.successMode;
+ case 'setMark':
+ return this._getMark();
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _renderCustomXML: function (uiFragment) {
+ if (this.modelCantChange) {
+ return;
+ }
+ // Add Action select
+ const firstOption = uiFragment.querySelector(':first-child');
+ uiFragment.insertBefore(this.selectActionEl.cloneNode(true), firstOption);
- var save = function () {
- var selectedFieldName = this.$el.find("[name='field_selection']").val();
- var selectedField = fields[selectedFieldName];
- self.append_field(selectedField);
- };
+ // Add Action related options
+ const formKey = this.activeForm.website_form_key;
+ const formInfo = FormEditorRegistry.get(formKey);
+ if (!formInfo || !formInfo.fields) {
+ return;
+ }
+ const proms = formInfo.fields.map(field => this._fetchFieldRecords(field));
+ return Promise.all(proms).then(() => {
+ formInfo.fields.forEach(field => {
+ let option;
+ switch (field.type) {
+ case 'many2one':
+ option = this._buildSelect(field);
+ break;
+ case 'char':
+ option = this._buildInput(field);
+ break;
+ }
+ uiFragment.insertBefore(option, firstOption);
+ });
+ });
+ },
- var dialog = new FormEditorDialog(self, {
- title: 'Field Parameters',
- size: 'medium',
- $content: '<form role="form">' + fieldSelection + '</form>',
- }).open();
- dialog.on('save', this, ev => {
- ev.stopPropagation();
- save.call(dialog);
- });
+ /**
+ * Returns a we-input element from the field
+ *
+ * @private
+ * @param {Object} field
+ * @returns {HTMLElement}
+ */
+ _buildInput: function (field) {
+ const inputEl = document.createElement('we-input');
+ inputEl.dataset.noPreview = 'true';
+ inputEl.dataset.fieldName = field.name;
+ inputEl.dataset.addActionField = '';
+ inputEl.setAttribute('string', field.string);
+ return inputEl;
+ },
+ /**
+ * Returns a we-select element with field's records as it's options
+ *
+ * @private
+ * @param {Object} field
+ * @return {HTMLElement}
+ */
+ _buildSelect: function (field) {
+ const selectEl = document.createElement('we-select');
+ selectEl.dataset.noPreview = 'true';
+ selectEl.dataset.fieldName = field.name;
+ selectEl.dataset.isSelect = 'true';
+ selectEl.setAttribute('string', field.string);
+ const noneButton = document.createElement('we-button');
+ noneButton.textContent = 'None';
+ noneButton.dataset.addActionField = 0;
+ selectEl.append(noneButton);
+ field.records.forEach(el => {
+ const button = document.createElement('we-button');
+ button.textContent = el.display_name;
+ button.dataset.addActionField = el.id;
+ selectEl.append(button);
+ });
+ return selectEl;
+ },
+ /**
+ * Apply the model on the form changing it's fields
+ *
+ * @private
+ * @param {Integer} modelId
+ */
+ _applyFormModel: async function (modelId) {
+ let oldFormInfo;
+ if (modelId) {
+ const oldFormKey = this.activeForm.website_form_key;
+ if (oldFormKey) {
+ oldFormInfo = FormEditorRegistry.get(oldFormKey);
+ }
+ this.$target.find('.s_website_form_field').remove();
+ this.activeForm = _.findWhere(this.models, {id: modelId});
+ }
+ const formKey = this.activeForm.website_form_key;
+ const formInfo = FormEditorRegistry.get(formKey);
+ // Success page
+ if (!this.$target[0].dataset.successMode) {
+ this.$target[0].dataset.successMode = 'redirect';
+ }
+ if (this.$target[0].dataset.successMode === 'redirect') {
+ const currentSuccessPage = this.$target[0].dataset.successPage;
+ if (formInfo && formInfo.successPage) {
+ this.$target[0].dataset.successPage = formInfo.successPage;
+ } else if (!oldFormInfo || (oldFormInfo !== formInfo && oldFormInfo.successPage && currentSuccessPage === oldFormInfo.successPage)) {
+ this.$target[0].dataset.successPage = '/contactus-thank-you';
+ }
+ }
+ // Model name
+ this.$target[0].dataset.model_name = this.activeForm.model;
+ // Load template
+ if (formInfo) {
+ const formatInfo = this._getDefaultFormat();
+ await formInfo.formFields.forEach(async field => {
+ field.formatInfo = formatInfo;
+ await this._fetchFieldRecords(field);
+ this.$target.find('.s_website_form_submit').before(this._renderField(field));
});
- },
-
- // Create a custom field
- website_form_custom_field: function (previewMode, widgetValue, params) {
- var default_field_name = 'Custom ' + this.el.querySelector(`[data-website_form_custom_field="${widgetValue}"]`).textContent; // TODO improve
- this.append_field({
- name: default_field_name,
- string: default_field_name,
- custom: true,
- type: widgetValue,
- // Default values for x2many fields
- records: [
- {
- id: 'Option 1',
- display_name: _t('Option 1')
- },
- {
- id: 'Option 2',
- display_name: _t('Option 2')
- },
- {
- id: 'Option 3',
- display_name: _t('Option 3')
- }
- ],
- // Default values for selection fields
- selection: [
- [
- 'Option 1',
- _t('Option 1')
- ],
- [
- 'Option 2',
- _t('Option 2')
- ],
- [
- 'Option 3',
- _t('Option 3')
- ],
- ]
+ }
+ },
+ /**
+ * Set the correct mark on all fields.
+ *
+ * @private
+ */
+ _setLabelsMark: function () {
+ this.$target[0].querySelectorAll('span.s_website_form_mark').forEach(el => el.remove());
+ const mark = this._getMark();
+ if (!mark) {
+ return;
+ }
+ let fieldsToMark = [];
+ const requiredSelector = '.s_website_form_model_required, .s_website_form_required';
+ const fields = Array.from(this.$target[0].querySelectorAll('.s_website_form_field'));
+ if (this._isRequiredMark()) {
+ fieldsToMark = fields.filter(el => el.matches(requiredSelector));
+ } else if (this._isOptionalMark()) {
+ fieldsToMark = fields.filter(el => !el.matches(requiredSelector));
+ }
+ fieldsToMark.forEach(field => {
+ let span = document.createElement('span');
+ span.classList.add('s_website_form_mark');
+ span.textContent = ` ${mark}`;
+ field.querySelector('.s_website_form_label').appendChild(span);
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onToggleEndMessageClick: function () {
+ this.showEndMessage = !this.showEndMessage;
+ this.$el.find(".toggle-edit-message").toggleClass('text-primary', this.showEndMessage);
+ this.updateUIEndMessage();
+ },
+});
+
+options.registry.WebsiteFieldEditor = FieldEditor.extend({
+ events: _.extend({}, FieldEditor.prototype.events, {
+ 'click we-button.o_we_select_remove_option': '_onRemoveItemClick',
+ 'click we-button.o_we_list_add_optional': '_onAddCustomItemClick',
+ 'click we-button.o_we_list_add_existing': '_onAddExistingItemClick',
+ 'click we-list we-select': '_onAddItemSelectClick',
+ 'input we-list input': '_onListItemInput',
+ }),
+
+ /**
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this.rerender = true;
+ },
+ /**
+ * @override
+ */
+ willStart: async function () {
+ const _super = this._super.bind(this);
+ // Get the authorized existing fields for the form model
+ this.existingFields = await this._rpc({
+ model: "ir.model",
+ method: "get_authorized_fields",
+ args: [this.formEl.dataset.model_name],
+ }).then(fields => {
+ this.fields = _.each(fields, function (field, fieldName) {
+ field.name = fieldName;
+ field.domain = field.domain || [];
});
- },
-
- // Re-render the field and replace the current one
- // website_form_editor_field_reset: function(previewMode, widgetValue, params) {
- // var self = this;
- // var target_field_name = this.$target.find('.col-form-label').attr('for');
- // this.fields().then(function(fields){
- // self.render_field(fields[target_field_name]).done(function(field){
- // self.$target.replaceWith(field);
- // })
- // });
- // },
-
- append_field: function (field) {
- var self = this;
- // Copy formatting classes of last row (field)
- var $lastField = this.$target.find('.form-field.row:last');
- field.formatInfo = {
- labelClass: $lastField.find('> div:first').attr('class'),
- contentClass: $lastField.find('> div:last').attr('class'),
- };
- this.render_field(field).then(function (field){
- self.$target.find(".form-group:has('.o_website_form_send')").before(field);
+ // Create the buttons for the type we-select
+ return Object.keys(fields).map(key => {
+ const field = fields[key];
+ const button = document.createElement('we-button');
+ button.textContent = field.string;
+ button.dataset.existingField = field.name;
+ return button;
+ }).sort((a, b) => (a.textContent > b.textContent) ? 1 : (a.textContent < b.textContent) ? -1 : 0);
+ });
+ return _super(...arguments);
+ },
+ /**
+ * @override
+ */
+ cleanForSave: function () {
+ this.$target[0].querySelectorAll('#editable_select').forEach(el => el.remove());
+ const select = this._getSelect();
+ if (select && this.listTable) {
+ select.style.display = '';
+ select.innerHTML = '';
+ // Rebuild the select from the we-list
+ this.listTable.querySelectorAll('input').forEach(el => {
+ const option = document.createElement('option');
+ option.textContent = el.value;
+ option.value = this._isFieldCustom() ? el.value : el.name;
+ select.appendChild(option);
});
- },
-
- render_field: function (field) {
- // Convert the required boolean to a value directly usable
- // in qweb js to avoid duplicating this in the templates
- field.required = field.required ? 1 : null;
-
- // Fetch possible values for relation fields
- var fieldRelationProm;
- if (field.relation && field.relation !== 'ir.attachment') {
- fieldRelationProm = this._rpc({
- model: field.relation,
- method: 'search_read',
- args: [
- field.domain || [],
- ['display_name']
- ],
- }).then(function (records) {
- field.records = records;
- });
+ }
+ },
+ /**
+ * @override
+ */
+ updateUI: async function () {
+ // See Form updateUI
+ if (this.rerender) {
+ const select = this._getSelect();
+ if (select && !this.$target[0].querySelector('#editable_select')) {
+ select.style.display = 'none';
+ const editableSelect = document.createElement('div');
+ editableSelect.id = 'editable_select';
+ editableSelect.classList = 'form-control s_website_form_input';
+ select.parentElement.appendChild(editableSelect);
}
+ this.rerender = false;
+ await this._rerenderXML().then(() => this._renderList());
+ return;
+ }
+ await this._super.apply(this, arguments);
+ },
+ /**
+ * @override
+ */
+ onFocus: function () {
+ // Other fields type might have change to an existing type.
+ // We need to reload the existing type list.
+ this.rerender = true;
+ },
- return Promise.resolve(fieldRelationProm).then(function () {
- var $content = $(qweb.render("website_form.field_" + field.type, {field: field}));
- $content.find('label:not(:has(span)), label span').addClass('o_fake_editable').attr('contentEditable', true);
- return $content;
- });
- },
-
- onBuilt: function () {
- // Open the parameters modal on snippet drop
- this.website_form_model_modal('click', null, null);
- },
-
- /**
- * Hide change form parameters option for forms
- * e.g. User should not be enable to change existing job application form to opportunity form in 'Apply job' page.
- *
- * @override
- */
- onFocus: function () {
- this.$el.filter('[data-website_form_model_modal]').toggleClass('d-none', this.$target.attr('hide-change-model') !== undefined);
- },
-
- init_form: function () {
- var self = this;
- var modelName = this.activeForm.model;
- var currentModel = this.$target.attr('data-model_name');
- var formKey = this.activeForm.website_form_key;
- if (!currentModel) {
- // Directly change the parameters if model is being set for the first time
- this.changeFormParameters(modelName, formKey);
- } else if (modelName !== currentModel) {
- var warningMessage = _t("Are you sure you want to change the parameters of your form? All the current fields will be discarded.");
- new Dialog(this, {
- title: _t("Warning"),
- size: 'medium',
- $content: $('<div>', {text: warningMessage}),
- buttons: [{
- text: _t("Ok"),
- classes: 'btn-primary',
- click: function () {
- self.changeFormParameters(modelName, formKey);
- },
- close: true,
- }, {
- text: _t("Discard"),
- close: true,
- }],
- }).open({shouldFocusButtons: true});
+ //----------------------------------------------------------------------
+ // Options
+ //----------------------------------------------------------------------
+
+ /**
+ * Replace the current field with the custom field selected.
+ */
+ customField: async function (previewMode, value, params) {
+ // Both custom Field and existingField are called when selecting an option
+ // value is '' for the method that should not be called.
+ if (!value) {
+ return;
+ }
+ const name = this.el.querySelector(`[data-custom-field="${value}"]`).textContent;
+ const field = this._getCustomField(value, `Custom ${name}`);
+ this._setActiveProperties(field);
+ await this._replaceField(field);
+ this.rerender = true;
+ },
+ /**
+ * Replace the current field with the existing field selected.
+ */
+ existingField: async function (previewMode, value, params) {
+ // see customField
+ if (!value) {
+ return;
+ }
+ const field = this.fields[value];
+ this._setActiveProperties(field);
+ await this._replaceField(field);
+ this.rerender = true;
+ },
+ /**
+ * Set the name of the field on the label
+ */
+ setLabelText: function (previewMode, value, params) {
+ this.$target[0].querySelector('.s_website_form_label').firstChild.textContent = value;
+ if (this._isFieldCustom()) {
+ const multiple = this.$target[0].querySelector('.s_website_form_multiple');
+ if (multiple) {
+ multiple.dataset.name = value;
}
- },
-
- changeFormParameters: function (modelName, formKey) {
- var self = this;
- this.$target.attr('data-model_name', modelName);
- this.$target.find(".form-field:not(:has('.o_website_form_send')), .o_form_heading").remove();
- if (formKey) {
- var formInfo = FormEditorRegistry.get(formKey);
- ajax.loadXML(formInfo.defaultTemplatePath, qweb).then(function () {
- // Append form title
- $('<h1>', {
- class: 'o_form_heading',
- text: self.activeForm.website_form_label,
- contentEditable: true,
- }).prependTo(self.$target.find('.container'));
- self.$target.find('.form-group:has(".o_website_form_send")').before($(qweb.render(formInfo.defaultTemplateName)));
- });
+ this.$target[0].querySelectorAll('.s_website_form_input').forEach(el => el.name = value);
+ }
+ },
+ /*
+ * Set the placeholder of the input
+ */
+ setPlaceholder: function (previewMode, value, params) {
+ this._setPlaceholder(value);
+ },
+ /**
+ * Replace the field with the same field having the label in a different position.
+ */
+ selectLabelPosition: async function (previewMode, value, params) {
+ const field = this._getActiveField();
+ field.formatInfo.labelPosition = value;
+ await this._replaceField(field);
+ this._renderListItems();
+ },
+ /**
+ * Select the display of the multicheckbox field (vertical & horizontal)
+ */
+ multiCheckboxDisplay: function (previewMode, value, params) {
+ const target = this._getMultipleInputs();
+ target.querySelectorAll('.checkbox, .radio').forEach(el => {
+ if (value === 'horizontal') {
+ el.classList.add('col-lg-4', 'col-md-6');
} else {
- // Force fetch the fields of the new model
- // and render all model required fields
- this.fetch_model_fields().then(function (fields) {
- _.each(fields, function (field, field_name) {
- if (field.required) {
- self.append_field(field);
- }
- });
- });
+ el.classList.remove('col-lg-4', 'col-md-6');
}
- },
-
- cleanForSave: function () {
- var model = this.$target.data('model_name');
- // because apparently this can be called on the wrong widget and
- // we may not have a model, or fields...
- if (model) {
- // we may be re-whitelisting already whitelisted fields. Doesn't
- // really matter.
- var fields = this.$target.find('input.form-field[name=email_to], .form-field:not(.o_website_form_custom) :input').map(function (_, node) {
- return node.getAttribute('name');
- }).get();
- if (fields.length) {
- // ideally we'd only do this if saving the form
- // succeeds... but no idea how to do that
- this._rpc({
- model: 'ir.model.fields',
- method: 'formbuilder_whitelist',
- args: [model, _.uniq(fields)],
- });
- }
+ });
+ target.dataset.display = value;
+ },
+ /**
+ * Set the field as required or not
+ */
+ toggleRequired: function (previewMode, value, params) {
+ const isRequired = this.$target[0].classList.contains(params.activeValue);
+ this.$target[0].classList.toggle(params.activeValue, !isRequired);
+ this.$target[0].querySelectorAll('input, select, textarea').forEach(el => el.toggleAttribute('required', !isRequired));
+ this.trigger_up('option_update', {
+ optionName: 'WebsiteFormEditor',
+ name: 'field_mark',
+ });
+ },
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _computeWidgetState: function (methodName, params) {
+ switch (methodName) {
+ case 'customField':
+ return this._isFieldCustom() ? this._getFieldType() : '';
+ case 'existingField':
+ return this._isFieldCustom() ? '' : this._getFieldName();
+ case 'setLabelText':
+ return this.$target[0].querySelector('.s_website_form_label').firstChild.textContent.trim();
+ case 'setPlaceholder':
+ return this._getPlaceholder();
+ case 'selectLabelPosition':
+ return this._getLabelPosition();
+ case 'multiCheckboxDisplay': {
+ const target = this._getMultipleInputs();
+ return target ? target.dataset.display : '';
}
+ case 'toggleRequired':
+ return this.$target[0].classList.contains(params.activeValue) ? params.activeValue : 'false';
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _computeWidgetVisibility: function (widgetName, params) {
+ switch (widgetName) {
+ case 'multi_check_display_opt':
+ return !!this._getMultipleInputs();
+ case 'placeholder_opt':
+ return !!this._getPlaceholderInput();
+ case 'required_opt':
+ case 'hidden_opt':
+ case 'type_opt':
+ return !this.$target[0].classList.contains('s_website_form_model_required');
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _renderCustomXML: function (uiFragment) {
+ const selectEl = uiFragment.querySelector('we-select[data-name="type_opt"]');
+ const currentFieldName = this._getFieldName();
+ const fieldsInForm = Array.from(this.formEl.querySelectorAll('.s_website_form_field:not(.s_website_form_custom) .s_website_form_input')).map(el => el.name).filter(el => el !== currentFieldName);
+ const availableFields = this.existingFields.filter(el => !fieldsInForm.includes(el.dataset.existingField));
+ if (availableFields.length) {
+ const title = document.createElement('we-title');
+ title.textContent = 'Existing fields';
+ availableFields.unshift(title);
+ availableFields.forEach(option => selectEl.append(option.cloneNode(true)));
+ }
+ },
+ /**
+ * Replace the target content with the field provided
+ *
+ * @private
+ * @param {Object} field
+ * @returns {Promise}
+ */
+ _replaceField: async function (field) {
+ await this._fetchFieldRecords(field);
+ const htmlField = this._renderField(field);
+ [...this.$target[0].childNodes].forEach(node => node.remove());
+ [...htmlField.childNodes].forEach(node => this.$target[0].appendChild(node));
+ [...htmlField.attributes].forEach(el => this.$target[0].removeAttribute(el.nodeName));
+ [...htmlField.attributes].forEach(el => this.$target[0].setAttribute(el.nodeName, el.nodeValue));
+ },
- // Prevent saving of the error colors // TODO: would be better on Edit
- this.$target.find('.o_has_error').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
-
- // Prevent saving of the status message // TODO: would be better on Edit
- this.$target.find('#o_website_form_result').empty();
-
- // Update values of custom inputs to mirror their labels
- var custom_inputs = this.$target.find('.o_website_form_custom .o_website_form_input');
- _.each(custom_inputs, function (input, index) {
- // Change the custom field name according to their label
- var field_label = $(input).closest('.form-field').find('label:first');
- input.name = field_label.text().trim();
- field_label.attr('for', input.name);
-
- // Change the custom radio or checkboxes values according to their label
- if (input.type === 'radio' || input.type === 'checkbox') {
- var checkbox_label = $(input).closest('label').text().trim();
- if (checkbox_label) {
- input.value = checkbox_label;
- }
- }
+ /**
+ * To do after rerenderXML to add the list to the options
+ *
+ * @private
+ */
+ _renderList: function () {
+ let addItemButton, addItemTitle, listTitle;
+ const select = this._getSelect();
+ const multipleInputs = this._getMultipleInputs();
+ this.listTable = document.createElement('table');
+ const isCustomField = this._isFieldCustom();
+
+ if (select) {
+ listTitle = 'Options List';
+ addItemTitle = 'Add new Option';
+ select.querySelectorAll('option').forEach(opt => {
+ this._addItemToTable(opt.value, opt.textContent.trim());
});
- }
- });
-
- // radio / checkboxes field option to display them horizontally or vertically
- options.registry['website_form_editor_choice_field_display'] = options.Class.extend({
- xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
-
- /**
- * @see this.selectClass for parameters
- */
- website_form_choice_field_display: function (previewMode, widgetValue, params) {
- this.$target.toggleClass('o_website_form_flex_fw', widgetValue === 'vertical');
- this.$target[0].dataset.display = widgetValue;
- },
-
- //----------------------------------------------------------------------
- // Private
- //----------------------------------------------------------------------
-
- /**
- * @override
- */
- _computeWidgetState: function (methodName, params) {
- if (methodName === 'website_form_choice_field_display') {
- return this.$target.attr('data-display');
- }
- return this._super(...arguments);
- },
- });
-
- // Generic custom field options
- options.registry['website_form_editor_field'] = options.Class.extend({
- xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
-
- // Option to toggle inputs required attribute
- website_form_field_require: function (previewMode, widgetValue, params) {
- this.$target.find('.o_website_form_input').each(function (index, input) {
- input.required = !input.required;
+ this._renderListItems();
+ } else if (multipleInputs) {
+ listTitle = multipleInputs.querySelector('.radio') ? 'Radio List' : 'Checkbox List';
+ addItemTitle = 'Add new Checkbox';
+ multipleInputs.querySelectorAll('.checkbox, .radio').forEach(opt => {
+ this._addItemToTable(opt.querySelector('input').value, opt.querySelector('span').textContent.trim());
});
+ } else {
+ return;
}
- });
-
- // Dirty hack to transform select fields into an editable construct
- options.registry['website_form_editor_field_select'] = options.Class.extend({
- xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
-
- start: function () {
- if (!this.$target.find('#editable_select').length) {
- var self = this;
- var select = this.$target.find('select');
- select.hide();
- this.editable_select = $('<div id="editable_select" class="form-control o_website_form_input" contenteditable="true"/>');
- _.each(select.children(), function (option) {
- self.editable_select.append(
- $('<div id="' + $(option).attr('value') + '" class="o_website_form_select_item">' + $(option).text().trim() + '</div>')
- );
- });
- select.after(this.editable_select);
- }
- return this._super.apply(this, arguments);
- },
-
- cleanForSave: function () {
- if (this.$target.find('#editable_select').length) {
- var self = this;
- // Reconstruct the field from the select tag
- var select = this.$target.find('select');
- var field = {
- name: select.attr('name'),
- string: this.$target.find('.col-form-label').text().trim(),
- required: self.$target.hasClass('o_website_form_required'),
- custom: self.$target.hasClass('o_website_form_custom'),
- formatInfo: {
- labelClass: this.$target.find('> div:first').attr('class'),
- contentClass: this.$target.find('> div:last').attr('class'),
- },
- };
- // Build the new records list from the editable select field
- var records = [];
- var editable_options = this.$target.find('#editable_select .o_website_form_select_item');
- _.each(editable_options, function (option) {
- records.push({
- id: self.$target.hasClass('o_website_form_custom') ? $(option).text().trim() : $(option).attr('id'),
- display_name: $(option).text().trim()
+ if (isCustomField) {
+ addItemButton = document.createElement('we-button');
+ addItemButton.textContent = addItemTitle;
+ addItemButton.classList.add('o_we_list_add_optional');
+ addItemButton.dataset.noPreview = 'true';
+ } else {
+ addItemButton = document.createElement('we-select');
+ addItemButton.classList.add('o_we_user_value_widget'); // Todo dont use user value widget class
+ const togglerEl = document.createElement('we-toggler');
+ togglerEl.textContent = addItemTitle;
+ addItemButton.appendChild(togglerEl);
+ const selectMenuEl = document.createElement('we-select-menu');
+ addItemButton.appendChild(selectMenuEl);
+ this._loadListDropdown(selectMenuEl);
+ }
+ const selectInputEl = document.createElement('we-list');
+ const title = document.createElement('we-title');
+ title.textContent = listTitle;
+ selectInputEl.appendChild(title);
+ const tableWrapper = document.createElement('div');
+ tableWrapper.classList.add('oe_we_table_wraper');
+ tableWrapper.appendChild(this.listTable);
+ selectInputEl.appendChild(tableWrapper);
+ selectInputEl.appendChild(addItemButton);
+ this.el.insertBefore(selectInputEl, this.el.querySelector('[data-set-placeholder]'));
+ this._makeListItemsSortable();
+ },
+ /**
+ * Load the dropdown of the list with the records missing from the list.
+ *
+ * @private
+ * @param {HTMLElement} selectMenu
+ */
+ _loadListDropdown: function (selectMenu) {
+ selectMenu = selectMenu || this.el.querySelector('we-list we-select-menu');
+ if (selectMenu) {
+ selectMenu.innerHTML = '';
+ const field = this.fields[this._getFieldName()];
+ this._fetchFieldRecords(field).then(() => {
+ let buttonItems, availableRecords;
+ if (field.selection) {
+ availableRecords = field.selection.map(el => {
+ return {id: el[0], display_name: el[1]};
});
+ } else if (field.records) {
+ availableRecords = field.records;
+ }
+ const optionIds = Array.from(this.listTable.querySelectorAll('input')).map(opt => {
+ return field.type === 'selection' ? opt.name : parseInt(opt.name);
});
- field.records = records;
-
- // Replace this field by the new one
- var $new_select = $(qweb.render("website_form.field_many2one", {field: field}));
- // Reapply the custom style classes
- if (this.$target.hasClass('o_website_form_required_custom')) {
- $new_select.addClass('o_website_form_required_custom');
+ availableRecords = (availableRecords || []).filter(el => !optionIds.includes(el.id));
+ if (availableRecords.length) {
+ buttonItems = availableRecords.map(el => {
+ const option = document.createElement('we-button');
+ option.classList.add('o_we_list_add_existing');
+ option.dataset.addOption = el.id;
+ option.dataset.noPreview = 'true';
+ option.textContent = el.display_name;
+ return option;
+ });
+ } else {
+ const title = document.createElement('we-title');
+ title.textContent = 'No more records';
+ buttonItems = [title];
}
- if (this.$target.hasClass('o_website_form_field_hidden')) {
- $new_select.addClass('o_website_form_field_hidden');
+ buttonItems.forEach(button => selectMenu.appendChild(button));
+ });
+ }
+ },
+ /**
+ * @private
+ */
+ _makeListItemsSortable: function () {
+ $(this.listTable).sortable({
+ axis: 'y',
+ handle: '.o_we_drag_handle',
+ items: 'tr',
+ cursor: 'move',
+ opacity: 0.6,
+ stop: (event, ui) => {
+ this._renderListItems();
+ },
+ });
+ },
+ /**
+ * @private
+ * @param {string} id
+ * @param {string} text
+ */
+ _addItemToTable: function (id, text) {
+ const isCustomField = this._isFieldCustom();
+ const draggableEl = document.createElement('we-button');
+ draggableEl.classList.add('o_we_drag_handle', 'fa', 'fa-fw', 'fa-arrows');
+ draggableEl.dataset.noPreview = 'true';
+ const inputEl = document.createElement('input');
+ inputEl.type = 'text';
+ if (text) {
+ inputEl.value = text;
+ }
+ if (!isCustomField && id) {
+ inputEl.name = id;
+ }
+ inputEl.disabled = !isCustomField;
+ const trEl = document.createElement('tr');
+ const buttonEl = document.createElement('we-button');
+ buttonEl.classList.add('o_we_select_remove_option', 'fa', 'fa-fw', 'fa-minus');
+ buttonEl.dataset.removeOption = id;
+ buttonEl.dataset.noPreview = 'true';
+ const draggableTdEl = document.createElement('td');
+ const inputTdEl = document.createElement('td');
+ const buttonTdEl = document.createElement('td');
+ draggableTdEl.appendChild(draggableEl);
+ trEl.appendChild(draggableTdEl);
+ inputTdEl.appendChild(inputEl);
+ trEl.appendChild(inputTdEl);
+ buttonTdEl.appendChild(buttonEl);
+ trEl.appendChild(buttonTdEl);
+ this.listTable.appendChild(trEl);
+ if (isCustomField) {
+ inputEl.focus();
+ }
+ },
+ /**
+ * Apply the we-list on the target and rebuild the input(s)
+ *
+ * @private
+ */
+ _renderListItems: function () {
+ const multiInputsWrap = this._getMultipleInputs();
+ const selectWrap = this.$target[0].querySelector('#editable_select');
+ const isCustomField = this._isFieldCustom();
+ const isRequiredField = this._isFieldRequired();
+ const name = this._getFieldName();
+ if (multiInputsWrap) {
+ const type = multiInputsWrap.querySelector('.radio') ? 'radio' : 'checkbox';
+ multiInputsWrap.innerHTML = '';
+ this.listTable.querySelectorAll('input').forEach(el => {
+ const params = {
+ field: {
+ name: name,
+ required: isRequiredField,
+ formatInfo: {
+ multiPosition: multiInputsWrap.dataset.display,
+ }
+ }
+ };
+ const id = isCustomField ? el.value : el.name;
+ if (type === 'radio') {
+ params.record = [id, el.value];
+ } else {
+ params.record = {
+ id: id,
+ display_name: el.value,
+ };
}
- this.$target.replaceWith($new_select);
- }
+ const template = document.createElement('template');
+ template.innerHTML = qweb.render(`website_form.${type}`, params);
+ multiInputsWrap.appendChild(template.content.firstElementChild);
+ });
+ } else if (selectWrap) {
+ selectWrap.innerHTML = '';
+ this.listTable.querySelectorAll('input').forEach(el => {
+ const option = document.createElement('div');
+ option.id = (el.name || el.value);
+ option.classList.add('s_website_form_select_item');
+ option.textContent = el.value;
+ selectWrap.appendChild(option);
+ });
}
- });
-
- // allow breaking of form select items, to create new ones
- Wysiwyg.include({
- /**
- * @override
- */
- _editorOptions: function () {
- var options = this._super.apply(this, arguments);
- var isUnbreakableNode = options.isUnbreakableNode;
- options.isUnbreakableNode = function (node) {
- var isSelItem = $(node).hasClass('o_website_form_select_item');
- return isUnbreakableNode(node) && !isSelItem;
- };
- return options;
- },
- });
-
- // Superclass for options that need to disable a button from the snippet overlay
- var disable_overlay_button_option = options.Class.extend({
- xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
-
- // Disable a button of the snippet overlay
- disable_button: function (button_name, message) {
- // TODO refactor in master
- var className = 'oe_snippet_' + button_name;
- this.$overlay.add(this.$overlay.data('$optionsSection')).on('click', '.' + className, this.prevent_button);
- var $button = this.$overlay.add(this.$overlay.data('$optionsSection')).find('.' + className);
- $button.attr('title', message).tooltip({delay: 0});
- $button.removeClass(className); // Disable the functionnality
- },
-
- prevent_button: function (event) {
- // Snippet options bind their functions before the editor, so we
- // can't cleanly unbind the editor onRemove function from here
- event.preventDefault();
- event.stopImmediatePropagation();
- }
- });
-
- // Disable duplicate button for model fields
- options.registry['website_form_editor_field_model'] = disable_overlay_button_option.extend({
- start: function () {
- this.disable_button('clone', _t('You can\'t duplicate a model field.'));
- return this._super.apply(this, arguments);
- }
- });
-
- // Disable delete button for model required fields
- options.registry['website_form_editor_field_required'] = disable_overlay_button_option.extend({
- start: function () {
- this.disable_button('remove', _t('You can\'t remove a field that is required by the model itself.'));
- return this._super.apply(this, arguments);
- }
- });
-
- // Disable duplicate button for non-custom checkboxes and radio buttons
- options.registry['website_form_editor_field_x2many'] =disable_overlay_button_option.extend({
- start: function () {
- this.disable_button('clone', _t('You can\'t duplicate an item which refers to an actual record.'));
- return this._super.apply(this, arguments);
- }
- });
+ },
+ /**
+ * Returns the select element if it exist else null
+ *
+ * @private
+ * @returns {HTMLElement}
+ */
+ _getSelect: function () {
+ return this.$target[0].querySelector('select');
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onRemoveItemClick: function (ev) {
+ ev.target.closest('tr').remove();
+ this._loadListDropdown();
+ this._renderListItems();
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onAddCustomItemClick: function (ev) {
+ this._addItemToTable();
+ this._makeListItemsSortable();
+ this._renderListItems();
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onAddExistingItemClick: function (ev) {
+ const value = ev.currentTarget.dataset.addOption;
+ this._addItemToTable(value, ev.currentTarget.textContent);
+ this._makeListItemsSortable();
+ this._loadListDropdown();
+ this._renderListItems();
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onAddItemSelectClick: function (ev) {
+ ev.currentTarget.querySelector('we-toggler').classList.toggle('active');
+ },
+ /**
+ * @private
+ */
+ _onListItemInput: function () {
+ this._renderListItems();
+ },
+});
+
+options.registry.AddFieldForm = FormEditor.extend({
+ isTopOption: true,
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Add a char field at the end of the form.
+ * New field is set as active
+ */
+ addField: async function (previewMode, value, params) {
+ const field = this._getCustomField('char', 'Custom Text');
+ field.formatInfo = this._getDefaultFormat();
+ const htmlField = this._renderField(field);
+ this.$target.find('.s_website_form_submit').before(htmlField);
+ this.trigger_up('activate_snippet', {
+ $snippet: $(htmlField),
+ });
+ },
+});
+
+options.registry.AddField = FieldEditor.extend({
+ isTopOption: true,
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Add a char field with active field properties after the active field.
+ * New field is set as active
+ */
+ addField: async function (previewMode, value, params) {
+ this.trigger_up('option_update', {
+ optionName: 'WebsiteFormEditor',
+ name: 'add_field',
+ data: {
+ formatInfo: this._getFieldFormat(),
+ $target: this.$target,
+ },
+ });
+ },
+});
+
+// Superclass for options that need to disable a button from the snippet overlay
+const DisableOverlayButtonOption = options.Class.extend({
+ // Disable a button of the snippet overlay
+ disableButton: function (buttonName, message) {
+ // TODO refactor in master
+ const className = 'oe_snippet_' + buttonName;
+ this.$overlay.add(this.$overlay.data('$optionsSection')).on('click', '.' + className, this.preventButton);
+ const $button = this.$overlay.add(this.$overlay.data('$optionsSection')).find('.' + className);
+ $button.attr('title', message).tooltip({delay: 0});
+ $button.removeClass(className); // Disable the functionnality
+ },
+
+ preventButton: function (event) {
+ // Snippet options bind their functions before the editor, so we
+ // can't cleanly unbind the editor onRemove function from here
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+});
+
+// Disable duplicate button for model fields
+options.registry.WebsiteFormFieldModel = DisableOverlayButtonOption.extend({
+ start: function () {
+ this.disableButton('clone', _t('You can\'t duplicate a model field.'));
+ return this._super.apply(this, arguments);
+ }
+});
+
+// Disable delete button for model required fields
+options.registry.WebsiteFormFieldRequired = DisableOverlayButtonOption.extend({
+ start: function () {
+ this.disableButton('remove', _t('You can\'t remove a field that is required by the model itself.'));
+ return this._super.apply(this, arguments);
+ }
+});
+
+// Disable delete and duplicate button for submit
+options.registry.WebsiteFormSubmitRequired = DisableOverlayButtonOption.extend({
+ start: function () {
+ this.disableButton('remove', _t('You can\'t remove the submit button of the form'));
+ this.disableButton('clone', _t('You can\'t duplicate the submit button of the form.'));
+ return this._super.apply(this, arguments);
+ }
+});
});
diff --git a/addons/website_form/static/src/xml/website_form.xml b/addons/website_form/static/src/xml/website_form.xml
index c3779f9bc01..5403f763e24 100644
--- a/addons/website_form/static/src/xml/website_form.xml
+++ b/addons/website_form/static/src/xml/website_form.xml
@@ -3,74 +3,22 @@
<!-- Success status -->
<t t-name="website_form.status_success">
- <span id="o_website_form_result" class="text-success ml8">
+ <span id="s_website_form_result" class="text-success ml8">
<i class="fa fa-check mr4" role="img" aria-label="Success" title="Success"/>The form has been sent successfully.
</span>
</t>
<!-- Missing field status -->
<t t-name="website_form.status_invalid">
- <span id="o_website_form_result" class="text-danger ml8">
+ <span id="s_website_form_result" class="text-danger ml8">
<i class="fa fa-close mr4" role="img" aria-label="Error" title="Error"/>Please fill in the form correctly.
</span>
</t>
<!-- Error status -->
<t t-name="website_form.status_error">
- <span id="o_website_form_result" class="text-danger ml8">
+ <span id="s_website_form_result" class="text-danger ml8">
<i class="fa fa-close mr4" role="img" aria-label="Error" title="Error"/>An error has occured, the form has not been sent.
</span>
</t>
-
- <t t-name="website_form.default_contactus_form">
- <div class="form-group row form-field o_website_form_custom o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Name">Your Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="Name" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Phone">Phone Number</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="Phone"/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email_from">Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="email" class="form-control o_website_form_input" name="email_from" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Partner Name">Your Company</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="Partner Name"/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="subject">Subject</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="subject" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Description">Your Question</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <textarea class="form-control o_website_form_input" name="Description" required=""></textarea>
- </div>
- </div>
- </t>
-
</templates>
diff --git a/addons/website_form/static/src/xml/website_form_editor.xml b/addons/website_form/static/src/xml/website_form_editor.xml
index 3fb85a7dded..70c90f55593 100644
--- a/addons/website_form/static/src/xml/website_form_editor.xml
+++ b/addons/website_form/static/src/xml/website_form_editor.xml
@@ -1,32 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
+ <!-- End Message -->
+ <t t-name="website_form.s_website_form_end_message">
+ <div class="s_website_form_end_message d-none">
+ <div class="text-center alert alert-info css_non_editable_mode_hidden o_not_editable" t-ignore="True" role="status">
+ The following message will become visible <strong>only</strong> once the form is sent.
+ </div>
+ <div class="oe_structure">
+ <section class="s_text_block pt64 pb64">
+ <div class="container">
+ <h2 class="text-center">Thank You For Your Feedback</h2>
+ <p class="text-center">
+ Our team will message you back as soon as possible.<br/>
+ In the meantime we invite you to try our awesome <a href="/">website</a>.<br/>
+ </p>
+ </div>
+ </section>
+ </div>
+ </div>
+ </t>
+
<!-- Generic Field Layout -->
+ <!-- Changes made here needs to be reflected in the different Form view (Contact Us, Jobs, ...) -->
<t t-name="website_form.field">
- <div t-att-class="'row form-group form-field' + (field.custom ? ' o_website_form_custom' : '') + (field.required ? ' o_website_form_required' : '')">
- <div t-att-class="field.formatInfo and field.formatInfo.labelClass || 'col-lg-3 col-md-4'">
- <label class="col-form-label" t-att-for="field.name">
- <t t-esc="field.string"/>
+ <div t-attf-class="form-group s_website_form_field #{field.formatInfo.col or 'col-12'} #{field.custom and 's_website_form_custom' or ''} #{(field.required and 's_website_form_required' or '') or (field.modelRequired and 's_website_form_model_required' or '')} #{field.hidden and 's_website_form_field_hidden' or ''} #{field.dnone and 'd-none' or ''}"
+ t-att-data-type="field.type"
+ t-att-data-name="Field">
+ <div t-if="field.formatInfo.labelPosition != 'none' and field.formatInfo.labelPosition != 'top'" class="row s_col_no_resize s_col_no_bgcolor">
+ <label t-attf-class="col-form-label col-sm-auto s_website_form_label #{field.formatInfo.labelPosition == 'right' and 'text-right' or ''}" t-attf-style="width: #{field.formatInfo.labelWidth or '200px'}" t-att-for="field.id">
+ <t t-call="website_form.label_content"/>
</label>
- <span t-if="field.name == 'email_cc'" title="Separate email addresses with a comma.">
- <i class="fa fa-info-circle"/>
- </span>
+ <div class="col-sm">
+ <t t-raw="0"/>
+ </div>
</div>
- <div t-att-class="field.formatInfo and field.formatInfo.contentClass || 'col-lg-7 col-md-8'">
+ <t t-else="">
+ <label t-attf-class="s_website_form_label #{field.formatInfo.labelPosition == 'none' and 'd-none' or ''}" t-attf-style="width: #{field.formatInfo.labelWidth or '200px'}" t-att-for="field.id">
+ <t t-call="website_form.label_content"/>
+ </label>
<t t-raw="0"/>
- </div>
+ </t>
</div>
</t>
+ <t t-name="website_form.label_content">
+ <t t-if="field.custom" t-set="field.string" t-value="field.name"/>
+ <t t-esc="field.string"/>
+ <t t-if="field.required or field.modelRequired">
+ <span class="s_website_form_mark" t-if="field.formatInfo.requiredMark" t-esc="' ' + field.formatInfo.mark"/>
+ </t>
+ <t t-else="">
+ <span class="s_website_form_mark" t-if="field.formatInfo.optionalMark" t-esc="' ' + field.formatInfo.mark"/>
+ </t>
+ <span t-if="['email_cc', 'email_to'].includes(field.name)" title="Separate email addresses with a comma.">
+ <i class="fa fa-info-circle"/>
+ </span>
+ </t>
+
+ <!-- Hidden Field -->
+ <t t-name="website_form.field_hidden">
+ <t t-set="field.dnone" t-value="true"/>
+ <t t-set="field.formatInfo" t-value="{}"/>
+ <t t-call="website_form.field">
+ <input
+ type="hidden"
+ class="form-control s_website_form_input"
+ t-att-name="field.name"
+ t-att-value="field.value"
+ t-att-id="field.id"
+ />
+ </t>
+ </t>
+
<!-- Char Field -->
<t t-name="website_form.field_char">
<t t-call="website_form.field">
<input
type="text"
- class="form-control o_website_form_input"
+ class="form-control s_website_form_input"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
t-att-value="field.value"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
</t>
</t>
@@ -35,9 +92,11 @@
<t t-name="website_form.field_text">
<t t-call="website_form.field">
<textarea
- class="form-control o_website_form_input"
+ class="form-control s_website_form_input"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
</t>
</t>
@@ -47,9 +106,11 @@
<t t-call="website_form.field">
<input
type="email"
- class="form-control o_website_form_input"
+ class="form-control s_website_form_input"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
</t>
</t>
@@ -68,10 +129,12 @@
<t t-call="website_form.field">
<input
type="number"
- class="form-control o_website_form_input"
+ class="form-control s_website_form_input"
t-att-name="field.name"
step="1"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
</t>
</t>
@@ -81,10 +144,12 @@
<t t-call="website_form.field">
<input
type="number"
- class="form-control o_website_form_input"
+ class="form-control s_website_form_input"
t-att-name="field.name"
step="any"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
</t>
</t>
@@ -93,13 +158,15 @@
<t t-name="website_form.field_date">
<t t-call="website_form.field">
<t t-set="datepickerID" t-value="_.uniqueId('datepicker')"/>
- <div class="o_website_form_date input-group date" t-att-id="datepickerID" data-target-input="nearest">
+ <div class="s_website_form_date input-group date" t-att-id="datepickerID" data-target-input="nearest">
<input
type="text"
- class="form-control datetimepicker-input o_website_form_input"
+ class="form-control datetimepicker-input s_website_form_input"
t-attf-data-target="##{datepickerID}"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
<div class="input-group-append" t-attf-data-target="##{datepickerID}" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
@@ -112,13 +179,15 @@
<t t-name="website_form.field_datetime">
<t t-call="website_form.field">
<t t-set="datetimepickerID" t-value="_.uniqueId('datetimepicker')"/>
- <div class="o_website_form_datetime input-group date" t-att-id="datetimepickerID" data-target-input="nearest">
+ <div class="s_website_form_datetime input-group date" t-att-id="datetimepickerID" data-target-input="nearest">
<input
type="text"
- class="form-control datetimepicker-input o_website_form_input"
+ class="form-control datetimepicker-input s_website_form_input"
t-attf-data-target="##{datetimepickerID}"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-placeholder="field.placeholder"
+ t-att-id="field.id"
/>
<div class="input-group-append" t-attf-data-target="##{datetimepickerID}" data-toggle="datetimepicker">
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
@@ -134,9 +203,10 @@
<input
type="checkbox"
value="Yes"
- class="o_website_form_input"
+ class="s_website_form_input"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
+ t-att-id="field.id"
/>
</div>
</t>
@@ -145,27 +215,41 @@
<!-- Selection Field -->
<t t-name="website_form.field_selection">
<t t-call="website_form.field">
- <div class="o_website_form_flex" data-display="horizontal">
- <t t-foreach="field.selection" t-as="option">
- <div class="radio o_website_form_flex_item">
- <label class="col-form-label">
- <input
- type="radio"
- class="o_website_form_input"
- t-att-name="field.name"
- t-att-value="option[0]"
- t-att-required="field.required"
- />
- <span>
- <t t-esc="option[1]"/>
- </span>
- </label>
- </div>
+ <div class="row s_col_no_resize s_col_no_bgcolor s_website_form_multiple" t-att-data-name="field.name" t-att-data-display="field.formatInfo.multiPosition">
+ <t t-if="!field.selection">
+ <input
+ class="s_website_form_input"
+ t-att-name="field.name"
+ t-att-value="record.id"
+ t-att-required="field.required || field.modelRequired || None"
+ placeholder="No matching record !"
+ />
+ </t>
+ <t t-foreach="field.selection" t-as="record">
+ <t t-call="website_form.radio"/>
</t>
</div>
</t>
</t>
+ <!-- Radio -->
+ <t t-name="website_form.radio">
+ <div t-attf-class="radio col-12 #{field.formatInfo.multiPosition === 'horizontal' and 'col-lg-4 col-md-6' or ''}">
+ <label class="col-form-label">
+ <input
+ type="radio"
+ class="s_website_form_input"
+ t-att-name="field.name"
+ t-att-value="record[0]"
+ t-att-required="field.required || field.modelRequired || None"
+ />
+ <span>
+ <t t-esc="record[1]"/>
+ </span>
+ </label>
+ </div>
+ </t>
+
<!-- Many2One Field -->
<t t-name="website_form.field_many2one">
<!-- Binary one2many -->
@@ -175,7 +259,7 @@
<!-- Generic one2many -->
<t t-if="field.relation != 'ir.attachment'">
<t t-call="website_form.field">
- <select class="form-control o_website_form_input" t-att-name="field.name" t-att-required="field.required">
+ <select class="form-control s_website_form_input" t-att-name="field.name" t-att-required="field.required || field.modelRequired || None" t-att-id="field.id">
<t t-foreach="field.records" t-as="record">
<option t-att-value="record.id" t-att-selected="record.selected">
<t t-esc="record.display_name"/>
@@ -197,37 +281,42 @@
<!-- Generic one2many -->
<t t-if="field.relation != 'ir.attachment'">
<t t-call="website_form.field">
- <div class="o_website_form_flex" data-display="horizontal">
+ <div class="row s_col_no_resize s_col_no_bgcolor s_website_form_multiple" t-att-data-name="field.name" t-att-data-display="field.formatInfo.multiPosition">
<t t-if="!field.records">
<input
- class="o_website_form_input"
+ class="s_website_form_input"
t-att-name="field.name"
t-att-value="record.id"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
placeholder="No matching record !"
/>
</t>
<t t-foreach="field.records" t-as="record">
- <div class="checkbox o_website_form_flex_item">
- <label class="col-form-label">
- <input
- type="checkbox"
- class="o_website_form_input"
- t-att-name="field.name"
- t-att-value="record.id"
- t-att-required="field.required"
- />
- <span>
- <t t-esc="record.display_name"/>
- </span>
- </label>
- </div>
+ <t t-call="website_form.checkbox"/>
</t>
</div>
</t>
</t>
</t>
+ <!-- Checkbox -->
+ <t t-name="website_form.checkbox">
+ <div t-attf-class="checkbox col-12 #{field.formatInfo.multiPosition === 'horizontal' and 'col-lg-4 col-md-6' or ''}">
+ <label class="col-form-label">
+ <input
+ type="checkbox"
+ class="s_website_form_input"
+ t-att-name="field.name"
+ t-att-value="record.id"
+ t-att-required="field.required || field.modelRequired || None"
+ />
+ <span>
+ <t t-esc="record.display_name"/>
+ </span>
+ </label>
+ </div>
+ </t>
+
<!-- Many2Many Field -->
<t t-name="website_form.field_many2many">
<t t-call="website_form.field_one2many"/>
@@ -238,10 +327,11 @@
<t t-call="website_form.field">
<input
type="file"
- class="form-control o_website_form_input"
+ class="form-control-file s_website_form_input"
t-att-name="field.name"
- t-att-required="field.required"
+ t-att-required="field.required || field.modelRequired || None"
t-att-multiple="multiple"
+ t-att-id="field.id"
/>
</t>
</t>
diff --git a/addons/website_form/static/tests/tours/website_form_editor.js b/addons/website_form/static/tests/tours/website_form_editor.js
index ceb5f9a4efb..60723cb4118 100644
--- a/addons/website_form/static/tests/tours/website_form_editor.js
+++ b/addons/website_form/static/tests/tours/website_form_editor.js
@@ -1,459 +1,238 @@
-odoo.define('website_form_editor.tour', function(require) {
+odoo.define('website_form_editor.tour', function (require) {
'use strict';
- var rpc = require('web.rpc');
- var tour = require("web_tour.tour");
+ const rpc = require('web.rpc');
+ const tour = require("web_tour.tour");
+
+ const selectButtonByText = function (text) {
+ return [{
+ content: "Open the select",
+ trigger: `we-select:has(we-button:contains("${text}")) we-toggler`,
+ },
+ {
+ content: "Click on the option",
+ trigger: `we-select we-button:contains("${text}")`,
+ }];
+ };
+ const selectButtonByData = function (data) {
+ return [{
+ content: "Open the select",
+ trigger: `we-select:has(we-button[${data}]) we-toggler`,
+ }, {
+ content: "Click on the option",
+ trigger: `we-select we-button[${data}]`,
+ }];
+ };
+ const addField = function (data, name, type, label, required, hidden) {
+ const ret = [{
+ content: "Select form",
+ extra_trigger: '.s_website_form_field',
+ trigger: 'section.s_website_form',
+ }, {
+ content: "Add field",
+ trigger: 'we-button[data-add-field]',
+ },
+ ...selectButtonByData(data),
+ {
+ content: "Wait for field to load",
+ trigger: `.s_website_form_field[data-type="${name}"], .s_website_form_input[name="${name}"]`, //custom or existing field
+ run: function () {},
+ }];
+ let testText = '.s_website_form_field';
+ if (required) {
+ testText += '.s_website_form_required';
+ ret.push({
+ content: "Mark the field as required",
+ trigger: 'we-button[data-name="required_opt"]',
+ });
+ }
+ if (hidden) {
+ testText += '.s_website_form_field_hidden';
+ ret.push({
+ content: "Mark the field as hidden",
+ trigger: 'we-button[data-name="hidden_opt"]',
+ });
+ }
+ if (label) {
+ testText += `:has(label:contains("${label}"))`;
+ ret.push({
+ content: "Change the label text",
+ trigger: 'we-input[data-set-label-text] input',
+ run: `text ${label}`,
+ });
+ }
+ if (type !== 'checkbox' && type !== 'radio' && type !== 'select') {
+ let inputType = type === 'textarea' ? type : `input[type="${type}"]`;
+ testText += `:has(${inputType}[name="${name}"]${required ? '[required]' : ''})`;
+ }
+ ret.push({
+ content: "Check the resulting field",
+ trigger: testText,
+ run: function () {},
+ });
+ return ret;
+ };
+ const addCustomField = function (name, type, label, required, hidden) {
+ return addField(`data-custom-field="${name}"`, name, type, label, required, hidden);
+ };
+ const addExistingField = function (name, type, label, required, hidden) {
+ return addField(`data-existing-field="${name}"`, name, type, label, required, hidden);
+ };
tour.register("website_form_editor_tour", {
test: true,
}, [
// Drop a form builder snippet and configure it
{
- content: "Enter edit mode",
- trigger: "a[data-action=edit]"
- },
- {
- content: "Drop the form snippet",
- trigger: "#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail",
- run: "drag_and_drop #wrap",
- },
- {
- content: "Check dropped snippet",
- trigger: "body:has(form[action*='/website_form/'])",
- },
- {
- content: "Check if the snippet is dropped and if the modal is opened",
- trigger: "body:has(form[action*='/website_form/'])" +
- ":has(.modal-body:has(select[name='model_selection'])" +
- ":has(input[name='success_page']))",
- run: function () {},
- in_modal: false,
- },
- {
- content: "Change the action to create issues",
- trigger: ".modal-body select",
- run: "text project.issue"
- },
- {
- content: "Change the action to Send an E-mail",
- trigger: ".modal-body select",
- run: function (actions) {
- actions.text($('select[name="model_selection"] option:contains("Send an E-mail")').val());
- }
- },
- {
- content: "Complete Recipient E-mail",
- extra_trigger: ".modal-body input[name='email_to']",
- trigger: ".modal-body input[name='email_to']",
- run: "text test@test.test"
- },
- {
- content: "Click on Save",
- trigger: ".modal-footer button:first"
- },
- // Add the date field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]"
- },
- {
- content: "Click on Add a model field",
- trigger: "[data-website_form_field_modal]"
- },
- {
- content: "Select the date field",
- trigger: "select[name='field_selection']",
- run: "text date"
- },
- {
- content: "Click on Save",
- trigger: ".modal-footer button:first"
- },
-
- // Customize date field
- {
- content: "Change the label",
- trigger: ".col-form-label[for='date']",
- run: "text Test Date"
- },
- {
- content: "Click on Required",
- trigger: "[data-website_form_field_require]"
- },
- {
- content: "Check the resulting field",
- trigger: ".form-field.o_website_form_required_custom" +
- ":has(input[type=text][name=date][required])" +
- ":has(label:contains('Test Date'))",
- run: function () {},
- },
-
- // Add record_name field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]"
- },
- {
- content: "Click on Add a model field",
- trigger: "[data-website_form_field_modal]"
- },
- {
- content: "Select the record_name field",
- trigger: "select[name='field_selection']",
- run: "text record_name"
- },
- {
- content: "Click on Save",
- trigger: ".modal-footer button:first"
- },
-
- // Customize record_name field
- {
- content: "Change the label",
- trigger: ".col-form-label[for='record_name']",
- run: "text Awesome Label"
- },
- {
- content: "Click on Hidden",
- trigger: "[data-select-class='o_website_form_field_hidden']"
- },
- {
- content: "Check the resulting field",
- trigger: ".form-field.o_website_form_field_hidden" +
- ":has(input:not([required])[type=text][name=record_name])" +
- ":has(label:contains('Awesome Label'))",
- run: function () {},
- },
+ content: "Enter edit mode",
+ trigger: 'a[data-action=edit]',
+ }, {
+ content: "Drop the form snippet",
+ trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',
+ run: 'drag_and_drop #wrap',
+ }, {
+ content: "Check dropped snippet and select it",
+ extra_trigger: '.s_website_form_field',
+ trigger: 'section.s_website_form',
+ },
+ ...selectButtonByText('Send an E-mail'),
+ {
+ content: "Form has a model name",
+ trigger: 'section.s_website_form form[data-model_name="mail.mail"]',
+ }, {
+ content: "Complete Recipient E-mail",
+ trigger: '[data-field-name="email_to"] input',
+ run: 'text_blur test@test.test',
+ },
+ ...addExistingField('date', 'text', 'Test Date', true),
+ ...addExistingField('record_name', 'text', 'Awesome Label', false, true),
- // Add body_html field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]"
- },
- {
- content: "Click on Add a model field",
- trigger: "[data-website_form_field_modal]"
- },
- {
- content: "Select the body_html field",
- trigger: "select[name='field_selection']",
- run: "text body_html"
- },
- {
- content: "Click on Save",
- trigger: ".modal-footer button:first"
- },
+ ...addExistingField('body_html', 'textarea', 'Your Message', true),
- // Customize subject field
- {
- content: "Change the label",
- trigger: ".col-form-label[for='body_html']",
- run: "text Your Message"
- },
- {
- content: "Click on Required",
- trigger: "[data-website_form_field_require]"
- },
- {
- content: "Check the resulting field",
- trigger: ".form-field.o_website_form_required_custom" +
- ":has(textarea[name=body_html][required])" +
- ":has(label:contains('Your Message'))",
- run: function () {},
- },
+ ...addExistingField('recipient_ids', 'checkbox'),
- // Add recipient_ids relational field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]"
- },
- {
- content: "Click on Add a model field",
- trigger: "[data-website_form_field_modal]"
- },
- {
- content: "Select the recipient_ids field",
- trigger: "select[name='field_selection']",
- run: "text recipient_ids"
- },
- {
- content: "Click on Save",
- trigger: ".modal-footer button:first"
- },
- {
- content: "Check the resulting field",
- trigger: ".form-field:has(.col-form-label:contains('To (Partners)'))",
- run: function () {},
- },
-
- // Add a custom multiple checkboxes field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]",
- },
- {
- content: "Open custom fields menu",
- trigger: "we-select:contains('Multiple Checkboxes')",
- },
- {
- content: "Click on Multiple Checkboxes in menu Add a custom field",
- trigger: "we-button:contains('Multiple Checkboxes')",
- },
-
- // Customize custom multiple checkboxes field
- {
- content: "Change the label",
- trigger: ".col-form-label[for='Custom Multiple Checkboxes']",
- extra_trigger: ".s_website_form:not(:has(.col-form-label[for='Custom Multiple Checkboxes']:eq(1)))",
- run: "text Products"
- },
- {
- content: "Change Option 1 label",
- trigger: "label:contains('Option 1') span",
- run: "text Iphone"
- },
- {
- content: "Change Option 2 label",
- trigger: "label:contains('Option 2') span",
- run: "text Galaxy S"
- },
- {
- content: "Click on Option 3",
- trigger: "label:contains('Option 3') span",
- },
- {
- content: "Duplicate Option 3",
- trigger: ".oe_snippet_clone:last",
- },
- {
- content: "Change first Option 3 label",
- trigger: "label:contains('Option 3'):first span",
- run: "text Xperia"
- },
- {
- content: "Change last Option label",
- trigger: "label:contains('Option 3') span",
- run: "text Wiko Stairway"
- },
- {
- content: "Click on field",
- trigger: ".col-form-label:contains('Products')"
- },
- {
- content: "Click on Required",
- trigger: "[data-website_form_field_require]"
- },
- {
- content: "Click on first Check Box",
- trigger: "label:contains('Iphone') span",
- },
- {
- content: "Open display menu",
- trigger: "we-select:contains('Vertical')",
- },
- {
- content: "Click on 'Vertical' in menu 'Display'",
- trigger: "we-button:contains('Vertical')",
- },
- {
- content: "Check the resulting field",
- trigger: ".form-field.o_website_form_custom.o_website_form_required_custom" +
- ":has(.col-form-label:contains('Products'))" +
- ":has(.o_website_form_flex[data-display='vertical'])" +
- ":has(.checkbox label:contains('Iphone'):has(input[type='checkbox'][required]))" +
- ":has(.checkbox label:contains('Galaxy S'):has(input[type='checkbox'][required]))" +
- ":has(.checkbox label:contains('Xperia'):has(input[type='checkbox'][required]))" +
- ":has(.checkbox label:contains('Wiko Stairway'):has(input[type='checkbox'][required]))",
- run: function () {},
- },
-
- // Add a custom radio field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]",
- },
- {
- content: "Open custom fields menu",
- trigger: "we-select:contains('Radio Buttons')"
+ ...addCustomField('one2many', 'checkbox', 'Products', true),
+ {
+ content: "Change Option 1 label",
+ trigger: 'we-list table input:eq(0)',
+ run: 'text Iphone',
+ }, {
+ content: "Change Option 2 label",
+ trigger: 'we-list table input:eq(1)',
+ run: 'text Galaxy S',
+ }, {
+ content: "Change first Option 3 label",
+ trigger: 'we-list table input:eq(2)',
+ run: 'text Xperia',
+ }, {
+ content: "Click on Add new Checkbox",
+ trigger: 'we-list we-button.o_we_list_add_optional',
+ }, {
+ content: "Change added Option label",
+ trigger: 'we-list table input:eq(3)',
+ run: 'text Wiko Stairway',
+ }, {
+ content: "Check the resulting field",
+ trigger: ".s_website_form_field.s_website_form_custom.s_website_form_required" +
+ ":has(.s_website_form_multiple[data-display='horizontal'])" +
+ ":has(.checkbox label:contains('Iphone'):has(input[type='checkbox'][required]))" +
+ ":has(.checkbox label:contains('Galaxy S'):has(input[type='checkbox'][required]))" +
+ ":has(.checkbox label:contains('Xperia'):has(input[type='checkbox'][required]))" +
+ ":has(.checkbox label:contains('Wiko Stairway'):has(input[type='checkbox'][required]))",
+ run: function () {},
},
+ ...selectButtonByData('data-multi-checkbox-display="vertical"'),
{
- content: "Click on Radio Buttons in menu Add a custom field",
- trigger: "we-button:contains('Radio Buttons')"
+ content: "Check the resulting field",
+ trigger: ".s_website_form_field.s_website_form_custom.s_website_form_required" +
+ ":has(.s_website_form_multiple[data-display='vertical'])" +
+ ":has(.checkbox label:contains('Iphone'):has(input[type='checkbox'][required]))" +
+ ":has(.checkbox label:contains('Galaxy S'):has(input[type='checkbox'][required]))" +
+ ":has(.checkbox label:contains('Xperia'):has(input[type='checkbox'][required]))" +
+ ":has(.checkbox label:contains('Wiko Stairway'):has(input[type='checkbox'][required]))",
+ run: function () {},
},
- // Customize Radio Buttons field
- {
- content: "Change the label",
- trigger: ".col-form-label[for='Custom Radio Buttons']",
- run: "text Service"
- },
- {
- content: "Change Option 1 label",
- trigger: "label:contains('Option 1') span",
- run: "text After-sales Service"
- },
- {
- content: "Change Option 2 label",
- trigger: "label:contains('Option 2') span",
- run: "text Invoicing Service"
- },
- {
- content: "Click on Option 3",
- trigger: "label:contains('Option 3') span",
- },
- {
- content: "Duplicate Option 3",
- trigger: ".oe_snippet_clone:last",
- },
- {
- content: "Change first Option 3 label",
- trigger: "label:contains('Option 3'):first span",
- run: "text Development Service"
- },
- {
- content: "Change last Option label",
- trigger: "label:contains('Option 3') span",
- run: "text Management Service"
- },
- {
- content: "Click on field",
- trigger: ".col-form-label:contains('Service')"
- },
- {
- content: "Click on Required",
- trigger: "[data-website_form_field_require]"
- },
- {
- content: "Click on Required",
- trigger: "[data-website_form_field_require]",
- extra_trigger: ".form-field.o_website_form_custom.o_website_form_required_custom" +
- ":has(.col-form-label:contains('Service'))",
- },
- {
- content: "Check the resulting field",
- trigger: ".form-field.o_website_form_custom:not(.o_website_form_required_custom)" +
- ":has(.col-form-label:contains('Service'))" +
- ":has(.radio label:contains('After-sales Service'):has(input[type='radio']:not([required])))" +
- ":has(.radio label:contains('Invoicing Service'):has(input[type='radio']:not([required])))" +
- ":has(.radio label:contains('Development Service'):has(input[type='radio']:not([required])))" +
- ":has(.radio label:contains('Management Service'):has(input[type='radio']:not([required])))",
- run: function () {},
+ ...addCustomField('selection', 'radio', 'Service', true),
+ {
+ content: "Change Option 1 label",
+ trigger: 'we-list table input:eq(0)',
+ run: 'text After-sales Service',
+ }, {
+ content: "Change Option 2 label",
+ trigger: 'we-list table input:eq(1)',
+ run: 'text Invoicing Service',
+ }, {
+ content: "Change first Option 3 label",
+ trigger: 'we-list table input:eq(2)',
+ run: 'text Development Service',
+ }, {
+ content: "Click on Add new Checkbox",
+ trigger: 'we-list we-button.o_we_list_add_optional',
+ }, {
+ content: "Change last Option label",
+ trigger: 'we-list table input:eq(3)',
+ run: 'text Management Service',
+ }, {
+ content: "Mark the field as not required",
+ trigger: 'we-button[data-name="required_opt"]',
+ }, {
+ content: "Check the resulting field",
+ trigger: ".s_website_form_field.s_website_form_custom:not(.s_website_form_required)" +
+ ":has(.radio label:contains('After-sales Service'):has(input[type='radio']:not([required])))" +
+ ":has(.radio label:contains('Invoicing Service'):has(input[type='radio']:not([required])))" +
+ ":has(.radio label:contains('Development Service'):has(input[type='radio']:not([required])))" +
+ ":has(.radio label:contains('Management Service'):has(input[type='radio']:not([required])))",
+ run: function () {},
},
-
- // Add a custom selection field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]",
- },
- {
- content: "Open custom fields menu",
- trigger: "we-select:contains('Selection')"
- },
- {
- content: "Click on Selection in menu Add a custom field",
- trigger: "we-button:contains('Selection')",
- },
+ ...addCustomField('many2one', 'select', 'State', true),
// Customize custom selection field
{
- content: "Change the label",
- trigger: ".col-form-label[for='Custom Selection']",
- run: "text State"
- },
- {
- content: "Change Option 1 Label",
- trigger: ".o_website_form_select_item:contains('Option 1')",
- run: "text Germany"
- },
- {
- content: "Change Option 2 Label",
- trigger: ".o_website_form_select_item:contains('Option 2')",
- run: "text Belgium"
- },
- {
- content: "Click on Option 3",
- trigger: ".o_website_form_select_item:contains('Option 3')",
- },
- {
- content: "Duplicate Option 3",
- trigger: ".oe_snippet_clone:last",
- },
- {
- content: "Change first Option 3 label",
- trigger: ".o_website_form_select_item:contains('Option 3'):first",
- run: "text France"
- },
- {
- content: "Change last Option label",
- trigger: ".o_website_form_select_item:contains('Option 3')",
- run: "text Canada"
- },
- {
- content: "Click on Germany Option",
- trigger: ".o_website_form_select_item:contains('Germany')",
- },
- {
- content: "Remove Germany Option",
- trigger: ".oe_snippet_remove:last",
- },
- {
- content: "Click on field",
- trigger: ".col-form-label:contains('State')"
- },
- {
- content: "Click on Required",
- trigger: "[data-website_form_field_require]"
- },
- {
- content: "Check the resulting snippet",
- trigger: ".form-field.o_website_form_custom.o_website_form_required_custom" +
- ":has(label:contains('State'))" +
- ":has(select[required]:hidden)" +
- ":has(.o_website_form_select_item:contains('Belgium'))" +
- ":has(.o_website_form_select_item:contains('France'))" +
- ":has(.o_website_form_select_item:contains('Canada'))" +
- ":not(:has(.o_website_form_select_item:contains('Germany')))",
- run: function () {},
+ content: "Change Option 1 Label",
+ trigger: 'we-list table input:eq(0)',
+ run: 'text Germany',
+ }, {
+ content: "Change Option 2 Label",
+ trigger: 'we-list table input:eq(1)',
+ run: 'text Belgium',
+ }, {
+ content: "Change first Option 3 label",
+ trigger: 'we-list table input:eq(2)',
+ run: 'text France',
+ }, {
+ content: "Click on Add new Checkbox",
+ trigger: 'we-list we-button.o_we_list_add_optional',
+ }, {
+ content: "Change last Option label",
+ trigger: 'we-list table input:eq(3)',
+ run: 'text Canada',
+ }, {
+ content: "Remove Germany Option",
+ trigger: '.o_we_select_remove_option:eq(0)',
+ }, {
+ content: "Check the resulting snippet",
+ trigger: ".s_website_form_field.s_website_form_custom.s_website_form_required" +
+ ":has(label:contains('State'))" +
+ ":has(select[required]:hidden)" +
+ ":has(.s_website_form_select_item:contains('Belgium'))" +
+ ":has(.s_website_form_select_item:contains('France'))" +
+ ":has(.s_website_form_select_item:contains('Canada'))" +
+ ":not(:has(.s_website_form_select_item:contains('Germany')))",
+ run: function () {},
},
- // Add attachment_ids field
- {
- content: "Click on Form snippet",
- trigger: ".s_website_form[data-model_name]"
- },
- {
- content: "Click on Add a model field",
- trigger: "[data-website_form_field_modal]"
- },
- {
- content: "Select the attachment_ids field",
- trigger: "select[name='field_selection']",
- run: "text attachment_ids"
- },
- {
- content: "Click on Save",
- trigger: ".modal-footer button:first"
- },
+ ...addExistingField('attachment_ids', 'file', 'Invoice Scan'),
- // Customize attachment_ids field
- {
- content: "Change the label",
- trigger: ".col-form-label[for='attachment_ids']",
- run: "text Invoice Scan"
- },
+ // Save the page
{
- content: "Check the resulting field",
- trigger: ".form-field" +
- ":has(input[type=file][name=attachment_ids])" +
- ":has(label:contains('Invoice Scan'))",
- run: function () {
+ trigger: 'body',
+ run: function () {
$('body').append('<div id="completlyloaded"></div>');
},
},
-
- // Save the page
{
content: "Save the page",
trigger: "button[data-action=save]",
@@ -470,87 +249,87 @@ odoo.define('website_form_editor.tour', function(require) {
{
content: "Try to send empty form",
extra_trigger: "form[data-model_name='mail.mail']" +
- "[data-success_page='/contactus-thank-you']" +
- ":has(.form-field:has(label:contains('Your Name')):has(input[type='text'][name='Your Name'][required]))" +
- ":has(.form-field:has(label:contains('Email')):has(input[type='email'][name='email_from'][required]))" +
- ":has(.form-field:has(label:contains('Your Question')):has(textarea[name='Your Question'][required]))" +
- ":has(.form-field:has(label:contains('Subject')):has(input[type='text'][name='subject'][required]))" +
- ":has(.form-field:has(label:contains('Test Date')):has(input[type='text'][name='date'][required]))" +
- ":has(.form-field:has(label:contains('Awesome Label')):hidden)" +
- ":has(.form-field:has(label:contains('Your Message')):has(textarea[name='body_html'][required]))" +
- ":has(.form-field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Iphone'][required]))" +
- ":has(.form-field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Galaxy S'][required]))" +
- ":has(.form-field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Xperia'][required]))" +
- ":has(.form-field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Wiko Stairway'][required]))" +
- ":has(.form-field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='After-sales Service']:not([required])))" +
- ":has(.form-field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Invoicing Service']:not([required])))" +
- ":has(.form-field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Development Service']:not([required])))" +
- ":has(.form-field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Management Service']:not([required])))" +
- ":has(.form-field:has(label:contains('State')):has(select[name='State'][required]:has(option[value='Belgium'])))" +
- ":has(.form-field.o_website_form_required_custom:has(label:contains('State')):has(select[name='State'][required]:has(option[value='France'])))" +
- ":has(.form-field:has(label:contains('State')):has(select[name='State'][required]:has(option[value='Canada'])))" +
- ":has(.form-field:has(label:contains('Invoice Scan')))" +
- ":has(.form-field:has(input[name='email_to'][value='test@test.test']))",
- trigger: ".o_website_form_send"
+ "[data-success-page='/contactus-thank-you']" +
+ ":has(.s_website_form_field:has(label:contains('Your Name')):has(input[type='text'][name='Your Name'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Email')):has(input[type='email'][name='email_from'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Your Question')):has(textarea[name='Your Question'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Subject')):has(input[type='text'][name='subject'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Test Date')):has(input[type='text'][name='date'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Awesome Label')):hidden)" +
+ ":has(.s_website_form_field:has(label:contains('Your Message')):has(textarea[name='body_html'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Iphone'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Galaxy S'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Xperia'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Products')):has(input[type='checkbox'][name='Products'][value='Wiko Stairway'][required]))" +
+ ":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='After-sales Service']:not([required])))" +
+ ":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Invoicing Service']:not([required])))" +
+ ":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Development Service']:not([required])))" +
+ ":has(.s_website_form_field:has(label:contains('Service')):has(input[type='radio'][name='Service'][value='Management Service']:not([required])))" +
+ ":has(.s_website_form_field:has(label:contains('State')):has(select[name='State'][required]:has(option[value='Belgium'])))" +
+ ":has(.s_website_form_field.s_website_form_required:has(label:contains('State')):has(select[name='State'][required]:has(option[value='France'])))" +
+ ":has(.s_website_form_field:has(label:contains('State')):has(select[name='State'][required]:has(option[value='Canada'])))" +
+ ":has(.s_website_form_field:has(label:contains('Invoice Scan')))" +
+ ":has(.s_website_form_field:has(input[name='email_to'][value='test@test.test']))",
+ trigger: ".s_website_form_send"
},
{
content: "Check if required fields were detected and complete the Subject field",
- extra_trigger: "form:has(#o_website_form_result.text-danger)" +
- ":has(.form-field:has(label:contains('Your Name')).o_has_error)" +
- ":has(.form-field:has(label:contains('Email')).o_has_error)" +
- ":has(.form-field:has(label:contains('Your Question')).o_has_error)" +
- ":has(.form-field:has(label:contains('Subject')).o_has_error)" +
- ":has(.form-field:has(label:contains('Test Date')).o_has_error)" +
- ":has(.form-field:has(label:contains('Your Message')).o_has_error)" +
- ":has(.form-field:has(label:contains('Products')).o_has_error)" +
- ":has(.form-field:has(label:contains('Service')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('State')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('Invoice Scan')):not(.o_has_error))",
+ extra_trigger: "form:has(#s_website_form_result.text-danger)" +
+ ":has(.s_website_form_field:has(label:contains('Your Name')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Email')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Your Question')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Subject')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Test Date')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Your Message')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Products')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Service')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('State')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))",
trigger: "input[name=subject]",
run: "text Jane Smith"
},
{
content: "Update required field status by trying to Send again",
- trigger: ".o_website_form_send"
+ trigger: ".s_website_form_send"
},
{
content: "Check if required fields were detected and complete the Message field",
- extra_trigger: "form:has(#o_website_form_result.text-danger)" +
- ":has(.form-field:has(label:contains('Your Name')).o_has_error)" +
- ":has(.form-field:has(label:contains('Email')).o_has_error)" +
- ":has(.form-field:has(label:contains('Your Question')).o_has_error)" +
- ":has(.form-field:has(label:contains('Subject')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('Test Date')).o_has_error)" +
- ":has(.form-field:has(label:contains('Your Message')).o_has_error)" +
- ":has(.form-field:has(label:contains('Products')).o_has_error)" +
- ":has(.form-field:has(label:contains('Service')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('State')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('Invoice Scan')):not(.o_has_error))",
+ extra_trigger: "form:has(#s_website_form_result.text-danger)" +
+ ":has(.s_website_form_field:has(label:contains('Your Name')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Email')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Your Question')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Subject')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('Test Date')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Your Message')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Products')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Service')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('State')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))",
trigger: "textarea[name=body_html]",
run: "text A useless message"
},
{
content: "Update required field status by trying to Send again",
- trigger: ".o_website_form_send"
+ trigger: ".s_website_form_send"
},
{
content: "Check if required fields was detected and check a product. If this fails, you probably broke the cleanForSave.",
- extra_trigger: "form:has(#o_website_form_result.text-danger)" +
- ":has(.form-field:has(label:contains('Your Name')).o_has_error)" +
- ":has(.form-field:has(label:contains('Email')).o_has_error)" +
- ":has(.form-field:has(label:contains('Your Question')).o_has_error)" +
- ":has(.form-field:has(label:contains('Subject')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('Test Date')).o_has_error)" +
- ":has(.form-field:has(label:contains('Your Message')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('Products')).o_has_error)" +
- ":has(.form-field:has(label:contains('Service')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('State')):not(.o_has_error))" +
- ":has(.form-field:has(label:contains('Invoice Scan')):not(.o_has_error))",
+ extra_trigger: "form:has(#s_website_form_result.text-danger)" +
+ ":has(.s_website_form_field:has(label:contains('Your Name')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Email')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Your Question')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Subject')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('Test Date')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Your Message')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('Products')).o_has_error)" +
+ ":has(.s_website_form_field:has(label:contains('Service')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('State')):not(.o_has_error))" +
+ ":has(.s_website_form_field:has(label:contains('Invoice Scan')):not(.o_has_error))",
trigger: "input[name=Products][value='Wiko Stairway']"
},
{
content: "Complete Date field",
- trigger: ".o_website_form_datetime [data-toggle='datetimepicker']",
+ trigger: ".s_website_form_datetime [data-toggle='datetimepicker']",
},
{
content: "Check another product",
@@ -571,13 +350,18 @@ odoo.define('website_form_editor.tour', function(require) {
run: "text test@mail.com"
},
{
+ content: "Complete Subject field",
+ trigger: 'input[name="subject"]',
+ run: 'text subject',
+ },
+ {
content: "Complete Your Question field",
trigger: "textarea[name='Your Question']",
run: "text magan"
},
{
content: "Send the form",
- trigger: ".o_website_form_send"
+ trigger: ".s_website_form_send"
},
{
content: "Check form is submitted without errors",
diff --git a/addons/website_form/views/assets.xml b/addons/website_form/views/assets.xml
index 11445b89b63..b5433e0da00 100644
--- a/addons/website_form/views/assets.xml
+++ b/addons/website_form/views/assets.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
-
<template id="assets_editor" name="Website Form Editor Assets Editor" inherit_id="website.assets_editor">
<xpath expr="." position="inside">
+ <link rel="stylesheet" type="text/scss" href="/website_form/static/src/scss/wysiwyg_snippets.scss"/>
<script type="text/javascript" src="/website_form/static/src/snippets/s_website_form/options.js"/>
<script type="text/javascript" src="/website_form/static/src/js/website_form_editor_registry.js"/>
</xpath>
diff --git a/addons/website_form/views/snippets/s_website_form.xml b/addons/website_form/views/snippets/s_website_form.xml
index 2c49bfccd62..9a4a28fe6db 100644
--- a/addons/website_form/views/snippets/s_website_form.xml
+++ b/addons/website_form/views/snippets/s_website_form.xml
@@ -1,86 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
-<template id="s_website_form" name="Form Builder">
- <form action="/website_form/" method="post" class="s_website_form container-fluid mt32" enctype="multipart/form-data">
+<template id="s_website_form" name="Form">
+ <section class="s_website_form" data-vcss="001">
<div class="container">
- <div class="form-group row">
- <div class="offset-lg-3 offset-md-4 col-md-8 col-lg-7">
- <button class="btn btn-primary btn-lg o_website_form_send">Send</button>
- <span id="o_website_form_result"></span>
+ <form action="/website_form/" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*">
+ <div class="s_website_form_rows row s_col_no_bgcolor">
+ <div class="form-group col-12 s_website_form_submit">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <div style="width: 200px" class="col-sm-auto s_website_form_label"/>
+ <div class="col-sm">
+ <a href="#" role="button" class="btn btn-primary btn-lg s_website_form_send">Submit</a>
+ <span id="s_website_form_result"></span>
+ </div>
+ </div>
+ </div>
</div>
- </div>
+ </form>
</div>
- </form>
+ </section>
</template>
-<template id="snippet_options" inherit_id="website.snippet_options">
- <xpath expr="//*[@t-set='so_snippet_addition_selector']" position="inside">, .s_website_form</xpath>
+<template id="s_website_form_options" inherit_id="website.snippet_options">
<xpath expr="//div" position="after">
<!-- Form -->
- <div data-js="website_form_editor" data-selector=".s_website_form">
- <we-button data-website_form_model_modal="" data-no-preview="true">Change Form Parameters</we-button>
- <we-button data-website_form_field_modal="" data-no-preview="true">Add an existing field</we-button>
- <we-select string="Add a custom field" data-no-preview="true">
- <we-button data-website_form_custom_field="char">Text</we-button>
- <we-button data-website_form_custom_field="text">Long Text</we-button>
- <we-button data-website_form_custom_field="email">Email</we-button>
- <we-button data-website_form_custom_field="integer">Number</we-button>
- <we-button data-website_form_custom_field="float">Decimal Number</we-button>
- <we-button data-website_form_custom_field="boolean">Checkbox</we-button>
- <we-button data-website_form_custom_field="selection">Radio Buttons</we-button>
- <we-button data-website_form_custom_field="many2one">Selection</we-button>
- <we-button data-website_form_custom_field="one2many">Multiple Checkboxes</we-button>
- <we-button data-website_form_custom_field="date">Date</we-button>
- <we-button data-website_form_custom_field="datetime">Date &amp; Time</we-button>
- <we-button data-website_form_custom_field="binary">File Upload</we-button>
+ <div data-js="WebsiteFormEditor" data-selector=".s_website_form" data-target="form">
+ <we-select string="Display Fields Mark" data-name="field_mark_select" data-no-preview="true">
+ <we-button data-select-class="">None</we-button>
+ <we-button data-select-class="o_mark_required" data-name="form_required_opt">Required</we-button>
+ <we-button data-select-class="o_mark_optional" data-name="form_optional_opt">Optional</we-button>
</we-select>
+ <we-input data-set-mark="" data-dependencies="form_required_opt, form_optional_opt"/>
+ <we-input string="Labels Width"
+ data-select-style="" data-css-property="width"
+ data-unit="px" data-apply-to=".s_website_form_label"/>
+ <we-row>
+ <we-select string="On Success" data-no-preview="true">
+ <we-button data-on-success="nothing">Nothing</we-button>
+ <we-button data-on-success="redirect" data-name="show_redirect_opt">Redirect</we-button>
+ <we-button data-on-success="message" data-name="show_message_opt">Show Message</we-button>
+ </we-select>
+ <we-button class="fa fa-fw fa-edit align-self-end toggle-edit-message" title="Edit Message" data-name="message_opt" data-dependencies="show_message_opt"/>
+ </we-row>
+ <we-urlpicker string="URL" data-select-data-attribute="/contactus-thank-you" data-attribute-name="successPage" data-name="url_opt" data-dependencies="show_redirect_opt"/>
</div>
- <!-- Field -->
- <div data-js='website_form_editor' data-selector=".form-field" data-drop-near=".form-field">
- <we-checkbox string="Hidden" data-no-preview="true"
- data-select-class="o_website_form_field_hidden"/>
+ <!-- Add Field Form -->
+ <div data-js="AddFieldForm" data-selector=".s_website_form" data-target="form">
+ <we-button class="fa fa-fw fa-plus"
+ title="Add a new field at the end"
+ data-add-field=""
+ data-no-preview="true"/>
</div>
- <!-- Add an option to display radio buttons / checkboxes horizontally or vertically -->
- <div data-js='website_form_editor_choice_field_display' data-selector=".o_website_form_flex">
- <we-select string="Display" data-no-preview="true">
- <we-button data-website_form_choice_field_display="horizontal">Horizontal</we-button>
- <we-button data-website_form_choice_field_display="vertical">Vertical</we-button>
- </we-select>
+ <!-- Add Field -->
+ <div data-js="AddField" data-selector=".s_website_form_field">
+ <we-button class="fa fa-fw fa-plus"
+ title="Add a new field after this one"
+ data-add-field=""
+ data-no-preview="true"/>
</div>
- <!-- Add move, duplicate and remove controllers to checkboxes and radio buttons -->
- <div data-selector=".o_website_form_flex_item" data-drop-near=".o_website_form_flex_item"/>
-
- <!-- Add move and remove controllers to select items -->
- <div data-selector=".s_website_form .form-field.o_website_form_custom .o_website_form_select_item" data-drop-near=".o_website_form_select_item"/>
-
- <!-- Required option for fields that are not required fields of the model -->
- <div data-js='website_form_editor_field' data-selector=".form-field:not(.o_website_form_required)">
- <we-checkbox string="Required" data-no-preview="true"
- data-website_form_field_require=""
- data-select-class="o_website_form_required_custom"/>
+ <!-- Field -->
+ <div data-js='WebsiteFieldEditor' data-selector=".s_website_form_field" data-drop-near=".s_website_form_field">
+ <we-select data-name="type_opt" string="Type" data-no-preview="true">
+ <we-title>Custom field</we-title>
+ <we-button data-custom-field="char">Text</we-button>
+ <we-button data-custom-field="text">Long Text</we-button>
+ <we-button data-custom-field="email">Email</we-button>
+ <we-button data-custom-field="integer">Number</we-button>
+ <we-button data-custom-field="float">Decimal Number</we-button>
+ <we-button data-custom-field="boolean">Checkbox</we-button>
+ <we-button data-custom-field="one2many">Multiple Checkboxes</we-button>
+ <we-button data-custom-field="selection">Radio Buttons</we-button>
+ <we-button data-custom-field="many2one">Selection</we-button>
+ <we-button data-custom-field="date">Date</we-button>
+ <we-button data-custom-field="datetime">Date &amp; Time</we-button>
+ <we-button data-custom-field="binary">File Upload</we-button>
+ </we-select>
+ <we-select string="Display" data-name="multi_check_display_opt" data-no-preview="true">
+ <we-button data-multi-checkbox-display="horizontal">Horizontal</we-button>
+ <we-button data-multi-checkbox-display="vertical">Vertical</we-button>
+ </we-select>
+ <we-input string="Label Name" data-set-label-text=""/>
+ <we-select string="Label Position" data-no-preview="true">
+ <we-button data-select-label-position="none">None</we-button>
+ <we-button data-select-label-position="top">Top</we-button>
+ <we-button data-select-label-position="left">Left</we-button>
+ <we-button data-select-label-position="right">Right</we-button>
+ </we-select>
+ <we-input string="Input Placeholder" data-name="placeholder_opt" data-set-placeholder=""/>
+ <we-checkbox string="Required" data-name="required_opt" data-no-preview="true"
+ data-toggle-required="s_website_form_required"/>
+ <we-checkbox string="Hidden" data-name="hidden_opt" data-no-preview="true"
+ data-select-class="s_website_form_field_hidden"/>
</div>
- <!-- Remove the duplicate options of model fields -->
- <div data-js="website_form_editor_field_model" data-selector=".s_website_form .form-field:not(.o_website_form_custom)"/>
-
- <!-- Remove the delete options of model required fields -->
- <div data-js="website_form_editor_field_required" data-selector=".s_website_form .o_website_form_required"/>
-
- <!-- Remove the duplicate options of radio and checkboxes of model fields -->
- <div data-js="website_form_editor_field_x2many" data-selector=".s_website_form .form-field:not(.o_website_form_custom) .o_website_form_flex_item"/>
-
- <!-- Transform the select inputs into editable constructs -->
- <div data-js="website_form_editor_field_select" data-selector=".s_website_form .form-field:has(select)"/>
+ <!-- Remove the duplicate option of model fields -->
+ <div data-js="WebsiteFormFieldModel" data-selector=".s_website_form .s_website_form_field:not(.s_website_form_custom)"/>
- <!-- Remove the duplicate option of model select items -->
- <div data-js="website_form_editor_field_x2many" data-selector=".s_website_form .form-field:not(.o_website_form_custom) .o_website_form_select_item" data-drop-near=".o_website_form_select_item"/>
+ <!-- Remove the delete option of model required fields -->
+ <div data-js="WebsiteFormFieldRequired" data-selector=".s_website_form .s_website_form_model_required"/>
- <!-- Remove the delete options of the Submit button -->
- <div data-js="website_form_editor_field_required" data-selector=".s_website_form .o_website_form_send"/>
+ <!-- Remove the delete and duplicate option of the submit button -->
+ <div data-js="WebsiteFormSubmitRequired" data-selector=".s_website_form .s_website_form_submit"/>
</xpath>
</template>
@@ -89,6 +112,11 @@
<link rel="stylesheet" type="text/scss" href="/website_form/static/src/snippets/s_website_form/000.scss"/>
</xpath>
</template>
+<template id="assets_snippet_s_website_form_css_001" inherit_id="website.assets_frontend">
+ <xpath expr="//link[last()]" position="after">
+ <link rel="stylesheet" type="text/scss" href="/website_form/static/src/snippets/s_website_form/001.scss"/>
+ </xpath>
+</template>
<template id="assets_snippet_s_website_form_js_000" inherit_id="website.assets_frontend">
<xpath expr="//script[last()]" position="after">
diff --git a/addons/website_form/views/website_form_templates.xml b/addons/website_form/views/website_form_templates.xml
index ea6e3a5a9aa..256a9ad81f7 100644
--- a/addons/website_form/views/website_form_templates.xml
+++ b/addons/website_form/views/website_form_templates.xml
@@ -3,70 +3,82 @@
<template id="contactus_form" name="Contact Form" inherit_id="website.contactus" customize_show="True">
<xpath expr="//div[@name='mail_button']" position="replace">
- <form id="contactus_form" t-ignore="true" action="/website_form/" method="post" data-model_name="mail.mail" class="s_website_form container-fluid mt32" enctype="multipart/form-data" data-success_page="/contactus-thank-you">
- <div class="form-group row form-field o_website_form_custom o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Name">Your Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="Name" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Phone">Phone Number</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="Phone"/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email_from">Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="email" class="form-control o_website_form_input" name="email_from" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Partner Name">Your Company</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="Partner Name"/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="subject">Subject</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="subject" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Description">Your Question</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <textarea class="form-control o_website_form_input" name="Description" required=""></textarea>
- </div>
- </div>
- <div class="form-group row form-field d-none">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email_to">Email To</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="hidden" class="form-control o_website_form_input" name="email_to" t-att-value="res_company.email"/>
- </div>
- </div>
- <div class="form-group row">
- <div class="offset-lg-3 offset-md-4 col-md-8 col-lg-7">
- <a href="#" class="btn btn-primary btn-lg o_website_form_send">Send</a>
- <span id="o_website_form_result"></span>
+ <span class="hidden" data-for="contactus_form" t-att-data-values="{'email_to': res_company.email}" />
+ <div>
+ <section id="contactus_section" class="s_website_form" data-vcss="001">
+ <div class="container">
+ <form id="contactus_form" action="/website_form/" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-model_name="mail.mail" data-success-mode="redirect" data-success-page="/contactus-thank-you">
+ <div class="s_website_form_rows row s_col_no_bgcolor">
+ <div class="form-group col-12 s_website_form_field s_website_form_custom s_website_form_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact1">Your Name<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="contact1" type="text" class="form-control s_website_form_input" name="Name" required=""/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact2">Phone Number</label>
+ <div class="col-sm">
+ <input id="contact2" type="text" class="form-control s_website_form_input" name="Phone"/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="email">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact3">Email<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="contact3" type="email" class="form-control s_website_form_input" name="email_from" required=""/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact4">Your Company</label>
+ <div class="col-sm">
+ <input id="contact4" type="text" class="form-control s_website_form_input" name="Partner Name"/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact5">Subject<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="contact5" type="text" class="form-control s_website_form_input" name="subject" required=""/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field s_website_form_custom s_website_form_required" data-type="text">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact6">Your Question</label>
+ <div class="col-sm">
+ <textarea id="contact6" class="form-control s_website_form_input" name="Description" required=""></textarea>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_field d-none">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="contact7">Email To</label>
+ <div class="col-sm">
+ <input id="contact7" type="hidden" class="form-control s_website_form_input" name="email_to"/>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-12 s_website_form_submit">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <div style="width: 200px" class="col-sm-auto s_website_form_label"/>
+ <div class="col-sm">
+ <a href="#" role="button" class="btn btn-primary btn-lg s_website_form_send">Submit</a>
+ <span id="s_website_form_result"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
</div>
- </div>
- </form>
+ </section>
+ </div>
</xpath>
</template>
diff --git a/addons/website_form_project/static/src/js/website_form_project_editor.js b/addons/website_form_project/static/src/js/website_form_project_editor.js
index 33f2a728ee4..0956c12475e 100644
--- a/addons/website_form_project/static/src/js/website_form_project_editor.js
+++ b/addons/website_form_project/static/src/js/website_form_project_editor.js
@@ -7,8 +7,21 @@ var FormEditorRegistry = require('website_form.form_editor_registry');
var _t = core._t;
FormEditorRegistry.add('create_task', {
- defaultTemplateName: 'website_form_project.default_task_form',
- defaultTemplatePath: '/website_form_project/static/src/xml/website_form_project.xml',
+ formFields: [{
+ type: 'char',
+ modelRequired: true,
+ name: 'name',
+ string: 'Task Title',
+ }, {
+ type: 'email',
+ modelRequired: true,
+ name: 'email_from',
+ string: 'Your Email',
+ }, {
+ type: 'char',
+ name: 'description',
+ string: 'Description',
+ }],
fields: [{
name: 'project_id',
type: 'many2one',
diff --git a/addons/website_form_project/static/src/xml/website_form_project.xml b/addons/website_form_project/static/src/xml/website_form_project.xml
deleted file mode 100644
index e60631feac9..00000000000
--- a/addons/website_form_project/static/src/xml/website_form_project.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates>
-
- <t t-name="website_form_project.default_task_form">
- <div class="form-group row form-field o_website_form_required">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="name">Task Title</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="name" required="" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email_from">Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="email_from" required="" />
- </div>
- </div>
- <div class="form-group row form-field">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="description">Description</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="description" />
- </div>
- </div>
- </t>
-
-</templates>
diff --git a/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_editor.js b/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_editor.js
index bc3cef27f49..9cfd7810daf 100644
--- a/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_editor.js
+++ b/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_editor.js
@@ -7,8 +7,30 @@ var FormEditorRegistry = require('website_form.form_editor_registry');
var _t = core._t;
FormEditorRegistry.add('apply_job', {
- defaultTemplateName: 'website_hr_recruitment.default_job_form',
- defaultTemplatePath: '/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml',
+ formFields: [{
+ type: 'char',
+ modelRequired: true,
+ name: 'partner_name',
+ string: 'Your Name',
+ }, {
+ type: 'email',
+ required: true,
+ name: 'email_from',
+ string: 'Your Email',
+ }, {
+ type: 'char',
+ required: true,
+ name: 'partner_phone',
+ string: 'Phone Number',
+ }, {
+ type: 'text',
+ name: 'description',
+ string: 'Short Introduction',
+ }, {
+ type: 'binary',
+ custom: true,
+ name: 'Resume',
+ }],
fields: [{
name: 'job_id',
type: 'many2one',
diff --git a/addons/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml b/addons/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml
deleted file mode 100644
index 5a63e604014..00000000000
--- a/addons/website_hr_recruitment/static/src/xml/website_hr_recruitment.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates>
-
- <t t-name="website_hr_recruitment.default_job_form">
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="partner_name">Your Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="partner_name" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email_from">Your Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="email_from" required="" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="partner_phone">Phone Number</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="partner_phone" required="" />
- </div>
- </div>
- <div class="form-group row form-field">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="description">Short Introduction</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <textarea class="form-control o_website_form_input" name="description"></textarea>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="Resume">Resume</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="file" class="form-control o_website_form_input" name="Resume" />
- </div>
- </div>
- </t>
-
-</templates>
diff --git a/addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js b/addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js
index f75157b9d6e..c80dd1bd427 100644
--- a/addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js
+++ b/addons/website_hr_recruitment/static/tests/tours/website_hr_recruitment.js
@@ -30,7 +30,7 @@ odoo.define('website_hr_recruitment.tour', function(require) {
run: "text ### HR RECRUITMENT TEST DATA ###"
}, { // TODO: Upload a file ?
content: "Send the form",
- trigger: ".o_website_form_send"
+ trigger: ".s_website_form_send"
}, {
content: "Check the form is submited without errors",
trigger: ".oe_structure:has(h1:contains('Congratulations'))"
diff --git a/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml b/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml
index a19bc7e7e24..beba5e518e5 100644
--- a/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml
+++ b/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml
@@ -134,76 +134,81 @@
<h2 t-if="job" class="text-center text-muted">
<span t-field="job.name"/>
</h2>
-
- <div class="row mt-3">
- <section id="forms" class="col">
- <span class="hidden" data-for="hr_recruitment_form" t-att-data-values="{'department_id': job and job.department_id.id or '', 'job_id': job and job.id or ''}" />
- <form id="hr_recruitment_form" action="/website_form/" method="post" class="s_website_form" enctype="multipart/form-data" data-model_name="hr.applicant" data-success_page="/job-thank-you" hide-change-model="true">
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4 text-right">
- <label class="col-form-label" for="partner_name">Your Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="partner_name" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4 text-right">
- <label class="col-form-label" for="email_from">Your Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="email" class="form-control o_website_form_input" name="email_from" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4 text-right">
- <label class="col-form-label" for="partner_phone">Your Phone Number</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="partner_phone" required=""/>
+ <span class="hidden" data-for="hr_recruitment_form" t-att-data-values="{'department_id': job and job.department_id.id or '', 'job_id': job and job.id or ''}" />
+ <section id="forms" class="s_website_form" data-vcss="001">
+ <div class="container">
+ <form id="hr_recruitment_form" action="/website_form/" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-model_name="hr.applicant" data-success-mode="redirect" data-success-page="/job-thank-you" hide-change-model="true">
+ <div class="s_website_form_rows row s_col_no_bgcolor">
+ <div class="form-group col-12 s_website_form_field s_website_form_required s_website_form_model_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment1">Your Name<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="recruitment1" type="text" class="form-control s_website_form_input" name="partner_name" required=""/>
+ </div>
+ </div>
</div>
- </div>
- <div class="form-group row form-field">
- <div class="col-lg-3 col-md-4 text-right">
- <label class="col-form-label" for="description">Short Introduction</label>
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="email">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment2">Your Email<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="recruitment2" type="email" class="form-control s_website_form_input" name="email_from" required=""/>
+ </div>
+ </div>
</div>
- <div class="col-lg-7 col-md-8">
- <textarea class="form-control o_website_form_input" name="description"></textarea>
+ <div class="form-group col-12 s_website_form_field s_website_form_required" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment3">Your Phone Number<span class="s_website_form_mark"> *</span></label>
+ <div class="col-sm">
+ <input id="recruitment3" type="text" class="form-control s_website_form_input" name="partner_phone" required=""/>
+ </div>
+ </div>
</div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4 text-right">
- <label class="col-form-label" for="Resume">Resume</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="file" class="form-control o_website_form_input" name="Resume"/>
- </div>
- </div>
- <div class="form-group row form-field d-none">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="job_id">Job</label>
+ <div class="form-group col-12 s_website_form_field" data-type="text">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment4">Short Introduction</label>
+ <div class="col-sm">
+ <textarea id="recruitment4" class="form-control s_website_form_input" name="description"></textarea>
+ </div>
+ </div>
</div>
- <div class="col-lg-7 col-md-8">
- <input type="hidden" class="form-control o_website_form_input" name="job_id"/>
+ <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="binary">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment5">Resume</label>
+ <div class="col-sm">
+ <input id="recruitment5" type="file" class="form-control s_website_form_input" name="Resume"/>
+ </div>
+ </div>
</div>
- </div>
- <div class="form-group row form-field d-none">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="department_id">Department</label>
+ <div class="form-group col-12 s_website_form_field d-none">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment6">Job</label>
+ <div class="col-sm">
+ <input id="recruitment6" type="hidden" class="form-control s_website_form_input" name="job_id"/>
+ </div>
+ </div>
</div>
- <div class="col-lg-7 col-md-8">
- <input type="hidden" class="form-control o_website_form_input" name="department_id"/>
+ <div class="form-group col-12 s_website_form_field d-none">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="recruitment7">Department</label>
+ <div class="col-sm">
+ <input id="recruitment7" type="hidden" class="form-control s_website_form_input" name="department_id"/>
+ </div>
+ </div>
</div>
- </div>
- <div class="form-group row">
- <div class="offset-lg-3 offset-md-4 col-md-8 col-lg-7">
- <a href="#" role="button" class="btn btn-primary btn-lg o_website_form_send">Submit</a>
- <span id="o_website_form_result"></span>
+ <div class="form-group col-12 s_website_form_submit">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <div style="width: 200px" class="col-sm-auto s_website_form_label"/>
+ <div class="col-sm">
+ <a href="#" role="button" class="btn btn-primary btn-lg s_website_form_send">Submit</a>
+ <span id="s_website_form_result"></span>
+ </div>
+ </div>
</div>
</div>
</form>
- </section>
- </div>
+ </div>
+ </section>
+ <div class="oe_structure mt-2"/>
</div>
</t>
</template>
diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml
index 64502e84ea0..430fdc87b98 100644
--- a/addons/website_sale/data/data.xml
+++ b/addons/website_sale/data/data.xml
@@ -41,10 +41,6 @@
</record>
</data>
<data>
- <record id="sale.model_sale_order" model="ir.model">
- <field name="website_form_access" eval="True"/>
- <field name="website_form_label">Sales Order</field>
- </record>
<function model="ir.model.fields" name="formbuilder_whitelist">
<value>sale.order</value>
<value eval="[
diff --git a/addons/website_sale/static/src/js/website_sale_form_editor.js b/addons/website_sale/static/src/js/website_sale_form_editor.js
index f9345d7f9af..8ae105f1108 100644
--- a/addons/website_sale/static/src/js/website_sale_form_editor.js
+++ b/addons/website_sale/static/src/js/website_sale_form_editor.js
@@ -4,8 +4,25 @@ odoo.define('website_sale.form', function (require) {
var FormEditorRegistry = require('website_form.form_editor_registry');
FormEditorRegistry.add('create_customer', {
- defaultTemplateName: 'website_sale.default_customer_form',
- defaultTemplatePath: '/website_sale/static/src/xml/website_sale_form.xml',
+ formFields: [{
+ type: 'char',
+ modelRequired: true,
+ name: 'name',
+ string: 'Your Name',
+ }, {
+ type: 'email',
+ required: true,
+ name: 'email',
+ string: 'Your Email',
+ }, {
+ type: 'char',
+ name: 'phone',
+ string: 'Phone Number',
+ }, {
+ type: 'char',
+ name: 'company_name',
+ string: 'Company Name',
+ }],
});
});
diff --git a/addons/website_sale/static/src/xml/website_sale_form.xml b/addons/website_sale/static/src/xml/website_sale_form.xml
deleted file mode 100644
index f2488067d00..00000000000
--- a/addons/website_sale/static/src/xml/website_sale_form.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates>
-
- <t t-name="website_sale.default_customer_form">
- <div class="form-group row form-field o_website_form_required">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="name">Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="name" required=""/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_required_custom">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="email">Email</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="email" required="" />
- </div>
- </div>
- <div class="form-group row form-field">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="phone">Phone</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="phone" />
- </div>
- </div>
- <div class="form-group row form-field">
- <div class="col-lg-3 col-md-4">
- <label class="col-form-label" for="company_name">Company Name</label>
- </div>
- <div class="col-lg-7 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="company_name" />
- </div>
- </div>
- </t>
-
-</templates>
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 41007201327..e476bfa2077 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -746,44 +746,47 @@
<t t-call="website_sale.cart_summary"/>
</div>
<div class="col-12 col-xl order-xl-1 oe_cart">
- <div class="row">
- <div class="col">
- <form action="/website_form/" method="post" class="s_website_form container-fluid" enctype="multipart/form-data" data-force_action="shop.sale.order" data-model_name="sale.order" data-success_page="/shop/payment">
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4 text-md-right">
- <label class="col-form-label" for="client_order_ref">Your Reference</label>
- </div>
- <div class="col-lg-9 col-md-8">
- <input type="text" class="form-control o_website_form_input" name="client_order_ref"/>
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4 text-md-right">
- <label class="col-form-label" for="Give us your feedback">Give us your feedback</label>
- </div>
- <div class="col-lg-9 col-md-8">
- <textarea class="form-control o_website_form_input" rows="8" name="Give us your feedback" />
- </div>
- </div>
- <div class="form-group row form-field o_website_form_custom">
- <div class="col-lg-3 col-md-4 text-md-right">
- <label class="col-form-label" for="a_document">A document to provide</label>
+ <section class="s_website_form" data-vcss="001">
+ <div class="container">
+ <form action="/website_form/" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-force_action="shop.sale.order" data-model_name="sale.order" data-success-mode="redirect" data-success-page="/shop/payment" hide-change-model="true">
+ <div class="s_website_form_rows row s_col_no_bgcolor">
+ <div class="form-group col-12 s_website_form_field" data-type="char">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="sale1">Your Reference</label>
+ <div class="col-sm">
+ <input id="sale1" type="text" class="form-control s_website_form_input" name="client_order_ref"/>
+ </div>
+ </div>
</div>
- <div class="col-lg-9 col-md-8">
- <input type="file" class="form-control o_website_form_input" name="a_document" />
+ <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="text">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="sale2">Give us your feedback</label>
+ <div class="col-sm">
+ <textarea id="sale2" class="form-control s_website_form_input" name="Give us your feedback" />
+ </div>
+ </div>
</div>
- </div>
- <div class="form-group row">
- <div class="col-lg-3 col-md-4">
+ <div class="form-group col-12 s_website_form_field s_website_form_custom" data-type="binary">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <label class="col-form-label col-sm-auto s_website_form_label" style="width: 200px" for="sale3">A document to provide</label>
+ <div class="col-sm">
+ <input id="sale3" type="file" class="form-control-file s_website_form_input" name="a_document" />
+ </div>
+ </div>
</div>
- <div class="col-lg-9 col-md-8">
- <a role="button" href="/shop/checkout" class="btn btn-secondary mb32 float-left"><span class="fa fa-chevron-left" /> Previous</a>
- <a role="button" class="btn btn-primary float-right mb32 o_website_form_send" href="/shop/confirm_order">Next <span class="fa fa-chevron-right" /></a>
+ <div class="form-group col-12 s_website_form_submit">
+ <div class="row s_col_no_resize s_col_no_bgcolor">
+ <div style="width: 200px" class="col-sm-auto s_website_form_label"/>
+ <div class="col-sm">
+ <a role="button" href="/shop/checkout" class="btn btn-secondary float-left"><span class="fa fa-chevron-left"/> Previous</a>
+ <a role="button" class="btn btn-primary float-right s_website_form_send" href="/shop/confirm_order">Next <span class="fa fa-chevron-right" /></a>
+ </div>
+ </div>
</div>
</div>
</form>
</div>
- </div>
+ </section>
</div>
</div>
</div>