Attention: pas testé (pas eu besoin au final…), mais la méthode me parait propre en regard des bonnes pratiques de coding de Magento 2.
Source: How to access custom attributes from the current product in JavaScript?.
Le bloc-notes professionnel d'un développeur front-end senior
Attention: pas testé (pas eu besoin au final…), mais la méthode me parait propre en regard des bonnes pratiques de coding de Magento 2.
Source: How to access custom attributes from the current product in JavaScript?.
Objectif: créer un widget Toggle flexible dont le rôle sera uniquement d’affiche/masquer des éléments du DOM lorsque les utilisateurs interagissent avec un autre élément d’interface (bouton, checkbox, autre…).
Source: Marking elements expandable using aria-expanded. Exemple d’utilisation sur l’élément HTML Button. Exemple d’utilisation sur l’élément HTML Checkbox.
Source: https://www.accessibility-developer-guide.com/.
1 2 3 4 5 6 7 |
<p> Please click the following checkbox. </p> <input id="checkbox" type="checkbox" /><label for="checkbox">Show tooltip</label> <div hidden="" id="tooltip"> Thanks for toggling! Please click again to hide me. </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
;(function () { $(document).ready(function () { var $checkbox, $tooltip $checkbox = $('input') $tooltip = $('#tooltip') return $checkbox.change(() => { if ($tooltip.attr('hidden') === 'hidden') { return $tooltip.removeAttr('hidden') } else { return $tooltip.attr('hidden', true) } }) }) }.call(this)) |
wcag-toggle.js
:
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 |
// Source: https://www.accessibility-developer-guide.com/examples/sensible-aria-usage/expanded/ define([ 'jquery', 'jquery-ui-modules/widget' ], function($) { 'use strict'; $.widget('mage.wcagToggle', { options: { toggleableElement: '' }, /** * Create widget * @private */ _create: function() { // console.log("%c wcagToggle", "background: yellow"); this.element.on('click', $.proxy(this._toggleOnOff, this)); // ...is similar to: // this.element.on('click', this._toggleOnOff.bind(this)); }, /** * Toggle On/Off * @private */ _toggleOnOff: function() { if ($(this.options.toggleableElement).attr('hidden') === 'hidden') { $(this.options.toggleableElement).removeAttr('hidden'); this.element.attr('aria-expanded', true); } else { $(this.options.toggleableElement).attr('hidden', true); this.element.attr('aria-expanded', false); } } }); return $.mage.wcagToggle; }); |
PHTML
:Partie DOM (attention, je mets juste les grandes lignes). Se référer à l’example de code proposé par le site source.
1 2 3 4 5 6 7 |
<div class="field choice newsletter"> <input type="checkbox" id="is_subscribed" /> <label for="is_subscribed" class="label"></label> </div> <div id="is_subscribed_is_checked" hidden=""> </div> |
1 2 3 4 5 6 7 8 9 |
<script type="text/x-magento-init"> { "#is_subscribed": { "wcagToggle": { "toggleableElement": "#is_subscribed_is_checked" } } } </script> |
requirejs-config.js
:
1 2 3 4 5 6 7 |
var config = { map: { '*': { wcagToggle: 'Magento_Theme/js/widgets/wcag-toggle' } } }; |
Testé fonctionnel Webpack Encore v1.1.2 (tournant sous Webpack v5.34.0).
Source: blog de PHP Roberto – Integrating Purgecss with Symfony Encore (version PDF: phproberto.com-Integrating Purgecss with Symfony Encore).
Quelques ressources en ligne:
Oui, le tree shaking est activé par défaut en mode « production » dans Webpack Encore. C’est juste que la documentation officielle sur symfony.com oublie de le mentionner (grrr…)!
Vous pouvez effectuer le test qui est décrit ici (aka « le tree shaking dans Webpack Encore expliqué en 30 secondes ») pour vous en assurer.
Pour exemple, si vous avez les deux fichiers suivants:
1 2 3 4 5 6 7 8 |
// src/imports.js export function testA() { console.log('Test A'); } export function testB() { console.log('Test B'); } |
1 2 3 4 |
// src/index.js (entry point) import { testA } from './imports'; testA(); |
Exécuter la commande yarn encore dev
devrait générer quelque chose de similaire à ceci:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/***/ "./src/imports.js": /*!************************!*\ !*** ./src/imports.js ***! \************************/ /*! exports provided: testA, testB */ /*! exports used: testA */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (immutable) */ __webpack_exports__["a"] = testA; /* unused harmony export testB */ function testA() { console.log('Test A'); } function testB() { console.log('Test B'); } /***/ }), |
Cependant, en exécutant la commande yarn encore production
le commentaire /* unused harmony export testB */
présent avec la précédente commande sera pris en compte par UglifyJs qui va en conséquence supprimer l’export en relation lors de la phase de minification. Dans ce cas, on ne devrait pas retrouver la chaîne de caractère « Test B » dans le fichier généré:
1 |
!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/",t(t.s="lVK7")}({dxJD:function(e,t,n){"use strict";function r(){console.log("Test A")}t.a=r},lVK7:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n("dxJD");Object(r.a)()}}); |
/* unused harmony export */
en développement avec le paramètre config.optimization.usedExports = true;
Ceci vous permettra de vérifier avec du code humainement lisible quels sont les parties de votre code qui ne sont pas exploitées et qui seront supprimées dans le bundle minifié de production.
Tout à la fin de votre fichier webpack.config.js
:
1 2 3 4 5 6 7 8 9 10 11 12 |
[...] ; // fetch the config, then modify it! const config = Encore.getWebpackConfig(); config.optimization.usedExports = true; // export the final config module.exports = config; // was: // module.exports = Encore.getWebpackConfig(); |
Vous pouvez tout-à-fait laisser cette config activée. Elle ne portera pas préjudice au poids final de votre build de production.
Note: vous pouvez vous référer à l’article: [Webpack Encore] Utiliser la méthode addAliases pour résoudre les problèmes de sous-imports de fichiers sass/scss à l’utilisation de frameworks comme UiKit ou Foundation 6 pour résoudre la même question côté CSS.
Sources:
Utiliser la méthode addAliases()
de Webpack Encore pour créer un alias vers le chemin de référence du fichier non trouvé dans node_modules. Dans le fichier webpack.config.js:
1 2 3 4 5 6 7 8 9 10 |
const path = require('path'); [...] Encore .addAliases({ 'uikit-util': path.resolve(__dirname, './node_modules/uikit/src/js/util') }) [...] |
Dans le fichier webpack.config.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const Encore = require('@symfony/webpack-encore'); Encore // Ici, toute la config Webpack Encore... ; // fetch the config, then modify it! const config = Encore.getWebpackConfig(); // add an extension config.resolve.modules = ['assets', 'node_modules']; // console.log(config); // export the final config module.exports = config; // was: module.exports = Encore.getWebpackConfig(); |
Ici, Webpack va d’abord chercher le fichier JS dans le dossier assets. Si il ne le trouve pas, il va se rabattre sur le dossier node_modules.
Ajouter un console.log(config);
vous permettra de visualiser la config au format JSON dans votre console.
Dans le fichier JS qui sert de point d’entrée à Webpack (dans mon exemple assets/app.js):
1 2 3 4 5 |
// UIkit import UIkit from './uikit'; // components can be called from the imported UIkit reference UIkit.notification('Hello world.'); |
Dans le fichier JS qui sert à importer uniquement les modules voulus (dans mon exemple assets/uikit.js):
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 |
import UIkit from 'uikit/dist/js/uikit-core'; // import Countdown from 'uikit/dist/js/components/countdown'; // import Filter from 'uikit/dist/js/components/filter'; // import Lightbox from 'uikit/dist/js/components/lightbox'; // import LightboxPanel from 'uikit/dist/js/components/lightbox-panel'; import Notification from 'uikit/dist/js/components/notification'; // import Parallax from 'uikit/dist/js/components/parallax'; // import Slider from 'uikit/dist/js/components/slider'; // import SliderParallax from 'uikit/dist/js/components/slider-parallax'; // import Slideshow from 'uikit/dist/js/components/slideshow'; // import Sortable from 'uikit/dist/js/components/sortable'; import Tooltip from 'uikit/dist/js/components/tooltip'; // import Upload from 'uikit/dist/js/components/upload'; import Icons from 'uikit/dist/js/uikit-icons'; UIkit.use(Icons); // UIkit.component('countdown', Countdown); // UIkit.component('filter', Filter); // new // UIkit.component('gridParallax', GridParallax); // UIkit.component('lightbox', Lightbox); // UIkit.component('lightboxPanel', LightboxPanel); // new UIkit.component('notification', Notification); // UIkit.component('parallax', Parallax); // UIkit.component('slider', Slider); // new // UIkit.component('sliderParallax', SliderParallax); // new // UIkit.component('slideshow', Slideshow); // UIkit.component('sortable', Sortable); UIkit.component('tooltip', Tooltip); // UIkit.component('upload', Upload); export default UIkit; |
Ici, j’importe uniquement les modules notification
, tooltip
et uikit-icons
. Le fichier généré par webpack pour le front ne contiendra pas le code des autres modules du framework UIkit.
Testé fonctionnel Magento 2.4. Source: Extending Magento 2 default JS components. Version PDF – inchoo.net-Extending Magento 2 default JS components.
app/design/frontend/MyVendor/mytheme/web/js/tabs-custom.js
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 |
define([ 'jquery', 'jquery-ui-modules/widget', 'jquery-ui-modules/core', 'mage/mage', 'mage/collapsible'], function($){ $.widget('custom.tabs', $.mage.tabs, { options: { indicator: false }, _create: function () { // IF NO options ARE POSSIBLE: // if ( $(".items.has-indicator").length > 0 ){ // this._addIndicator(); // } if (this.options.indicator) { this._addIndicator(); } }, _addIndicator: function () { var $menu = $(".items.has-indicator"), indicator = $('<span class="indicator"></span>'); $menu.append(indicator); position_indicator($menu.find("li.current")); setTimeout(function(){indicator.css("opacity", 1);}, 500); $menu.find("li").mouseenter(function(){ position_indicator($(this)); }); $menu.find("li").mouseleave(function(){ position_indicator($menu.find("li.current")); }); $(window).on('resize', function(){ position_indicator($menu.find("li.current")); }); function position_indicator(ele){ var left = ele.position().left, width = ele.outerWidth(), top = ele.position().top, height = ele.outerHeight(); if(window.matchMedia("(max-width:890px)").matches){ indicator.stop().animate({ top: top, height: height, width: 3, left: 0 }); } else{ indicator.stop().animate({ left: left, width: width, height: 3, top: "100%" }); } } } }); return $.custom.tabs; }); |
app/design/frontend/MyVendor/mytheme/requirejs-config.js
1 2 3 4 5 6 7 |
var config = { map: { '*' : { 'tabs': 'js/tabs-custom' } } }; |
app/design/frontend/MyVendor/mytheme/Magento_Theme/templates/html/sections.phtml
1 2 |
<div class="section-items <?= $block->escapeHtmlAttr($groupCss) ?>-items" data-mage-init='{"tabs":{"openedState":"active","indicator":"true"}}'> |
app/design/frontend/MyVendor/mytheme/web/css/source/tabs.less
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 |
@tab-item-height: 50px; @tab-item-border-width: 3px; .items:not(.nav) { border-bottom: none 0; display: flex; .nav.item { margin: 0; display: flex; > a, &.current > strong { padding: 0 @tab-item-height / 2.25; position: relative; display: flex; align-items: center; } &.current > strong { font-weight: 500; &:hover { color: @red; } } } &.has-indicator { position: relative; .indicator{ left: 0; height: 3px; position: absolute; bottom: 0; width: 0; opacity: 0; background: @red; overflow: visible !important; &:before{ content:""; position: absolute; bottom: 0; background-color: @red; } } } } // // Mobile // _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .items:not(.nav) { flex-direction: column; background: linear-gradient(90deg, @greyLight 0%, @greyLight @tab-item-border-width, transparent @tab-item-border-width, transparent 100%); .nav.item { height: @tab-item-height; } &.has-indicator { .indicator { &:before{ width: 13px; height: 2px; left: 0; top: 50%; transform: translateY(-50%) translateX(0); } } } } } // // Desktop // _____________________________________________ .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { .items:not(.nav) { flex-direction: row; background: linear-gradient(180deg, transparent 0%, transparent @tab-item-height, @greyLight @tab-item-height, @greyLight 100%); .nav.item { // total height: @tab-item-height + @tab-item-border-width; height: @tab-item-height + @tab-item-border-width; } &.has-indicator { .indicator { transform: translateY(-3px); &:before{ width: 2px; height: 13px; left: 50%; transform: translateX(-50%); } } } } } |
app/design/frontend/MyVendor/mytheme/Magento_Sales/layout/sales_order_info_links.xml et app/design/frontend/MyVendor/mytheme/Magento_Sales/layout/sales_order_guest_info_links.xml
1 2 3 4 5 6 |
<body> <referenceContainer name="content"> <block class="Magento\Framework\View\Element\Html\Links" as="links" name="sales.order.info.links" before="-"> <arguments> <argument name="css_class" xsi:type="string">items order-links has-indicator</argument> </arguments> |
Etendre une extension de widget existante dans Magento 2 n’est pas possible. Il faut obligatoirement surcharger l’extension existante en déclarant l’extension initiale à false
dans le fichier requirejs-config.js
du module (pouvant être différent) qui contient la nouvelle extension:
1 2 3 4 5 6 7 8 9 10 |
var config = { config: { mixins: { 'Magento_Checkout/js/view/shipping': { 'Vendor_Module/js/new-widgetextension': true, 'Vendor_Module/js/alreadyexisting-widgetextension': false } } } }; |
Notes:
app/code/...
) plutôt que dans un thème.Source: How to include and use custom js file with require js in magento2.
Source: Four Ways to Add JavaScript to Magento 2.
Se référer à la documentation officielle de Magento 2 pour voir comment créer un nouveau module. Dans mon exemple, le vendor s’appelle Naked
et le module s’appelle HelloWorld
.
Dans app/code/Naked/HelloWorld/
créer une arborescence de dossiers et les trois fichiers suivants:
view/frontend/requirejs-config.js
view/frontend/web/js/hello-world.js
view/frontend/web/js/methods.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var config = { // You can also set the map property and define the prefixes for various modules. // In future, we can use these prefixes to connect the required JS modules in other modules. map: { '*': { helloWorld: 'Naked_HelloWorld/js/hello-world', methods: 'Naked_HelloWorld/js/methods', } }, // In this case, the scripts we have connected will work on all pages of the module. // Usually, some third-party libraries are connected in this way. deps: [ 'Naked_HelloWorld/js/hello-world', ] }; |
Les scripts déclarés sous la variable deps
vont fonctionner immédiatement et sur toutes les pages du site. Inutile d’initialiser quoi que ce soit dans un fichier *.phtml
.
1 2 3 4 5 6 7 8 9 10 |
define(['jquery', 'methods'], function($, methods) { "use strict"; console.log('hello world!'); $('a.logo').on('click', function(e){ e.preventDefault(); methods.showAlert('Logo clicked!'); }); }); |
1 2 3 4 5 6 7 8 9 10 11 |
define(['jquery'], function($) { var methods = {}; methods.showAlert = function(arg) { alert(arg); } return methods; }); |
Article de lullabot.com: Importing CSS Breakpoints Into JavaScript. Demo Codepen.