Skip to content

Commit 188029c

Browse files
sick-sad-worldCeleste Glavin
authored andcommitted
Event propagation fix for Dropdown component (#255)
* Fixed event propagation for onClick handler and provided correct target for option click handler * Add possibility to update options and markup via [update] method * Test if this fixes possible memory leak * Test if this fixes possible memory leak * Revert "Test if this fixes possible memory leak" This reverts commit 0265e4e. * Template renderer as method. Add [update] method testing btn for "Outside controls" story * Fix documentation for error stated Dropdowns * Add options to [update] test
1 parent 9ae19cf commit 188029c

File tree

5 files changed

+75
-32
lines changed

5 files changed

+75
-32
lines changed

docs/html/dropdown/dropdown--error.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
<option value="Squirtle">Squirtle</option>
66
<option value="Squirtle">Charmander</option>
77
</select>
8+
<label class="ray-dropdown__label">
9+
What's your favorite Pokémon?
10+
</label>
811
</div>
9-
<label class="ray-dropdown__label">
10-
What's your favorite Pokémon?
11-
</label>
12-
<span class="ray-dropdown__hint">Is anything wrong?</span>
1312
</div>
13+
<span class="ray-dropdown__hint">Is anything wrong?</span>

docs/html/dropdown/rtl-dropdown--error.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
<label class="ray-dropdown__label">
1111
What's your favorite Pokémon?
1212
</label>
13-
<span class="ray-dropdown__hint">Is anything wrong?</span>
1413
</div>
15-
</div>
14+
<span class="ray-dropdown__hint">Is anything wrong?</span>
15+
</div>

packages/core/src/components/dropdown/index.js

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -136,21 +136,11 @@ class Dropdown {
136136
this._id = id;
137137
this._inputElement = el;
138138

139-
markupTemplates.forEach(template => {
140-
const { tpl, elements, position } = template({
141-
value: this._selectedOption.innerHTML,
142-
id: this._id
143-
});
144-
insertMarkup(this._inputElement, position, tpl);
145-
this._cacheEl(elements);
146-
});
139+
this._renderMarkup();
147140
this._processLabel();
148141
this._setAriaProps();
149142
this._fillOptionsList();
150-
151-
if (!this._inputElement.disabled) {
152-
this._bindEventListeners();
153-
}
143+
this._bindEventListeners();
154144

155145
this._value = this._inputElement.value;
156146

@@ -170,6 +160,17 @@ class Dropdown {
170160
});
171161
}
172162

163+
_renderMarkup() {
164+
markupTemplates.forEach(template => {
165+
const { tpl, elements, position } = template({
166+
value: this._selectedOption.innerHTML,
167+
id: this._id
168+
});
169+
insertMarkup(this._inputElement, position, tpl);
170+
this._cacheEl(elements);
171+
});
172+
}
173+
173174
_setAriaProps() {
174175
const listId = this._list.getAttribute('id');
175176

@@ -264,12 +265,13 @@ class Dropdown {
264265
}
265266

266267
_bindEventListeners() {
268+
if (this._inputElement.disabled) return;
267269
window.addEventListener('click', this.onClick);
268270
this._body.addEventListener('focus', this.onFocus);
269271
this._body.addEventListener('blur', this.onFocus);
270272
this._inputElement.addEventListener('change', this.onChange);
271273
this._getEl('option', true).forEach(el => {
272-
el.addEventListener('click', this.onOptionClick);
274+
el.addEventListener('click', this.onOptionClick(this));
273275
});
274276
}
275277

@@ -308,8 +310,16 @@ class Dropdown {
308310
switchClassName(this._root, ['ACTIVE', 'OPEN'], true);
309311
};
310312

311-
update = () => {
313+
update = options => {
314+
if (options) {
315+
this.settings = {
316+
...defaults,
317+
...options
318+
};
319+
}
320+
this._removeEventListeners();
312321
this._fillOptionsList();
322+
this._bindEventListeners();
313323
this._value = '';
314324
};
315325

@@ -330,25 +340,22 @@ class Dropdown {
330340
};
331341

332342
onClick = e => {
333-
e.stopPropagation();
334-
e.preventDefault();
335343
const isClickInside =
336344
this._root === e.target || this._root.contains(e.target);
337-
switchClassName(
338-
this._root,
339-
'OPEN',
340-
isClickInside && !e.target.dataset.rayIdx
341-
);
345+
const isOptionClick = this._list.contains(e.target);
346+
switchClassName(this._root, 'OPEN', isClickInside && !isOptionClick);
342347
if (!isClickInside) {
343348
switchClassName(this._root, 'ACTIVE', false);
344349
emitEvent(this._root, 'blur');
345350
}
346351
};
347352

348-
onOptionClick = e => {
349-
if (e.target.hasAttribute('disabled') || !e.target.dataset.rayIdx) return;
350-
this._value = this._options[e.target.dataset.rayIdx].value;
351-
};
353+
onOptionClick(plugin) {
354+
return function onClickListener() {
355+
if (this.hasAttribute('disabled') || !this.dataset.rayIdx) return;
356+
plugin._value = plugin._options[this.dataset.rayIdx].value; //eslint-disable-line
357+
};
358+
}
352359

353360
destroy() {
354361
this._removeEventListeners();

packages/core/stories/dropdown.stories.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,30 @@ storiesOf('Dropdown', module)
651651
const inst = getInst();
652652
inst.disable();
653653
}
654+
function updateDrop(e) {
655+
e.preventDefault();
656+
const input = document.getElementById('test-example');
657+
const label = document.getElementById('test-example-label');
658+
const inst = getInst();
659+
label.innerHTML = 'Currency';
660+
input.innerHTML = `
661+
<option value="USD">USD|United states dollar</option>
662+
<option disabled data-ray-separator />
663+
<option value="GBP">GBP|Great Britain Pound</option>
664+
<option value="EUR">EUR|Euro</option>
665+
<option value="JPY">JPY|Japaneese Yen</option>
666+
`;
667+
inst.update({
668+
renderSelected: option => {
669+
const [symbol] = option.innerHTML.split('|');
670+
return `<b>${symbol}</b>`;
671+
},
672+
renderOption: option => {
673+
const [symbol, name] = option.innerHTML.split('|');
674+
return `<b>${symbol}</b><em>${name}</em>`;
675+
}
676+
});
677+
}
654678
return (
655679
<>
656680
<div className="ray-form-item">
@@ -710,6 +734,12 @@ storiesOf('Dropdown', module)
710734
>
711735
Disable
712736
</button>
737+
<button
738+
className="ray-button ray-button--primary ray-button--compact"
739+
onClick={updateDrop}
740+
>
741+
Update
742+
</button>
713743
</div>
714744
</>
715745
);

packages/core/test/components/dropdown.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,14 @@ describe('Select', () => {
128128
test('#update should reset value and update option list e.g. adopt chagnges to original select', () => {
129129
const { select, selectEl } = setupTest();
130130
selectEl.querySelector('select').innerHTML = newOptionsFixture;
131-
select.update();
131+
select.update({
132+
renderOption: option => {
133+
const [symbol, name] = option.innerHTML.split('|');
134+
return `<b class="this-is-option">${symbol}</b><em>${name}</em>`;
135+
}
136+
});
132137
expect(select.value()).toEqual('');
138+
expect(selectEl.querySelector('.this-is-option')).toBeTruthy();
133139
select.destroy();
134140
});
135141

0 commit comments

Comments
 (0)