, 2018\n"
+"Language-Team: Vietnamese (https://www.transifex.com/odoo/teams/41243/vi/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: vi\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:12
+#, python-format
+msgid "×"
+msgstr "×"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add a custom field"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add an existing field"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Change Form Parameters"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Checkbox"
+msgstr "Hộp kiểm"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:21
+#, python-format
+msgid "Close"
+msgstr "Đóng"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date"
+msgstr "Ngày"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date & Time"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Decimal Number"
+msgstr "Số thập phân"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "File Upload"
+msgstr "Tập tin tải lên"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Hidden"
+msgstr "Ẩn"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Long Text"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Multiple Checkboxes"
+msgstr ""
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:202
+#, python-format
+msgid "No matching record !"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Number"
+msgstr "Số"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Radio Buttons"
+msgstr "Nút Radio"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Required"
+msgstr "Bắt buộc"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:22
+#, python-format
+msgid "Save"
+msgstr "Lưu"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Selection"
+msgstr "Lựa chọn"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.s_website_form
+msgid "Send"
+msgstr "Gửi"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Text"
+msgstr "Văn bản"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.ir_model_view
+msgid "Website Forms"
+msgstr ""
diff --git a/addons/website_form_editor/i18n/website_form_editor.pot b/addons/website_form_editor/i18n/website_form_editor.pot
new file mode 100644
index 00000000000..b5afeb2444d
--- /dev/null
+++ b/addons/website_form_editor/i18n/website_form_editor.pot
@@ -0,0 +1,135 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_form_editor
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server saas~11.5+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-09-18 10:05+0000\n"
+"PO-Revision-Date: 2018-09-18 10:05+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:12
+#, python-format
+msgid "×"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add a custom field"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add an existing field"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Change Form Parameters"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Checkbox"
+msgstr ""
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:21
+#, python-format
+msgid "Close"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date & Time"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Decimal Number"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "File Upload"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Hidden"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Long Text"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Multiple Checkboxes"
+msgstr ""
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:202
+#, python-format
+msgid "No matching record !"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Number"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Radio Buttons"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Required"
+msgstr ""
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:22
+#, python-format
+msgid "Save"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Selection"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.s_website_form
+msgid "Send"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Text"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.ir_model_view
+msgid "Website Forms"
+msgstr ""
+
diff --git a/addons/website_form_editor/i18n/zh_CN.po b/addons/website_form_editor/i18n/zh_CN.po
new file mode 100644
index 00000000000..65d8bcfa859
--- /dev/null
+++ b/addons/website_form_editor/i18n/zh_CN.po
@@ -0,0 +1,137 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_form_editor
+#
+# Translators:
+# Martin Trigaux, 2018
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server saas~11.5+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-09-18 10:05+0000\n"
+"PO-Revision-Date: 2018-09-18 10:05+0000\n"
+"Last-Translator: Martin Trigaux, 2018\n"
+"Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:12
+#, python-format
+msgid "×"
+msgstr "×"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add a custom field"
+msgstr "添加自定义字段"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add an existing field"
+msgstr "添加现有字段"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Change Form Parameters"
+msgstr "更改表单参数"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Checkbox"
+msgstr "复选框"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:21
+#, python-format
+msgid "Close"
+msgstr "关闭"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date"
+msgstr "日期"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date & Time"
+msgstr "日期时间"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Decimal Number"
+msgstr "小数分割符"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "File Upload"
+msgstr "文件上传"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Hidden"
+msgstr "隐藏"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Long Text"
+msgstr "长文本"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Multiple Checkboxes"
+msgstr "多个复选框"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:202
+#, python-format
+msgid "No matching record !"
+msgstr "没有符合条件的记录!"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Number"
+msgstr "号码"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Radio Buttons"
+msgstr "单选按钮"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Required"
+msgstr "必须的"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:22
+#, python-format
+msgid "Save"
+msgstr "保存"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Selection"
+msgstr "选中内容"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.s_website_form
+msgid "Send"
+msgstr "发送"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Text"
+msgstr "文本"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.ir_model_view
+msgid "Website Forms"
+msgstr "网站表单"
diff --git a/addons/website_form_editor/i18n/zh_TW.po b/addons/website_form_editor/i18n/zh_TW.po
new file mode 100644
index 00000000000..e32408a33a0
--- /dev/null
+++ b/addons/website_form_editor/i18n/zh_TW.po
@@ -0,0 +1,137 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_form_editor
+#
+# Translators:
+# Martin Trigaux, 2018
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server saas~11.5+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-09-18 10:05+0000\n"
+"PO-Revision-Date: 2018-09-18 10:05+0000\n"
+"Last-Translator: Martin Trigaux, 2018\n"
+"Language-Team: Chinese (Taiwan) (https://www.transifex.com/odoo/teams/41243/zh_TW/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: zh_TW\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:12
+#, python-format
+msgid "×"
+msgstr "×"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add a custom field"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Add an existing field"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Change Form Parameters"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Checkbox"
+msgstr ""
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:21
+#, python-format
+msgid "Close"
+msgstr "關閉"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date"
+msgstr "日期 "
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Date & Time"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Decimal Number"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "File Upload"
+msgstr "文件上傳"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Hidden"
+msgstr "隱藏"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Long Text"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Multiple Checkboxes"
+msgstr ""
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:202
+#, python-format
+msgid "No matching record !"
+msgstr ""
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Number"
+msgstr "號碼"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Radio Buttons"
+msgstr "單選按鈕"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Required"
+msgstr "必填項"
+
+#. module: website_form_editor
+#. openerp-web
+#: code:addons/website_form_editor/static/src/xml/website_form_editor.xml:22
+#, python-format
+msgid "Save"
+msgstr "保存"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Selection"
+msgstr "選擇"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.s_website_form
+msgid "Send"
+msgstr "發送"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.snippet_options
+msgid "Text"
+msgstr "文本"
+
+#. module: website_form_editor
+#: model_terms:ir.ui.view,arch_db:website_form_editor.ir_model_view
+msgid "Website Forms"
+msgstr ""
diff --git a/addons/website_form_editor/static/description/icon.png b/addons/website_form_editor/static/description/icon.png
new file mode 100644
index 00000000000..212df7a906a
Binary files /dev/null and b/addons/website_form_editor/static/description/icon.png differ
diff --git a/addons/website_form_editor/static/description/icon.svg b/addons/website_form_editor/static/description/icon.svg
new file mode 100644
index 00000000000..c6dd1249e8e
--- /dev/null
+++ b/addons/website_form_editor/static/description/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/website_form_editor/static/src/js/website_form_editor.js b/addons/website_form_editor/static/src/js/website_form_editor.js
new file mode 100644
index 00000000000..6500ecb99c5
--- /dev/null
+++ b/addons/website_form_editor/static/src/js/website_form_editor.js
@@ -0,0 +1,474 @@
+odoo.define('website_form_editor', function (require) {
+ 'use strict';
+
+ /**
+ * @todo this should be entirely refactored
+ */
+
+ var core = require('web.core');
+ var options = require('web_editor.snippets.options');
+ var wUtils = require('website.utils');
+
+ var qweb = core.qweb;
+
+ options.registry['website_form_editor'] = options.Class.extend({
+ xmlDependencies: ['/website_form_editor/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').addClass('o_fake_editable').attr('contentEditable', true);
+ },
+
+ // Generic modal code
+ build_modal: function (modal_title, modal_body, on_save) {
+ var self = this;
+
+ // Build the form parameters modal
+ var modal = qweb.render("website_form_editor.modal", {
+ modal_title: modal_title,
+ modal_body: modal_body
+ });
+
+ self.$modal = $(modal);
+ self.$modal.appendTo('body');
+
+ // Process the modal on_save then hide it
+ self.$modal.find("#modal-save").on('click', function (e){
+ if (self.$modal.find("form")[0].checkValidity()) {
+ on_save();
+ self.$modal.modal('hide');
+ } else {
+ _.each(self.$modal.find('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');
+ }
+ });
+ }
+ });
+
+ // Destroy the modal when it is closed, as we will use many of them
+ self.$modal.on('hidden.bs.modal', function (e) {
+ self.$modal.remove();
+ });
+
+ self.$modal.modal();
+
+ return self.$modal;
+ },
+
+ // 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, value, $li) {
+ var self = this;
+ this._rpc({
+ model: "ir.model",
+ method: "search_read",
+ args: [
+ [['website_form_access', '=', true]],
+ ['id', 'model', 'name', 'website_form_label']
+ ],
+ }).then(function (models) {
+ // Models selection input
+ var model_selection = qweb.render("website_form_editor.field_many2one", {
+ field: {
+ name: 'model_selection',
+ string: 'Action',
+ records: _.map(models, function (m) {
+ return {
+ id: m.model,
+ display_name: m.website_form_label || m.name,
+ selected: (m.model === self.$target.attr('data-model_name')) ? 1 : null,
+ };
+ }),
+ }
+ });
+
+ // Success page input
+ var success_page = qweb.render("website_form_editor.field_char", {
+ field: {
+ name: 'success_page',
+ string: 'Thank You Page',
+ value: self.$target.attr('data-success_page')
+ }
+ });
+
+ // Form parameters modal
+ self.build_modal(
+ "Form Parameters",
+ model_selection + success_page,
+ function () {
+ var model_name = self.$modal.find("[name='model_selection']").val();
+ var success_page = self.$modal.find("[name='success_page']").val();
+ self.init_form(model_name);
+ self.$target.attr('data-success_page', success_page);
+
+ // Add magic email_to input if model is mail.mail
+ self.$target.find("input.form-field[name='email_to']").remove();
+ if (model_name === 'mail.mail') {
+ var email_to = self.$modal.find("input[name='email_to']").val();
+ self.$target.append("");
+ }
+ }
+ );
+
+ self.$modal.find("label.col-form-label[for='success_page']").css('font-weight', 'normal');
+ wUtils.autocompleteWithPages(self, self.$modal.find("input[name='success_page']"));
+ self.toggle_email_to();
+
+ self.$modal.find("[name='model_selection']").on('change', function () {
+ self.toggle_email_to();
+ });
+
+ // On modal close, if there is no data-model, it means
+ // that the user refused to configure the form on the
+ // first modal, so we remove the snippet.
+ self.$modal.on('hidden.bs.modal', function (e) {
+ if (!self.$target.attr('data-model_name')){
+ self.$target.remove();
+ }
+ });
+ });
+ },
+
+ // Choose a field modal
+ website_form_field_modal: function (previewMode, value, $li) {
+ 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 field_selection = qweb.render("website_form_editor.field_many2one", {
+ field: {
+ name: 'field_selection',
+ string: 'Field',
+ records: _.sortBy(available_fields, 'display_name')
+ }
+ });
+
+ // Form parameters modal
+ self.build_modal(
+ "Field Parameters",
+ field_selection,
+ function () {
+ var selected_field_name = self.$modal.find("[name='field_selection']").val();
+ var selected_field = fields[selected_field_name];
+ self.append_field(selected_field);
+ }
+ );
+ });
+ },
+
+ // Create a custom field
+ website_form_custom_field: function (previewMode, value, $li) {
+ var default_field_name = 'Custom ' + $li.text();
+ this.append_field({
+ name: default_field_name,
+ string: default_field_name,
+ custom: true,
+ type: value,
+ // Default values for x2many fields
+ records: [
+ {
+ id: 'Option 1',
+ display_name: 'Option 1'
+ },
+ {
+ id: 'Option 2',
+ display_name: 'Option 2'
+ },
+ {
+ id: 'Option 3',
+ display_name: 'Option 3'
+ }
+ ],
+ // Default values for selection fields
+ selection: [
+ [
+ 'Option 1',
+ 'Option 1'
+ ],
+ [
+ 'Option 2',
+ 'Option 2'
+ ],
+ [
+ 'Option 3',
+ 'Option 3'
+ ],
+ ]
+ });
+ },
+
+ // Re-render the field and replace the current one
+ // website_form_editor_field_reset: function(previewMode, value, $li) {
+ // 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);
+ // })
+ // });
+ // },
+
+ toggle_email_to: function () {
+ // Display or remove the magic email_to field for model mail.mail
+ var selected_model_name = this.$modal.find("[name='model_selection']").val();
+ var $current = this.$modal.find(".form-field:has(input[name='email_to'])");
+ if (selected_model_name !== 'mail.mail') {
+ $current.remove();
+ } else if (!$current.length) { // only add field if it's not already here
+ this.$modal.find("form").append(
+ // Email To input
+ $(qweb.render("website_form_editor.field_char", {
+ field: {
+ name: 'email_to',
+ string: 'Recipient email',
+ required: true,
+ value: this.$target.find("input[name='email_to']").val()
+ }
+ }))
+ );
+ }
+ },
+
+ append_field: function (field) {
+ var self = this;
+ this.render_field(field).then(function (field){
+ self.$target.find(".form-group:has('.o_website_form_send')").before(field);
+ });
+ },
+
+ 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;
+ });
+ }
+
+ return Promise.resolve(fieldRelationProm).then(function () {
+ var $content = $(qweb.render("website_form_editor.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);
+ },
+
+ init_form: function (model_name) {
+ var self = this;
+ if (model_name !== this.$target.attr('data-model_name')) {
+ // Reset the form
+ this.$target.attr('data-model_name', model_name);
+ this.$target.find(".form-field:not(:has('.o_website_form_send'))").remove();
+
+ // 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);
+ }
+ });
+ });
+ }
+ },
+
+ 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)],
+ });
+ }
+ }
+
+ // 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;
+ }
+ }
+ });
+ }
+ });
+
+ // Generic custom field options
+ options.registry['website_form_editor_field'] = options.Class.extend({
+ xmlDependencies: ['/website_form_editor/static/src/xml/website_form_editor.xml'],
+
+ // Option to toggle inputs required attribute
+ website_form_field_require: function (previewMode, value, $li) {
+ this.$target.find('.o_website_form_input').each(function (index, input) {
+ input.required = !input.required;
+ });
+ }
+ });
+
+ // Dirty hack to transform select fields into an editable construct
+ options.registry['website_form_editor_field_select'] = options.Class.extend({
+ xmlDependencies: ['/website_form_editor/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 = $('');
+ _.each(select.children(), function (option) {
+ self.editable_select.append(
+ $('' + $(option).text().trim() + '
')
+ );
+ });
+ select.after(this.editable_select);
+ }
+ },
+
+ 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'),
+ };
+
+ // 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()
+ });
+ });
+ field.records = records;
+
+ // Replace this field by the new one
+ var $new_select = $(qweb.render("website_form_editor.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');
+ }
+ if (this.$target.hasClass('o_website_form_field_hidden')) {
+ $new_select.addClass('o_website_form_field_hidden');
+ }
+ this.$target.replaceWith($new_select);
+ }
+ }
+ });
+
+ // Superclass for options that need to disable a button from the snippet overlay
+ var disable_overlay_button_option = options.Class.extend({
+ xmlDependencies: ['/website_form_editor/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.on('click', '.' + className, this.prevent_button);
+ var $button = this.$overlay.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', 'You can\'t duplicate a model field.');
+ }
+ });
+
+ // 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', 'You can\'t remove a field that is required by the model itself.');
+ }
+ });
+
+ // 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', 'You can\'t duplicate an item which refers to an actual record.');
+ }
+ });
+});
diff --git a/addons/website_form_editor/static/src/js/website_form_editor_tour.js b/addons/website_form_editor/static/src/js/website_form_editor_tour.js
new file mode 100644
index 00000000000..42c85592e41
--- /dev/null
+++ b/addons/website_form_editor/static/src/js/website_form_editor_tour.js
@@ -0,0 +1,634 @@
+odoo.define('website_form_editor.tour', function(require) {
+ 'use strict';
+
+ var rpc = require('web.rpc');
+ var tour = require("web_tour.tour");
+
+ 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: "text mail.mail"
+ },
+ {
+ 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 #modal-save"
+ },
+ // Add the subject field
+ {
+ content: "Click on Form snippet",
+ trigger: ".s_website_form[data-model_name]"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Add a model field",
+ trigger: ".oe_overlay_options .dropdown-item[data-website_form_field_modal]"
+ },
+ {
+ content: "Select the subject field",
+ trigger: "select[name='field_selection']",
+ run: "text subject"
+ },
+ {
+ content: "Click on Save",
+ trigger: ".modal-footer #modal-save"
+ },
+
+ // Customize subject field
+ {
+ content: "Change the label",
+ trigger: ".col-form-label[for='subject']",
+ run: "text Name"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Required",
+ trigger: ".oe_overlay_options .dropdown-item[data-website_form_field_require]"
+ },
+ {
+ content: "Check the resulting field",
+ trigger: ".form-field.o_website_form_required_custom" +
+ ":has(input[type=text][name=subject][required])" +
+ ":has(label:contains('Name'))",
+ run: function () {},
+ },
+
+ // Add record_name field
+ {
+ content: "Click on Form snippet",
+ trigger: ".s_website_form[data-model_name]"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Add a model field",
+ trigger: ".oe_overlay_options:visible .dropdown-item[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 #modal-save"
+ },
+
+ // Customize record_name field
+ {
+ content: "Change the label",
+ trigger: ".col-form-label[for='record_name']",
+ run: "text Awesome Label"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Hidden",
+ trigger: ".oe_overlay_options .dropdown-item[data-toggle-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 () {},
+ },
+
+
+ // Add body_html field
+ {
+ content: "Click on Form snippet",
+ trigger: ".s_website_form[data-model_name]"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Add a model field",
+ trigger: ".oe_overlay_options .dropdown-item[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 #modal-save"
+ },
+
+ // Customize subject field
+ {
+ content: "Change the label",
+ trigger: ".col-form-label[for='body_html']",
+ run: "text Your Message"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Required",
+ trigger: ".oe_overlay_options .dropdown-item[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 () {},
+ },
+
+ // Add recipient_ids relational field
+ {
+ content: "Click on Form snippet",
+ trigger: ".s_website_form[data-model_name]"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Add a model field",
+ trigger: ".oe_overlay_options .dropdown-item[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 #modal-save"
+ },
+ {
+ 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: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']",
+ run: function (actions) {
+ actions.auto();
+ // I didn't find any other way to make that submenu element appear
+ $(".oe_options .dropdown-submenu .dropdown-menu").css("display", "block");
+ }
+ },
+ {
+ content: "Click on Multiple Checkboxes in menu Add a custom field",
+ trigger: ".oe_options .dropdown-item: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_overlay_options:visible .oe_snippet_clone",
+ },
+ {
+ 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 Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Required",
+ trigger: ".oe_overlay_options .dropdown-item[data-website_form_field_require]"
+ },
+ {
+ content: "Check the resulting field",
+ trigger: ".form-field.o_website_form_custom.o_website_form_required_custom" +
+ ":has(.col-form-label:contains('Products'))" +
+ ":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: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Radio Buttons in menu Add a custom field",
+ trigger: ".oe_options .dropdown-item:contains('Radio Buttons')"
+ },
+
+ // 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_overlay_options:visible .oe_snippet_clone",
+ },
+ {
+ 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 Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Required",
+ trigger: ".oe_overlay_options .dropdown-item[data-website_form_field_require]"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Required",
+ trigger: ".oe_overlay_options .dropdown-item[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 () {},
+ },
+
+
+ // Add a custom selection field
+ {
+ content: "Click on Form snippet",
+ trigger: ".s_website_form[data-model_name]",
+ onend: function() {
+ // I didn't find any other way to make that submenu element appear
+ $(".oe_options > ul > li:has(ul) > ul").css("display", "block");
+ }
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Selection in menu Add a custom field",
+ trigger: ".oe_options .dropdown-item:contains('Selection')"
+ },
+
+ // 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_overlay_options:visible .oe_snippet_clone",
+ },
+ {
+ 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_overlay_options:visible .oe_snippet_remove",
+ },
+ {
+ content: "Click on field",
+ trigger: ".col-form-label:contains('State')"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Required",
+ trigger: ".oe_overlay_options .dropdown-item[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 () {},
+ },
+
+ // Add attachment_ids field
+ {
+ content: "Click on Form snippet",
+ trigger: ".s_website_form[data-model_name]"
+ },
+ {
+ content: "Click on Customize",
+ trigger: ".oe_overlay_options [title='Customize']"
+ },
+ {
+ content: "Click on Add a model field",
+ trigger: ".oe_overlay_options .dropdown-item[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 #modal-save"
+ },
+
+ // Customize attachment_ids field
+ {
+ content: "Change the label",
+ trigger: ".col-form-label[for='attachment_ids']",
+ run: "text Invoice Scan"
+ },
+ {
+ content: "Check the resulting field",
+ trigger: ".form-field" +
+ ":has(input[type=file][name=attachment_ids])" +
+ ":has(label:contains('Invoice Scan'))",
+ run: function () {
+ $('body').append('');
+ },
+ },
+
+ // Save the page
+ {
+ content: "Save the page",
+ trigger: "button[data-action=save]",
+ },
+ {
+ content: "Wait reloading...",
+ trigger: "html:not(:has(#completlyloaded)) div",
+ }
+ ]);
+
+ tour.register("website_form_editor_tour_submit", {
+ test: true,
+ },[
+ {
+ content: "Try to send empty form",
+ extra_trigger: "form[data-model_name='mail.mail']" +
+ "[data-success_page='']" +
+ ":has(.form-field:has(label:contains('Name')):has(input[type='text'][name='subject'][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(input.form-field[name='email_to'][value='test@test.test'])",
+ trigger: ".o_website_form_send"
+ },
+ {
+ content: "Check if required fields were detected and complete the Name field",
+ extra_trigger: "form:has(#o_website_form_result.text-danger)" +
+ ":has(.form-field:has(label:contains('Name')).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))",
+ trigger: "input[name=subject]",
+ run: "text Jane Smith"
+ },
+ {
+ content: "Update required field status by trying to Send again",
+ trigger: ".o_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('Name')):not(.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))",
+ 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"
+ },
+ {
+ 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('Name')):not(.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))",
+ trigger: "input[name=Products][value='Wiko Stairway']"
+ },
+ {
+ content: "Check another product",
+ trigger: "input[name='Products'][value='Xperia']"
+ },
+ {
+ content: "Check a service",
+ trigger: "input[name='Service'][value='Development Service']"
+ },
+ {
+ content: "Send the form",
+ trigger: ".o_website_form_send"
+ },
+ {
+ content: "Check form is submitted without errors",
+ trigger: "form:has(#o_website_form_result.text-success)" +
+ ":has(.form-field:has(label:contains('Name')):not(.o_has_error))" +
+ ":has(.form-field:has(label:contains('Your Message')):not(.o_has_error))" +
+ ":has(.form-field:has(label:contains('Products')):not(.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))"
+ }
+ ]);
+
+ tour.register("website_form_editor_tour_results", {
+ test: true,
+ }, [
+ {
+ content: "Check mail.mail records have been created",
+ trigger: "body",
+ run: function () {
+ var mailDef = rpc.query({
+ model: 'mail.mail',
+ method: 'search_count',
+ args: [[
+ ['email_to', '=', 'test@test.test'],
+ ['body_html', 'like', 'A useless message'],
+ ['body_html', 'like', 'Service : Development Service'],
+ ['body_html', 'like', 'State : Belgium'],
+ ['body_html', 'like', 'Products : Xperia,Wiko Stairway']
+ ]],
+ });
+ var success = function(model, count) {
+ if (count > 0) {
+ $('body').append('');
+ }
+ };
+ mailDef.then(_.bind(success, this, 'mail_mail'));
+ }
+ },
+ {
+ content: "Check mail.mail records have been created",
+ trigger: "#website_form_editor_success_test_tour_mail_mail"
+ }
+ ]);
+
+ return {};
+});
diff --git a/addons/website_form_editor/static/src/xml/website_form_editor.xml b/addons/website_form_editor/static/src/xml/website_form_editor.xml
new file mode 100644
index 00000000000..90157883da2
--- /dev/null
+++ b/addons/website_form_editor/static/src/xml/website_form_editor.xml
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_form_editor/tests/__init__.py b/addons/website_form_editor/tests/__init__.py
new file mode 100644
index 00000000000..b7c97e7f8c5
--- /dev/null
+++ b/addons/website_form_editor/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_website_form_editor
\ No newline at end of file
diff --git a/addons/website_form_editor/tests/test_website_form_editor.py b/addons/website_form_editor/tests/test_website_form_editor.py
new file mode 100644
index 00000000000..0bb993cb051
--- /dev/null
+++ b/addons/website_form_editor/tests/test_website_form_editor.py
@@ -0,0 +1,12 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+# -*- coding: utf-8 -*-
+
+import odoo.tests
+
+
+@odoo.tests.tagged('post_install','-at_install')
+class TestWebsiteFormEditor(odoo.tests.HttpCase):
+ def test_tour(self):
+ self.start_tour("/", 'website_form_editor_tour', login="admin")
+ self.start_tour("/", 'website_form_editor_tour_submit')
+ self.start_tour("/", 'website_form_editor_tour_results', login="admin")
diff --git a/addons/website_form_editor/views/ir_model_views.xml b/addons/website_form_editor/views/ir_model_views.xml
new file mode 100644
index 00000000000..2e56769458f
--- /dev/null
+++ b/addons/website_form_editor/views/ir_model_views.xml
@@ -0,0 +1,37 @@
+
+
+
+
+ website_form_editor.ir.model.view.form
+ ir.model
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ website_form_editor.ir.model.fields.view.form
+ ir.model.fields
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_form_editor/views/snippets.xml b/addons/website_form_editor/views/snippets.xml
new file mode 100644
index 00000000000..9e399a13f54
--- /dev/null
+++ b/addons/website_form_editor/views/snippets.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_form_editor/views/website_form_editor_templates.xml b/addons/website_form_editor/views/website_form_editor_templates.xml
new file mode 100644
index 00000000000..9b2dbd7c266
--- /dev/null
+++ b/addons/website_form_editor/views/website_form_editor_templates.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file