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?.
Exemple de widget complet
$(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; }); |
Autres manières d’utiliser des événements:
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); } |
Cas d’utilisation de .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); }, |
Cas avec l’utilisation de matchMedia/mediaCheck
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) }); |
Utiliser la méthode .bindAll
de Underscore
La 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); }, |