- app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/preview-mixin.js
- app/code/Vendor/PageBuilder/view/adminhtml/web/js/utils/prod-all.js
- app/code/Vendor/PageBuilder/view/adminhtml/web/js/utils/prod-delayed-loading.js
- app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/module-13/preview.js
- app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/module-13-item/preview.js
- app/code/Vendor/PageBuilder/view/adminhtml/web/template/content-type/module-13-item/default/preview.html
- app/code/Vendor/PageBuilder/view/frontend/templates/widget/product/simple.phtml
- app/code/Vendor/PageBuilder/view/adminhtml/web/js/resource/vendor/sly.js (Librairie 3-rd party Sly v1.6.1)
app/code/Vendor/PageBuilder/view/adminhtml/web/template/content-type/module-13-item/default/preview.html
- la classe
ow-prod-delayed-loading
- la valeur
initProductDelayedLoading
de l’attributafterRender
qui correspond au libellé utilisé pour initialiser le composant dans app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/preview-mixin.js
1 2 3 4 5 6 7 8 9 |
<div class="ow-slider__item pagebuilder-content-type pagebuilder-slide children-min-height" attr="data.main.attributes" event="{ mouseover: onMouseOver, mouseout: onMouseOut }, mouseoverBubble: false"> <render args="getOptions().template"></render> <div class="ow-prod-delayed-loading ow-mod__placeholder ow-mod__placeholder--prod-v" data-bind="html: data.main.html" afterRender="initProductDelayedLoading"></div> </div> |
app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/preview-mixin.js
Pour l’ajout de notre widget Vendor_PageBuilder/js/utils/prod-delayed-loading
en tant que composant:
1 2 3 4 5 6 7 8 9 |
define([ 'Vendor_PageBuilder/js/utils/prod-delayed-loading' ], function (_prodDelayedLoading) { 'use strict'; return function (Component) { Component.prototype.retrieveOptions = wrapper.wrapSuper(Component.prototype.retrieveOptions, function () { [...] } Component.prototype.copy = function () { [...] } Component.prototype... |
…et en toute fin de fichier, l’initialisation du composant:
1 2 3 4 5 6 7 8 9 10 |
/** * Init products delayed loading */ Component.prototype.initProductDelayedLoading = function (element) { _prodDelayedLoading.initProdDelayedLoading($(element)); } return Component; } }); |
app/code/Vendor/PageBuilder/view/adminhtml/web/js/utils/prod-all.js
Un module façon requireJS pour stocker des fonctions amenées à être utilisées dans plusieurs fichiers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
define(["jquery"], function ($) { function getProductId (str) { let regex = /entity=["']*[^"' >]+/; let match; try { match = str.match(regex)[0]; } catch (e) { return; } // We aren't concerned if this fails let replaceRegex = /entity=["' ]*/; match = match.replace(replaceRegex, ""); return match; } function setProductId (elem, str) { [...] } function showAllModuleItems (elem) { [...] } return { getProductId: getProductId, setProductId: setProductId, showAllModuleItems: showAllModuleItems }; }); |
app/code/Vendor/PageBuilder/view/adminhtml/web/js/utils/prod-delayed-loading.js
Dans ce module, de nombreuses utilisations de la variable widget
qui contient, sous forme de string, les données nécessaires à l’affichage du produit:
1 2 3 4 5 6 7 8 9 10 11 |
{{widget type="Reflet\PageBuilder\Block\Selector\Product" template="Reflet_PageBuilder::widget/product/ecom.phtml" selector="product_popover_3" entity="930" module="39-item" theme="01" prod_classes="ow-prod--m-h ow-prod--t-h ow-prod--l-h ow-prod--l-h--small" is_popover="1" store_id="0" }} |
- La valeur de l’attribut
entity
correspond l’ID du produit dans le catalogue. Je la récupère via une regex dans la fonctiongetProductId
consignée dans le fichier app/code/Vendor/PageBuilder/view/adminhtml/web/js/utils/prod-all.js. - La valeur de l’attribut
template
correspond à la vue dans laquelle les données vont être affichées, en front comme en admin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
window.discoveredItems = []; define([ 'jquery', 'mage/translate', "Magento_PageBuilder/js/config", "Vendor_PageBuilder/js/utils/prod-all" ], function ( $, $t, _config, _prodAll ) { $.widget('mage.prodDelayedLoading', { $modalsWrapper: null, $pagebuilderStageWrapper: null, $emptySlot: null, loadedProducts: [], _create: function () { this.$modalsWrapper = $('.modals-wrapper'); this.$pagebuilderStageWrapper = $('.pagebuilder-stage-wrapper'); let widget = this.element[0].innerHTML; if (widget === '') { // Slot is empty this.$emptySlot = $('.ow-prod-delayed-loading:not([data-product-id])', this.$pagebuilderStageWrapper); this._initEvents(this.$emptySlot); } else { // Slot is filled with a product this._initDom(); this._initEvents(this.element); } this._keepDiscoveredItemsVisible(); }, _initDom: function () { let widget = this.element[0].innerHTML; let $item = $(this.element); _prodAll.setProductId($item, widget); }, _initEvents: function (thisElem) { thisElem.on('click', this._loadItem.bind(this)); thisElem.on('click', this._storeDiscoveredItemId.bind(this)); }, _storeDiscoveredItemId: function (e) { let productId = $(e.target).attr('data-product-id'); if ($.inArray(productId, window.discoveredItems) === -1) { window.discoveredItems.push(productId); } }, _showSimilarItem: function (productId) { // Show all occurrences of the same product within the page let $item = $('.ow-prod-delayed-loading[data-product-id="' + productId + '"]:not(._loaded)', this.$pagebuilderStageWrapper); $item.click(); }, _keepDiscoveredItemsVisible: function () { // Keep already discovered products visible within the module, // after a user action (move left/right, duplicate...) let i; for (i = 0; i < window.discoveredItems.length; ++i) { let $item = $('.ow-prod-delayed-loading[data-product-id="' + window.discoveredItems[i] + '"]:not(._loaded)', this.$pagebuilderStageWrapper); $item.click(); } }, _loadItem: function (e) { let $item = $(e.target); let _self = this; if ($item.hasClass('_loaded') || $item.hasClass('_loading') || $item.html().indexOf('{{widget') === -1) { return; } $item.find('div').remove(); let widget = $.trim($item.html()); widget = widget.replace('}}', ' prod_is_backend="1"}}'); if (this.loadedProducts[widget]) { $item.addClass('_loaded').html(this.loadedProducts[widget]); $item.removeClass('ow-mod__placeholder ow-mod__placeholder--prod-h ow-mod__placeholder--prod-v'); return; } $item.addClass('_loading').append('<div class="omega-bg-loader"></div>'); $.ajax( _config.getConfig("preview_url"), { method: "POST", data: { role: 'product', directive: widget } } ).done(function (response) { $item.removeClass('_loading ow-mod__placeholder ow-mod__placeholder--prod-h ow-mod__placeholder--prod-v'); if (typeof response.data !== "object" || !Boolean(response.data.content)) { $item.prepend('<div class="message">' + $t("An unknown error occurred. Please try again.") + '</div>'); return; } if (response.data.error) { $item.prepend('<div class="message">' + response.data.error + '</div>'); } else { _self.loadedProducts[widget] = response.data.content; $item.html(response.data.content).addClass('_loaded'); } _self._showSimilarItem(_prodAll.getProductId(widget)); }).fail(function () { $item.removeClass('_loading'); $item.prepend('<div class="message">' + $t("An unknown error occurred. Please try again.") + '</div>'); }); } }); /** * Init product delayedLoading * @param {object} $delayedLoading * @returns void */ function initProdDelayedLoading($delayedLoading) { $delayedLoading.prodDelayedLoading(); } return { initProdDelayedLoading: initProdDelayedLoading }; }); |
app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/module-13/preview.js
1 |
define(["Reflet_PageBuilder/js/utils/prod-all"], function (_prodAll) { |
Ajout d’une fonctionnalité « Show All » dans les options du module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
_proto.retrieveOptions = function retrieveOptions() { var options = _previewCollection2.prototype.retrieveOptions.call(this); [...] // autres fonctionnalités options.showAll = new _option({ preview: this, icon: "<i class='icon-pagebuilder-show'></i>", title: (0, _translate)("Show All"), action: this.showAll, classes: ["show-all"], sort: 45 }); return options; }; |
Plus bas, définition de la fonction showAll
(noter comment l’habituel $
de jQuery est remplacé par (0, _jquery)
):
1 2 3 4 5 6 7 8 |
/** * */ ; _proto.showAll = function showAll() { _prodAll.showAllModuleItems((0, _jquery)('#'+this.contentType.id)); } |
app/code/Vendor/PageBuilder/view/adminhtml/web/js/content-type/module-13-item/preview.js
1 2 3 4 5 |
define([ "Vendor_PageBuilder/js/utils/prod-all" ], function ( _prodAll ) { |
Points d’intérêt pour explorer et étendre le code:
_events.on("<em><nom_du_module></em>:renderAfter", function (args) {
correspond à différents événements (:renderAfter
, :updateAfter
, …) qui se déclenchent au chargement du module en BO.
Les objets contentType
(cf. bout de code suivant) et args
contiennent également pas mal d’infos exploitables.
1 2 3 4 5 |
_proto.bindEvents = function bindEvents() { [...] _events.on("module_13:renderAfter", function (args) { [...] _events.on("module_13:" + _this2.contentType.parentContentType.id + ":updateAfter", function (args) { |
afterObservablesUpdated
: le code qui s’exécute après que l’utilisateur ait cliqué sur le bouton « Save » (j’ai par exemple ajouté l’exécution d’une fonction setProductId
qui ajoute un data-attribute
dans le DOM, avec pour valeur un ID de produit):
this.contentType.id
me donne l’ID du produit sur lequel j’ai itéré en sauvegardant.
1 2 3 4 5 6 7 8 9 |
_proto.afterObservablesUpdated = function afterObservablesUpdated() { [...] // Show uploaded product on "Save" let $item = (0, _jquery)('.ow-prod-delayed-loading', (0, _jquery)('#'+this.contentType.id)); if($item.length) { _prodAll.setProductId($item, $item.html()); $item.click(); } |
app/code/Vendor/PageBuilder/view/frontend/templates/widget/product/simple.phtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<?php declare(strict_types=1); /** @var $block \Vendor\PageBuilder\Block\Selector\Product */ /** @var $escaper \Magento\Framework\Escaper */ $product = $block->getProduct(); $module = $block->getModule(); $theme = $block->getTheme(); ?> <?php if ($product !== null && $product->getId()) : ?> <?php $imageAlt = $block->getProductAlt($product); $displayedName = $block->getProductDisplayedName($product); /** base64 placeholder */ $placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAhCAQAAABpC6MWAAAAHUlEQVR42mP8/5+BJMA4qmFUw6iGUQ2jGkY1YAEABt1B4Az9vjkAAAAASUVORK5CYII='; if ($block->isSunglasses($product)) { $placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAASCAQAAADr5dYVAAAAGElEQVR42mP8/5+BJMA4qmFUw6gG8jUAACa8I++mqcMDAAAAAElFTkSuQmCC'; } ?> <?php if ($module == '03') : ?> <?php /* MODULE 03 */ ?> [...] <?php elseif ($module == '07') : ?> <?php /* MODULE 07 */ ?> [...] <?php if ($theme == '01') : ?> [...] <?php else : ?> [...] <?php endif; ?> <?php elseif ($module == '12' && $block->isBackend()) : ?> // module 12 AND admin view ONLY! <img <?= $block->ariaHidden() ? 'aria-hidden="true"' : '' ?> src="<?= (string) $block->getProductImage($product) ?>" alt="<?= (string) $imageAlt ?>" /> <?php elseif ($module == '18-item') : ?> [...] <?php else : ?> <?php /* DEFAULT */ ?> <img <?= $block->ariaHidden() ? 'aria-hidden="true"' : '' ?> class="lazyload lazyautosizes" data-sizes="auto" src="<?= (string) $placeholder; ?>" data-src="<?= (string) $block->getProductImage($product) ?>" alt="<?= (string) $imageAlt ?>" /> <?php if (mb_strlen($displayedName)) : ?> <p class="pm-product-list-name"><?= (string) $displayedName ?></p> <?php endif; ?> <?php endif; ?> <?php endif; ?> |