[IMP] website, *: give flexibility to dynamic snippet templates
*: website_blog, website_event Commit [1] introduced new templates. This commit's goal is to build on that template system to provide more flexibility for designers. It adds new data-attributes to set on the first node of a template. - data-row-per-slide: Allows for the template to tell a dynamic snippet carousel how many rows of cards it should display. - data-arrow-position: Allows for the template to tell a dynamic snippet carousel where the arrows should be positioned. (for now just bottom, or side) - data-extra-classes: give the template the possibility to add extra classes to the dynamic snippet instead of just the card. This commit also removes options to select the amount of elements displayed on each row. This is now only changeable through templates, or by directly editing the snippet's DOM through debug tools. This was done to reduce the amount of options visible, so that the user just chooses a template and it is ready to go. [1]: https://github.com/odoo/odoo/commit/49bd79b0bdca76415780bdfa199195371c2692e5 task-2677203 Part-of: odoo/odoo#80128
This commit is contained in:
@@ -398,6 +398,9 @@ class Website(Home):
|
||||
t['numOfEl'] = attribs.get('data-number-of-elements')
|
||||
t['numOfElSm'] = attribs.get('data-number-of-elements-sm')
|
||||
t['numOfElFetch'] = attribs.get('data-number-of-elements-fetch')
|
||||
t['rowPerSlide'] = attribs.get('data-row-per-slide')
|
||||
t['arrowPosition'] = attribs.get('data-arrow-position')
|
||||
t['extraClasses'] = attribs.get('data-extra-classes')
|
||||
return templates
|
||||
|
||||
@http.route('/website/get_current_currency', type='json', auth="public", website=True)
|
||||
|
||||
@@ -5,6 +5,8 @@ const core = require('web.core');
|
||||
const config = require('web.config');
|
||||
const publicWidget = require('web.public.widget');
|
||||
const {Markup} = require('web.utils');
|
||||
const DEFAULT_NUMBER_OF_ELEMENTS = 4;
|
||||
const DEFAULT_NUMBER_OF_ELEMENTS_SM = 1;
|
||||
|
||||
const DynamicSnippet = publicWidget.Widget.extend({
|
||||
selector: '.s_dynamic_snippet',
|
||||
@@ -134,28 +136,31 @@ const DynamicSnippet = publicWidget.Widget.extend({
|
||||
* @private
|
||||
*/
|
||||
_prepareContent: function () {
|
||||
if (this.$target[0].dataset.numberOfElements && this.$target[0].dataset.numberOfElementsSmallDevices) {
|
||||
this.renderedContent = core.qweb.render(
|
||||
this.template_key,
|
||||
this._getQWebRenderOptions());
|
||||
} else {
|
||||
this.renderedContent = '';
|
||||
}
|
||||
this.renderedContent = core.qweb.render(
|
||||
this.template_key,
|
||||
this._getQWebRenderOptions()
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Method to be overridden in child components in order to prepare QWeb
|
||||
* options.
|
||||
* @private
|
||||
*/
|
||||
_getQWebRenderOptions: function () {
|
||||
_getQWebRenderOptions: function () {
|
||||
const dataset = this.$target[0].dataset;
|
||||
const numberOfRecords = parseInt(dataset.numberOfRecords);
|
||||
let numberOfElements;
|
||||
if (config.device.isMobile) {
|
||||
numberOfElements = parseInt(dataset.numberOfElementsSmallDevices) || DEFAULT_NUMBER_OF_ELEMENTS_SM;
|
||||
} else {
|
||||
numberOfElements = parseInt(dataset.numberOfElements) || DEFAULT_NUMBER_OF_ELEMENTS;
|
||||
}
|
||||
const chunkSize = numberOfRecords < numberOfElements ? numberOfRecords : numberOfElements;
|
||||
return {
|
||||
chunkSize: parseInt(
|
||||
config.device.isMobile
|
||||
? this.$target[0].dataset.numberOfElementsSmallDevices
|
||||
: this.$target[0].dataset.numberOfElements
|
||||
),
|
||||
chunkSize: chunkSize,
|
||||
data: this.data,
|
||||
uniqueId: this.uniqueId
|
||||
uniqueId: this.uniqueId,
|
||||
extraClasses: dataset.extraClasses || '',
|
||||
};
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<t t-set="rowIndexGenerator" t-value="Array.from(Array(Math.ceil(data.length/chunkSize)).keys())"/>
|
||||
<t t-set="colIndexGenerator" t-value="Array.from(Array(chunkSize).keys())"/>
|
||||
<t t-foreach="rowIndexGenerator" t-as="rowIndex">
|
||||
<div class="row my-4">
|
||||
<div t-attf-class="row my-4 #{extraClasses}">
|
||||
<t t-foreach="colIndexGenerator" t-as="colIndex">
|
||||
<t t-if="(rowIndex * chunkSize + colIndex) < data.length">
|
||||
<div t-attf-class="#{colClass}">
|
||||
|
||||
@@ -47,18 +47,6 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
if (params.attributeName === 'templateKey' && previewMode === false) {
|
||||
this._templateUpdated(widgetValue, params.activeValue);
|
||||
}
|
||||
if (params.attributeName === 'numberOfRecords' && previewMode === false) {
|
||||
const dataSet = this.$target.get(0).dataset;
|
||||
const numberOfElements = parseInt(dataSet.numberOfElements);
|
||||
const numberOfRecords = parseInt(dataSet.numberOfRecords);
|
||||
const numberOfElementsSmallDevices = parseInt(dataSet.numberOfElementsSmallDevices);
|
||||
if (numberOfElements > numberOfRecords) {
|
||||
dataSet.numberOfElements = numberOfRecords;
|
||||
}
|
||||
if (numberOfElementsSmallDevices > numberOfRecords) {
|
||||
dataSet.numberOfElementsSmallDevices = numberOfRecords;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -79,16 +67,6 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
await this._super(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async updateUIVisibility() {
|
||||
await this._super(...arguments);
|
||||
const template = this._getCurrentTemplate();
|
||||
const groupingMessage = this.el.querySelector('.o_grouping_message');
|
||||
groupingMessage.classList.toggle('d-none', template && !!template.numOfEl && !!template.numOfElSm);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -112,16 +90,6 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
return Object.keys(this.dynamicFilters).length !== 1;
|
||||
}
|
||||
|
||||
if (widgetName === 'number_of_elements_opt') {
|
||||
const template = this._getCurrentTemplate();
|
||||
return template && !template.numOfEl;
|
||||
}
|
||||
|
||||
if (widgetName === 'number_of_elements_small_devices_opt') {
|
||||
const template = this._getCurrentTemplate();
|
||||
return template && !template.numOfElSm;
|
||||
}
|
||||
|
||||
if (widgetName === 'number_of_records_opt') {
|
||||
const template = this._getCurrentTemplate();
|
||||
return template && !template.numOfElFetch;
|
||||
@@ -249,8 +217,6 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
// numberOfElements or numberOfElementsSmallDevices) might throw an
|
||||
// exception by not finding the attribute on the element.
|
||||
this.options.wysiwyg.odooEditor.observerUnactive();
|
||||
this._setOptionValue('numberOfElements', 4);
|
||||
this._setOptionValue('numberOfElementsSmallDevices', 1);
|
||||
const filterKeys = this.$el.find("we-select[data-attribute-name='filterId'] we-selection-items we-button");
|
||||
if (filterKeys.length > 0) {
|
||||
this._setOptionValue('numberOfRecords', this.dynamicFilters[Object.keys(this.dynamicFilters)[0]].limit);
|
||||
@@ -285,13 +251,22 @@ const dynamicSnippetOptions = options.Class.extend({
|
||||
const template = this.dynamicFilterTemplates[newTemplate];
|
||||
if (template.numOfEl) {
|
||||
this.$target[0].dataset.numberOfElements = template.numOfEl;
|
||||
} else {
|
||||
delete this.$target[0].dataset.numberOfElements;
|
||||
}
|
||||
if (template.numOfElSm) {
|
||||
this.$target[0].dataset.numberOfElementsSmallDevices = template.numOfElSm;
|
||||
} else {
|
||||
delete this.$target[0].dataset.numberOfElementsSmallDevices;
|
||||
}
|
||||
if (template.numOfElFetch) {
|
||||
this.$target[0].dataset.numberOfRecords = template.numOfElFetch;
|
||||
}
|
||||
if (template.extraClasses) {
|
||||
this.$target[0].dataset.extraClasses = template.extraClasses;
|
||||
} else {
|
||||
delete this.$target[0].dataset.extraClasses;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sets the option value.
|
||||
|
||||
@@ -3,6 +3,7 @@ odoo.define('website.s_dynamic_snippet_carousel', function (require) {
|
||||
|
||||
const publicWidget = require('web.public.widget');
|
||||
const DynamicSnippet = require('website.s_dynamic_snippet');
|
||||
const config = require('web.config');
|
||||
|
||||
const DynamicSnippetCarousel = DynamicSnippet.extend({
|
||||
selector: '.s_dynamic_snippet_carousel',
|
||||
@@ -26,6 +27,8 @@ const DynamicSnippetCarousel = DynamicSnippet.extend({
|
||||
this._super.apply(this, arguments),
|
||||
{
|
||||
interval: parseInt(this.$target[0].dataset.carouselInterval),
|
||||
rowPerSlide: parseInt(config.device.isMobile ? 1 : this.$target[0].dataset.rowPerSlide || 1),
|
||||
arrowPosition: this.$target[0].dataset.arrowPosition || '',
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -18,4 +18,9 @@
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.s_dynamic_snippet_arrow_bottom {
|
||||
> .carousel-control-prev, .carousel-control-next {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="website.s_dynamic_snippet.carousel.arrows">
|
||||
<a t-attf-href="##{uniqueId}" class="carousel-control-prev" data-bs-slide="prev" role="button" aria-label="Previous" title="Previous">
|
||||
<span class="fa fa-chevron-circle-left fa-2x"/>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</a>
|
||||
<a t-attf-href="##{uniqueId}" class="carousel-control-next" data-bs-slide="next" role="button" aria-label="Next" title="Next">
|
||||
<span class="fa fa-chevron-circle-right fa-2x"/>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</a>
|
||||
</t>
|
||||
<t t-name="website.s_dynamic_snippet.carousel">
|
||||
<div t-att-id="uniqueId" class="carousel slide" t-att-data-bs-interval="interval">
|
||||
<!-- Content -->
|
||||
<t t-set="colClass" t-value="'d-flex flex-grow-0 flex-shrink-0 col-' + Math.trunc(12 / chunkSize).toString()"/>
|
||||
<t t-set="slideIndexGenerator" t-value="Array.from(Array(Math.ceil(data.length/chunkSize)).keys())"/>
|
||||
<t t-set="itemIndexGenerator" t-value="Array.from(Array(chunkSize).keys())"/>
|
||||
<t t-set="rowSize" t-value="chunkSize" />
|
||||
<t t-set="slideSize" t-value="rowSize * rowPerSlide" />
|
||||
<t t-set="colClass" t-value="'d-flex flex-grow-0 flex-shrink-0 col-' + Math.trunc(12 / rowSize).toString()"/>
|
||||
<t t-set="slideIndexGenerator" t-value="Array.from(Array(Math.ceil(data.length/slideSize)).keys())"/>
|
||||
<t t-set="rowIndexGenerator" t-value="Array.from(Array(rowPerSlide).keys())"/>
|
||||
<t t-set="colIndexGenerator" t-value="Array.from(Array(rowSize).keys())"/>
|
||||
<div class="carousel-inner row w-100 mx-auto" role="listbox">
|
||||
<t t-foreach="slideIndexGenerator" t-as="slideIndex">
|
||||
<div t-attf-class="carousel-item #{slideIndex_first ? 'active' : ''}">
|
||||
<div class="row">
|
||||
<t t-foreach="itemIndexGenerator" t-as="itemIndex">
|
||||
<t t-if="(slideIndex * chunkSize + itemIndex) < data.length">
|
||||
<div t-attf-class="#{colClass}">
|
||||
<t t-out="data[slideIndex * chunkSize + itemIndex]"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-set="slideOffset" t-value="slideIndex * slideSize"/>
|
||||
<div t-attf-class="carousel-item #{extraClasses} #{slideIndex_first ? 'active' : ''}">
|
||||
<t t-foreach="rowIndexGenerator" t-as="rowIndex">
|
||||
<t t-set="rowOffset" t-value="slideOffset + (rowIndex * rowSize)"/>
|
||||
<t t-if="rowOffset < data.length">
|
||||
<div class="row">
|
||||
<t t-foreach="colIndexGenerator" t-as="colIndex">
|
||||
<t t-set="itemIndex" t-value="rowOffset + colIndex"/>
|
||||
<t t-if="itemIndex < data.length">
|
||||
<div t-attf-class="#{colClass}">
|
||||
<t t-out="data[itemIndex]"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<!-- Controls -->
|
||||
<t t-if='slideIndexGenerator.length > 1'>
|
||||
<a t-attf-href="##{uniqueId}" class="carousel-control-prev" data-bs-slide="prev" role="button" aria-label="Previous" title="Previous">
|
||||
<span class="fa fa-chevron-circle-left fa-2x"/>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</a>
|
||||
<a t-attf-href="##{uniqueId}" class="carousel-control-next" data-bs-slide="next" role="button" aria-label="Next" title="Next">
|
||||
<span class="fa fa-chevron-circle-right fa-2x"/>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</a>
|
||||
<t t-if="arrowPosition === 'bottom'">
|
||||
<div class="s_dynamic_snippet_arrow_bottom pt-2 d-flex justify-content-center">
|
||||
<t t-call="website.s_dynamic_snippet.carousel.arrows">
|
||||
<t t-set="uniqueId" t-value="uniqueId" />
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="website.s_dynamic_snippet.carousel.arrows">
|
||||
<t t-set="uniqueId" t-value="uniqueId" />
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -18,7 +18,28 @@ const dynamicSnippetCarouselOptions = s_dynamic_snippet_options.extend({
|
||||
_setOptionsDefaultValues: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._setOptionValue('carouselInterval', '5000');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Take the new template selection into account
|
||||
*
|
||||
* @param {number} newTemplate id of the newly selected template
|
||||
* @param {number} oldTemplate id of the previously selected template
|
||||
* @override
|
||||
*/
|
||||
_templateUpdated(newTemplate, oldTemplate) {
|
||||
this._super(...arguments);
|
||||
const template = this.dynamicFilterTemplates[newTemplate];
|
||||
if (template.rowPerSlide) {
|
||||
this.$target[0].dataset.rowPerSlide = template.rowPerSlide;
|
||||
} else {
|
||||
delete this.$target[0].dataset.rowPerSlide;
|
||||
}
|
||||
if (template.arrowPosition) {
|
||||
this.$target[0].dataset.arrowPosition = template.arrowPosition;
|
||||
} else {
|
||||
delete this.$target[0].dataset.arrowPosition;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -26,19 +26,6 @@
|
||||
<we-select string="Fetched elements" data-name="number_of_records_opt" data-attribute-name="numberOfRecords" data-no-preview="true">
|
||||
<we-button t-foreach="range(1, 17)" t-as="value" t-att-data-select-data-attribute="value" t-esc="value"/>
|
||||
</we-select>
|
||||
<we-title class="mt-2 o_grouping_message"><t t-esc="grouping_message"/></we-title>
|
||||
<we-select string="Desktop" class="o_we_sublevel_1" data-name="number_of_elements_opt" data-attribute-name="numberOfElements" data-no-preview="true">
|
||||
<we-button data-select-data-attribute="1">1</we-button>
|
||||
<we-button data-select-data-attribute="2">2</we-button>
|
||||
<we-button data-select-data-attribute="3">3</we-button>
|
||||
<we-button data-select-data-attribute="4">4</we-button>
|
||||
<we-button data-select-data-attribute="6">6</we-button>
|
||||
</we-select>
|
||||
<we-select string="Mobile" class="o_we_sublevel_1" data-name="number_of_elements_small_devices_opt" data-attribute-name="numberOfElementsSmallDevices" data-no-preview="true">
|
||||
<we-button data-select-data-attribute="1">1</we-button>
|
||||
<we-button data-select-data-attribute="2">2</we-button>
|
||||
<we-button data-select-data-attribute="3">3</we-button>
|
||||
</we-select>
|
||||
<t t-out="0"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,7 +34,6 @@
|
||||
<t t-call="website.s_dynamic_snippet_options_template">
|
||||
<t t-set="snippet_name" t-value="'dynamic_snippet'"/>
|
||||
<t t-set="snippet_selector" t-value="'.s_dynamic_snippet'"/>
|
||||
<t t-set="grouping_message">Items per row</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<t t-call="website.s_dynamic_snippet_options_template">
|
||||
<t t-set="snippet_name" t-value="snippet_name"/>
|
||||
<t t-set="snippet_selector" t-value="snippet_selector"/>
|
||||
<t t-set="grouping_message">Items per slide</t>
|
||||
<we-input string="Slider Speed"
|
||||
data-select-data-attribute="1s" data-name="speed_opt" data-attribute-name="carouselInterval" data-no-preview="true"
|
||||
data-unit="s" data-save-unit="ms" data-step="0.1"/>
|
||||
|
||||
@@ -78,7 +78,6 @@ const dynamicSnippetBlogPostsOptions = dynamicSnippetOptions.extend({
|
||||
* @private
|
||||
*/
|
||||
_setOptionsDefaultValues: function () {
|
||||
this._setOptionValue('numberOfElements', 3);
|
||||
this._setOptionValue('filterByBlogId', -1);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- Load-time templates (rendered in JS on page load) -->
|
||||
<!-- List layout -->
|
||||
<template id="dynamic_filter_template_blog_post_list" name="List layout">
|
||||
<div t-foreach="records" t-as="data" class="d-flex mt-3 s_blog_posts_post">
|
||||
<div t-foreach="records" t-as="data" class="d-flex mt-3 s_blog_posts_post" data-number-of-elements="2">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<a class="s_blog_posts_post_cover flex-grow-0 flex-shrink-0 align-self-baseline position-relative" t-att-href="data['call_to_action_url']" t-att-title="'Read' + data['name']">
|
||||
<t t-call="website.record_cover">
|
||||
@@ -34,7 +34,7 @@
|
||||
</template>
|
||||
<!-- Big picture layout -->
|
||||
<template id="dynamic_filter_template_blog_post_big_picture" name="Big picture layout">
|
||||
<figure t-foreach="records" t-as="data" class="my-3 w-100 s_blog_posts_post">
|
||||
<figure t-foreach="records" t-as="data" class="my-3 w-100 s_blog_posts_post" data-number-of-elements="3">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<a class="s_blog_posts_post_cover position-relative d-flex flex-column shadow-sm overflow-hidden rounded text-decoration-none" t-att-href="data['call_to_action_url']">
|
||||
<t t-call="website.record_cover">
|
||||
@@ -55,7 +55,7 @@
|
||||
</template>
|
||||
<!-- Horizontal layout -->
|
||||
<template id="dynamic_filter_template_blog_post_horizontal" name="Horizontal layout">
|
||||
<figure t-foreach="records" t-as="data" class="post s_blog_posts_post w-100">
|
||||
<figure t-foreach="records" t-as="data" class="post s_blog_posts_post w-100" data-number-of-elements="3">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<span t-if="is_sample" class="h5 float-end bg-primary text-uppercase rounded-circle px-2 py-2">Sample</span>
|
||||
<figcaption>
|
||||
@@ -76,7 +76,7 @@
|
||||
</template>
|
||||
<!-- Card layout -->
|
||||
<template id="dynamic_filter_template_blog_post_card" name="Card layout">
|
||||
<div t-foreach="records" t-as="data" class="s_blog_posts_post pb32 w-100">
|
||||
<div t-foreach="records" t-as="data" class="s_blog_posts_post pb32 w-100" data-number-of-elements="3">
|
||||
<t t-set="record" t-value="data['_record']"/>
|
||||
<div class="card">
|
||||
<a class="s_blog_posts_post_cover" t-att-href="data['call_to_action_url']">
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
<t t-call="website_event.s_dynamic_snippet_options_template">
|
||||
<t t-set="snippet_name" t-value="'event_upcoming_snippet'"/>
|
||||
<t t-set="snippet_selector" t-value="'.s_event_upcoming_snippet'"/>
|
||||
<t t-set="grouping_message">Items per row</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user