Source: how to iterate elements in reverse order in jquery? | JQuery .each() backwards.
1 2 3 |
$.fn.reverse = [].reverse; $('li').reverse(); |
Le bloc-notes professionnel d'un développeur front-end senior
Source: how to iterate elements in reverse order in jquery? | JQuery .each() backwards.
1 2 3 |
$.fn.reverse = [].reverse; $('li').reverse(); |
Ce post sur stack overflow qui explique très bien le problème et donne des solutions: How to handle events in jQuery UI widgets.
Cet autre post sur stack overflow qui explique comment solutionner le problème à l’aide de la méthode .bind()
: Pass correct « this » context to setTimeout callback?.
$(document).on('click', <DOM element>, <JS function>.bind(this));
et setTimeout(function () { ... }.bind(this), 250);
L’objet this
passé dans l’événement .on('click', ...)
par l’intermédiaire de la méthode .bind()
est bien celui du widget et pas la cible du click (récupérable au besoin dans la fonction _handleNewOptionSelection
via un e.target
.
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 |
define([ 'jquery', 'jquery-ui-modules/widget' ], function($) { 'use strict'; $.widget('mage.configurableProduct', { options: { attributeCodeArr: [], }, /** * @private */ _create: function () { this.productOptionsWrapperElem = $('#product-options-wrapper'); this.anySwatchOptionElem = $('.swatch-option', this.productOptionsWrapperElem); this._initialize(); }, _initialize: function() { this._setKlarnaBannerVisibility(this.options.attributeCodeArr); $(document).on('click', this.anySwatchOptionElem, this._handleNewOptionSelection.bind(this)); }, _setKlarnaBannerVisibility: function (attributeCode) { for (var i = 0; i < attributeCode.length; i++) { if (this._selectedSwatchOptionIsOutOfStock(attributeCode[i])) { this.element.attr('aria-hidden', true); break; } else { this.element.attr('aria-hidden', false); } } }, _selectedSwatchOptionIsOutOfStock: function (attributeCode) { var swatchAttributeElem = $('.swatch-attribute[data-attribute-code="' + attributeCode + '"]', this.productOptionsWrapperElem); return $('.swatch-option.selected.out-of-stock', swatchAttributeElem).length != 0 ? true : false; }, _handleNewOptionSelection: function() { setTimeout(function () { this._setKlarnaBannerVisibility(this.options.attributeCodeArr) }.bind(this), 250); } }); return $.mage.configurableProduct; }); |
1 2 3 4 5 6 |
this.searchLabel.on('click', function (e) { // allow input to lose its' focus when clicking on label if (this.isExpandable && this.isActive()) { e.preventDefault(); } }.bind(this)); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
this.element.on('focus', this.setActiveState.bind(this, true)); // Fonction de référence: /** * Sets state of the search field to provided value. * * @param {Boolean} isActive */ setActiveState: function (isActive) { this.searchLabel.toggleClass('active', isActive); if (this.isExpandable) { this.element.attr('aria-expanded', isActive); } if(isActive) { this.autoComplete.show(); } else { this.autoComplete.hide(); } }, |
ATTENTION: dans l’exemple ci-dessus, la présence du paramètre true
dans this.element.on('focus', this.setActiveState.bind(this, true));
peut dans certains cas faire que la fonction cible ne soit pas exécutée!
A ce moment, essayer ceci:
1 2 3 4 5 |
lpTag.events.bind('lpUnifiedWindow', 'state', this._setEngagementButtonVisibility.bind(this)); [...] _setEngagementButtonVisibility: function(data) { alert(data.state); } |
.bind()
avec setInterval()
:
1 2 3 4 5 6 7 8 9 10 |
_initialize: function() { var checkLpTagVariableAvailability = setInterval(function() { if (lpTag != undefined) { clearInterval(checkLpTagVariableAvailability); this._bindLpTagEvents(); this._handleFiltersMobileBtnVisibility(); } }.bind(this), 1000); }, |
Notez la présence de .bind(this)
après chaque fonction (entry, exit
).
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', "matchMedia", 'jquery-ui-modules/widget' ], function ($, mediaCheck) { 'use strict'; $.widget('mage.mwSearch', { options: { isExpandable: null }, _create: function () { this.isExpandable = this.options.isExpandable; _.bindAll(this, '_onKeyDown', '_onPropertyChange', '_onSubmit'); mediaCheck({ media: '(max-width: 768px)', entry: function () { this.isExpandable = true; }.bind(this), exit: function () { this.isExpandable = false; }.bind(this) }); |
.bindAll
de UnderscoreLa méthode dans le code source du framework Underscore (version 1.8.2):
1 2 3 4 5 6 7 8 9 10 11 12 |
// Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { var i, length = arguments.length, key; if (length <= 1) throw new Error('bindAll must be passed function names'); for (i = 1; i < length; i++) { key = arguments[i]; obj[key] = _.bind(obj[key], obj); } return obj; }; |
Dans les faits:
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 |
define([ 'jquery', 'underscore', 'jquery-ui-modules/widget' ], function ($, _) { 'use strict'; $.widget('mage.toto', { options: {}, _create: function () { _.bindAll(this, '_onKeyDown', '_onPropertyChange', '_onSubmit'); }, /** * Executes when the search box is submitted. Sets the search input field to the * value of the selected item. * @private * @param {Event} e - The submit event */ _onSubmit: function (e) { var value = this.element.val(); if (isEmpty(value)) { e.preventDefault(); } }, /** * Executes when keys are pressed in the search input field. Performs specific actions * depending on which keys are pressed. * @private * @param {Event} e - The key down event * @return {Boolean} Default return type for any unhandled keys */ _onKeyDown: function (e) { var keyCode = e.keyCode || e.which; switch (keyCode) { case $.ui.keyCode.HOME: ... default: return true; } }, /** * Executes when the value of the search input field changes. Executes a GET request * to populate a suggestion list based on entered text. Handles click (select), hover, * and mouseout events on the populated suggestion list dropdown. * @private */ _onPropertyChange: function () { clearTimeout(this.searchTimer); this.searchTimer = setTimeout($.proxy(this._launchSearch, this), 500); }, |
L’invocation de l’Object Manager dans un template PHTML pour de la récupération d’informations est une mauvaise pratique dans Magento 2. Pourtant, sur le web, bon nombre de solutions données à nos problèmes partent de ce principe…
Dans mon exemple, je souhaitais récupérer depuis une page produit des informations sur les attributs produit associées (leurs attribute_code
à proprement parler). Le template PHTML et le Block PHP desquels je suis parti sont détaillés dans le billet [Magento 2] Etendre un Block pour en hériter et lui ajouter de nouvelles fonctionnalités.
J’ai d’abord procédé comme suggéré ici (How to Get Product Options in Magento 2) en invoquant l’object manager directement dans le template PHTML (mauvaise pratique).
1 2 3 4 |
$_objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $productTypeInstance = $_objectManager->get('Magento\ConfigurableProduct\Model\Product\Type\Configurable'); $productAttributeOptions = $productTypeInstance->getConfigurableAttributesAsArray($product); $attributeCodeArr = array_column($productAttributeOptions, 'attribute_code'); |
Et ça fonctionne: la variable $attributeCodeArr
me remonte bien un tableau avec plusieurs attribute_code
.
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 |
*/ protected \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig; // DEBUT: AJOUT /** * @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable */ protected \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurable; // FIN: AJOUT /** * Product constructor. * * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Locale\Resolver $locale * @param \Magento\Catalog\Helper\Data $productHelper * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig // DEBUT: AJOUT * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurable // FIN: AJOUT * @param \Magento\Framework\Registry $registry * @param array $data */ \Magento\Framework\Locale\Resolver $locale, \Magento\Catalog\Helper\Data $productHelper, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, // DEBUT: AJOUT \Magento\ConfigurableProduct\Model\Product\Type\Configurable $configurable, // FIN: AJOUT \Magento\Framework\Registry $registry, array $data = [] ) $productHelper, $data ); // DEBUT: AJOUT $this->configurable = $configurable; // FIN: AJOUT $this->registry = $registry; } && $isAvailable && $finalPrice <= $finalPriceThreshold; } // DEBUT: AJOUT /** * @param \Magento\Catalog\Model\Product $product * @return array */ public function getConfigurableAttributesAsArray(\Magento\Catalog\Model\Product $product): array { return $this->configurable->getConfigurableAttributesAsArray($product); } // FIN: AJOUT } |
1 2 |
$productAttributeOptions = $block->getConfigurableAttributesAsArray($product); $attributeCodeArr = array_column($productAttributeOptions, 'attribute_code'); |
De la même manière, la variable $attributeCodeArr
me remonte bien un tableau avec plusieurs attribute_code
.
Note: dans un but d’anonymisation, le véritable libellé du vendor a été remplacé par MyVendor
.
Ressource en ligne: Créer un bloc sous magento 2.
app/code/MyVendor/KlarnaOnsitemessaging/registration.php
1 2 3 4 5 6 |
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'MyVendor_KlarnaOnsitemessaging', __DIR__ ); |
app/code/MyVendor/KlarnaOnsitemessaging/etc/module.xml
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="MyVendor_KlarnaOnsitemessaging" > <sequence> <module name="Klarna_Onsitemessaging" /> </sequence> </module> </config> |
app/code/MyVendor/KlarnaOnsitemessaging/etc/di.xml
1 2 3 4 5 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Klarna\Onsitemessaging\Block\Product" type="MyVendor\KlarnaOnsitemessaging\Block\Product"/> </config> |
vendor/klarna/module-onsitemessaging/Block/Product.php
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 |
<?php /** * This file is part of the Klarna Onsitemessaging module * * (c) Klarna Bank AB (publ) * * For the full copyright and license information, please view the NOTICE * and LICENSE files that were distributed with this source code. */ declare(strict_types=1); namespace Klarna\Onsitemessaging\Block; use Magento\Catalog\Helper\Data; use Magento\Framework\Locale\Resolver; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\Template\Context; use Magento\Store\Model\ScopeInterface; /** * @api */ class Product extends Template { /** * @var Data */ private $productHelper; /** * @var Resolver */ private $locale; /** * @param Context $context * @param Resolver $locale * @param Data $productHelper * @param array $data * @codeCoverageIgnore */ public function __construct(Context $context, Resolver $locale, Data $productHelper, array $data = []) { parent::__construct($context, $data); $this->locale = $locale; $this->productHelper = $productHelper; } /** * Check to see if display on product is enabled or not * * @return bool */ public function showOnProduct(): bool { return $this->isSetFlag('klarna/osm/enabled') && $this->isSetFlag('klarna/osm/product_enabled'); } /** * Get the locale according to ISO_3166-1 standard * * @return string */ public function getLocale(): string { return str_replace('_', '-', $this->locale->getLocale()); } /** * Get placement id * * @return string */ public function getPlacementId(): string { $placementId = $this->getValue('klarna/osm/product_placement_select'); if ($placementId && $placementId === 'other') { $placementId = $this->getValue('klarna/osm/product_placement_other'); } return $placementId; } /** * Get theme (default or dark) * * @return string */ public function getTheme(): string { return $this->getValue('klarna/osm/theme'); } /** * Get the amount of the purchase formated as an integer `round(amount * 100)` * * @return int */ public function getPurchaseAmount(): int { $product = $this->productHelper->getProduct(); $price = $product->getFinalPrice($product->getQty()); return (int)round($price * 100); } /** * Wrapper around `$this->_scopeConfig->isSetFlag` that ensures store scope is checked * * @param string $path * @return bool */ private function isSetFlag(string $path): bool { return $this->_scopeConfig->isSetFlag($path, ScopeInterface::SCOPE_STORE, $this->_storeManager->getStore()); } /** * Wrapper around `$this->_scopeConfig->getValue` that ensures store scope is checked * * @param string $path * @return mixed */ private function getValue(string $path) { return $this->_scopeConfig->getValue($path, ScopeInterface::SCOPE_STORE, $this->_storeManager->getStore()); } } |
app/code/MyVendor/KlarnaOnsitemessaging/Block/Product.php
Raison de l’extension: dans la fiche produit, nous avons besoin de cacher la bannière Klarna pour les produits > 10 000 euros ainsi que les produits not salable ou qui ont un stock = 0.
Nous allons pour ce faire créer une fonction publique getProduct()
qui va nous permettre de récupérer tout un tas d’informations sur le produit affiché et notamment celles que nous allons exploiter dans la fonction publique conditionalShowOnProduct()
qui teste que toutes les conditions sont remplies pour afficher la bannière.
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 |
<?php /** * This file is part of the Klarna Onsitemessaging module * * (c) Klarna Bank AB (publ) * * For the full copyright and license information, please view the NOTICE * and LICENSE files that were distributed with this source code. */ declare(strict_types=1); namespace MyVendor\KlarnaOnsitemessaging\Block; /** * @api */ class Product extends \Klarna\Onsitemessaging\Block\Product { const CHECKOUT_PRICES_ABOVE = 'checkout/prices_above'; /** * @var \Magento\Framework\Registry */ protected \Magento\Framework\Registry $registry; /** * @var \Magento\Framework\App\Config\ScopeConfigInterface */ protected \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig; /** * Product constructor. * * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Locale\Resolver $locale * @param \Magento\Catalog\Helper\Data $productHelper * @param \Magento\Framework\Registry $registry * @param array $data */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Framework\Locale\Resolver $locale, \Magento\Catalog\Helper\Data $productHelper, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Registry $registry, array $data = [] ) { parent::__construct( $context, $locale, $productHelper, $data ); $this->registry = $registry; } /** * @return \Magento\Catalog\Model\Product */ public function getProduct(): \Magento\Catalog\Model\Product { return $this->registry->registry('current_product'); } /** * Check to see: * - if display on product is enabled or not * - whether the product type or stock allows to purchase the product (isSalable = true && isInStock = true) * - if product final price < 10000 * * @return bool */ public function conditionalShowOnProduct(): bool { $product = $this->getProduct(); $finalPrice = $product->getFinalPrice(); $finalPriceThreshold = (int) $this->_scopeConfig->getValue( self::CHECKOUT_PRICES_ABOVE, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); $isAvailable = $product->isAvailable(); return $this->showOnProduct() && $isAvailable && $finalPrice <= $finalPriceThreshold; } } |
app/code/MyVendor/KlarnaOnsitemessaging/etc/config.xml
Dans la class Product
que nous venons d’étendre (cf. extension de /Block/Product.php), nous utilisons une config const CHECKOUT_PRICES_ABOVE = 'checkout/prices_above';
que nous définissons dans ce fichier (plutôt qu’en dur).
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> <websites> <de> <checkout> <prices_above>10000</prices_above> </checkout> </de> </websites> </config> |
app/design/frontend/MyTheme/default/Klarna_Onsitemessaging/templates/html/placement/product.phtml
On interroge la fonction conditionalShowOnProduct()
présente dans notre Block étendu pour afficher ou non la bannière Klarna.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php /** @var MyVendor\KlarnaOnsitemessaging\Block\Product $block */ /** @var Magento\Framework\Escaper $escaper */ ?> <?php if ($block->conditionalShowOnProduct()): ?> <!-- Placement v2 --> <div id="klarna-placement"> <klarna-placement data-key="<?= $escaper->escapeHtml($block->getPlacementId()) ?>" data-locale="<?= $escaper->escapeHtml($block->getLocale()) ?>" <?php if ($block->getPurchaseAmount()): ?> data-purchase-amount="<?= $escaper->escapeHtml($block->getPurchaseAmount()) ?>" <?php endif ?> data-theme="<?= $escaper->escapeHtml($block->getTheme()) ?>" ></klarna-placement> </div> <!-- end Placement --> <?php endif ?> |
Ressource: How to loop through array in jQuery?. très bien expliqué avec des exemples spécifiques pour du javasqcript vanilla (ES5, ES6, …) et l’utilisation avec this
.
Dans mon exemple, je cherche simplement à ré-initialiser un carousel Slick Slider dès qu’un nouveau breakpoint est rencontré. Il est important de comprendre que c’est le même code qui est exécuté à chaque fois.
Si vous souhaitez utiliser des paramètres différents en fonction de vos vues (Mobile, Tablet, Desktop) avec Slick Slider, il faut utiliser l’option responsive.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var slickBreakpoints = [ '(max-width: 639px)', '(min-width: 640px) and (max-width: 959px)', '(min-width: 960px)' ]; $.each(slickBreakpoints, function(index, breakpoint) { mediaCheck({ media: breakpoint, entry: function () { msgContainer .slick('unslick') .slick(slickConfig); } }); }); |
…revient à déclarer ceci:
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 |
mediaCheck({ media: '(max-width: 639px)', entry: function () { msgContainer .slick('unslick') .slick(slickConfig); } }); mediaCheck({ media: '(min-width: 640px) and (max-width: 959px)', entry: function () { msgContainer .slick('unslick') .slick(slickConfig); } }); mediaCheck({ media: '(min-width: 960px)', entry: function () { msgContainer .slick('unslick') .slick(slickConfig); } }); |