diff options
| author | Jeremy Kersten <jke@odoo.com> | 2019-04-08 07:07:52 +0000 |
|---|---|---|
| committer | Jeremy Kersten <jke@odoo.com> | 2019-04-08 10:07:49 +0000 |
| commit | 82ea590264f0cfd904ad21dbbd2202ec2f48be57 (patch) | |
| tree | b3a07c168d3f5374901e24a891d092876ef6cf5b /addons/website_form | |
| parent | 48b373f8e40045fc6f307d6758c4a90256419796 (diff) | |
[MRG] website_form[_editor]: merge modules together
closes odoo/odoo#32491
Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
Diffstat (limited to 'addons/website_form')
| -rw-r--r-- | addons/website_form/__manifest__.py | 14 | ||||
| -rw-r--r-- | addons/website_form/data/mail_mail_data.xml | 19 | ||||
| -rw-r--r-- | addons/website_form/static/description/icon.png | bin | 0 -> 5565 bytes | |||
| -rw-r--r-- | addons/website_form/static/description/icon.svg | 1 | ||||
| -rw-r--r-- | addons/website_form/static/src/js/website_form_editor.js | 474 | ||||
| -rw-r--r-- | addons/website_form/static/src/js/website_form_editor_tour.js | 634 | ||||
| -rw-r--r-- | addons/website_form/static/src/xml/website_form_editor.xml | 257 | ||||
| -rw-r--r-- | addons/website_form/tests/__init__.py | 4 | ||||
| -rw-r--r-- | addons/website_form/tests/test_website_form_editor.py | 12 | ||||
| -rw-r--r-- | addons/website_form/views/assets.xml | 7 | ||||
| -rw-r--r-- | addons/website_form/views/ir_model_views.xml | 37 | ||||
| -rw-r--r-- | addons/website_form/views/snippets.xml | 88 |
12 files changed, 1543 insertions, 4 deletions
diff --git a/addons/website_form/__manifest__.py b/addons/website_form/__manifest__.py index 132f48ade84..0dd557ca5bf 100644 --- a/addons/website_form/__manifest__.py +++ b/addons/website_form/__manifest__.py @@ -1,13 +1,19 @@ { - 'name': 'Generic Form Controller', - 'category': 'Hidden', - 'summary': 'Generic controller for web forms', - 'description': 'Generic controller for web forms', + 'name': 'Website Form', + 'category': 'Website', + 'summary': 'Build custom web forms', + 'description': """ + Customize and create your own web forms. + This module adds a new building block in the website builder in order to build new forms from scratch in any website page. + """, 'version': '1.0', 'depends': ['website', 'mail'], 'data': [ + 'data/mail_mail_data.xml', 'views/assets.xml', + 'views/ir_model_views.xml', 'views/res_config_view.xml', + 'views/snippets.xml', ], 'installable': True, 'auto_install': False, diff --git a/addons/website_form/data/mail_mail_data.xml b/addons/website_form/data/mail_mail_data.xml new file mode 100644 index 00000000000..6997b346792 --- /dev/null +++ b/addons/website_form/data/mail_mail_data.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="mail.model_mail_mail" model="ir.model"> + <field name="website_form_default_field_id" ref="mail.field_mail_mail__body_html" /> + <field name="website_form_access">True</field> + <field name="website_form_label">Send an E-mail</field> + </record> + <function model="ir.model.fields" name="formbuilder_whitelist"> + <value>mail.mail</value> + <value eval="[ + 'subject', + 'body_html', + 'email_to', + 'email_from', + 'record_name', + 'attachment_ids', + ]"/> + </function> +</odoo> diff --git a/addons/website_form/static/description/icon.png b/addons/website_form/static/description/icon.png Binary files differnew file mode 100644 index 00000000000..212df7a906a --- /dev/null +++ b/addons/website_form/static/description/icon.png diff --git a/addons/website_form/static/description/icon.svg b/addons/website_form/static/description/icon.svg new file mode 100644 index 00000000000..c6dd1249e8e --- /dev/null +++ b/addons/website_form/static/description/icon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#B06161"/><stop offset="45.785%" stop-color="#984E4E"/><stop offset="100%" stop-color="#7C3838"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M41.118 69H4c-2 0-4-1-4-4V28.375L15 12h41v42L41.118 69z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><path fill="#FFF" d="M34 17v-2h2v2h2v2h-2v2h-2v-2h-2v-2h2zM15 42h41v12H15V42zm19.51 8.985a.608.608 0 0 0 .73 0l5.942-4.792c.202-.162.202-.426 0-.589l-.73-.59a.608.608 0 0 0-.731 0l-4.846 3.909-2.262-1.825a.608.608 0 0 0-.731 0l-.73.59c-.202.162-.202.426 0 .589l3.358 2.708zM15 12h41v12H15V12zm0 15h41v12H15V27zm2-13v8h37v-8H17zm0 15v8h37v-8H17zm15 3h6v2h-6v-2z"/></g></g></svg>
\ No newline at end of file diff --git a/addons/website_form/static/src/js/website_form_editor.js b/addons/website_form/static/src/js/website_form_editor.js new file mode 100644 index 00000000000..9eb80cc854b --- /dev/null +++ b/addons/website_form/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/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.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.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.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("<input class='form-field' type='hidden' name='email_to' value=" + email_to + ">"); + } + } + ); + + 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.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.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.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/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/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"/>'); + _.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); + } + }, + + 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.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/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/static/src/js/website_form_editor_tour.js b/addons/website_form/static/src/js/website_form_editor_tour.js new file mode 100644 index 00000000000..42c85592e41 --- /dev/null +++ b/addons/website_form/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('<div id="completlyloaded"></div>'); + }, + }, + + // 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('<div id="website_form_editor_success_test_tour_'+model+'"></div>'); + } + }; + 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/static/src/xml/website_form_editor.xml b/addons/website_form/static/src/xml/website_form_editor.xml new file mode 100644 index 00000000000..0165bd6ee99 --- /dev/null +++ b/addons/website_form/static/src/xml/website_form_editor.xml @@ -0,0 +1,257 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <!-- Generic Modal Layout --> + <t t-name="website_form.modal"> + <div role="dialog" class="modal fade"> + <div class="modal-dialog"> + <div class="modal-content"> + <header class="modal-header"> + <h4 class="modal-title"><t t-esc="modal_title"/></h4> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span>&times;</span> + </button> + </header> + <main class="modal-body"> + <form role="form"> + <t t-raw="modal_body"/> + </form> + </main> + <footer class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> + <button type="button" class="btn btn-primary" id="modal-save">Save</button> + </footer> + </div> + </div> + </div> + </t> + + <!-- Generic Field Layout --> + <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 class="col-md-4 text-md-right"> + <label class="col-form-label" t-att-for="field.name"> + <t t-esc="field.string"/> + </label> + </div> + <div class="col-md-8"> + <t t-raw="0"/> + </div> + </div> + </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" + t-att-name="field.name" + t-att-required="field.required" + t-att-value="field.value" + /> + </t> + </t> + + <!-- Text Field --> + <t t-name="website_form.field_text"> + <t t-call="website_form.field"> + <textarea + class="form-control o_website_form_input" + t-att-name="field.name" + t-att-required="field.required" + /> + </t> + </t> + + <!-- HTML Field --> + <t t-name="website_form.field_html"> + <!-- + Maybe use web_editor ? Not sure it actually makes + sense to have random people editing html in a form... + --> + <t t-call="website_form.field_text"/> + </t> + + <!-- Integer Field --> + <t t-name="website_form.field_integer"> + <t t-call="website_form.field"> + <input + type="number" + class="form-control o_website_form_input" + t-att-name="field.name" + step="1" + t-att-required="field.required" + /> + </t> + </t> + + <!-- Float Field --> + <t t-name="website_form.field_float"> + <t t-call="website_form.field"> + <input + type="number" + class="form-control o_website_form_input" + t-att-name="field.name" + step="any" + t-att-required="field.required" + /> + </t> + </t> + + <!-- Date Field --> + <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"> + <input + type="text" + class="form-control datetimepicker-input o_website_form_input" + t-attf-data-target="##{datepickerID}" + t-att-name="field.name" + t-att-required="field.required" + /> + <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> + </div> + </div> + </t> + </t> + + <!-- Datetime Field --> + <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"> + <input + type="text" + class="form-control datetimepicker-input o_website_form_input" + t-attf-data-target="##{datetimepickerID}" + t-att-name="field.name" + t-att-required="field.required" + /> + <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> + </div> + </div> + </t> + </t> + + <!-- Boolean Field --> + <t t-name="website_form.field_boolean"> + <t t-call="website_form.field"> + <input + type="checkbox" + value="Yes" + class="o_website_form_input" + t-att-name="field.name" + t-att-required="field.required" + /> + </t> + </t> + + <!-- Selection Field --> + <t t-name="website_form.field_selection"> + <t t-call="website_form.field"> + <div class="o_website_form_flex"> + <t t-foreach="field.selection" t-as="option"> + <div class="radio o_website_form_flex_item"> + <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> + </t> + </div> + </t> + </t> + + <!-- Many2One Field --> + <t t-name="website_form.field_many2one"> + <!-- Binary one2many --> + <t t-if="field.relation == 'ir.attachment'"> + <t t-call="website_form.field_binary"/> + </t> + <!-- 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"> + <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"/> + </option> + </t> + </select> + </t> + </t> + </t> + + <!-- One2Many Field --> + <t t-name="website_form.field_one2many"> + <!-- Binary one2many --> + <t t-if="field.relation == 'ir.attachment'"> + <t t-call="website_form.field_binary"> + <t t-set="multiple" t-value="1"/> + </t> + </t> + <!-- Generic one2many --> + <t t-if="field.relation != 'ir.attachment'"> + <t t-call="website_form.field"> + <div class="o_website_form_flex"> + <t t-if="!field.records"> + <input + class="o_website_form_input" + t-att-name="field.name" + t-att-value="record.id" + t-att-required="field.required" + placeholder="No matching record !" + /> + </t> + <t t-foreach="field.records" t-as="record"> + <div class="checkbox o_website_form_flex_item"> + <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> + </div> + </t> + </t> + </t> + + <!-- Many2Many Field --> + <t t-name="website_form.field_many2many"> + <t t-call="website_form.field_one2many"/> + </t> + + <!-- Binary Field --> + <t t-name="website_form.field_binary"> + <t t-call="website_form.field"> + <input + type="file" + class="form-control o_website_form_input" + t-att-name="field.name" + t-att-required="field.required" + t-att-multiple="multiple" + /> + </t> + </t> + +</templates> diff --git a/addons/website_form/tests/__init__.py b/addons/website_form/tests/__init__.py new file mode 100644 index 00000000000..b7c97e7f8c5 --- /dev/null +++ b/addons/website_form/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/tests/test_website_form_editor.py b/addons/website_form/tests/test_website_form_editor.py new file mode 100644 index 00000000000..0bb993cb051 --- /dev/null +++ b/addons/website_form/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/views/assets.xml b/addons/website_form/views/assets.xml index 2ee98adf76c..cdf869294c1 100644 --- a/addons/website_form/views/assets.xml +++ b/addons/website_form/views/assets.xml @@ -6,6 +6,13 @@ <xpath expr="." position="inside"> <link rel="stylesheet" type="text/scss" href="/website_form/static/src/scss/website_form.scss"/> <script type="text/javascript" src="/website_form/static/src/js/website_form.js"/> + <script type="text/javascript" src="/website_form/static/src/js/website_form_editor_tour.js"/> + </xpath> + </template> + + <template id="assets_editor" name="Website Form Editor Assets Editor" inherit_id="website.assets_editor"> + <xpath expr="." position="inside"> + <script type="text/javascript" src="/website_form/static/src/js/website_form_editor.js"/> </xpath> </template> diff --git a/addons/website_form/views/ir_model_views.xml b/addons/website_form/views/ir_model_views.xml new file mode 100644 index 00000000000..2e56769458f --- /dev/null +++ b/addons/website_form/views/ir_model_views.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="ir_model_view" model="ir.ui.view"> + <field name="name">website_form_editor.ir.model.view.form</field> + <field name="model">ir.model</field> + <field name="inherit_id" ref="base.view_model_form"/> + <field name="arch" type="xml"> + <xpath expr="//notebook" position="inside"> + <page string="Website Forms"> + <group> + <field name="website_form_access"/> + <field name="website_form_label"/> + <field name="website_form_default_field_id"/> + </group> + </page> + </xpath> + + <xpath expr="//page[@name='base']/group/group/field[@name='translate']" position="after"> + <field name="website_form_blacklisted"/> + </xpath> + </field> + </record> + + <record id="ir_model_fields_view" model="ir.ui.view"> + <field name="name">website_form_editor.ir.model.fields.view.form</field> + <field name="model">ir.model.fields</field> + <field name="inherit_id" ref="base.view_model_fields_form"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='translate']" position="after"> + <field name="website_form_blacklisted"/> + </xpath> + </field> + </record> + + +</odoo> diff --git a/addons/website_form/views/snippets.xml b/addons/website_form/views/snippets.xml new file mode 100644 index 00000000000..b47e02eeda5 --- /dev/null +++ b/addons/website_form/views/snippets.xml @@ -0,0 +1,88 @@ +<?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"> + <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> + </div> + </div> + </div> + </form> + </template> + + <template id="remove_external_snippets" inherit_id="website.external_snippets"> + <xpath expr="//t[@t-install='website_form']" position="replace"/> + </template> + + <template id="register_s_website_form" inherit_id="website.snippets" name="Snippet Form Builder"> + <xpath expr="//div[@id='snippet_feature']//t[@t-snippet][last()]" position="after"> + <t t-snippet="website_form.s_website_form" t-thumbnail="/website/static/src/img/s_website_form.png"/> + </xpath> + </template> + + <template id="snippet_options" inherit_id="website.snippet_options"> + <xpath expr="//*[@id='so_snippet_addition']" position="attributes"> + <attribute name="data-selector" add=".s_website_form" separator=","/> + </xpath> + <xpath expr="//div" position="after"> + <!-- Form --> + <div data-js="website_form_editor" data-selector=".s_website_form"> + <a href="#" class="dropdown-item" data-website_form_model_modal="" data-no-preview="true">Change Form Parameters</a> + <a href="#" class="dropdown-item" data-website_form_field_modal="" data-no-preview="true">Add an existing field</a> + <div class="dropdown-submenu"> + <a href="#" class="dropdown-item">Add a custom field</a> + <div class="dropdown-menu" role="menu" data-no-preview="true"> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="char">Text</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="text">Long Text</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="integer">Number</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="float">Decimal Number</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="boolean">Checkbox</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="selection">Radio Buttons</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="many2one">Selection</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="one2many">Multiple Checkboxes</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="date">Date</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="datetime">Date & Time</a> + <a href="#" class="dropdown-item" role="menuitem" data-website_form_custom_field="binary">File Upload</a> + </div> + </div> + </div> + + <!-- Field --> + <div data-js='website_form_editor' data-selector=".form-field" data-drop-near=".form-field"> + <a href="#" class="dropdown-item" data-toggle-class="o_website_form_field_hidden" data-no-preview="true">Hidden</a> + </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)"> + <a href="#" class="dropdown-item" data-website_form_field_require="" data-toggle-class="o_website_form_required_custom" data-no-preview="true">Required</a> + </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 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 options of the Submit button --> + <div data-js="website_form_editor_field_required" data-selector=".s_website_form .o_website_form_send"/> + </xpath> + </template> +</odoo> |
