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
Astuces jquery pour développeurs front-end / intégrateurs html.
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); }, |
Dans mon exemple, j’ai trois containers <div class="payment-method" />
qui sont présents dans le DOM. Chacun de ces containers possède son propre bouton. Je cherche à ajouter un seul nouveau bouton dans chaque container qui n’est pas tout-à-fait un clone de celui qui existe déjà, mais qui en reprend le code HTML enfant.
Problème: avec le code ci-dessous, les trois boutons que j’ajoute sont créés une fois dans chaque container. En d’autres termes, je me retrouve avec quatre boutons (au lieu de deux) dans chaque container.
1 2 3 4 5 6 7 |
$(".payment-method").each(function(e) { let $this = $(this); let placeOrderBtn = $("button.checkout", $this); let placeOrderBtnHtml = placeOrderBtn.html(); $('<div class="primary"><button class="action primary">' + placeOrderBtnHtml + '</button></div>').appendTo('.actions-toolbar', $this); }); |
Solution: le souci provient d’une mauvaise utilisation de $(this)
en combinaison de .each()
. Ci-dessous le code qui me donne le résultat attendu:
1 2 3 4 5 6 7 8 |
$(".payment-method").each(function(index, element) { let $this = $(this); let placeOrderBtn = $this.find("button.checkout"); let placeOrderBtnHtml = placeOrderBtn.html(); let actionsToolbar = $this.find('.actions-toolbar'); $('<div class="primary"><button class="action secondary" type="button">' + placeOrderBtnHtml + '</button></div>').appendTo(actionsToolbar); }); |
Source: jQuery, how to use multiple cached elements
1 2 3 4 5 |
const $_CATEGORY_ICON_MEGAMENU_SECONDARY = $('#megaMenuSecondaryCategoryIcon'), $_CATEGORY_LABEL_MEGAMENU_SECONDARY = $('#megaMenuSecondaryCategoryLabel'), $_CATEGORY_LINK_MEGAMENU_SECONDARY = $('#megaMenuSecondaryCategoryLink'), $_CATEGORY_NAV_MEGAMENU_SECONDARY = $('#megaMenuSecondaryCategoryNav'), $_CATEGORY_IMAGE_MEGAMENU_SECONDARY = $('#megaMenuSecondaryCategoryImage'); |
1 2 3 4 5 6 7 |
console.time('emptyThings'); $_CATEGORY_ICON_MEGAMENU_SECONDARY.empty(); $_CATEGORY_LABEL_MEGAMENU_SECONDARY.empty(); $_CATEGORY_LINK_MEGAMENU_SECONDARY.empty(); $_CATEGORY_NAV_MEGAMENU_SECONDARY.empty(); $_CATEGORY_IMAGE_MEGAMENU_SECONDARY.empty(); console.timeEnd('emptyThings'); |
emptyThings: 1.92041015625 ms
megamenu.js:127 emptyThings: 4.44091796875 ms
megamenu.js:127 emptyThings: 1.739990234375 ms
megamenu.js:127 emptyThings: 2.5419921875 ms
megamenu.js:127 emptyThings: 1.958740234375 ms
megamenu.js:127 emptyThings: 2.581787109375 ms
megamenu.js:127 emptyThings: 2.59912109375 ms
megamenu.js:127 emptyThings: 2.1240234375 ms
megamenu.js:127 emptyThings: 3.0361328125 ms
megamenu.js:127 emptyThings: 3.2548828125 ms
1 2 3 |
console.time('emptyThings'); $([$_CATEGORY_ICON_MEGAMENU_SECONDARY[0], $_CATEGORY_LABEL_MEGAMENU_SECONDARY[0], $_CATEGORY_LINK_MEGAMENU_SECONDARY[0], $_CATEGORY_NAV_MEGAMENU_SECONDARY[0], $_CATEGORY_IMAGE_MEGAMENU_SECONDARY[0]]).empty(); console.timeEnd('emptyThings'); |
emptyThings: 1.552001953125 ms
megamenu.js:131 emptyThings: 4.814697265625 ms
megamenu.js:131 emptyThings: 2.326904296875 ms
megamenu.js:131 emptyThings: 2.762939453125 ms
megamenu.js:131 emptyThings: 3.281982421875 ms
megamenu.js:131 emptyThings: 1.970947265625 ms
megamenu.js:131 emptyThings: 2.184814453125 ms
megamenu.js:131 emptyThings: 3.62109375 ms
megamenu.js:131 emptyThings: 3.26123046875 ms
megamenu.js:131 emptyThings: 3.065673828125 ms
Cette méthode fait écho à tous les posts dans différents blogs et forums qui incitent à utiliser la méthode .unwrap()
de jQuery. La méthode .contents()
récupère absolument tout ce que contient l’élément parent qu’on souhaite supprimer alors qu'.unwrap()
se montre plus sélective, ce qui peut entrainer quelques désagréments (perte de contenu, etc…).
Source: How to remove only the parent element and not its child elements in JavaScript?
1 2 3 4 5 6 7 8 9 |
<div> pre text <div class="remove-just-this"> <p>child foo</p> <p>child bar</p> nested text </div> post text </div> |
1 2 |
var cnt = $(".remove-just-this").contents(); $(".remove-just-this").replaceWith(cnt); |
1 2 3 4 5 6 7 |
<div> pre text <p>child foo</p> <p>child bar</p> nested text post text </div> |
I might have found a reliable fix for this. The problem happens in Chrome because it doesn’t fire a click event after pointerup unlike other browsers such as Firefox. This is because bxslider in onTouchStart function sets slider viewport as target of all succeeding pointer events. Now if you have a link inside a slide when you click the link the target of pointerdown is not slider viewport, but the link content and since this target is different from pointerup target (which is slider viewport), then Chrome doesn’t fire a click. The solution is to make sure pointerup fires on the same element as pointerdown so that Chrome fires a click. So we need to refactor this snippet:
1 2 3 4 |
if (slider.viewport.get(0).setPointerCapture) { slider.pointerId = orig.pointerId; slider.viewport.get(0).setPointerCapture(slider.pointerId); } |
to this:
1 2 3 4 |
if (e.target.setPointerCapture) { slider.pointerId = orig.pointerId; e.target.setPointerCapture(slider.pointerId); } |
This solved the problem for me in Chrome without loss of any functionality in any browser or mobile device.
Source: Can I use multiple versions of jQuery on the same page?. Utiliser de multiples versions de jQuery dans une même page.
1 2 3 4 5 6 7 8 9 10 11 |
<!-- load jQuery 1.1.3 --> <script type="text/javascript" src="http://example.com/jquery-1.1.3.js"></script> <script type="text/javascript"> var jQuery_1_1_3 = $.noConflict(true); </script> <!-- load jQuery 1.3.2 --> <script type="text/javascript" src="http://example.com/jquery-1.3.2.js"></script> <script type="text/javascript"> var jQuery_1_3_2 = $.noConflict(true); </script> |
A l’utilisation, à la place de $('#selector').function();
, vous utiliseriez jQuery_1_3_2('#selector').function();
ou jQuery_1_1_3('#selector').function();
Placer une classe .dontCloseDropdown
sur chaque élément cliquable présent dans le Dropdown qui ne doit pas refermer ce dernier.
1 2 3 |
$('.dontCloseDropdown').on('click', function(e) { e.stopPropagation(); }); |
Sources:
1 |
<input type="checkbox" id="" class"js-selectRow" name="" value="" /> |
1 2 3 4 5 6 7 8 9 10 |
$(document).on('change', '.js-selectRow', function() { const $_THIS = $(this); if ( $_THIS.prop('checked') === true ) { // if is checked do sth... $_THIS.closest('tr').addClass('row-isSelected'); } else if ( $_THIS.prop('checked') === false ) { // if is not checked do sth else... $_THIS.closest('tr').removeClass('row-isSelected'); } }); |
Dans cet exemple, on vide tous les champs de formulaire enfant de l’élément qui a la classe .form-group.hidden
1 2 3 4 5 6 7 8 9 |
$.fn.clearHiddenControls = function(){ // https://stackoverflow.com/questions/6364289/clear-form-fields-with-jquery/16105303 $('.form-group.hidden', $_USER_ASSISTANCE_FORM).each(function(){ const $_THIS = $(this); $_THIS.find('input[type="radio"], input[type="checkbox"]').prop('checked', false); $_THIS.find('option').prop('selected', false); $_THIS.find('input[type="file"], textarea').val(''); }); } |
1 |
$(document).clearHiddenControls(); |