diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js index fe9c5021404..207328f82b8 100644 --- a/addons/website/static/src/snippets/s_website_form/000.js +++ b/addons/website/static/src/snippets/s_website_form/000.js @@ -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(); + }, }); }); diff --git a/addons/website/static/src/snippets/s_website_form/001.scss b/addons/website/static/src/snippets/s_website_form/001.scss index c4f5654053e..445c7e9cd54 100644 --- a/addons/website/static/src/snippets/s_website_form/001.scss +++ b/addons/website/static/src/snippets/s_website_form/001.scss @@ -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 { diff --git a/addons/website/static/src/snippets/s_website_form/options.js b/addons/website/static/src/snippets/s_website_form/options.js index ab578fea3e7..d2711fc3a9b 100644 --- a/addons/website/static/src/snippets/s_website_form/options.js +++ b/addons/website/static/src/snippets/s_website_form/options.js @@ -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 diff --git a/addons/website/static/tests/tours/website_form_editor.js b/addons/website/static/tests/tours/website_form_editor.js index dacc63ccb7d..8149ee5c94f 100644 --- a/addons/website/static/tests/tours/website_form_editor.js +++ b/addons/website/static/tests/tours/website_form_editor.js @@ -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" diff --git a/addons/website/views/snippets/s_website_form.xml b/addons/website/views/snippets/s_website_form.xml index 1f05efe502a..da436168824 100644 --- a/addons/website/views/snippets/s_website_form.xml +++ b/addons/website/views/snippets/s_website_form.xml @@ -133,8 +133,61 @@ data-apply-to=".s_website_form_date input"/> - + + Always Visible + Hidden + Visible only if + + + + + + + Is equal to + Is not equal to + + + + Contains + Doesn't contain + Is equal to + Is not equal to + Is set + Is not set + + + + Is equal to + Is not equal to + Is greater than + Is less than + Is greater than or equal to + Is less than or equal to + Is set + Is not set + + + + Is equal to + Is not equal to + Is after + Is before + Is after or equal to + Is before or equal to + Is set + Is not set + Is between (included) + Is not between (excluded) + + + + + + + + + +