summaryrefslogtreecommitdiff
path: root/addons/website/static/src/snippets/s_dynamic_snippet
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website/static/src/snippets/s_dynamic_snippet
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website/static/src/snippets/s_dynamic_snippet')
-rw-r--r--addons/website/static/src/snippets/s_dynamic_snippet/000.js244
-rw-r--r--addons/website/static/src/snippets/s_dynamic_snippet/000.scss11
-rw-r--r--addons/website/static/src/snippets/s_dynamic_snippet/000.xml20
-rw-r--r--addons/website/static/src/snippets/s_dynamic_snippet/options.js136
4 files changed, 411 insertions, 0 deletions
diff --git a/addons/website/static/src/snippets/s_dynamic_snippet/000.js b/addons/website/static/src/snippets/s_dynamic_snippet/000.js
new file mode 100644
index 00000000..d6a3e0ff
--- /dev/null
+++ b/addons/website/static/src/snippets/s_dynamic_snippet/000.js
@@ -0,0 +1,244 @@
+odoo.define('website.s_dynamic_snippet', function (require) {
+'use strict';
+
+const core = require('web.core');
+const config = require('web.config');
+const publicWidget = require('web.public.widget');
+
+const DynamicSnippet = publicWidget.Widget.extend({
+ selector: '.s_dynamic_snippet',
+ xmlDependencies: ['/website/static/src/snippets/s_dynamic_snippet/000.xml'],
+ read_events: {
+ 'click [data-url]': '_onCallToAction',
+ },
+ disabledInEditableMode: false,
+
+ /**
+ *
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ /**
+ * The dynamic filter data source data formatted with the chosen template.
+ * Can be accessed when overriding the _render_content() function in order to generate
+ * a new renderedContent from the original data.
+ *
+ * @type {*|jQuery.fn.init|jQuery|HTMLElement}
+ */
+ this.data = [];
+ this.renderedContent = '';
+ this.isDesplayedAsMobile = config.device.isMobile;
+ this.uniqueId = _.uniqueId('s_dynamic_snippet_');
+ this.template_key = 'website.s_dynamic_snippet.grid';
+ },
+ /**
+ *
+ * @override
+ */
+ willStart: function () {
+ return this._super.apply(this, arguments).then(
+ () => Promise.all([
+ this._fetchData(),
+ this._manageWarningMessageVisibility()
+ ])
+ );
+ },
+ /**
+ *
+ * @override
+ */
+ start: function () {
+ return this._super.apply(this, arguments)
+ .then(() => {
+ this._setupSizeChangedManagement(true);
+ this._render();
+ this._toggleVisibility(true);
+ });
+ },
+ /**
+ *
+ * @override
+ */
+ destroy: function () {
+ this._toggleVisibility(false);
+ this._setupSizeChangedManagement(false);
+ this._clearContent();
+ this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ *
+ * @private
+ */
+ _clearContent: function () {
+ const $dynamicSnippetTemplate = this.$el.find('.dynamic_snippet_template');
+ if ($dynamicSnippetTemplate) {
+ $dynamicSnippetTemplate.html('');
+ }
+ },
+ /**
+ * Method to be overridden in child components if additional configuration elements
+ * are required in order to fetch data.
+ * @private
+ */
+ _isConfigComplete: function () {
+ return this.$el.get(0).dataset.filterId !== undefined && this.$el.get(0).dataset.templateKey !== undefined;
+ },
+ /**
+ * Method to be overridden in child components in order to provide a search
+ * domain if needed.
+ * @private
+ */
+ _getSearchDomain: function () {
+ return [];
+ },
+ /**
+ * Fetches the data.
+ * @private
+ */
+ _fetchData: function () {
+ if (this._isConfigComplete()) {
+ return this._rpc(
+ {
+ 'route': '/website/snippet/filters',
+ 'params': {
+ 'filter_id': parseInt(this.$el.get(0).dataset.filterId),
+ 'template_key': this.$el.get(0).dataset.templateKey,
+ 'limit': parseInt(this.$el.get(0).dataset.numberOfRecords),
+ 'search_domain': this._getSearchDomain()
+ },
+ })
+ .then(
+ (data) => {
+ this.data = data;
+ }
+ );
+ } else {
+ return new Promise((resolve) => {
+ this.data = [];
+ resolve();
+ });
+ }
+ },
+ /**
+ *
+ * @private
+ */
+ _mustMessageWarningBeHidden: function() {
+ return this._isConfigComplete() || !this.editableMode;
+ },
+ /**
+ *
+ * @private
+ */
+ _manageWarningMessageVisibility: async function () {
+ this.$el.find('.missing_option_warning').toggleClass(
+ 'd-none',
+ this._mustMessageWarningBeHidden()
+ );
+ },
+ /**
+ * Method to be overridden in child components in order to prepare content
+ * before rendering.
+ * @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 = '';
+ }
+ },
+ /**
+ * Method to be overridden in child components in order to prepare QWeb
+ * options.
+ * @private
+ */
+ _getQWebRenderOptions: function () {
+ return {
+ chunkSize: parseInt(
+ config.device.isMobile
+ ? this.$target[0].dataset.numberOfElementsSmallDevices
+ : this.$target[0].dataset.numberOfElements
+ ),
+ data: this.data,
+ uniqueId: this.uniqueId
+ };
+ },
+ /**
+ *
+ * @private
+ */
+ _render: function () {
+ if (this.data.length) {
+ this._prepareContent();
+ } else {
+ this.renderedContent = '';
+ }
+ this._renderContent();
+ },
+ /**
+ *
+ * @private
+ */
+ _renderContent: function () {
+ this.$el.find('.dynamic_snippet_template').html(this.renderedContent);
+ },
+ /**
+ *
+ * @param {Boolean} enable
+ * @private
+ */
+ _setupSizeChangedManagement: function (enable) {
+ if (enable === true) {
+ config.device.bus.on('size_changed', this, this._onSizeChanged);
+ } else {
+ config.device.bus.off('size_changed', this, this._onSizeChanged);
+ }
+ },
+ /**
+ *
+ * @param visible
+ * @private
+ */
+ _toggleVisibility: function (visible) {
+ this.$el.toggleClass('d-none', !visible);
+ },
+
+ //------------------------------------- -------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Navigates to the call to action url.
+ * @private
+ */
+ _onCallToAction: function (ev) {
+ window.location = $(ev.currentTarget).attr('data-url');
+ },
+ /**
+ * Called when the size has reached a new bootstrap breakpoint.
+ *
+ * @private
+ * @param {number} size as Integer @see web.config.device.SIZES
+ */
+ _onSizeChanged: function (size) {
+ if (this.isDesplayedAsMobile !== config.device.isMobile) {
+ this.isDesplayedAsMobile = config.device.isMobile;
+ this._render();
+ }
+ },
+});
+
+publicWidget.registry.dynamic_snippet = DynamicSnippet;
+
+return DynamicSnippet;
+
+});
diff --git a/addons/website/static/src/snippets/s_dynamic_snippet/000.scss b/addons/website/static/src/snippets/s_dynamic_snippet/000.scss
new file mode 100644
index 00000000..536aba85
--- /dev/null
+++ b/addons/website/static/src/snippets/s_dynamic_snippet/000.scss
@@ -0,0 +1,11 @@
+.s_dynamic {
+ [data-url] {
+ cursor: pointer;
+ }
+ .card-img-top {
+ height: 12rem;
+ }
+ img {
+ object-fit: scale-down;
+ }
+}
diff --git a/addons/website/static/src/snippets/s_dynamic_snippet/000.xml b/addons/website/static/src/snippets/s_dynamic_snippet/000.xml
new file mode 100644
index 00000000..105078e0
--- /dev/null
+++ b/addons/website/static/src/snippets/s_dynamic_snippet/000.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+ <t t-name="website.s_dynamic_snippet.grid">
+ <!-- Content -->
+ <t t-set="colClass" t-value="'col-' + (12 / chunkSize).toString()"/>
+ <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">
+ <t t-foreach="colIndexGenerator" t-as="colIndex">
+ <t t-if="(rowIndex * chunkSize + colIndex) &lt; data.length">
+ <div t-attf-class="#{colClass}">
+ <t t-raw="data[rowIndex * chunkSize + colIndex]"/>
+ </div>
+ </t>
+ </t>
+ </div>
+ </t>
+ </t>
+</templates>
diff --git a/addons/website/static/src/snippets/s_dynamic_snippet/options.js b/addons/website/static/src/snippets/s_dynamic_snippet/options.js
new file mode 100644
index 00000000..2cbdcb1c
--- /dev/null
+++ b/addons/website/static/src/snippets/s_dynamic_snippet/options.js
@@ -0,0 +1,136 @@
+odoo.define('website.s_dynamic_snippet_options', function (require) {
+'use strict';
+
+const options = require('web_editor.snippets.options');
+
+const dynamicSnippetOptions = options.Class.extend({
+
+ /**
+ *
+ * @override
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+ this.dynamicFilters = {};
+ this.dynamicFilterTemplates = {};
+ },
+ /**
+ *
+ * @override
+ */
+ onBuilt: function () {
+ this._setOptionsDefaultValues();
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ *
+ * @see this.selectClass for parameters
+ */
+ selectDataAttribute: function (previewMode, widgetValue, params) {
+ this._super.apply(this, arguments);
+ if (params.attributeName === 'filterId' && previewMode === false) {
+ this.$target.get(0).dataset.numberOfRecords = this.dynamicFilters[parseInt(widgetValue)].limit;
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Fetches dynamic filters.
+ * @private
+ * @returns {Promise}
+ */
+ _fetchDynamicFilters: function () {
+ return this._rpc({route: '/website/snippet/options_filters'});
+ },
+ /**
+ * Fetch dynamic filters templates.
+ * @private
+ * @returns {Promise}
+ */
+ _fetchDynamicFilterTemplates: function () {
+ return this._rpc({route: '/website/snippet/filter_templates'});
+ },
+ /**
+ *
+ * @override
+ * @private
+ */
+ _renderCustomXML: async function (uiFragment) {
+ await this._renderDynamicFiltersSelector(uiFragment);
+ await this._renderDynamicFilterTemplatesSelector(uiFragment);
+ },
+ /**
+ * Renders the dynamic filter option selector content into the provided uiFragment.
+ * @param {HTMLElement} uiFragment
+ * @private
+ */
+ _renderDynamicFiltersSelector: async function (uiFragment) {
+ const dynamicFilters = await this._fetchDynamicFilters();
+ for (let index in dynamicFilters) {
+ this.dynamicFilters[dynamicFilters[index].id] = dynamicFilters[index];
+ }
+ const filtersSelectorEl = uiFragment.querySelector('[data-name="filter_opt"]');
+ return this._renderSelectUserValueWidgetButtons(filtersSelectorEl, this.dynamicFilters);
+ },
+ /**
+ * Renders we-buttons into a SelectUserValueWidget element according to provided data.
+ * @param {HTMLElement} selectUserValueWidgetElement the SelectUserValueWidget buttons
+ * have to be created into.
+ * @param {JSON} data
+ * @private
+ */
+ _renderSelectUserValueWidgetButtons: async function (selectUserValueWidgetElement, data) {
+ for (let id in data) {
+ const button = document.createElement('we-button');
+ button.dataset.selectDataAttribute = id;
+ button.innerHTML = data[id].name;
+ selectUserValueWidgetElement.appendChild(button);
+ }
+ },
+ /**
+ * Renders the template option selector content into the provided uiFragment.
+ * @param {HTMLElement} uiFragment
+ * @private
+ */
+ _renderDynamicFilterTemplatesSelector: async function (uiFragment) {
+ const dynamicFilterTemplates = await this._fetchDynamicFilterTemplates();
+ for (let index in dynamicFilterTemplates) {
+ this.dynamicFilterTemplates[dynamicFilterTemplates[index].key] = dynamicFilterTemplates[index];
+ }
+ const templatesSelectorEl = uiFragment.querySelector('[data-name="template_opt"]');
+ return this._renderSelectUserValueWidgetButtons(templatesSelectorEl, this.dynamicFilterTemplates);
+ },
+ /**
+ * Sets default options values.
+ * Method to be overridden in child components in order to set additional
+ * options default values.
+ * @private
+ */
+ _setOptionsDefaultValues: function () {
+ this._setOptionValue('numberOfElements', 4);
+ this._setOptionValue('numberOfElementsSmallDevices', 1);
+ },
+ /**
+ * Sets the option value.
+ * @param optionName
+ * @param value
+ * @private
+ */
+ _setOptionValue: function (optionName, value) {
+ if (this.$target.get(0).dataset[optionName] === undefined) {
+ this.$target.get(0).dataset[optionName] = value;
+ }
+ },
+});
+
+options.registry.dynamic_snippet = dynamicSnippetOptions;
+
+return dynamicSnippetOptions;
+});