Skip to content

Commit 3ea19c1

Browse files
committed
bug #2399 [LiveComponent] Refactor elementBelongsToThisComponent (smnandre)
This PR was merged into the 2.x branch. Discussion ---------- [LiveComponent] Refactor elementBelongsToThisComponent | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Issues | Fix #2388 | License | MIT This method is called from multiple places, sometimes before children component are instanciated and stored in the component map. So let's back to the quickest selector we have: the browser DOM selector :) We'll know tomorrow, but it _could_ solve #2388 Commits ------- 7f6aa8f [LiveComponent] Refactor elementBelongsToThisComponent
2 parents 9219cdf + 7f6aa8f commit 3ea19c1

File tree

3 files changed

+90
-102
lines changed

3 files changed

+90
-102
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 78 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -131,82 +131,6 @@ function getElementAsTagText(element) {
131131
: element.outerHTML;
132132
}
133133

134-
let componentMapByElement = new WeakMap();
135-
let componentMapByComponent = new Map();
136-
const registerComponent = (component) => {
137-
componentMapByElement.set(component.element, component);
138-
componentMapByComponent.set(component, component.name);
139-
};
140-
const unregisterComponent = (component) => {
141-
componentMapByElement.delete(component.element);
142-
componentMapByComponent.delete(component);
143-
};
144-
const getComponent = (element) => new Promise((resolve, reject) => {
145-
let count = 0;
146-
const maxCount = 10;
147-
const interval = setInterval(() => {
148-
const component = componentMapByElement.get(element);
149-
if (component) {
150-
clearInterval(interval);
151-
resolve(component);
152-
}
153-
count++;
154-
if (count > maxCount) {
155-
clearInterval(interval);
156-
reject(new Error(`Component not found for element ${getElementAsTagText(element)}`));
157-
}
158-
}, 5);
159-
});
160-
const findComponents = (currentComponent, onlyParents, onlyMatchName) => {
161-
const components = [];
162-
componentMapByComponent.forEach((componentName, component) => {
163-
if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) {
164-
return;
165-
}
166-
if (onlyMatchName && componentName !== onlyMatchName) {
167-
return;
168-
}
169-
components.push(component);
170-
});
171-
return components;
172-
};
173-
const findChildren = (currentComponent) => {
174-
const children = [];
175-
componentMapByComponent.forEach((componentName, component) => {
176-
if (currentComponent === component) {
177-
return;
178-
}
179-
if (!currentComponent.element.contains(component.element)) {
180-
return;
181-
}
182-
let foundChildComponent = false;
183-
componentMapByComponent.forEach((childComponentName, childComponent) => {
184-
if (foundChildComponent) {
185-
return;
186-
}
187-
if (childComponent === component) {
188-
return;
189-
}
190-
if (childComponent.element.contains(component.element)) {
191-
foundChildComponent = true;
192-
}
193-
});
194-
children.push(component);
195-
});
196-
return children;
197-
};
198-
const findParent = (currentComponent) => {
199-
let parentElement = currentComponent.element.parentElement;
200-
while (parentElement) {
201-
const component = componentMapByElement.get(parentElement);
202-
if (component) {
203-
return component;
204-
}
205-
parentElement = parentElement.parentElement;
206-
}
207-
return null;
208-
};
209-
210134
function getValueFromElement(element, valueStore) {
211135
if (element instanceof HTMLInputElement) {
212136
if (element.type === 'checkbox') {
@@ -320,16 +244,8 @@ function elementBelongsToThisComponent(element, component) {
320244
if (!component.element.contains(element)) {
321245
return false;
322246
}
323-
let foundChildComponent = false;
324-
findChildren(component).forEach((childComponent) => {
325-
if (foundChildComponent) {
326-
return;
327-
}
328-
if (childComponent.element === element || childComponent.element.contains(element)) {
329-
foundChildComponent = true;
330-
}
331-
});
332-
return !foundChildComponent;
247+
const closestLiveComponent = element.closest('[data-controller~="live"]');
248+
return closestLiveComponent === component.element;
333249
}
334250
function cloneHTMLElement(element) {
335251
const newElement = element.cloneNode(true);
@@ -1875,6 +1791,82 @@ class ExternalMutationTracker {
18751791
}
18761792
}
18771793

1794+
let componentMapByElement = new WeakMap();
1795+
let componentMapByComponent = new Map();
1796+
const registerComponent = (component) => {
1797+
componentMapByElement.set(component.element, component);
1798+
componentMapByComponent.set(component, component.name);
1799+
};
1800+
const unregisterComponent = (component) => {
1801+
componentMapByElement.delete(component.element);
1802+
componentMapByComponent.delete(component);
1803+
};
1804+
const getComponent = (element) => new Promise((resolve, reject) => {
1805+
let count = 0;
1806+
const maxCount = 10;
1807+
const interval = setInterval(() => {
1808+
const component = componentMapByElement.get(element);
1809+
if (component) {
1810+
clearInterval(interval);
1811+
resolve(component);
1812+
}
1813+
count++;
1814+
if (count > maxCount) {
1815+
clearInterval(interval);
1816+
reject(new Error(`Component not found for element ${getElementAsTagText(element)}`));
1817+
}
1818+
}, 5);
1819+
});
1820+
const findComponents = (currentComponent, onlyParents, onlyMatchName) => {
1821+
const components = [];
1822+
componentMapByComponent.forEach((componentName, component) => {
1823+
if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) {
1824+
return;
1825+
}
1826+
if (onlyMatchName && componentName !== onlyMatchName) {
1827+
return;
1828+
}
1829+
components.push(component);
1830+
});
1831+
return components;
1832+
};
1833+
const findChildren = (currentComponent) => {
1834+
const children = [];
1835+
componentMapByComponent.forEach((componentName, component) => {
1836+
if (currentComponent === component) {
1837+
return;
1838+
}
1839+
if (!currentComponent.element.contains(component.element)) {
1840+
return;
1841+
}
1842+
let foundChildComponent = false;
1843+
componentMapByComponent.forEach((childComponentName, childComponent) => {
1844+
if (foundChildComponent) {
1845+
return;
1846+
}
1847+
if (childComponent === component) {
1848+
return;
1849+
}
1850+
if (childComponent.element.contains(component.element)) {
1851+
foundChildComponent = true;
1852+
}
1853+
});
1854+
children.push(component);
1855+
});
1856+
return children;
1857+
};
1858+
const findParent = (currentComponent) => {
1859+
let parentElement = currentComponent.element.parentElement;
1860+
while (parentElement) {
1861+
const component = componentMapByElement.get(parentElement);
1862+
if (component) {
1863+
return component;
1864+
}
1865+
parentElement = parentElement.parentElement;
1866+
}
1867+
return null;
1868+
};
1869+
18781870
class Component {
18791871
constructor(element, name, props, listeners, id, backend, elementDriver) {
18801872
this.fingerprint = '';

src/LiveComponent/assets/src/dom_utils.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type ValueStore from './Component/ValueStore';
22
import { type Directive, parseDirectives } from './Directive/directives_parser';
33
import { normalizeModelName } from './string_utils';
44
import type Component from './Component';
5-
import { findChildren } from './ComponentRegistry';
65
import getElementAsTagText from './Util/getElementAsTagText';
76

87
/**
@@ -200,19 +199,9 @@ export function elementBelongsToThisComponent(element: Element, component: Compo
200199
return false;
201200
}
202201

203-
let foundChildComponent = false;
204-
findChildren(component).forEach((childComponent) => {
205-
if (foundChildComponent) {
206-
// return early
207-
return;
208-
}
209-
210-
if (childComponent.element === element || childComponent.element.contains(element)) {
211-
foundChildComponent = true;
212-
}
213-
});
202+
const closestLiveComponent = element.closest('[data-controller~="live"]');
214203

215-
return !foundChildComponent;
204+
return closestLiveComponent === component.element;
216205
}
217206

218207
export function cloneHTMLElement(element: HTMLElement): HTMLElement {

src/LiveComponent/assets/test/dom_utils.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,19 @@ describe('elementBelongsToThisComponent', () => {
271271
expect(elementBelongsToThisComponent(targetElement, component)).toBeFalsy();
272272
});
273273

274-
it('returns true if element lives inside of controller', () => {
275-
const targetElement = htmlToElement('<input name="user[firstName]">');
274+
it('returns true if element lives inside of a div', () => {
275+
const targetElement = htmlToElement('<input name="user[firstName]"/>');
276276
const component = createComponent('<div></div>');
277277
component.element.appendChild(targetElement);
278278

279+
expect(elementBelongsToThisComponent(targetElement, component)).toBeFalsy();
280+
});
281+
282+
it('returns true if element lives inside of live controller', () => {
283+
const targetElement = htmlToElement('<input name="user[firstName]"/>');
284+
const component = createComponent('<div data-controller="live" data-live-name-value="parentLabel"></div>');
285+
component.element.appendChild(targetElement);
286+
279287
expect(elementBelongsToThisComponent(targetElement, component)).toBeTruthy();
280288
});
281289

@@ -287,7 +295,6 @@ describe('elementBelongsToThisComponent', () => {
287295
const component = createComponent('<div class="parent"></div>');
288296
component.element.appendChild(childComponent.element);
289297

290-
//expect(elementBelongsToThisComponent(targetElement, childComponent)).toBeTruthy();
291298
expect(elementBelongsToThisComponent(targetElement, component)).toBeFalsy();
292299
});
293300

0 commit comments

Comments
 (0)