Skip to content

Commit 69a5db2

Browse files
authored
Merge pull request #40 from mariusandra/master
Cache collected elements and support ">" in selectors
2 parents 86fe168 + 83b202f commit 69a5db2

File tree

2 files changed

+93
-25
lines changed

2 files changed

+93
-25
lines changed

src/querySelectorDeep.js

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
* Another example querySelectorAllDeep('#downloads-list div#title-area + a');
1818
e.g.
1919
*/
20-
export function querySelectorAllDeep(selector, root = document) {
21-
return _querySelectorDeep(selector, true, root);
20+
export function querySelectorAllDeep(selector, root = document, allElements = null) {
21+
return _querySelectorDeep(selector, true, root, allElements);
2222
}
2323

24-
export function querySelectorDeep(selector, root = document) {
25-
return _querySelectorDeep(selector, false, root);
24+
export function querySelectorDeep(selector, root = document, allElements = null) {
25+
return _querySelectorDeep(selector, false, root, allElements);
2626
}
2727

28-
function _querySelectorDeep(selector, findMany, root) {
28+
function _querySelectorDeep(selector, findMany, root, allElements = null) {
2929
selector = normalizeSelector(selector)
3030
let lightElement = root.querySelector(selector);
3131

@@ -48,10 +48,14 @@ function _querySelectorDeep(selector, findMany, root) {
4848
//remove white space at start of selector
4949
.replace(/^\s+/g, '')
5050
.replace(/\s*([>+~]+)\s*/g, '$1'), ' ')
51-
// filter out entry white selectors
52-
.filter((entry) => !!entry);
51+
// filter out entry white selectors
52+
.filter((entry) => !!entry)
53+
// convert "a > b" to ["a", "b"]
54+
.map((entry) => splitByCharacterUnlessQuoted(entry, '>'));
55+
5356
const possibleElementsIndex = splitSelector.length - 1;
54-
const possibleElements = collectAllElementsDeep(splitSelector[possibleElementsIndex], root);
57+
const lastSplitPart = splitSelector[possibleElementsIndex][splitSelector[possibleElementsIndex].length - 1]
58+
const possibleElements = collectAllElementsDeep(lastSplitPart, root, allElements);
5559
const findElements = findMatchingElement(splitSelector, possibleElementsIndex, root);
5660
if (findMany) {
5761
acc = acc.concat(possibleElements.filter(findElements));
@@ -79,7 +83,23 @@ function findMatchingElement(splitSelector, possibleElementsIndex, root) {
7983
let parent = element;
8084
let foundElement = false;
8185
while (parent && !isDocumentNode(parent)) {
82-
const foundMatch = parent.matches(splitSelector[position]);
86+
let foundMatch = true
87+
if (splitSelector[position].length === 1) {
88+
foundMatch = parent.matches(splitSelector[position]);
89+
} else {
90+
// selector is in the format "a > b"
91+
// make sure a few parents match in order
92+
const reversedParts = ([]).concat(splitSelector[position]).reverse()
93+
let newParent = parent
94+
for (const part of reversedParts) {
95+
if (!newParent || !newParent.matches(part)) {
96+
foundMatch = false
97+
break
98+
}
99+
newParent = findParentOrHost(newParent, root);
100+
}
101+
}
102+
83103
if (foundMatch && position === 0) {
84104
foundElement = true;
85105
break;
@@ -133,27 +153,30 @@ function findParentOrHost(element, root) {
133153
* @author ebidel@ (Eric Bidelman)
134154
* License Apache-2.0
135155
*/
136-
function collectAllElementsDeep(selector = null, root) {
137-
const allElements = [];
138-
139-
const findAllElements = function(nodes) {
140-
for (let i = 0, el; el = nodes[i]; ++i) {
141-
allElements.push(el);
142-
// If the element has a shadow root, dig deeper.
143-
if (el.shadowRoot) {
144-
findAllElements(el.shadowRoot.querySelectorAll('*'));
156+
export function collectAllElementsDeep(selector = null, root, cachedElements = null) {
157+
let allElements = [];
158+
159+
if (cachedElements) {
160+
allElements = cachedElements;
161+
} else {
162+
const findAllElements = function(nodes) {
163+
for (let i = 0, el; el = nodes[i]; ++i) {
164+
allElements.push(el);
165+
// If the element has a shadow root, dig deeper.
166+
if (el.shadowRoot) {
167+
findAllElements(el.shadowRoot.querySelectorAll('*'));
168+
}
145169
}
146170
}
147-
};
148-
if(root.shadowRoot) {
149-
findAllElements(root.shadowRoot.querySelectorAll('*'));
171+
if(root.shadowRoot) {
172+
findAllElements(root.shadowRoot.querySelectorAll('*'));
173+
}
174+
findAllElements(root.querySelectorAll('*'));
150175
}
151-
findAllElements(root.querySelectorAll('*'));
152176

153177
return allElements.filter(el => el.matches(selector));
154178
}
155179

156-
157180
// normalize-selector-rev-02.js
158181
/*
159182
author: kyle simpson (@getify)

test/basic.spec.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { querySelectorAllDeep, querySelectorDeep } from '../src/querySelectorDeep.js';
1+
import { querySelectorAllDeep, querySelectorDeep, collectAllElementsDeep } from '../src/querySelectorDeep.js';
22
import { createTestComponent, createNestedComponent, COMPONENT_NAME, createChildElements } from './createTestComponent.js';
33

44

@@ -33,6 +33,10 @@ describe("Basic Suite", function() {
3333
expect(querySelectorDeep).toEqual(jasmine.any(Function));
3434
});
3535

36+
it("exports collectAllElementsDeep function", function() {
37+
expect(collectAllElementsDeep).toEqual(jasmine.any(Function));
38+
});
39+
3640
it("querySelectorDeep returns null when not found", function() {
3741
expect(querySelectorDeep('whatever')).toBeNull();
3842
});
@@ -138,6 +142,18 @@ describe("Basic Suite", function() {
138142
expect(testComponents.length).toEqual(1);
139143
});
140144

145+
it('can see inside the shadowRoot with ">" in selector', function() {
146+
const testComponent = createTestComponent(parent, {
147+
childClassName: 'header-1',
148+
internalHTML: '<div class="header-2"><div class="find-me"></div></div>'
149+
});
150+
testComponent.shadowRoot.querySelector('.header-2').host = "test.com";
151+
testComponent.classList.add('container');
152+
const testComponents = querySelectorAllDeep(`.container > div > .header-2 > .find-me`);
153+
expect(testComponents.length).toEqual(1);
154+
expect(testComponents[0].classList.contains('find-me')).toEqual(true);
155+
});
156+
141157
it('can handle extra white space in selectors', function() {
142158
const testComponent = createTestComponent(parent, {
143159
childClassName: 'header-1',
@@ -313,6 +329,35 @@ describe("Basic Suite", function() {
313329

314330
});
315331

332+
it('can cache collected elements with collectAllElementsDeep', function() {
333+
const root = document.createElement('div');
334+
parent.appendChild(root);
335+
336+
createTestComponent(root, {
337+
childClassName: 'inner-content'
338+
});
339+
340+
createTestComponent(parent, {
341+
childClassName: 'inner-content'
342+
});
343+
const collectedElements = collectAllElementsDeep('', root)
344+
expect(collectedElements.length).toEqual(4);
345+
346+
const testComponents = querySelectorAllDeep('.inner-content', root, collectedElements);
347+
expect(testComponents.length).toEqual(1);
348+
349+
// remove element from dom
350+
testComponents[0].remove()
351+
352+
// not found in dom
353+
const testComponents2 = querySelectorAllDeep('.inner-content', root);
354+
expect(testComponents2.length).toEqual(0);
355+
356+
// still there with cached collectedElements
357+
const testComponents3 = querySelectorAllDeep('.inner-content', root, collectedElements);
358+
expect(testComponents3.length).toEqual(1);
359+
});
360+
316361
it('can query nodes in an iframe', function(done) {
317362

318363
const innerframe = `<p class='child'>Content</p>`;
@@ -352,4 +397,4 @@ describe("Basic Suite", function() {
352397
});
353398

354399

355-
});
400+
});

0 commit comments

Comments
 (0)