[ADD] website_form_editor: editor for website_form forms

Contains the new ajax post function introduced by e8d58dd
in the community version of web that comes from mdo's
original code and that I was not able to get rid of.
This commit is contained in:
David Monjoie
2015-08-27 09:11:20 +02:00
commit 6815b491d6
13 changed files with 1506 additions and 0 deletions
View File
+22
View File
@@ -0,0 +1,22 @@
{
'name': 'Website Form Editor',
'category': 'Website',
'summary': 'Build custom web forms using the website builder',
'version': '1.0',
'description': """
Odoo Form Editor
====================
Allows you to build web forms on the website using the website builder.
""",
'depends': ['website_form'],
'data': [
'views/assets.xml',
'views/snippets.xml',
'views/ir_model_view.xml',
'views/snippet_options.xml',
'data/website_form_mail.xml',
],
'installable': True,
'auto_install': False,
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<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>
</data>
</openerp>
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

+470
View File
@@ -0,0 +1,470 @@
odoo.define('website_form_editor', function (require) {
'use strict';
var core = require('web.core');
var ajax = require('web.ajax');
var Model = require('web.Model');
var base = require('web_editor.base');
var options = require('web_editor.snippets.options');
var qweb = core.qweb;
qweb.add_template('/website_form_editor/static/src/xml/website_form_editor.xml');
options.registry['website_form_editor'] = options.Class.extend({
// 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(){
on_save();
self.$modal.modal('hide');
});
// 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 deferred if we already issued a model
// fields fetch request, or issue said request.
fields: function() {
return this.fields_deferred || this.fetch_model_fields();
},
fetch_model_fields: function() {
var self = this;
var ir_model = new Model("ir.model");
this.fields_deferred = new $.Deferred();
ir_model.call(
"search_read",
[
[['model', '=', this.$target.closest('form').attr('data-model_name')]],
['id']
],
{
context: base.get_context()
}
).then(function(model) {
var model_id = model[0].id;
ir_model.call(
"get_authorized_fields",
[
model_id
],
{
context: base.get_context()
}
).then(function(fields) {
// The get_fields function doesn't return the name
// in the field dict since it uses it has the key
_.map(fields, function(field, field_name){
field.name = field_name;
return field;
});
self.fields_deferred.resolve(fields)
})
})
return this.fields_deferred;
},
// Choose a model modal
website_form_model_modal: function (type, value, $li) {
if (type !== 'click') return;
var self = this;
new Model("ir.model").call(
"search_read",
[
[['website_form_access', '=', true]],
['id', 'model', 'name', 'website_form_label']
],
{
context: base.get_context()
}
).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: 'Success 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.toggle_email_to();
self.$modal.find("[name='model_selection']").on('change', function() {
self.toggle_email_to();
});
})
},
// Choose a field modal
website_form_field_modal: function (type, value, $li) {
if (type !== 'click') return;
var self = this;
var ir_model = new Model("ir.model");
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('.control-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);
}
);
self.$modal.find("[name='field_selection']").on('change', function() {
console.log('changed');
});
});
},
// Create a custom field
website_form_custom_field: function(type, value, $li) {
if (type !== 'click') return;
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(type, value, $li) {
// if (type !== 'click') return;
// var self = this;
// var target_field_name = this.$target.find('.control-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();
if (selected_model_name != 'mail.mail') {
this.$modal.find(".form-field:has(input[name='email_to'])").remove();
} else {
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).done(function(field){
self.$target.find(".form-group:has('.o_website_form_send')").before(field);
});
},
render_field: function(field) {
var self = this;
var field_rendered = $.Deferred();
// 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 fetch_field_relation = $.Deferred();
if(field.relation && field.relation != 'ir.attachment') {
ajax.jsonRpc('/web/dataset/call_kw', 'call', {
model: field.relation,
method: 'search_read',
args: [
field.domain,
['display_name']
],
kwargs:{"context": base.get_context()}
}).then(function (records) {
field.records = records;
fetch_field_relation.resolve();
});
}
else {
fetch_field_relation.resolve();
}
fetch_field_relation.done(function() {
field_rendered.resolve(
qweb.render("website_form_editor.field_" + field.type, {field: field})
);
});
return field_rendered;
},
drop_and_build_snippet: 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);
}
});
});
}
},
clean_for_save: function () {
// Prevent saving of the error colors // TODO: would be better on Edit
this.$target.find('.has-error').removeClass('has-error');
// 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({
// Option to toggle inputs required attribute
website_form_field_require: function(type, value, $li) {
if (type !== 'click') return;
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({
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);
}
},
clean_for_save: 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('.control-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({
// Disable a button of the snippet overlay
disable_button: function(button_name, message) {
this.$overlay.on('click', '.oe_snippet_' + button_name, this.prevent_button);
var remove_button = this.$overlay.find('.oe_snippet_' + button_name);
remove_button.css('background-color', 'lightgray');
remove_button.attr('title', message);
},
prevent_button: function(event) {
// Snippet options bind their functions before the editor, so we
// can't cleanly unbind the editor on_remove 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.');
}
});
});
+620
View File
@@ -0,0 +1,620 @@
odoo.define('website_form_editor.tour', function(require) {
'use strict';
var base = require('web_editor.base');
var core = require('web.core');
var Tour = require('web.Tour');
var Model = require('web.Model');
var Session = require('web.Session');
base.ready().done(function () {
Tour.register({
id: 'website_form_editor_tour',
name: "Try to create some forms",
path: '/',
mode: 'test',
steps: [
// Drop a form builder snippet and configure it
{
title: "Enter edit mode",
element: "button[data-action=edit]"
},
{
title: "Drop the form snippet",
snippet: "#snippet_feature .oe_snippet[name='Form Builder']"
},
{
title: "Check if the snippet is dropped and if the modal is opened",
waitFor: "body:has(form[action*='/website_form/'])" +
":has(.modal-body:has(select[name='model_selection'])" +
":has(input[name='success_page']))"
},
{
title: "Change the action to create issues",
element: ".modal-body select",
sampleText: "project.issue"
},
{
title: "Change the action to Send an E-mail",
// waitFor: ".modal-body .o_form-action-mailto:hidden",
element: ".modal-body select",
sampleText: "mail.mail"
},
{
title: "Complete Recipient E-mail",
waitFor: ".modal-body input[name='email_to']",
element: ".modal-body input[name='email_to']",
sampleText: "test@test.test"
},
{
title: "Click on Save",
element: ".modal-footer #modal-save"
},
// Add the display_name field
{
title: "Click on Form snippet",
element: ".s_website_form[data-model_name]"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Add a model field",
element: ".oe_overlay_options:visible li[data-website_form_field_modal] a"
},
{
title: "Select the display_name field",
element: "select[name='field_selection']",
sampleText: "display_name"
},
{
title: "Click on Save",
element: ".modal-footer #modal-save"
},
// Customize display_name field
{
title: "Change the label",
element: ".control-label[for='display_name']",
sampleText: "Name"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Required",
element: ".oe_overlay_options:visible li[data-website_form_field_require] a"
},
{
title: "Check the resulting field",
waitFor: ".form-field.o_website_form_required_custom" +
":has(input[type=text][name=display_name][required])" +
":has(label:contains('Name'))"
},
// Add record_name field
{
title: "Click on Form snippet",
element: ".s_website_form[data-model_name]"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Add a model field",
element: ".oe_overlay_options:visible li[data-website_form_field_modal] a"
},
{
title: "Select the display_name field",
element: "select[name='field_selection']",
sampleText: "record_name"
},
{
title: "Click on Save",
element: ".modal-footer #modal-save"
},
// Customize record_name field
{
title: "Change the label",
element: ".control-label[for='record_name']",
sampleText: "Awesome Label"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Hidden",
element: ".oe_overlay_options:visible li[data-toggle_class='o_website_form_field_hidden'] a"
},
{
title: "Check the resulting field",
waitFor: ".form-field.o_website_form_field_hidden" +
":has(input:not([required])[type=text][name=record_name])" +
":has(label:contains('Awesome Label'))"
},
// Add body_html field
{
title: "Click on Form snippet",
element: ".s_website_form[data-model_name]"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Add a model field",
element: ".oe_overlay_options:visible li[data-website_form_field_modal] a"
},
{
title: "Select the display_name field",
element: "select[name='field_selection']",
sampleText: "body_html"
},
{
title: "Click on Save",
element: ".modal-footer #modal-save"
},
// Customize display_name field
{
title: "Change the label",
element: ".control-label[for='body_html']",
sampleText: "Your Message"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Required",
element: ".oe_overlay_options:visible li[data-website_form_field_require] a"
},
{
title: "Check the resulting field",
waitFor: ".form-field.o_website_form_required_custom" +
":has(textarea[name=body_html][required])" +
":has(label:contains('Your Message'))"
},
// Add a custom multiple checkboxes field
{
title: "Click on Form snippet",
element: ".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");
}
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Multiple Checkboxes in menu Add a custom field",
element: ".oe_options li a:contains('Multiple Checkboxes')"
},
// Customize custom multiple checkboxes field
{
title: "Change the label",
element: ".control-label[for='Custom Multiple Checkboxes']",
sampleText: "Products"
},
{
title: "Change Option 1 label",
element: "label:contains('Option 1') span",
sampleText: "Iphone"
},
{
title: "Change Option 2 label",
element: "label:contains('Option 2') span",
sampleText: "Galaxy S"
},
{
title: "Click on Option 3",
element: "label:contains('Option 3') span",
},
{
title: "Duplicate Option 3",
element: ".oe_overlay_options:visible .oe_snippet_clone",
},
{
title: "Change first Option 3 label",
element: "label:contains('Option 3'):first span",
sampleText: "Xperia"
},
{
title: "Change last Option label",
element: "label:contains('Option 3') span",
sampleText: "Wiko Stairway"
},
{
title: "Click on field",
element: ".control-label:contains('Products')"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Required",
element: ".oe_overlay_options:visible li[data-website_form_field_require] a"
},
{
title: "Check the resulting field",
waitFor: ".form-field.o_website_form_custom.o_website_form_required_custom" +
":has(.control-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]))"
},
// Add a custom radio field
{
title: "Click on Form snippet",
element: ".s_website_form[data-model_name]",
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Radio Buttons in menu Add a custom field",
element: ".oe_options li a:contains('Radio Buttons')"
},
// Customize custom multiple checkboxes field
{
title: "Change the label",
element: ".control-label[for='Custom Radio Buttons']",
sampleText: "Service"
},
{
title: "Change Option 1 label",
element: "label:contains('Option 1') span",
sampleText: "After-sales Service"
},
{
title: "Change Option 2 label",
element: "label:contains('Option 2') span",
sampleText: "Invoicing Service"
},
{
title: "Click on Option 3",
element: "label:contains('Option 3') span",
},
{
title: "Duplicate Option 3",
element: ".oe_overlay_options:visible .oe_snippet_clone",
},
{
title: "Change first Option 3 label",
element: "label:contains('Option 3'):first span",
sampleText: "Development Service"
},
{
title: "Change last Option label",
element: "label:contains('Option 3') span",
sampleText: "Management Service"
},
{
title: "Click on field",
element: ".control-label:contains('Service')"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Required",
element: ".oe_overlay_options:visible li[data-website_form_field_require] a"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Required",
element: ".oe_overlay_options:visible li[data-website_form_field_require] a"
},
{
title: "Check the resulting field",
waitFor: ".form-field.o_website_form_custom:not(.o_website_form_required_custom)" +
":has(.control-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])))"
},
// Add a custom selection field
{
title: "Click on Form snippet",
element: ".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");
}
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Selection in menu Add a custom field",
element: ".oe_options li a:contains('Selection')"
},
// Customize custom selection field
{
title: "Change the label",
element: ".control-label[for='Custom Selection']",
sampleText: "State"
},
{
title: "Change Option 1 Label",
element: ".o_website_form_select_item:contains('Option 1')",
sampleText: "Germany"
},
{
title: "Change Option 2 Label",
element: ".o_website_form_select_item:contains('Option 2')",
sampleText: "Belgium"
},
{
title: "Click on Option 3",
element: ".o_website_form_select_item:contains('Option 3')",
},
{
title: "Duplicate Option 3",
element: ".oe_overlay_options:visible .oe_snippet_clone",
},
{
title: "Change first Option 3 label",
element: ".o_website_form_select_item:contains('Option 3'):first",
sampleText: "France"
},
{
title: "Change last Option label",
element: ".o_website_form_select_item:contains('Option 3')",
sampleText: "Canada"
},
{
title: "Click on Germany Option",
element: ".o_website_form_select_item:contains('Germany')",
},
{
title: "Remove Germany Option",
element: ".oe_overlay_options:visible .oe_snippet_remove",
},
{
title: "Click on field",
element: ".control-label:contains('State')"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Required",
element: ".oe_overlay_options:visible li[data-website_form_field_require] a"
},
{
title: "Check the resulting snippet",
waitFor: ".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')))"
},
// Add attachment_ids field
{
title: "Click on Form snippet",
element: ".s_website_form[data-model_name]"
},
{
title: "Click on Customize",
element: ".oe_overlay_options:visible a[title='Customize']"
},
{
title: "Click on Add a model field",
element: ".oe_overlay_options:visible li[data-website_form_field_modal] a"
},
{
title: "Select the display_name field",
element: "select[name='field_selection']",
sampleText: "attachment_ids"
},
{
title: "Click on Save",
element: ".modal-footer #modal-save"
},
// Customize attachment_ids field
{
title: "Change the label",
element: ".control-label[for='attachment_ids']",
sampleText: "Invoice Scan"
},
{
title: "Check the resulting field",
waitFor: ".form-field" +
":has(input[type=file][name=attachment_ids])" +
":has(label:contains('Invoice Scan'))"
},
// Save the page
{
title: "Save the page",
element: "button[data-action=save]",
onload: function (tour) {
setTimeout(function(){
$('html').append('<div id="completlyloaded"></div>');
}, 1000);
}
},
{
title: "Wait reloading...",
waitFor: "#completlyloaded",
onload: function (tour) {
$("#completlyloaded").remove();
}
}
]
});
Tour.register({
id: 'website_form_editor_tour_submit',
name: "Check created form works correctly",
path: '',
mode: 'test',
steps: [
{
title: "Try to send empty form",
waitFor: "form[data-model_name='mail.mail']" +
"[data-success_page='']" +
":has(.form-field:has(label:contains('Name')):has(input[type='text'][name='display_name'][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'])",
element: ".o_website_form_send"
},
{
title: "Check if required fields were detected and complete the Name field",
waitFor: "form:has(#o_website_form_result.text-danger)" +
":has(.form-field:has(label:contains('Name')).has-error)" +
":has(.form-field:has(label:contains('Your Message')).has-error)" +
":has(.form-field:has(label:contains('Products')).has-error)" +
":has(.form-field:has(label:contains('Service')):not(.has-error))" +
":has(.form-field:has(label:contains('State')):not(.has-error))" +
":has(.form-field:has(label:contains('Invoice Scan')):not(.has-error))",
element: "input[name=display_name]",
sampleText: "Jane Smith"
},
{
title: "Update required field status by trying to Send again",
element: ".o_website_form_send"
},
{
title: "Check if required fields were detected and complete the Message field",
waitFor: "form:has(#o_website_form_result.text-danger)" +
":has(.form-field:has(label:contains('Name')):not(.has-error))" +
":has(.form-field:has(label:contains('Your Message')).has-error)" +
":has(.form-field:has(label:contains('Products')).has-error)" +
":has(.form-field:has(label:contains('Service')):not(.has-error))" +
":has(.form-field:has(label:contains('State')):not(.has-error))" +
":has(.form-field:has(label:contains('Invoice Scan')):not(.has-error))",
element: "textarea[name=body_html]",
sampleText: "A useless message"
},
{
title: "Update required field status by trying to Send again",
element: ".o_website_form_send"
},
{
title: "Check if required fields was detected and check a product",
waitFor: "form:has(#o_website_form_result.text-danger)" +
":has(.form-field:has(label:contains('Name')):not(.has-error))" +
":has(.form-field:has(label:contains('Your Message')):not(.has-error))" +
":has(.form-field:has(label:contains('Products')).has-error)" +
":has(.form-field:has(label:contains('Service')):not(.has-error))" +
":has(.form-field:has(label:contains('State')):not(.has-error))" +
":has(.form-field:has(label:contains('Invoice Scan')):not(.has-error))",
element: "input[name=Products][value='Wiko Stairway']"
},
{
title: "Check another product",
element: "input[name='Products'][value='Xperia']"
},
{
title: "Check a service",
element: "input[name='Service'][value='Development Service']"
},
{
title: "Send the form",
element: ".o_website_form_send"
},
{
title: "Check form is submitted without errors",
waitFor: "form:has(#o_website_form_result.text-success)" +
":has(.form-field:has(label:contains('Name')):not(.has-error))" +
":has(.form-field:has(label:contains('Your Message')):not(.has-error))" +
":has(.form-field:has(label:contains('Products')):not(.has-error))" +
":has(.form-field:has(label:contains('Service')):not(.has-error))" +
":has(.form-field:has(label:contains('State')):not(.has-error))" +
":has(.form-field:has(label:contains('Invoice Scan')):not(.has-error))"
}
]
});
Tour.register({
id: 'website_form_editor_tour_results',
name: "Check records have been created",
path: '',
mode: 'test',
steps: [
{
title: "Check mail.mail records have been created",
waitFor: ".o-apps",
onload: function (tour) {
var mailDef = new Model("mail.mail").call(
"search_read",
[
// TODO: add other fields in domain !
[
['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, data) {
if(data.length) {
$('body').append('<div id="website_form_editor_success_test_tour_'+model+'"></div>');
}
};
mailDef.then(_.bind(success, this, 'mail_mail'));
}
},
{
title: "Check mail.mail records have been created",
waitFor: "#website_form_editor_success_test_tour_mail_mail"
},
{
title: "Final Step",
waitFor: "html"
}
]
});
});
return {};
});
+231
View File
@@ -0,0 +1,231 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<!-- Generic Modal Layout -->
<t t-name="website_form_editor.modal">
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&amp;times;</span>
</button>
<h4 class="modal-title"><t t-esc="modal_title"/></h4>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form">
<t t-raw="modal_body"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="modal-save">Save</button>
</div>
</div>
</div>
</div>
</t>
<!-- Generic Field Layout -->
<t t-name="website_form_editor.field">
<div t-att-class="'form-group form-field' + (field.custom ? ' o_website_form_custom' : '') + (field.required ? ' o_website_form_required' : '')">
<div class="col-md-3 col-sm-4 text-right">
<label class="control-label" t-att-for="field.name">
<t t-esc="field.string"/>
</label>
</div>
<div class="col-md-7 col-sm-8">
<t t-raw="0"/>
</div>
</div>
</t>
<!-- Char Field -->
<t t-name="website_form_editor.field_char">
<t t-call="website_form_editor.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_editor.field_text">
<t t-call="website_form_editor.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_editor.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_editor.field_text"/>
</t>
<!-- Integer Field -->
<t t-name="website_form_editor.field_integer">
<t t-call="website_form_editor.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_editor.field_float">
<t t-call="website_form_editor.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>
<!-- Datetime Field -->
<t t-name="website_form_editor.field_datetime">
<t t-call="website_form_editor.field">
<input
type="text"
class="form-control o_website_form_input o_website_form_datetime"
t-att-name="field.name"
t-att-required="field.required"
/>
</t>
</t>
<!-- Boolean Field -->
<t t-name="website_form_editor.field_boolean">
<t t-call="website_form_editor.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_editor.field_selection">
<t t-call="website_form_editor.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_editor.field_many2one">
<!-- Binary one2many -->
<t t-if="field.relation == 'ir.attachment'">
<t t-call="website_form_editor.field_binary"/>
</t>
<!-- Generic one2many -->
<t t-if="field.relation != 'ir.attachment'">
<t t-call="website_form_editor.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_editor.field_one2many">
<!-- Binary one2many -->
<t t-if="field.relation == 'ir.attachment'">
<t t-call="website_form_editor.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_editor.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_editor.field_many2many">
<t t-call="website_form_editor.field_one2many"/>
</t>
<!-- Binary Field -->
<t t-name="website_form_editor.field_binary">
<t t-call="website_form_editor.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>
+1
View File
@@ -0,0 +1 @@
import test_website_form_editor
+8
View File
@@ -0,0 +1,8 @@
import openerp.tests
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestWebsiteFormEditor(openerp.tests.HttpCase):
def test_tour(self):
self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_form_editor_tour', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_form_editor_tour", login="admin")
self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_form_editor_tour_submit', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_form_editor_tour_submit")
self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_form_editor_tour_results', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_form_editor_tour_results", login="admin")
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<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_editor/static/src/js/website_form_editor.js"/>
</xpath>
</template>
<template id="assets_common" name="Website Form Editor Assets Common" inherit_id="web.assets_common">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_form_editor/static/src/js/website_form_editor_tour.js"/>
</xpath>
</template>
</data>
</openerp>
+29
View File
@@ -0,0 +1,29 @@
<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="//group" position="inside">
<group>
<field name="website_form_access"/>
<field name="website_form_label"/>
<field name="website_form_default_field_id"/>
</group>
</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>
+74
View File
@@ -0,0 +1,74 @@
<odoo>
<template id="snippet_options" inherit_id="website.snippet_options">
<xpath expr="//div" position="after">
<!-- Form -->
<div data-js='website_form_editor' data-selector='.s_website_form' data-drop-in=".oe_structure, [data-oe-type=html]">
<li data-website_form_model_modal="">
<a>Change Form Parameters</a>
</li>
<li data-website_form_field_modal="">
<a>Add a model field</a>
</li>
<li class="dropdown-submenu">
<a>Add a custom field</a>
<ul class="dropdown-menu">
<li data-website_form_custom_field="char"><a>Text</a></li>
<li data-website_form_custom_field="text"><a>Long Text</a></li>
<li data-website_form_custom_field="integer"><a>Number</a></li>
<li data-website_form_custom_field="float"><a>Decimal Number</a></li>
<li data-website_form_custom_field="boolean"><a>Checkbox</a></li>
<li data-website_form_custom_field="selection"><a>Radio Buttons</a></li>
<li data-website_form_custom_field="many2one"><a>Selection</a></li>
<li data-website_form_custom_field="one2many"><a>Multiple Checkboxes</a></li>
<li data-website_form_custom_field="binary"><a>File Upload</a></li>
</ul>
</li>
</div>
<!-- Field -->
<div data-js='website_form_editor' data-selector=".form-field" data-drop-near=".form-field">
<!-- <li data-website_form_field_parameters="">
<a>Field Parameters</a>
</li> -->
<!-- <li data-website_form_editor_field_reset="">
<a>Reset Field</a>
</li> -->
<li data-toggle_class="o_website_form_field_hidden">
<a>Hidden</a>
</li>
</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)">
<li data-website_form_field_require="" data-toggle_class="o_website_form_required_custom">
<a>Required</a>
</li>
</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"/>
</xpath>
</template>
</odoo>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="s_website_form" name="Form Builder">
<form action="/website_form/" method="post" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data">
<div class="form-group">
<div class="col-md-offset-3 col-sm-offset-4 col-sm-8 col-md-7">
<button class="btn btn-primary btn-lg o_website_form_send">Send</button>
<span id="o_website_form_result"/>
</div>
</div>
</form>
</template>
<template id="register_s_website_form" inherit_id="website.snippets" name="Snippet Form Builder">
<xpath expr="//div[@id='snippet_feature']/div[contains(@class, 'o_panel_body')]" position="inside">
<t t-snippet="website_form_editor.s_website_form" t-thumbnail="/website_form_editor/static/src/img/s_website_form.png"/>
</xpath>
</template>
</data>
</openerp>