Simple Query ๐
A simple library to query the DOM from your Astro components.
Migration from an older version
If you are using an older version of Simple Query, check the GitHub releases for migration instructions.
Installation
Simple Query is an Astro integration. We recommend installing with the astro add
CLI:
To install this integration manually, follow the manual installation instructions
Type checking
Simple Query will automatically set up necessary types when the integration is applied. However, you may need to update your tsconfig.json
to include these types in your project.
If your tsconfig.json
has an "include"
array, add your astro.config.mjs
or astro.config.ts
file (whichever is applicable). This ensures any types added by the integration are included by type checkers like astro check
:
Getting started
To get started, apply the global <RootElement>
wrapper around your Astro component markup:
Next, apply the data-target
attribute to the element you want to target. Simple Query will automatically scope the value to prevent conflicts with other components in your project:
Now create a <script>
tag, and use the RootElement.ready()
wrapper to define your client script. .ready()
provides the $()
function to select elements based on their data-target
attribute. This returns a web-standard HTMLElement
you can use to, say, add an event listener with .addEventListener()
:
Selecting elements
The $()
function provided by RootElement.ready()
will find the first match based on data-target
, and throw if no match is found.
The result will be a standard HTMLElement
by default. To narrow this type to a particular element, pass a type generic to the $()
function:
$.optional()
selector
$()
throws when no matching element is found. To handle undefined values, use the $.optional()
function:
$.all()
selector
You may want to select multiple targets with the same name. Use the $.all()
function to query for an array of results:
$.self
selector
To select the RootElement
itself, use the $.self
value:
Handling client state
When adding client interactivity, youโll often need to track โstate.โ This could be count
value for a button counter, an open
boolean for a dropdown, and so on.
Using data attributes
The simplest place to store state values is on an element itself. For example, you may track the open
state on a dropdown by toggling a data attribute. Call .toggleAttribute()
on the dropdown when a button is clicked, and adjust the dropdownโs visibility
style from invisible
to visible
based on the value:
Using signals with signal-polyfill
Overtime, you may find it difficult to keep elements on the page in-sync with the state you are tracking. You may need to update multiple elements based on a count
, an open
state, etc.
Browsers are considering a new standard to address this problem: Signals. Simple Query is built to support an early version of this proposal via the signal-polyfill
package.
First install signal-polyfill
into your project:
Then, create your first state variable by importing Signal
from signal-polyfill
and calling the new Signal.State()
constructor:
Update this value using .set()
, and retrieve the current value using .get()
. This example increments a counter when the btn
element is pressed:
To update the document, RootElement.ready()
provides an effect()
function. This defines a block of code that should rerun whenever a state variable inside that block changes.
Access the effect()
function from the ctx
object. This passed as the second argument to RootElement.ready()
.
This example will update a buttonโs textContent
whenever count
changes:
Passing server data
You may need to pass information from your Astro component to the client. For boolean values, the simplest method is via data attributes. This example sets the initial state of a dropdown by setting data-open
from the template:
Still, you may need to pass other values including numbers, arrays, or objects. This is common when using Signals for state management.
For this, <RootElement>
accepts a data
property. This supports any JSON-serializable value:
Then, retrieve this value from the client using the data
object. This is available from the ctx
object passed as the second argument by RootElement.ready()
.
RootElement
also accepts a type argument to enforce the type of data
:
Passing data-target
as a component prop
You can pass a data-target
value as a prop to nested components as well. This allows you to target elements defined deeper in the component tree that depend on the same client state.
This example defines a Button
component that accepts a target
prop applied to the data-target
attribute:
To pass a scoped target
value, you will need to wrap your value using the scope()
function from the simple:scope
module. This applies the same scoping Simple Query applies by default to data-target
attributes:
Handling event cleanup
You may have logic in your RootElement.ready()
function that should be cleaned up on route change. This includes:
fetch()
callsdocument
event listeners- intersection and mutation observers
Clean up fetch
and document
callbacks
The fetch()
and document.addEventListener()
functions accept an AbortSignal
to cancel events. Simple Query provides an abortSignal
via the ctx
property, which will trigger whenever the RootElement
is removed from the page.
Apply ctx.abortSignal
to any fetch()
or document.addEventListener()
calls in your client script using the signal
property:
Clean up observers and other events
You may listen to events that do not accept an AbortSignal
. This includes intersection and mutation observers. For these cases, return a callback function from RootElement.ready()
with any cleanup logic you need to run on navigation: