Skip to content

Commit

Permalink
Implement dot notation queries (#6)
Browse files Browse the repository at this point in the history
* Implement dot notation queries

* Update documenation

* Clean the coverage before the tests

* Remove empty space
  • Loading branch information
elchininet authored Dec 4, 2023
1 parent 4744e83 commit 975f349
Show file tree
Hide file tree
Showing 9 changed files with 598 additions and 123 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [2.0.0] - 2023-12-03

- Implement query selection through dot notation

## [1.0.2] - 2023-11-15

- Fix a bug selecting a shadowRoot from an element
Expand Down
206 changes: 152 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ const shadow = document.querySelector('section').shadowRoot.querySelector('artic
`shadow-dom-selector` allows you to do the same in the next way:

```javascript
// $ character at the end of a selector means to select its Shadom DOM

import { querySelector, querySelectorAll, shadowRootQuerySelector } from 'shadow-dom-selector';

// $ character at the end of a selector means to select its Shadom DOM
const secondLi querySelector('section$ article$ ul > li');
const allLis querySelectorAll('section$ article$ ul > li');
const shadow = shadowRootQuerySelector('section$ article$');
Expand All @@ -56,51 +57,92 @@ With the same previous DOM tree, if we do this:
const element = document.querySelector('article').shadowRoot.querySelector('div').shadowRoot.querySelector('section > h1');
```

It will throw an error, because none of the elements in those queries exists. If you don‘t know if the elements exist or not, you will require to use [optional chaining] or wrap all the code in conditions:
It will throw an error, because none of the elements in those queries exist. If you don‘t know if the elements exist or not, you will require to wrap all the code in conditions or use [optional chaining] if your target is `ES2015` or greater:

```javascript
// With conditions
const article = document.querySelector('article');
if (article) {
const articleShadowRoot = article.shadowRoot;
if (articleShadowRoot) {
const div = articleShadowRoot.querySelector('div');
if (div) {
const shadow = div.shadowRoot;
if (shadow) {
const element = shadow.querySelector('section > h1');
const elements = shadow.querySelectorAll('p');
}
}
}
}

// With optional chaining in ES2015+
const shadow = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot;
const element = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot?.querySelector('section > h1');
const elements = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot?.querySelectorAll('p');
const shadow = document.querySelector('article')?.shadowRoot?.querySelector('div')?.shadowRoot;
```
Which will return `undefined` if some element doesn‘t exist. With `shadow-dom-selector`, you just need to write the query and it will return the same that is returned by the native `querySelector` and `querySelectorAll` if the query cannot be satisfied.
```javascript
import { querySelector, querySelectorAll, shadowRootQuerySelector } from 'shadow-dom-selector';

const element = querySelector('article$ div$ section > h1'); // null
const elements = querySelectorAll('article div$ p'); // empty NodeList
const shadow = shadowRootQuerySelector('article$ div$'); // null
const element = querySelector('article$ div$ section > h1'); // null
const elements = querySelectorAll('article$ div$ p'); // empty NodeList
```
### Async queries
If the elements are not already rendered into the DOM in the moment that the query is made you will receive `null`. `shadow-dom-selector` allows it to wait for the elements to appear, allowing you to decide how many times it will try to query for the element before giving up and returning `null` or an empty `NodeList`.
If the elements are not already rendered into the DOM in the moment that the query is made you will receive `null`. `shadow-dom-selector` allows you to wait for the elements to appear deciding how many times it will try to query for the element before giving up and returning `null` or an empty `NodeList`.
```javascript
// Using the async methods
import { asyncQuerySelector, asyncQuerySelectorAll, asyncShadowRootQuerySelector } from 'shadow-dom-selector';

const element = asyncQuerySelector('article$ div$ section > h1')
.then((h1) => {
// Do stuff with the h1 element
// If it is not found after all the retries, it will return null
});
asyncShadowRootQuerySelector('article$ div$')
.then((shadowRoot) => {
// Do stuff with the shadowRoot
// If it is not found after all the retries, it will return null
});

asyncQuerySelector('article$ div$ section > h1')
.then((h1) => {
// Do stuff with the h1 element
// If it is not found after all the retries, it will return null
});

asyncQuerySelectorAll('article$ div$ p')
.then((paragraphs) => {
// Do stuff with the paragraphs
// If they are not found after all the retries, it will return an empty NodeList
});

const elements = asyncQuerySelectorAll('article div$ p')
.then((paragraphs) => {
// Do stuff with the paragraphs
// If they are not found after all the retries, it will return an empty NodeList
});
// Using async dot notation
import { AsyncSelector } from 'shadow-dom-selector';

const shadow = asyncShadowRootQuerySelector('article$ div$')
.then((shadowRoot) => {
// Do stuff with the shadowRoot
// If it is not found after all the retries, it will return null
});
const selector = AsyncSelector();

selector.article.$.div.$.element
.then((shadowRoot) => {
// Do stuff with the shadowRoot
// If it is not found after all the retries, it will return null
});

selector.article.$.div.$['section > h1'].element
.then((h1) => {
// Do stuff with the h1 element
// If it is not found after all the retries, it will return null
});

selector.article.$.div.$.p.all
.then((paragraphs) => {
// Do stuff with the paragraphs
// If they are not found after all the retries, it will return an empty NodeList
});
```
All these three functions allow you to to specify the amount of retries and the delay between each one of them. Consult the [API](#api) section for more details.
Either the async methods or the async dot notation allow you to to specify the amount of retries and the delay between each one of them. Consult the [API](#api) section for more details.
## Install
Expand Down Expand Up @@ -142,18 +184,19 @@ ShadowDomSelector.shadowRootQuerySelector;
ShadowDomSelector.asyncQuerySelector;
ShadowDomSelector.asyncQuerySelectorAll;
ShadowDomSelector.asyncShadowRootQuerySelector;
ShadowDomSelector.AsyncSelector;
```
## API
#### querySelector
```typescript
querySelector(selectors);
querySelector(selectors): Element | null;
```
```typescript
querySelector(root, selectors);
querySelector(root, selectors): Element | null;
```
| Parameter | Optional | Description |
Expand All @@ -164,11 +207,11 @@ querySelector(root, selectors);
#### querySelectorAll
```typescript
querySelectorAll(selectors);
querySelectorAll(selectors): NodeListOf<Element>;
```
```typescript
querySelectorAll(root, selectors);
querySelectorAll(root, selectors): NodeListOf<Element>;
```
| Parameter | Optional | Description |
Expand All @@ -179,11 +222,11 @@ querySelectorAll(root, selectors);
#### shadowRootQuerySelector
```typescript
shadowRootQuerySelector(selectors);
shadowRootQuerySelector(selectors): ShadowRoot | null;
```
```typescript
shadowRootQuerySelector(root, selectors);
shadowRootQuerySelector(root, selectors): ShadowRoot | null;
```
| Parameter | Optional | Description |
Expand All @@ -194,21 +237,27 @@ shadowRootQuerySelector(root, selectors);
#### asyncQuerySelector
```typescript
asyncQuerySelector(selectors);
asyncQuerySelector(selectors): Promise<Element | null>;
```
```typescript
asyncQuerySelector(root, selectors);
asyncQuerySelector(root, selectors): Promise<Element | null>;
```
```typescript
asyncQuerySelector(selectors, asyncParams);
asyncQuerySelector(selectors, asyncParams): Promise<Element | null>;
```
```typescript
asyncQuerySelector(root, selectors, asyncParams);
asyncQuerySelector(root, selectors, asyncParams): Promise<Element | null>;
```
| Parameter | Optional | Description |
| ------------ | ------------- | -------------------------------------------------- |
| selectors | no | A string containing one or more selectors to match. Selectors cannot end in a Shadow DOM (`$`) |
| root | yes | The element from where the query should be performed, it defaults to `document` |
| asyncParams | yes | An object containing the parameters which control the retries |
```typescript
// asyncParams properties
{
Expand All @@ -217,30 +266,30 @@ asyncQuerySelector(root, selectors, asyncParams);
}
```
| Parameter | Optional | Description |
| ------------ | ------------- | -------------------------------------------------- |
| selectors | no | A string containing one or more selectors to match. Selectors cannot end in a Shadow DOM (`$`) |
| root | yes | The element from where the query should be performed, it defaults to `document` |
| asyncParams | yes | An object containing the parameters which control the retries |
#### asyncQuerySelectorAll
```typescript
asyncQuerySelectorAll(selectors);
asyncQuerySelectorAll(selectors): Promise<NodeListOf<Element>>;
```

```typescript
asyncQuerySelectorAll(root, selectors);
asyncQuerySelectorAll(root, selectors): Promise<NodeListOf<Element>>;
```

```typescript
asyncQuerySelectorAll(selectors, asyncParams);
asyncQuerySelectorAll(selectors, asyncParams): Promise<NodeListOf<Element>>;
```

```typescript
asyncQuerySelectorAll(root, selectors, asyncParams);
asyncQuerySelectorAll(root, selectors, asyncParams): Promise<NodeListOf<Element>>;
```

| Parameter | Optional | Description |
| ------------ | ------------- | -------------------------------------------------- |
| selectors | no | A string containing one or more selectors to match. Selectors cannot end in a Shadow DOM (`$`) |
| root | yes | The element from where the query should be performed, it defaults to `document` |
| asyncParams | yes | An object containing the parameters which control the retries |

```typescript
// asyncParams properties
{
Expand All @@ -249,30 +298,30 @@ asyncQuerySelectorAll(root, selectors, asyncParams);
}
```

| Parameter | Optional | Description |
| ------------ | ------------- | -------------------------------------------------- |
| selectors | no | A string containing one or more selectors to match. Selectors cannot end in a Shadow DOM (`$`) |
| root | yes | The element from where the query should be performed, it defaults to `document` |
| asyncParams | yes | An object containing the parameters which control the retries |
#### asyncShadowRootQuerySelector

```typescript
asyncShadowRootQuerySelector(selectors);
asyncShadowRootQuerySelector(selectors): Promise<ShadowRoot | null>;
```

```typescript
asyncShadowRootQuerySelector(root, selectors);
asyncShadowRootQuerySelector(root, selectors): Promise<ShadowRoot | null>;
```

```typescript
asyncShadowRootQuerySelector(selectors, asyncParams);
asyncShadowRootQuerySelector(selectors, asyncParams): Promise<ShadowRoot | null>;
```

```typescript
asyncShadowRootQuerySelector(root, selectors, asyncParams);
asyncShadowRootQuerySelector(root, selectors, asyncParams): Promise<ShadowRoot | null>;
```

| Parameter | Optional | Description |
| ------------ | ------------- | -------------------------------------------------- |
| selectors | no | A string containing one or more selectors to match. Selectors must end in a Shadow DOM (`$`) |
| root | yes | The element from where the query should be performed, it defaults to `document` |
| asyncParams | yes | An object containing the parameters which control the retries |

```typescript
// asyncParams properties
{
Expand All @@ -281,12 +330,61 @@ asyncShadowRootQuerySelector(root, selectors, asyncParams);
}
```

#### AsyncSelector

```typescript
AsyncSelector(root): AsyncSelectorProxy;
```

```typescript
AsyncSelector(root, asyncParams): AsyncSelectorProxy;
```

| Parameter | Optional | Description |
| ------------ | ------------- | -------------------------------------------------- |
| selectors | no | A string containing one or more selectors to match. Selectors must end in a Shadow DOM (`$`) |
| root | yes | The element from where the query should be performed, it defaults to `document` |
| root | yes | The element or shadowRoot from where the query should be performed, it defaults to `document` |
| asyncParams | yes | An object containing the parameters which control the retries |

```typescript
// asyncParams properties
{
retries?: number; // how many retries before giving up (defaults to 10)
delay?: number; // delay between each retry (defaults to 10)
}
```

This function returns an object with the next properties:

```typescript
// AsyncSelectorProxy properties
{
element: Promise<Document | Element | ShadowRoot | null>; // A promise that resolves in the first queried element
all: Promise<NodeListOf<Element>>; // A promise that resolves in all the queried elements
$: Promise<ShadowRoot | null>; // A promise that resolves in the shadowRoot of the first queried element
asyncParams: { retries: number; delay: number; } // The asyncParameters being used in the chain
[any other property]: AsyncSelectorProxy; // Returns another AsyncSelectorProxy pointing to the element queried by the property name
}
```

##### Examples of AsyncSelector

```typescript
const selector = AsyncSelector(); // AsyncSelectorProxy starting in the document with the default asyncParams
await selector.element === document;
await selector.all; // Empty Node list
await selector.$; // null
```

```typescript
const selector = AsyncSelector({
retries: 100,
delay: 50
}); // AsyncSelectorProxy starting in the document and with custom asyncParams
await selector.section.$.element === document.querySelector('section').shadowRoot;
await selector.section.$.all; // Empty Node list
await selector.section.$.article.all === document.querySelector('section').shadowRoot.querySelectorAll('article');
selector.section.$.article.asyncParams; // { retries: 100, delay: 50 }
```

[Shadow DOM]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
[optional chaining]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Loading

0 comments on commit 975f349

Please sign in to comment.