You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
6.9 KiB
Markdown
187 lines
6.9 KiB
Markdown
# riot-query
|
|
|
|
A module for easily traversing through a Riot.js tag hierarchy, using a simple custom query language. Can be used stand-alone or as a tag mixin.
|
|
|
|
## Why you might need this
|
|
|
|
* Riot.js is inconsistent in how it exposes its `tags` - it will be an array if there are multiple, and a single element if there's only one. This means you constantly have to normalize the results.
|
|
* While selecting direct descendants is easy, it's a lot trickier when you need to search in the entire hierarchy, for example because you have a wrapper element that `yield`s its content.
|
|
|
|
## License
|
|
|
|
[WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), whichever you prefer. A donation and/or attribution are appreciated, but not required.
|
|
|
|
## Donate
|
|
|
|
Maintaining open-source projects takes a lot of time, and the more donations I receive, the more time I can dedicate to open-source. If this module is useful to you, consider [making a donation](http://cryto.net/~joepie91/donate.html)!
|
|
|
|
You can donate using Bitcoin, PayPal, Flattr, cash-in-mail, SEPA transfers, and pretty much anything else. Thank you!
|
|
|
|
## Contributing
|
|
|
|
Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the files in `src/`, not those in `lib/`.
|
|
|
|
Build tool of choice is `gulp`; simply run `gulp` while developing, and it will watch for changes.
|
|
|
|
Be aware that by making a pull request, you agree to release your modifications under the licenses stated above.
|
|
|
|
## Query language
|
|
|
|
A query consists of two parts: a Riot query and a DOM query. Both are optional, but you must always specify *at least one of them*.
|
|
|
|
In the Riot part of a query, the structure always refers to the hierarchy of *Riot tags*, not other DOM elements. In other words, the hierarchy follows the one you'd find in `this.tags`. In the DOM part of the query, you can specify DOM elements to select in the set of Riot tags that you've selected.
|
|
|
|
The `riot-query` language is somewhat syntactically similar to shell expansion. Below is a reference chart:
|
|
|
|
| Syntax | Meaning |
|
|
|-----------------------------|---------------------------------------------------|
|
|
| `foo` | Each `foo` tag. |
|
|
| `foo/bar` | Each `bar` tag that is a direct descendant of a `foo` tag. |
|
|
| `foo/**/bar` | Each `bar` tag that is a child of a `foo` tag, *anywhere* in its subtree - it does *not* have to be a direct descendant. |
|
|
| `{foo,bar}` | Each `foo` and `bar` tag |
|
|
| `foo/{bar,baz}` | Each `bar` and `baz` tag that is a direct descendant of a `foo` tag. |
|
|
| `foo/{bar,baz,}` | Each `foo`, `foo/bar` and `foo/baz` tag (note the last comma!) |
|
|
| `foo//.someClass` | Each DOM element matching `.someClass` that exists somewhere within a `foo` tag. |
|
|
| `foo//.someClass > .sub` | Each DOM element matching `.someClass > .sub` that exists somewhere within a `foo` tag. |
|
|
|
|
These are only some simplified examples - *any* combination of the above is valid, as long as the (optional) DOM part of the query comes last. `riot-query` uses a real query parser, so there are no edge cases.
|
|
|
|
## Usage
|
|
|
|
An example of a simple query:
|
|
|
|
```
|
|
menu-bar/{menu-button,menu-submenu}
|
|
```
|
|
|
|
This would select every `menu-button` and `menu-submenu` that is directly nested within a `menu-bar`.
|
|
|
|
|
|
An example of a contrived, complex query:
|
|
|
|
```
|
|
foo/bar/**/{baz,qux/quz,something/{hai,bai},}/{duck,swan}//.domFoo .domBar
|
|
```
|
|
|
|
This would select every DOM element matching `.domFoo .domBar` that exists within any of the following paths:
|
|
|
|
* `foo/bar/**/baz/duck`
|
|
* `foo/bar/**/baz/swan`
|
|
* `foo/bar/**/qux/quz/duck`
|
|
* `foo/bar/**/qux/quz/swan`
|
|
* `foo/bar/**/something/hai/duck`
|
|
* `foo/bar/**/something/hai/swan`
|
|
* `foo/bar/**/something/bai/duck`
|
|
* `foo/bar/**/something/bai/swan`
|
|
* `foo/bar/**/duck`
|
|
* `foo/bar/**/swan`
|
|
|
|
The last two might seem a little odd, but note the extra comma at the end of the first group - a trailing comma is considered to indicate an empty item, similar to how it works in Bash, and in `riot-query` that means that the entire segment is ignored.
|
|
|
|
An example of making a query using `riot-query` as a mixin:
|
|
|
|
```html
|
|
<menu-bar>
|
|
<menu-section>
|
|
<menu-item>Text 1</menu-item>
|
|
<menu-item>Text 2</menu-item>
|
|
</menu-section>
|
|
|
|
<script>
|
|
this.mixin(require("riot-query").mixin);
|
|
|
|
this.on("mount", () => {
|
|
this.query("**/menu-item").forEach((menuItem) => {
|
|
menuItem.on("clicked", () => {
|
|
console.log("Clicked menu item", menuItem);
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
</menu-bar>
|
|
```
|
|
|
|
A similar example, but selecting DOM nodes rather than Riot tags:
|
|
|
|
```html
|
|
<menu-bar>
|
|
<menu-section>
|
|
<menu-item>
|
|
<i class="icon icon-house"></i>
|
|
Text 1
|
|
</menu-item>
|
|
<menu-item>
|
|
<i class="icon icon-keys"></i>
|
|
Text 2
|
|
</menu-item>
|
|
</menu-section>
|
|
|
|
<script>
|
|
this.mixin(require("riot-query").mixin);
|
|
|
|
this.on("mount", () => {
|
|
this.query("**/menu-item//i.icon").forEach((icon) => {
|
|
console.log("Icon classes", icon.classList);
|
|
});
|
|
});
|
|
</script>
|
|
</menu-bar>
|
|
```
|
|
|
|
An example using `riot-query` stand-alone rather than as a mixin:
|
|
|
|
```html
|
|
<menu-bar>
|
|
<menu-section>
|
|
<menu-item>
|
|
<i class="icon icon-house"></i>
|
|
Text 1
|
|
</menu-item>
|
|
<menu-item>
|
|
<i class="icon icon-keys"></i>
|
|
Text 2
|
|
</menu-item>
|
|
</menu-section>
|
|
|
|
<script>
|
|
const riotQuery = require("riot-query");
|
|
|
|
this.on("mount", () => {
|
|
riotQuery(this, "**/menu-item//i.icon").forEach((icon) => {
|
|
console.log("Icon classes", icon.classList);
|
|
});
|
|
});
|
|
</script>
|
|
</menu-bar>
|
|
```
|
|
|
|
## API
|
|
|
|
### riotQuery(tag, query)
|
|
|
|
Run a `query` on a given `tag`, and return the results.
|
|
|
|
* __tag:__ The Riot.js tag to query from. This *cannot* be a regular DOM element, it *must* be a tag.
|
|
* __query:__ The query to run, following the query syntax described above.
|
|
|
|
If the `query` contains a DOM query, this function will return DOM elements. If it doesn't, it will return Riot tags. If only a DOM query is specified without a Riot query (eg. `//.someClass`), then `riot-query` will search through the DOM tree for the tag that `riotQuery` was called on.
|
|
|
|
Keep in mind that if your query selects DOM nodes rather than Riot tags, __they will be returned as an array, not as a NodeList.__ Additionally, DOM queries will *also* include DOM elements in child tags, not just those that are a part of the tag you're querying from.
|
|
|
|
### riotQuery.one(tag, query)
|
|
|
|
The same as `riotQuery`, but it only ever returns the *first* result, without being wrapped in an array.
|
|
|
|
### riotQuery.mixin
|
|
|
|
A mixin object that you can pass into the `this.mixin` method for any Riot tag.
|
|
|
|
## Mixin API
|
|
|
|
### this.query(query)
|
|
|
|
The same as `riotQuery`, but without the need to specify a `tag` - it will always apply to the current tag.
|
|
|
|
### this.queryOne(query)
|
|
|
|
The same as `riotQuery.one`, but without the need to specify a `tag`. |