summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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>