[IMP] website: add ability to conditionally show form fields

Allow the user to create forms whose fields can have a conditional
visibility, meaning that the field is only visible if another field in
the form meets certain conditions.

The previous hidden checkbox is replaced by a select with 3 options
(visible, hidden, visible only if).

task-2431285

closes odoo/odoo#77255

X-original-commit: 2dcbfecf3c10687a2cd3af36335d5be70c904fce
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Co-authored-by: Arthur Detroux (ard) <ard@odoo.com>
Co-authored-by: qsm-odoo <qsm@odoo.com>
This commit is contained in:
Guillaume (gdi)
2021-09-28 09:29:03 +00:00
committed by qsm-odoo
co-authored by Arthur Detroux qsm-odoo
parent 48092fcf85
commit e91beddac0
5 changed files with 502 additions and 19 deletions
@@ -48,6 +48,8 @@ odoo.define('website.s_website_form', function (require) {
init: function () {
this._super(...arguments);
this._recaptcha = new ReCaptcha();
this._visibilityFunctionByFieldName = new Map();
this._visibilityFunctionByFieldEl = new Map();
this.__started = new Promise(resolve => this.__startResolve = resolve);
},
willStart: async function () {
@@ -71,9 +73,28 @@ odoo.define('website.s_website_form', function (require) {
start: function () {
var self = this;
// Prepare visibility data and update field visibilities
const visibilityFunctionsByFieldName = new Map();
for (const fieldEl of this.$target[0].querySelectorAll('[data-visibility-dependency]')) {
const inputName = fieldEl.querySelector('.s_website_form_input').name;
if (!visibilityFunctionsByFieldName.has(inputName)) {
visibilityFunctionsByFieldName.set(inputName, []);
}
const func = this._buildVisibilityFunction(fieldEl);
visibilityFunctionsByFieldName.get(inputName).push(func);
this._visibilityFunctionByFieldEl.set(fieldEl, func);
}
for (const [name, funcs] of visibilityFunctionsByFieldName.entries()) {
this._visibilityFunctionByFieldName.set(name, () => funcs.some(func => func()));
}
this._updateFieldsVisibility();
this._onFieldInputDebounced = _.debounce(this._onFieldInput.bind(this), 400);
this.$el.on('input.s_website_form', '.s_website_form_field', this._onFieldInputDebounced);
// Initialize datetimepickers
var datepickers_options = {
minDate: moment({ y: 1000 }),
minDate: moment({y: 1000}),
maxDate: moment({y: 9999, M: 11, d: 31}),
calendarWeeks: true,
icons: {
@@ -119,6 +140,13 @@ odoo.define('website.s_website_form', function (require) {
});
}
// Check disabled states
this.inputEls = this.$target[0].querySelectorAll('.s_website_form_field.s_website_form_field_hidden_if .s_website_form_input');
this._disabledStates = new Map();
for (const inputEl of this.inputEls) {
this._disabledStates[inputEl] = inputEl.disabled;
}
return this._super(...arguments).then(() => this.__startResolve());
},
@@ -156,6 +184,16 @@ odoo.define('website.s_website_form', function (require) {
// Reinitialize dates
this.$allDates.removeClass('s_website_form_datepicker_initialized');
// Restore disabled attribute
for (const inputEl of this.inputEls) {
inputEl.disabled = !!this._disabledStates.get(inputEl);
}
// All 'hidden if' fields start with d-none
this.$target[0].querySelectorAll('.s_website_form_field_hidden_if:not(.d-none)').forEach(el => el.classList.add('d-none'));
this.$el.off('.s_website_form');
},
send: async function (e) {
@@ -395,6 +433,11 @@ odoo.define('website.s_website_form', function (require) {
message: message,
})));
},
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
/**
* Gets the user's field needed to be fetched to pre-fill the form.
*
@@ -403,5 +446,132 @@ odoo.define('website.s_website_form', function (require) {
_getUserPreFillFields() {
return ['name', 'phone', 'email', 'commercial_company_name'];
},
/**
* Compares the value with the comparable (and the between) with
* comparator as a means to compare
*
* @private
* @param {string} comparator The way that $value and $comparable have
* to be compared
* @param {string} [value] The value of the field
* @param {string} [comparable] The value to compare
* @param {string} [between] The maximum date value in case comparator
* is between or !between
* @returns {boolean}
*/
_compareTo(comparator, value = '', comparable, between) {
switch (comparator) {
case 'contains':
return value.includes(comparable);
case '!contains':
return !value.includes(comparable);
case 'equal':
case 'selected':
return value === comparable;
case '!equal':
case '!selected':
return value !== comparable;
case 'set':
return value;
case '!set':
return !value;
case 'greater':
return value > comparable;
case 'less':
return value < comparable;
case 'greater or equal':
return value >= comparable;
case 'less or equal':
return value <= comparable;
}
// Date & Date Time comparison requires formatting the value
if (value.includes(':')) {
const datetimeFormat = time.getLangDatetimeFormat();
value = moment(value, datetimeFormat)._d.getTime() / 1000;
} else {
const dateFormat = time.getLangDateFormat();
value = moment(value, dateFormat)._d.getTime() / 1000;
}
comparable = parseInt(comparable);
between = parseInt(between) || '';
switch (comparator) {
case 'dateEqual':
return value === comparable;
case 'date!equal':
return value !== comparable;
case 'before':
return value < comparable;
case 'after':
return value > comparable;
case 'equal or before':
return value <= comparable;
case 'between':
return value >= comparable && value <= between;
case '!between':
return !(value >= comparable && value <= between);
case 'equal or after':
return value >= comparable;
}
},
/**
* @private
* @param {HTMLElement} fieldEl the field we want to have a function
* that calculates its visibility
* @returns {function} the function to be executed when we want to
* recalculate the visibility of fieldEl
*/
_buildVisibilityFunction(fieldEl) {
const visibilityCondition = fieldEl.dataset.visibilityCondition;
const dependencyName = fieldEl.dataset.visibilityDependency;
const comparator = fieldEl.dataset.visibilityComparator;
const between = fieldEl.dataset.visibilityBetween;
return () => {
// To be visible, at least one field with the dependency name must be visible.
const dependencyVisibilityFunction = this._visibilityFunctionByFieldName.get(dependencyName);
const dependencyIsVisible = !dependencyVisibilityFunction || dependencyVisibilityFunction();
if (!dependencyIsVisible) {
return false;
}
const formData = new FormData(this.$target[0]);
const currentValueOfDependency = formData.get(dependencyName);
return this._compareTo(comparator, currentValueOfDependency, visibilityCondition, between);
};
},
/**
* Calculates the visibility for each field with conditional visibility
*/
_updateFieldsVisibility() {
for (const [fieldEl, visibilityFunction] of this._visibilityFunctionByFieldEl.entries()) {
this._updateFieldVisibility(fieldEl, visibilityFunction());
}
},
/**
* Changes the visibility of a field.
*
* @param {HTMLElement} fieldEl
* @param {boolean} haveToBeVisible
*/
_updateFieldVisibility(fieldEl, haveToBeVisible) {
const fieldContainerEl = fieldEl.closest('.s_website_form_field');
fieldContainerEl.classList.toggle('d-none', !haveToBeVisible);
for (const inputEl of fieldContainerEl.querySelectorAll('.s_website_form_input')) {
// Hidden inputs should also be disabled so that their data are
// not sent on form submit.
inputEl.disabled = !haveToBeVisible;
}
},
//----------------------------------------------------------------------
// Handlers
//----------------------------------------------------------------------
/**
* Calculates the visibility of the fields at each input event on the
* form (this method should be debounced in the start).
*/
_onFieldInput() {
this._updateFieldsVisibility();
},
});
});
@@ -4,6 +4,11 @@
display: block;
opacity: 0.5;
}
// Fields with conditional visibility are visible and identifiable in the editor
.s_website_form_field_hidden_if {
display: block !important;
background-color: $o-we-fg-light;
}
// Select inputs do not trigger the default browser behavior
// Since we use a custom editable element
.s_website_form_field select {
@@ -148,6 +148,8 @@ const FormEditor = options.Class.extend({
});
const FieldEditor = FormEditor.extend({
VISIBILITY_DATASET: ['visibilityDependency', 'visibilityCondition', 'visibilityComparator', 'visibilityBetween'],
/**
* @override
*/
@@ -908,6 +910,22 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
const fieldEl = this._renderField(field);
this._replaceFieldElement(fieldEl);
},
/**
* Removes the visibility conditions concerned by the deleted field
*
* @override
*/
onRemove() {
const fieldName = this.$target[0].querySelector('.s_website_form_input').name;
const isMultipleField = this.formEl.querySelectorAll(`.s_website_form_input[name="${fieldName}"]`).length > 1;
if (isMultipleField) {
return;
}
const dependentFieldContainerEl = this.formEl.querySelectorAll(`[data-visibility-dependency="${fieldName}"]`);
for (const fieldContainerEl of dependentFieldContainerEl) {
this._deleteConditionalVisibility(fieldContainerEl);
}
},
//----------------------------------------------------------------------
// Options
@@ -959,7 +977,15 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
if (multiple) {
multiple.dataset.name = value;
}
this.$target[0].querySelectorAll('.s_website_form_input').forEach(el => el.name = value);
const inputEls = this.$target[0].querySelectorAll('.s_website_form_input');
const previousInputName = inputEls[0].name;
inputEls.forEach(el => el.name = value);
// Synchronize the fields whose visibility depends on this field
const dependentEls = this.formEl.querySelectorAll(`.s_website_form_field[data-visibility-dependency="${previousInputName}"]`);
for (const dependentEl of dependentEls) {
dependentEl.dataset.visibilityDependency = value;
}
}
},
/**
@@ -1019,10 +1045,47 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
*/
renderListItems: async function (previewMode, value, params) {
const valueList = JSON.parse(value);
// Synchronize the possible values with the fields whose visibility
// depends on the current field
const newValuesText = valueList.map(value => value.name);
const inputEls = this.$target[0].querySelectorAll('.s_website_form_input, option');
const inputName = this.$target[0].querySelector('.s_website_form_input').name;
for (let i = 0; i < inputEls.length; i++) {
const input = inputEls[i];
if (newValuesText[i] && input.value && !newValuesText.includes(input.value)) {
for (const dependentEl of this.formEl.querySelectorAll(
`[data-visibility-condition="${input.value}"][data-visibility-dependency="${inputName}"]`)) {
dependentEl.dataset.visibilityCondition = newValuesText[i];
}
break;
}
}
const field = this._getActiveField(true);
field.records = valueList;
await this._replaceField(field);
},
/**
* Sets the visibility of the field.
*
* @see this.selectClass for parameters
*/
setVisibility(previewMode, widgetValue, params) {
if (widgetValue === 'conditional') {
// Set a default visibility dependency
const firstInputOfForm = this.formEl.querySelector('.s_website_form_field:not(.s_website_form_dnone) input');
this._setVisibilityDependency(firstInputOfForm.name);
return;
}
this._deleteConditionalVisibility(this.$target[0]);
},
/**
* @see this.selectClass for parameters
*/
setVisibilityDependency(previewMode, widgetValue, params) {
this._setVisibilityDependency(widgetValue);
},
//----------------------------------------------------------------------
// Private
@@ -1059,6 +1122,8 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
return this.$target[0].classList.contains(params.activeValue) ? params.activeValue : 'false';
case 'renderListItems':
return JSON.stringify(this._getListItems());
case 'setVisibilityDependency':
return this.$target[0].dataset.visibilityDependency || '';
}
return this._super(...arguments);
},
@@ -1066,7 +1131,52 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
* @override
*/
_computeWidgetVisibility: function (widgetName, params) {
const dependencyEl = this._getDependencyEl();
switch (widgetName) {
case 'hidden_condition_time_comparators_opt':
return dependencyEl && dependencyEl.dataset.target;
case 'hidden_condition_date_between':
return dependencyEl && dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#datepicker')
&& ['between', '!between'].includes(this.$target[0].getAttribute('data-visibility-comparator'));
case 'hidden_condition_datetime_between':
return dependencyEl && dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#datetimepicker')
&& ['between', '!between'].includes(this.$target[0].dataset.visibilityComparator);
case 'hidden_condition_additional_datetime':
return dependencyEl && dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#datetimepicker')
&& !['set', '!set'].includes(this.$target[0].dataset.visibilityComparator);
case 'hidden_condition_additional_date':
return dependencyEl && dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#datepicker')
&& !['set', '!set'].includes(this.$target[0].dataset.visibilityComparator);
case 'hidden_condition_additional_text':
if (!this.$target[0].classList.contains('s_website_form_field_hidden_if') ||
(dependencyEl && (['checkbox', 'radio'].includes(dependencyEl.type) || dependencyEl.nodeName === 'SELECT'))) {
return false;
}
if (!dependencyEl) {
return true;
}
if (dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#date')) {
return false;
}
return (['text', 'email', 'tel', 'url', 'search', 'password', 'number'].includes(dependencyEl.type)
|| dependencyEl.nodeName === 'TEXTAREA') && !['set', '!set'].includes(this.$target[0].dataset.visibilityComparator);
case 'hidden_condition_no_text_opt':
return dependencyEl && (dependencyEl.type === 'checkbox' || dependencyEl.type === 'radio' || dependencyEl.nodeName === 'SELECT');
case 'hidden_condition_num_opt':
return dependencyEl && dependencyEl.type === 'number';
case 'hidden_condition_text_opt':
if (!this.$target[0].classList.contains('s_website_form_field_hidden_if') || (dependencyEl &&
dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#date'))) {
return false;
}
return !dependencyEl || (['text', 'email', 'tel', 'url', 'search', 'password'].includes(dependencyEl.type) ||
dependencyEl.nodeName === 'TEXTAREA');
case 'hidden_condition_date_opt':
return dependencyEl && dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#datepicker');
case 'hidden_condition_datetime_opt':
return dependencyEl && dependencyEl.dataset.target && dependencyEl.dataset.target.includes('#datetimepicker');
case 'hidden_condition_opt':
return this.$target[0].classList.contains('s_website_form_field_hidden_if');
case 'char_input_type_opt':
return !this.$target[0].classList.contains('s_website_form_custom') && ['char', 'email', 'tel', 'url'].includes(this.$target[0].dataset.type);
case 'multi_check_display_opt':
@@ -1078,10 +1188,93 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
}
return this._super(...arguments);
},
/**
* Deletes all attributes related to conditional visibility.
*
* @param {HTMLElement} fieldEl
*/
_deleteConditionalVisibility(fieldEl) {
for (const name of this.VISIBILITY_DATASET) {
delete fieldEl.dataset[name];
}
fieldEl.classList.remove('s_website_form_field_hidden_if', 'd-none');
},
/**
* @returns {HTMLElement} The visibility dependency of the field
*/
_getDependencyEl() {
const dependencyName = this.$target[0].dataset.visibilityDependency;
return this.formEl.querySelector(`.s_website_form_input[name="${dependencyName}"]`);
},
/**
* @override
*/
_renderCustomXML: async function (uiFragment) {
// Update available visibility dependencies
const selectDependencyEl = uiFragment.querySelector('we-select[data-name="hidden_condition_opt"]');
if (selectDependencyEl) {
const existingDependencyNames = [];
for (const el of this.formEl.querySelectorAll('.s_website_form_field:not(.s_website_form_dnone)')) {
const inputEl = el.querySelector('.s_website_form_input');
if (el.querySelector('.s_website_form_label_content') && inputEl && inputEl.name
&& inputEl.name !== this.$target[0].querySelector('.s_website_form_input').name
&& !existingDependencyNames.includes(inputEl.name)) {
const button = document.createElement('we-button');
button.textContent = el.querySelector('.s_website_form_label_content').textContent;
button.dataset.setVisibilityDependency = inputEl.name;
selectDependencyEl.append(button);
existingDependencyNames.push(inputEl.name);
}
}
const comparator = this.$target[0].dataset.visibilityComparator;
const dependencyEl = this._getDependencyEl();
if (dependencyEl) {
if ((['radio', 'checkbox'].includes(dependencyEl.type) || dependencyEl.nodeName === 'SELECT')) {
// Update available visibility options
const selectOptEl = uiFragment.querySelectorAll('we-select[data-name="hidden_condition_no_text_opt"]')[1];
const inputContainerEl = this.$target[0];
const dependencyEl = this._getDependencyEl();
if (dependencyEl.nodeName === 'SELECT') {
for (const option of dependencyEl.querySelectorAll('option')) {
const button = document.createElement('we-button');
button.textContent = option.value;
button.dataset.selectDataAttribute = option.value;
selectOptEl.append(button);
}
if (!inputContainerEl.dataset.visibilityCondition) {
inputContainerEl.dataset.visibilityCondition = dependencyEl.querySelector('option').value;
}
} else { // DependecyEl is a radio or a checkbox
const dependencyContainerEl = dependencyEl.closest('.s_website_form_field');
const inputsInDependencyContainer = dependencyContainerEl.querySelectorAll('.s_website_form_input');
for (const el of inputsInDependencyContainer) {
const button = document.createElement('we-button');
button.textContent = el.value;
button.dataset.selectDataAttribute = el.value;
selectOptEl.append(button);
}
if (!inputContainerEl.dataset.visibilityCondition) {
inputContainerEl.dataset.visibilityCondition = inputsInDependencyContainer[0].value;
}
}
if (!inputContainerEl.dataset.visibilityComparator) {
inputContainerEl.dataset.visibilityComparator = 'selected';
}
this.rerender = comparator ? this.rerender : true;
}
if (!comparator) {
// Set a default comparator according to the type of dependency
if (dependencyEl.dataset.target) {
this.$target[0].dataset.visibilityComparator = 'after';
} else if (['text', 'email', 'tel', 'url', 'search', 'password', 'number'].includes(dependencyEl.type)
|| dependencyEl.nodeName === 'TEXTAREA') {
this.$target[0].dataset.visibilityComparator = 'equal';
}
}
}
}
const selectEl = uiFragment.querySelector('we-select[data-name="type_opt"]');
const currentFieldName = this._getFieldName();
const fieldsInForm = Array.from(this.formEl.querySelectorAll('.s_website_form_field:not(.s_website_form_custom) .s_website_form_input')).map(el => el.name).filter(el => el !== currentFieldName);
@@ -1119,7 +1312,11 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
await this._fetchFieldRecords(field);
list.dataset.availableRecords = JSON.stringify(field.records);
}
uiFragment.appendChild(list);
if (selectDependencyEl) {
uiFragment.insertBefore(list, uiFragment.querySelector('we-select[string="Visibility"]'));
} else {
uiFragment.appendChild(list);
}
},
/**
* Replaces the target content with the field provided.
@@ -1144,10 +1341,40 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
* @param {HTMLElement} fieldEl
*/
_replaceFieldElement(fieldEl) {
const hasConditionalVisibility = this.$target[0].classList.contains('s_website_form_field_hidden_if');
const previousName = this.$target[0].querySelector('.s_website_form_input').name;
[...this.$target[0].childNodes].forEach(node => node.remove());
[...fieldEl.childNodes].forEach(node => this.$target[0].appendChild(node));
[...fieldEl.attributes].forEach(el => this.$target[0].removeAttribute(el.nodeName));
[...fieldEl.attributes].forEach(el => this.$target[0].setAttribute(el.nodeName, el.nodeValue));
if (hasConditionalVisibility) {
this.$target[0].classList.add('s_website_form_field_hidden_if', 'd-none');
}
const dependentFieldEls = this.formEl.querySelectorAll(`.s_website_form_field[data-visibility-dependency="${previousName}"]`);
const newName = this.$target[0].querySelector('.s_website_form_input').name;
if (previousName !== newName && dependentFieldEls) {
// In order to keep the visibility conditions consistent,
// when the name has changed, it means that the type has changed so
// all fields whose visibility depends on this field must be updated so that
// they no longer have conditional visibility
for (const fieldEl of dependentFieldEls) {
this._deleteConditionalVisibility(fieldEl);
}
}
},
/**
* Sets the visibility dependency of the field.
*
* @param {string} value name of the dependency input
*/
_setVisibilityDependency(value) {
delete this.$target[0].dataset.visibilityCondition;
delete this.$target[0].dataset.visibilityComparator;
const previousDependency = this._getDependencyEl();
if (this.formEl.querySelector(`.s_website_form_input[name="${value}"]`).type !== (previousDependency && previousDependency.type)) {
this.rerender = true;
}
this.$target[0].dataset.visibilityDependency = value;
},
/**
* @private
@@ -4,6 +4,11 @@ odoo.define('website.tour.form_editor', function (require) {
const rpc = require('web.rpc');
const tour = require("web_tour.tour");
// Visibility possible values:
const VISIBLE = 'Always Visible';
const HIDDEN = 'Hidden';
const CONDITIONALVISIBILITY = 'Visible only if';
const selectButtonByText = function (text) {
return [{
content: "Open the select",
@@ -23,7 +28,7 @@ odoo.define('website.tour.form_editor', function (require) {
trigger: `we-select we-button[${data}]`,
}];
};
const addField = function (data, name, type, label, required, hidden) {
const addField = function (data, name, type, label, required, display = {visibility: VISIBLE, condition: ''}) {
const ret = [{
content: "Select form",
extra_trigger: '.s_website_form_field',
@@ -37,8 +42,17 @@ odoo.define('website.tour.form_editor', function (require) {
content: "Wait for field to load",
trigger: `.s_website_form_field[data-type="${name}"], .s_website_form_input[name="${name}"]`, //custom or existing field
run: function () {},
}];
},
...selectButtonByText(display.visibility),
];
let testText = '.s_website_form_field';
if (display.condition) {
ret.push({
content: "Set the visibility condition",
trigger: 'we-input[data-attribute-name="visibilityCondition"] input',
run: `text ${display.condition}`,
});
}
if (required) {
testText += '.s_website_form_required';
ret.push({
@@ -46,13 +60,6 @@ odoo.define('website.tour.form_editor', function (require) {
trigger: 'we-button[data-name="required_opt"] we-checkbox',
});
}
if (hidden) {
testText += '.s_website_form_field_hidden';
ret.push({
content: "Mark the field as hidden",
trigger: 'we-button[data-name="hidden_opt"] we-checkbox',
});
}
if (label) {
testText += `:has(label:contains("${label}"))`;
ret.push({
@@ -72,11 +79,11 @@ odoo.define('website.tour.form_editor', function (require) {
});
return ret;
};
const addCustomField = function (name, type, label, required, hidden) {
return addField(`data-custom-field="${name}"`, name, type, label, required, hidden);
const addCustomField = function (name, type, label, required, display) {
return addField(`data-custom-field="${name}"`, name, type, label, required, display);
};
const addExistingField = function (name, type, label, required, hidden) {
return addField(`data-existing-field="${name}"`, name, type, label, required, hidden);
const addExistingField = function (name, type, label, required, display) {
return addField(`data-existing-field="${name}"`, name, type, label, required, display);
};
tour.register("website_form_editor_tour", {
@@ -104,9 +111,11 @@ odoo.define('website.tour.form_editor', function (require) {
trigger: '[data-field-name="email_to"] input',
run: 'text_blur test@test.test',
},
...addExistingField('email_cc', 'text', 'Test conditional visibility', false, {visibility: CONDITIONALVISIBILITY, condition: 'odoo'}),
...addExistingField('date', 'text', 'Test Date', true),
...addExistingField('record_name', 'text', 'Awesome Label', false, true),
...addExistingField('record_name', 'text', 'Awesome Label', false, {visibility: HIDDEN}),
...addExistingField('body_html', 'textarea', 'Your Message', true),
@@ -394,6 +403,25 @@ odoo.define('website.tour.form_editor', function (require) {
trigger: "textarea[name='description']",
run: "text magan"
},
{
content: "Check if conditional field is visible, it shouldn't.",
trigger: "body",
run: function () {
const style = window.getComputedStyle(this.$anchor[0].getElementsByClassName('s_website_form_field_hidden_if')[0]);
if (style.display !== 'none') {
console.error('error This field should be invisible when the name is not odoo');
}
}
},
{
content: "Change name input",
trigger: "input[name='name']",
run: "text odoo",
},
{
content: "Check if conditional field is visible, it should.",
trigger: "input[name='email_cc']",
},
{
content: "Send the form",
trigger: ".s_website_form_send"
@@ -133,8 +133,61 @@
data-apply-to=".s_website_form_date input"/>
<we-checkbox string="Required" data-name="required_opt" data-no-preview="true"
data-toggle-required="s_website_form_required"/>
<we-checkbox string="Hidden" data-name="hidden_opt" data-no-preview="true"
data-select-class="s_website_form_field_hidden"/>
<we-select string="Visibility" data-no-preview="true">
<we-button data-set-visibility="visible" data-select-class="">Always Visible</we-button>
<we-button data-set-visibility="hidden" data-select-class="s_website_form_field_hidden">Hidden</we-button>
<we-button data-set-visibility="conditional" data-select-class="s_website_form_field_hidden_if d-none">Visible only if</we-button>
</we-select>
<we-row data-name="hidden_condition_opt" string="">
<we-select data-name="hidden_condition_opt" data-no-preview="true">
<!-- Load every existing form input -->
</we-select>
<we-select data-name="hidden_condition_no_text_opt" data-attribute-name="visibilityComparator" data-no-preview="true">
<we-button data-select-data-attribute="selected">Is equal to</we-button>
<we-button data-select-data-attribute="!selected">Is not equal to</we-button>
</we-select>
<we-select data-name="hidden_condition_text_opt" data-attribute-name="visibilityComparator" data-no-preview="true">
<!-- str comparator possibilities -->
<we-button data-select-data-attribute="contains">Contains</we-button>
<we-button data-select-data-attribute="!contains">Doesn't contain</we-button>
<we-button data-select-data-attribute="equal">Is equal to</we-button>
<we-button data-select-data-attribute="!equal">Is not equal to</we-button>
<we-button data-select-data-attribute="set">Is set</we-button>
<we-button data-select-data-attribute="!set">Is not set</we-button>
</we-select>
<we-select data-name="hidden_condition_num_opt" data-attribute-name="visibilityComparator" data-no-preview="true">
<!-- number comparator possibilities -->
<we-button data-select-data-attribute="equal">Is equal to</we-button>
<we-button data-select-data-attribute="!equal">Is not equal to</we-button>
<we-button data-select-data-attribute="greater">Is greater than</we-button>
<we-button data-select-data-attribute="less">Is less than</we-button>
<we-button data-select-data-attribute="greater or equal">Is greater than or equal to</we-button>
<we-button data-select-data-attribute="less or equal">Is less than or equal to</we-button>
<we-button data-select-data-attribute="set">Is set</we-button>
<we-button data-select-data-attribute="!set">Is not set</we-button>
</we-select>
<we-select data-name="hidden_condition_time_comparators_opt" data-attribute-name="visibilityComparator" data-no-preview="true">
<!-- date & datetime comparator possibilities -->
<we-button data-select-data-attribute="dateEqual">Is equal to</we-button>
<we-button data-select-data-attribute="date!equal">Is not equal to</we-button>
<we-button data-select-data-attribute="after">Is after</we-button>
<we-button data-select-data-attribute="before">Is before</we-button>
<we-button data-select-data-attribute="equal or after">Is after or equal to</we-button>
<we-button data-select-data-attribute="equal or before">Is before or equal to</we-button>
<we-button data-select-data-attribute="set">Is set</we-button>
<we-button data-select-data-attribute="!set">Is not set</we-button>
<we-button data-select-data-attribute='between'>Is between (included)</we-button>
<we-button data-select-data-attribute='!between'>Is not between (excluded)</we-button>
</we-select>
</we-row>
<we-select class="o_we_large" data-name="hidden_condition_no_text_opt" data-attribute-name="visibilityCondition" data-no-preview="true">
<!-- checkbox, select, radio possible values -->
</we-select>
<we-input class="o_we_large" data-name="hidden_condition_additional_text" data-attribute-name="visibilityCondition" data-select-data-attribute=""/>
<we-datetimepicker data-name="hidden_condition_additional_datetime" data-attribute-name="visibilityCondition" data-select-data-attribute="" />
<we-datepicker data-name="hidden_condition_additional_date" data-attribute-name="visibilityCondition" data-select-data-attribute=""/>
<we-datetimepicker data-name="hidden_condition_datetime_between" data-attribute-name="visibilityBetween" data-select-data-attribute=""/>
<we-datepicker data-name="hidden_condition_date_between" data-attribute-name="visibilityBetween" data-select-data-attribute="" />
</div>
<div data-js="WebsiteFormSubmit" data-selector=".s_website_form_submit" data-exclude=".s_website_form_no_submit_options">