jessquery
is a lightweight wrapper around the DOM API that offers the intuitive elegance of jQuery, but streamlined for the modern web.
Feel like a 🦕 for still using jQuery? Wish that it didn't bloat up your bundle size like a 🐖? Want something 🆕 and ✨?
Rekindle your love for method chaining-- now in a lightweight, type-safe package! With 43 custom methods that are sneakily powerful, jessquery
helps you seamlessly handle asynchronous tasks, customize error behaviors, and ensure your DOM operations always execute in order. And, the best part? 🏎️💨
Library | Size before gzip | Size after gzip |
---|---|---|
jQuery | 88.3kb | 31.7kb |
jessquery | 8.82kb | 3.76kb |
And, if that's too big for you, you can use our scrawny kid brother Droxy instead. He's only 2kb after gzip!
// Most things follow the DOM API closely with slightly different names.
// But, now you can chain them together
// They will always execute in order!
const fadeIn = [{ opacity: 0 }, { opacity: 1 }] // WAAPI keyframes
const fadeOut = [{ opacity: 1 }, { opacity: 0 }] // WAAPI keyframes
const animatedText = $$(".animated-text") // $$ ≈ querySelectorAll, use $ for querySelector
// <span hidden class="animated-text"></span>
// <span hidden class="animated-text"></span>
animatedText
.addClass("special")
.wait(1000) // Will not appear for one second
.toggle("hidden")
.text(
`<p>
In two seconds, every element matching the 'animated-text' class
will fade in and out twice then disappear.
</p>`
)
.wait(2000)
.transition(fadeIn, 1000)
.transition(fadeOut, 1000)
.transition(fadeIn, 1000)
.transition(fadeOut, 1000)
.purge() // All `.animated-text` elements will be removed from the DOM
You can install it via NPM, PNPM, Yarn, or Bun just like anything else on NPM.
npm install jessquery
pnpm install jessquery
yarn add jessquery
bun install jessquery
Or, since it's so small, you can just use a CDN like the good, old days. The big problem with this is that you lose the types and the JSDoc annotations. I keep those in the d.ts
file to keep the file size small, but I recently learned that gzip takes care of that for you. So, I'll probably change that in the future. For now, you can just use the index.d.ts
file in your project if you want the types without installing the package.
<script src="https://esm.sh/jessquery"></script>
<script src="https://unpkg.com/jessquery"></script>
jessquery
is quite different from jQuery, but it makes sense once you understand the rules. The concurrent chaining makes things a bit more complex. The key is understanding that each $()
or $$()
call is representative of a single queue-- not necessarily the elements that are being manipulated. It's a bit like PrototypeJS mixed with the async flow of something like RxJS.
The magic sauce here is that everything is a proxy, so you can still use the full DOM API if your use case isn't covered by one of the methods. So, if you forget about the .css
operator and use .style
instead when using $()
, it will just work. The NodeList that you get from $$()
is automatically turned into an array so you can use array methods on it like .map()
or .filter()
.
This is the benefit of using proxies, but I'm curious if this will scale well as they bring a tiny bit of overhead. This might get problematic in large applications, but I'm probably just being paranoid. I welcome anyone to do some tests! 😅
Here's a Stackblitz Playground if you want to try it out. The demo that will load in has an extremely long chain showing the mutability that a standard DomProxy
exhibits. To see how an error is thrown when that proxy is fixed
in place, simply add a true
argument to the $()
call like this: const container = $(".container", true)
.
Everything is fully type-safe, but there's no way for the $()
and $$()
functions to infer the type of the element you're selecting unless it's a tag name. Things like $$('input')
will always be fully inferred even if you map over the individual elements in the collection-- in that case, each element would automatically become an HTMLInputElement
. However, if you select a class or id, the type will always be HTMLElement
unless you specify the type yourself like this:
const button = $<HTMLButtonElement>(".button")
const coolInputs = $$<HTMLInputElement>(".cool-inputs")
I wrote a lot, but the main idea is that everything should be predictable. You probably only need to read the bold parts unless you start doing a lot of crazy DOM manipulation that operates on multiple elements at once while using the same variables for everything. If you're just doing simple stuff, you can probably just ignore the rest. 👌
$()
to build a queue that operates on a single element-- a DomProxy. However, if you use a method like pickAll()
or kids()
, you will switch to a DomProxyCollection
with multiple elements.$$()
to build a queue that operates on multiple elements at once-- a DomProxyCollection. However, if you use a method like pick()
or parent()
and there is only one element in the collection, you will switch to a DomProxy
with a single element.DomProxy
is mutable unless it was created with a fixed
argument set to true
. If you store it in a variable and you change the element with a method like next()
or siblings()
, any event handlers that use that variable for DOM manipulation will now operate on the new element unless you use the refresh()
method to reset the proxy to its original state.jessquery
custom methods can be chained together. Each method will operate on the element(s) held in the proxy at the time the function is called. If you switch context multiple times, it can get confusing. Try to only switch "element context" once per method chain. If you do not want your proxy to be mutable, set the fixed
argument to true.jessquery
custom methods are setters that return the proxy. If you need to check the value of something, just use the DOM API directly (textContent
instead of text()
, for example). This also helps to differentiate between set and get operations.$()
or $$()
call gets its own queue which runs every function sequentially, but the chains are executed concurrently. So, you can have multiple chains operating on the same element. Be careful of race conditions!wait()
calls or long animations.Generally, just try to keep each discrete chain of DOM operations for a single element together, and try to use a new variable for any event handlers. I mean, the whole point of this library is that $()
and $$()
are really easy to type, and you only need to worry about it when things aren't behaving the way you expect. If anything gets too hard, you can also use the defer()
and wait()
methods to let the DOM catch up while you re-evaluate your life choices. 😅
promisify
and setErrorHandler
While you can use $()
and $$()
as direct replacements for querySelector
and querySelectorAll
, you can also use them to create elements on the fly.
If you pass a string that starts with <
, it will create a new element with the given tag name and return it as a DomProxy.
You can also pass an HTMLElement or NodeList, which will be converted to a DomProxyCollection.
Once the proxy has been created, it can be mutated to represent something else in the DOM.
If you use a method like next()
or siblings()
, the proxy will represent those elements instead.
The refresh()
method will reset the proxy to its original state (What you passed to $()
or $$()
).
import { $, $$, promisify, setErrorHandler } from "jessquery"
// Single element.
const display = $(".display")
// Multiple elements.
const dynamicSpans = $$(".dynamic-spans")
// Dynamically create an element.
$(`<h1>I'm #1</h1>`).moveTo(".container", { position: "prepend" }) // append is the default
// You can completely replace the element(s). The proxy is what's stable-- not the element(s) inside.
// These changes are permanent as they operate on the DOM directly.
const button = $(".button")
button.html(`<h3>I am not a button</h3>`, true)
// The second argument determines if the parent element should be replaced as well.
// You can also use become() to transform the elements into something from elsewhere in the DOM.
const buttons = $(".button")
buttons.become($("#other-button"))
// Each button will now be a deep clone of the other-button element. (no event handlers)
// moveTo, cloneTo, and attach allow you to move elements around the DOM.
// If you don't want to move things permanently, you can:
// - use cloneTo to make a copy of the current element(s) and attach it to another element
// - set the mode to `clone` on attach to copy another element from elsewhere in the DOM
// However, this will not be able to move event handlers assigned to the original element(s).
const coolDiv = $(".cool-div")
coolDiv.cloneTo(".container", { position: "prepend" })
// But remember, you can use the same proxy to operate on different elements over time.
coolDiv.next().text("I'm the next element!")
// The coolDiv proxy will no longer operate on elements with the class "cool-div"
// When in doubt, just use refresh() to reset the proxy to its original state.
// /**
// * <div class="container">
// * <p id="ME">1</p>
// * <p>2</p>
// * <p>3</p>
// * </div>
// */
const ME = $("#ME")
ME.do((el) => console.log(el.textContent)) // 1
ME.next().do((el) => console.log(el.textContent)) // 2
ME.parent().do((el) => console.log(el.textContent)) // 123
ME.refresh().do((el) => console.log(el.textContent)) // 1
Usually, your best move is to simply use whatever DOM API you need to check the state of the element(s) in the proxy.
if
and takeWhile
allow you to conditionally execute functions in the middle of a chain.
They use a predicate function that returns a boolean to determine their behavior.
if
is used for conditionally executing functions based on the state of the element(s) in the proxy.
takeWhile
is used for conditionally filtering the elements held within the proxy.
const button = $(".button")
const display = $(".display")
// My first suggestion is to use a simple ternary with the DOM API.
button.on("click", () => {
display.text(display.textContent === "Click me!" ? "Clicked!" : "Click me!")
})
// But, the if method allows you to chain things together.
button.on("click", () => {
display
.if({
is: (el) => el.textContent === "Click me!",
then: (el) => el.text("Clicked!").css("color", "green"),
or: (el) => el.text("Click me!").css("color", "red"),
})
.wait(1000)
.if({
is: (el) => el.textContent === "Clicked!",
then: (el) => el.text("Click me!").css("color", "red"),
or: (el) => el.text("Clicked!").css("color", "green"),
})
})
// `takeWhile` actually changes the elements held within the proxy.
const buttons = $$(".button")
buttons
.takeWhile((el) => el.textContent !== "Click me!")
.css("color", "red") // Only the buttons that don't say "Click me!" will be red.
.wait(1000)
.css("color", "blue") // Those buttons will turn blue after one second.
It's important to remember that takeWhile
will alter the elements held within the proxy, and that any methods that follow it will only operate on the filtered elements.
As always, you can use the refresh
method to restore the proxy to its original state.
const buttons = $$(".button")
buttons
.takeWhile((el) => el.textContent !== "Click me!")
.css("color", "red") // Only the buttons that don't say "Click me!" will have red text.
.refresh()
.css("background-color", "blue") // All buttons will be blue.
Because all promises are automatically awaited, you can create async functions to stick right in the middle of your chains.
No need to try/catch anything. All methods are surrounded by the error handler.
// You can do anything you want for however long you want.
async function fetchData() {
const response = await fetch("https://api.github.com/users/jazzypants1989")
const data = await response.json()
return data.name
}
// Every promise is resolved automatically
button.on("click", () => {
// The next function never runs until the previous one is finished.
display
.text(fetchData()) // No await, no try/catch, no problem!
.css(color, display.textContent === "Jesse Pence" ? "green" : "red")
// Each proxy has full access to the DOM API-- useful for conditional logic.
display
.do(async (el) => {
// For more complex async logic, you can use the do method.
// It will hold the queue indefinitely while you do whatever you want.
el.text("Loading...")
const response = await fetch(
"https://api.github.com/users/jazzypants1989"
)
const data = await response.json()
el.text(data.name).css("color", "green")
await new Promise((resolve) => setTimeout(resolve, 3000))
})
.text(
"This will be green and replace the element's text, but only after three seconds of waiting!"
)
})
promisify
and setErrorHandler
promisify is a helper function that allows you to easily integrate asynchronous tasks into your chains.
It's particularly useful for things like setTimeout, setInterval, and any older APIs that use callbacks.
You can always just return a promise yourself if you want, but this provides a few extra features.
You can also use the setErrorHandler
function to customize the behavior when an error occurs.
// It's great for when you forget to cover all conditions like this troubling example,
// The next function will try to wait (five seconds by default).
// If it still hasn't resolved, the chain will keep moving
// (while passing an error to the error handler).
const pollAPIUntilItWorks = promisify(
(resolve, reject) => {
const response = await fetch("https://someCrappyAPI.com")
if (response.ok) {
resolve(response.json()) // you can just pass this promise through
}
// No reject, no problem! It'll be covered by the default error handler.
},
{
timeout: 10000,
// You can set the timeout to whatever you want.
interval: 500,
// It will usually only try the function once, but you can set an interval to retry.
url: "https://someCrappyAPI.com",
// You can pass extra metadata to the error handler.
}
)
// The default error handler catches most errors and promisify rejections
// It simply logs using console.error, but you can use setErrorHandler to override this.
setErrorHandler((err, context) => {
sendErrorToAnalytics(err, context)
})
display.on("mouseover", () => {
display
.html(`<pre>JSON.stringify(${pollAPIUntilItWorks()}, null, 2)</pre>`)
.attach(
`This will wait for ten seconds, but it will still show up if the API fails!`
)
})
There's also internal fromJSON
, fromHTML
, and fromStream
methods
(Above, I just wanted to show off the promisify
method and how you don't have to await anything.)
These automatically handle fetching and parsing.
These functions expand fetch to incude additional event hooks and error handling.
const fetchOptions = {
// You can use the same options as fetch, but with loads of extra features like:
// Automatic Retries with exponential backoff. (default is 0)
retries: 3,
// The first retry will be in one second, then two seconds, then four seconds...
retryDelay: 1000,
// You can set up an error handler that will be called if the fetch still fails after all retries.
onError: (err) => sendFetchErrorToAnalytics(err).
// Or, a success handler that will reflect the DOM AFTER the element has been updated.
onSuccess: () => dynamicSpans.attach("<h6>Data Loaded!</h6>"),
// This custom loading message will replace the element's text while it waits.
onWait: () => dynamicSpans.text("Hold your horses! I'm loading data!")
// But, only if it doesn't load within one second. (default is 250ms and no message)
waitTime: 1000,
// Everything is sanitized by default, but you can turn it off if you want.
runScripts: true,
sanitize: false,
// the full range of fetch options (request) are still supported.
headers: {
"Cool-Header": "Cool-Value",
},
}
// You can nest things as deep as you want (if you like making things confusing).
dynamicSpans.fromJSON(
"https://jessepence.com/api/cool-json",
(el, json) => {
el.html(
`<h2>${json.name}</h2>
<p>${json.bio}</p>
`
)
.wait(5000)
.fromHTML("/api/extended-bio", fetchOptions)
.attach(
"<h2>Enough about me, I'll replace this with a cool stream in 5 seconds!</h2>"
)
.wait(5000)
.fromStream("/api/cool-stream", fetchOptions)
},
fetchOptions
)
That covers most GET requests, but you can also use the send
method for sending HTTP requests.
It automatically attempts to find any form data and a URL from the element or its parents.
It serializes using the formData API by default, but you can also pass a custom serializer.
You can also pass a URL and body directly if you want.
// This will automatically serialize the form
// It will send it to the action attribute if it exists (The page's URL if not)
$("#bigForm").send()
// You can also customize each aspect of the request if you want.
// EVERYTHING is optional. You can just pass a URL if you want.
$("#otherSubmitButton").on("click", (event) => {
$$("#bigForm").send({
// Just pass in the event to prevent default submission
event,
// If no URL, it will use the formaction attribute or any ancestor form's action that exists.
url: "/api/cool-endpoint",
// If no body, the form's data would be used. If no form, the value/textContent would be used.
body: { cool: "data" },
// POST is the default
method: "PUT",
// You still get all the extra event hooks and options.
onWait: () => console.log("Waiting for the server to respond..."),
// And, of course, all the normal fetch options as well.
headers: {
"Cool-Header": "Cool-Value",
},
})
})
// (this will send multiple fetches ($$) though. No caching or batching... yet)
$(selector: string, fixed?: boolean): DomProxy
Finds the first element in the DOM that matches a CSS selector and returns it with some extra, useful methods.
If given a string that starts with <
, it will create a new element with the given tag name and return it as a DomProxyCollection
It can also accept HTMLElements and NodeLists, which will be converted to a DomProxyCollection.
These contain 43 methods that can be chained together to create a sequence of actions that will be executed in order (including asynchronous tasks).
Every method returns a DomProxy or DomProxyCollection object, which can be used to continue the chain.
All DOM API's can still be used, but they MUST COME LAST within a single chain.
Example:
$("#button")
.on("click", () => console.log("Clicked!"))
.css("color", "purple")
.wait(1000)
.css("color", "lightblue")
.text("Click me!").style.backgroundColor = "lightGreen" // This will work, but only because it's the last thing in the chain.
// It's also important to note that the style method call is not queued, so it will happen before everything else.
$$(selector: string, fixed?: boolean): DomProxyCollection
Finds all elements in the DOM that match a CSS selector and returns them with some extra, useful methods
If given a string that starts with <
, it will create a new element with the given tag name and return it as a DomProxyCollection
It can also accept HTMLElements and NodeLists, which will be converted to a DomProxyCollection.
These contain 43 methods that can be chained together to create a sequence of actions that will be executed in order (including asynchronous tasks).
Every method returns a DomProxy or DomProxyCollection object, which can be used to continue the chain.
All DOM API's can still be used, but they MUST COME LAST within a single chain.
Example:
$$(".buttons")
.on("click", () => console.log("Clicked!"))
.css("color", "purple")
.wait(1000)
.css("color", "lightblue")
.text("Click me!").style.backgroundColor = "lightGreen" // This will work, but only because it's the last thing in the chain.
// It's also important to note that the style method call is not queued, so it will happen before everything else.
Sets an error handler that will be called when an error occurs somewhere in JessQuery. The default behavior is to just log it to the console. You can override this behavior with this method to do something else (or nothing... no judgement here! 😉)
handler: (err: Error, context?: any) => void
Example:
setErrorHandler((err) => alert(err.message))
// Now, you'll get an annoying alert every time an error occurs like a good little developer
promisify(fn: (...args: any[]) => void, meta?: { timeout?: number, interval?: number, [key: string]: any }): () => Promise
Wraps a function in a promise, allowing easy integration into DomProxy chains.. This is particularly useful for things like setTimeout and any older APIs that use callbacks.
It accepts a function that works just like building a normal promise: call the resolve function when the function is successful, and call the reject function when it fails. The value that you pass will get passed to whatever method you use to consume the promise.
If the function does not call either resolve or reject within the specified timeout, the promise will reject with an error. Every promise that rejects inside of a promisified function will get routed through the default errorHandler (which you can set with the setErrorHandler function).
You can set the amount of time to wait before rejecting the promise with the timeout property on the meta object. If you don't provide a timeout, it will default to 5000ms.
You can also set an interval to retry the function if it fails. This is useful for things like polling an API. If you don't provide an interval, it will default to 0ms (no retries).
The easiest way to use the function that you get from this method is call it to provide values to one of the DomProxy
methods like text() or html(), but you can also use the DomProxy.do / DomProxyCollection.do method to execute the function and use the result on the element / elements represented by them.
You can include a meta object with any additional information you would like to pass to the error handler. This is useful as the function name will be anonymous for every promisified function, so you can use this to differentiate between them.
fn: (...args: any[]) => void
meta: optional
meta.timeout?: number
meta.interval?: number
Example:
const fetchApiData = promisify((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("GET", "https://jsonplaceholder.typicode.com/todos/1")
xhr.onload = () => resolve(xhr.responseText)
xhr.onerror = () => reject(xhr.statusText)
xhr.send()
}, {
timeout: 10000,
fnName: 'fetchApiData (XHR)'
url: 'https://jsonplaceholder.typicode.com/todos/1'
})
setErrorHandler((err) => $("#display").text(err.message))
button.on("click", () => {
display
.text("Hold on! I'm about to use XHR")
.wait(500)
.do(async (el) => {
const data = await fetchApiData()
el.text(data)
})
})
// Or remember, you can just pass it into the text method!
button.on("click", async () => {
display
.text("I betcha don't even know what XHR is!")
.wait(1000)
.text(fetchApiData())
})
A proxy covering a single HTML element that allows you to chain methods sequentially (including asynchronous tasks) and then execute them one after the other. It includes 43 of these custom methods, but you can still use the full DOM API if you need to.
on(ev: string, fn: EventListenerOrEventListenerObject): DomProxy
$('button').on('click', () => console.log('clicked'))
once(ev: string, fn: EventListenerOrEventListenerObject): DomProxy
$('button').once('click', () => console.log('clicked'))
off(ev: string, fn: EventListenerOrEventListenerObject): DomProxy
$('button').off('click', clickHandler)
delegate(event: string, subSelector: string, handler: EventListenerOrEventListenerObject): DomProxy
$('.container').delegate('click', '.buttons', (e) => console.log('Button clicked'))
html(newHtml: string, outerHTML?: boolean): DomProxy
Change the HTML of the element with an UNSANITIZED string of new HTML. This is useful if you want to add a script tag or something. If you want to sanitize the HTML, use sanitize
instead.
By default, only the element's children will be replaced (innerHTML). If you want to replace the entire element (outerHTML), you can pass true
as the second argument.
Example: $('button').html('<span>Click me!</span>')
Expectation: <button><span>Click me!</span></button>
Example: $('button').html('<span>Click me!</span>', true)
Expectation: <span>Click me!</span>
(The button will be replaced with the span)
sanitize: (html: string, sanitizer?: (html: string) => string) => DomProxy
const maliciousHTML =
'<span>Safe Content</span><script>alert("hacked!")</script>'
const customSanitizer = new Sanitizer({
allowElements: ["span"],
})
$("button").sanitize(maliciousHTML, customSanitizer)
// The button will only contain the 'Safe Content' span;
// Any scripts (or other unwanted tags) will be removed.
// Only span elements will be allowed.
text(newText: string): DomProxy
$('button').text('Click me!')
val(newValue: string | number | (string | number)[] | FileList): DomProxy
value
, checked
) will be adjusted. For other elements, the textContent
property will be set.$('input[type="text"]').val('New Value')
$('input[type="checkbox"]').val(true)
$('input[type="radio"]').val('radio1')
$('input[type="file"]').val(myFileList)
$('select[multiple]').val(['option1', 'option2'])
css(prop: string | Record<string, string>, value?: string): DomProxy
Adds one or more CSS Rules to the element.
If the first argument is an object, each key-value pair will be added as a CSS Rule.
If the first argument is a string, it will be treated as a CSS property and the second argument will be treated as its value.
Example: $('button').css('color', 'red')
Example: $('button').css({ color: 'red', backgroundColor: 'blue' })
addStyleSheet(cssString: string): DomProxy
Adds a stylesheet to the ENTIRE DOCUMENT (this is useful for things like :hover styles). Got an good idea for how to make this scoped to a single element? Open a PR!
This should be used only when you need to do something like set a pseudo-class on the fly. Otherwise, just write a real stylesheet.
Got a good idea for how to make this scoped to a single element? Open a PR! I was thinking something like the @scope
rule being automatically inserted, but that's still only in Chromium browsers.
-- Right now, every rule will be given an !important flag, so it will override any existing styles. This is drastic I know, but it's the only way to make this work if you're creating other inline styles.
$('button').addStyleSheet('button:hover { color: red; }')
addClass(className: string): DomProxy
Add one or more classes to the element.
Just add a comma in between each class name, or use spread syntax from an array.
Example: $('button').addClass('btn')
Example: $('button').addClass('btn', 'btn-primary')
Example:
const classes = ["btn", "btn-primary"]
$("button").addClass(...classes)
removeClass(className: string): DomProxy
Removes one or more classes from the element.
Just add a comma in between each class name, or use spread syntax from an array.
Example: $('button').removeClass('btn')
Example: $('button').removeClass('btn', 'btn-primary')
Example:
const classes = ["btn", "btn-primary"]
$("button").removeClass(...classes)
toggleClass(className: string): DomProxy
$('button').toggleClass('btn')
const mediumScreen = window.matchMedia("(min-width: 768px)").matches
$("button").toggleClass("btn", mediumScreen)
set(attr: attr: string | { [key: string]: string },value?: string): DomProxy
Sets one or more attributes on the element.
If the first argument is an object, each key-value pair will be treated as an attribute name and value.
If the first argument is a string, it will be treated as the attribute name and the second argument will be treated as the attribute value.
If the value is undefined, it will be set to ""
, which is useful for boolean attributes like disabled or hidden.
Example: $('button').set('disabled')
Example: $('button').set('disabled', 'true')
Example: $('button').set({ disabled: 'true', hidden: '' })
unset(attr: string): DomProxy
$('button').unset('disabled')
toggle(attr: string): DomProxy
Toggles an attribute on the element. If given a second argument, it will add the attribute if the second argument is truthy, and remove it if the second argument is falsy.
Example: $('button').toggle('disabled')
Example:
const mediumScreen = window.matchMedia("(min-width: 768px)").matches
$("button").toggle("disabled", mediumScreen)
datakey: string | { [key: string]: string }, value?: string: DomProxy
Sets one or more data attributes on the element.
If the first argument is an object, each key-value pair will be treated as a data attribute name and value.
If the first argument is a string, it will be treated as the data attribute name and the second argument will be treated as the data attribute value.
Example: $('button').data('id', '123')
Example: $('button').data({ id: '123', cool: 'true' })
attach(...children: (HTMLElement | DomProxy)[]): DomProxy
sanitize
option to false
.$('button').attach('<span>Click me!</span>')
$('button').attach($('.container'), { position: 'prepend' })
$('button').attach([$('.container'), '<span>Click me!</span>'], { position: 'before' })
$('button').attach('<image src="https://github.com/jazzypants1989/jessquery/raw/main/x" onerror="alert(\'hacked!\')">')
// No XSS attack here!$('button').attach('<image src="https://github.com/jazzypants1989/jessquery/raw/main/x" onerror="alert(\'hacked!\')">', { sanitize: false })
// XSS attack here!cloneTo(parentSelector: string, options?: { position: string; all: boolean }): DomProxy
Clone of the element to a new parent element in the DOM. The original element remains in its current location. If you want to move the element instead of cloning it, use moveTo
.
The position can be:
'append' (default): Adds the children to the end of the parent.
'prepend': Adds the children to the beginning of the parent.
'before': Adds the children before the parent.
'after': Adds the children after the parent.
The all option will clone the element into each new parent in the collection. If the all option is not passed, only the first parent in the collection will be used.
Example: $('div').cloneTo('.target')
// Clones and places inside first .target element (default behavior)
Example: $('div').cloneTo('.target', { position: 'after' })
// Clones and places after first .target element
Example: $('div').cloneTo('.target', { all: true })
// Clones and places inside all .target elements
Example: $('div').cloneTo('.target', { all: true, position: 'before' })
// Clones and places before all .target elements
moveTo(parentSelector: string, options?: { position: string }): DomProxy
Move the element to a new parent element in the DOM. The original element is moved from its current location. If you want to clone the element instead of moving it, use cloneTo
.
The position can be:
'append' (default): Adds the children to the end of the parent.
'prepend': Adds the children to the beginning of the parent.
'before': Adds the children before the parent.
'after': Adds the children after the parent.
The all option can technically be used, but it will only move the element to the last parent in the collection. This is because the element can only exist in one place at a time. Use cloneTo
if you want to move the element to multiple parents.
Example: $('div').moveTo('.target')
// Moves inside first .target element (default behavior)
Example: $('div').moveTo('.target', { position: 'after' })
// Moves after first .target element
become(replacement: HTMLElement | Array
The become method is used to replace a single element with a different element from elsewhere in the DOM.
Under the hood, it utilizes the native replaceWith
method but adds extra layers of functionality.
The replacement can be a simple HTMLElement, an array of HTMLElements, or another DomProxy instance.
Mode:
clone (default) - This makes a copy of the replacement element to use for the DomProxy. This clone includes the element, its attributes, and all its child nodes, but does not include event listeners. The original element is left untouched.
move - This moves the replacement element to the original element's position. The original element is removed from the DOM. This is the same as calling replaceWith
directly.
Example: $('div').become(newElement, {mode: "move"})
Expectation: Replaces div with newElement, literally moving it to the original div's position.
Example: $('div').become(newElement, {mode: "clone"})
Expectation: Replaces div with a deep clone of newElement, leaving the original newElement untouched.
Example: $('#button').become($$('.otherButtons'))
Expectation: Takes another DomProxy as the replacement. The first element of the DomProxy is chosen for the replacement.
purge(): DomProxy
$('button').purge()
send(options: {url?: string, json?: boolean, event?: Event, serializer?: (element) => void} & FetchOptions): DomProxy
Sends an HTTP request using the current element as the body of the request unless otherwise specified.
None of the options are required-- not even the URL.
If you do not provide a URL the method will:
First, look to see if it's in a form with an action property.
Next, it will look to see if the element is a button with a formaction property.
Next, it will try to see if the element is part of a form that has an action property.
Finally, it will take the current URL and slice off everything after the last slash. (http://example.com/foo/index.html -> http://example.com/foo/)
Unless the body
option is provided, it will be created automatically based on the element type:
If it's a form, the entire form will be serialized using the formData API unless a custom serializer is provided.
If it's an input, textarea, or select, the value will be used.
If it isn't a form or one of the above elements, we will check to see if the element has a form property or one can be found with the closest
method. If so, we will serialize the form using the formData API unless a custom serializer is provided.
If none of the above, the element's textContent will be used.
If the json
option is set to true, the request will be sent as JSON and the response will be parsed as JSON.
Otherwise, the request will be sent as FormData and the response will be parsed as text.
The serializer option can be used to provide a custom function to serialize the form. It will be passed the element as an argument and should return the serialized form data.
This will probably break if you don't return a FormData object, but I haven't tested it.
Example:
// Send a JSON request using input value, providing fallback and completion messages.
$("#myInput").send(myElement, {
url: "/api/data",
json: true,
onWait: () => $("#myInput").html('<span>Loading...</span>'),
waitTime: 500 // The onWait function only gets called if the request takes longer than 500ms
onSuccess: (data) => console.log("Received:", data),
onError: (error) => console.log("Error occurred:", error),
})
// Send form data using a custom serializer.
$("#myForm").send(myElement, {
url: "/api/data",
serializer: (form) => {
const formData = new FormData(form)
formData.append("extra", "data")
return formData
},
})
do(fn: (el: DomProxy) => Promise
Executes an asynchronous function and waits for it to resolve before continuing the chain (can be synchronous too).
Can receive the element as an argument, and you can still use all the proxy methods inside the function.
Example:
$("button").do(async (el) => {
const response = await fetch("https://api.github.com/users/jazzypants1989")
const data = await response.json()
el.text(data.name)
})
defer(fn: (el: DomProxy) => void): DomProxy
Schedules a function for deferred execution on the element.
This will push the operation to the very end of the internal event loop.
Usually, everything will happen in sequence anyways. Given the predictability of each queue, defer
has limited use cases and should be used sparingly. The whole point of JessQuery is to make things predictable, so you should just put the function at the end of the chain if you can.
The only problem is if you set up an event listener using the same variable that has lots of queued behavior-- especially calls to the wait method. Just wrap the wait call and everything after it in defer to ensure that event handlers don't get stuck behind these in the queue.
defer
will capture the element at the time of the call, so this should not be mixed with context switching methods like parent
or pickAll
.
Honestly, I'm not sure if this even makes much sense. I just spent a bunch of time building a crazy queue system, and I feel like I need to expose it. If you have any ideas for how to make this more useful, please open an issue or PR.
Example:
const button = $("button")
button
.text("this won't do anything for a second because of the wait call")
.on("click", () => button.text("clicked"))
.wait(1000)
//but if we wrap the wait call in defer, the events will not be queued behind it
button
.text("this will be immediately responsive due to the defer call")
.on("click", () => button.text("clicked"))
.defer((el) => el.wait(1000).text("Yay!"))
// THIS ONLY OCCURS BECAUSE THE SAME VARIABLE IS USED FOR THE EVENT LISTENER AND THE CHAIN
$("button")
.on("click", () => $("button").text("clicked"))
.wait(1000) // NO PROBLEM HERE
fromJSON(url: string, transformFunc: (el: DomProxy, json: any) => void, options?: FetchOptions): DomProxy
$("#item").fromJSON("/api/data", (element, json) => {
element.text(json.value)
})
$("#item").fromJSON("/api/data", (element, json) => {
element.html(`<span>${json.description}</span>`)
})
$('#news-item').fromJSON('/api/news-item', (element, json) => {
{ title, summary } = json;
element.html(`<h1>${title}</h1>
<p>${summary}</p>`);
},
{
error: 'Failed to load news item',
pnWait: () => $('#news-item').text('Loading news item...'),
onSuccess: () => console.log('News item loaded')
}
)
fromHTML(url: string, options?: FetchOptions): DomProxy
$('#template').fromHTML('/template.html')
$('#update').fromHTML('/update.html', { onWait: () => $('#fromHTML)'.text('Loading update...'), error: 'Failed to load update!' })
$('#content').fromHTML('/malicious-content.html', { sanitize: false })
fromStream(url: string, options?: { sse?: boolean; add?: boolean; toTop?: boolean; error?: string; onError?: () => void; onWait?: () => void; waitTime?: number; runScripts?: boolean; sanitize?: boolean; onSuccess?: (data: any) => void }): DomProxy
fromHTML
and fromJSON
methods for normal streams, but with a few caveats for SSEs.onSuccess
callback will be invoked after each message is received, and it will receive the last event as an argument.add
and onTop
, which determine how the new data is added to the existing content.$('#content').fromStream('/api/data', { sanitize: false })
<-- Only for trusted sources!$('#liveFeed').fromStream('/api/live', { sse: true, add: true, onSuccess: (data) => console.log('New data received:', data) })
transition(keyframes: Keyframe[] | PropertyIndexedKeyframes, options: KeyframeAnimationOptions): DomProxy
animate
method instead.$('button').transition([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 })
wait(ms: number): DomProxy
animate
method instead.defer
which will allow the events to skip ahead in the queue.wait
will always reliably hold up a queue when used in the middle of a chain, but each method that contains the element as an argument will operate on a different queue so wait()
calls inside of them will not be respected in subsequent methods on the parent chain.do
, fromJSON
, and if
, will respect wait
calls held within them for their own individual queues, but they will not hold up the queue of the parent chain.wait
inside the callback for the method, you must continue the chain inside. Otherwise, the chain will continue immediately.$('button').wait(1000)
testButton
.on("click", () => {
testButton.text(`Clicked ${++count} times`)
})
.wait(2000)
.css({ color: "red" }) // BAD! The click handler will not register for 2 seconds!
testButton
.on("click", () => {
testButton.text(`Clicked ${++count} times`)
})
.defer((el) => el.wait(2000).css({ color: "red" })) // Good! The click handler will register immediately!
testButton
.on("click", () => {
$(".testButton").text(`Clicked ${++count} times`)
})
.wait(2000)
.css({ color: "red" }) // Less good, but still better!
// You made a new proxy for no reason, but at least the user can click the button!
testButton
.do((el) => el.wait(2000).css({ color: "green" }).wait(2000))
.css({ color: "red" })
// This will turn red, then green. NOT GREEN, THEN RED!
next(): DomProxy
$('button').css('color', 'red').next().css('color', 'blue')
prev(): DomProxy
$('button').css('color', 'red').prev().css('color', 'blue')
first(): DomProxy
$('button').css('color', 'red').first().css('color', 'blue')
last(): DomProxy
$('button').css('color', 'red').last().css('color', 'blue')
parent(): DomProxy
$('button').css('color', 'red').parent().css('color', 'blue')
ancestor(ancestorSelector: string): DomProxy
closest
API under the hood.$('.buttons').css('color', 'red').ancestor('.container').css('color', 'blue')
pick(subSelector: string): DomProxy
$('.container').css('color', 'red').pick('.buttons').css('color', 'blue')
pickAll(subSelector: string): DomProxyCollection
$('.container').css('color', 'red').pickAll('.buttons').css('color', 'blue')
siblings(): DomProxyCollection
$('button').css('color', 'red').siblings().css('color', 'blue')
kids(): DomProxyCollection
$('button').css('color', 'red').kids().css('color', 'blue')
if: (options: { is: (el: DomProxy
Executes conditional logic on the element based on its current state.
The is
option is a function that receives the element as an argument and returns a boolean.
The then
option is a function that receives the element as an argument and is executed if the is
function returns true.
The or
option is a function that receives the element as an argument and is executed if the is
function returns false.
Example:
$("button").if({
is: (el) => el.hasClass("active"),
then: (el) => el.text("Active"),
or: (el) => el.text("Inactive"),
})
takeWhile: (predicate: (el: DomProxy
Filters the current element in the proxy based on a predicate.
If the current element does not satisfy the predicate, the proxy will be emptied.
This will throw an error if the proxy was created as "fixed" (with a second argument of true).
The predicate is a function that receives the element as an argument and returns a boolean.
The function will run from the first element to the last, unless you pass a second argument of true. Then, it will run from the last element to the first. This generally corresponds with position in the DOM, but it's not guaranteed.
It's essential to use this method with caution as it can empty the proxy if the current element does not match the predicate.
The refresh()
method can be used to restore the proxy to its original state.
For simple, conditional logic, use the if
method instead.
Example:
$("button")
.kids()
.takeWhile((el) => el.hasClass("active"))
.css("color", "red")
// will do nothing if the button does not have the active class
refresh(): DomProxy
$('button').css('color', 'red').next().css('color', 'blue').refresh().css('color', 'green')
A proxy covering a collection of HTML elements that allows you to chain methods sequentially (including asynchronous tasks) and then execute them one after the other. It includes 43 of these custom methods, but you can still use the full DOM API if you need to.
on(ev: string, fn: EventListenerOrEventListenerObject): DomProxyCollection
$$('button').on('click', () => console.log('clicked'))
once(ev: string, fn: EventListenerOrEventListenerObject): DomProxyCollection
$$('button').once('click', () => console.log('clicked'))
off(ev: string, fn: EventListenerOrEventListenerObject): DomProxyCollection
$$('button').off('click', clickHandler)
delegate(event: string, subSelector: string, handler: EventListenerOrEventListenerObject): DomProxyCollection
$$('.container').delegate('click', '.buttons', (e) => console.log('Button clicked'))
html(newHtml: string, outerHTML?: boolean): DomProxyCollection
Change the HTML of the elements with an UNSANITIZED string of new HTML. This is useful if you want to add a script tag or something. If you want to sanitize the HTML, use sanitize
instead.
By default, only the element's children will be replaced (innerHTML). If you want to replace the element itself (outerHTML), set the second argument to true.
Example: $$('button').html('<span>Click me!</span>')
Expectation: Every button will have a span inside of it.
Example: $$('button').html('<span>Click me!</span>', true)
Expectation: Every button will be replaced with a span.
sanitize: (html: string, sanitizer?: (html: string) => string) => DomProxyCollection
$$('button').sanitize('<span>Click me!</span>')
text(newText: string): DomProxyCollection
$$('.buttons').text('Click me!')
val(newValue: string | number | (string | number)[] | FileList): DomProxyCollection
value
, checked
) will be adjusted. For other elements, the textContent
property will be set.$$('input[type="text"]').val('New Value')
$$('input[type="checkbox"]').val(true)
$$('input[type="radio"]').val('radio1')
$$('input[type="file"]').val(myFileList)
$$('select[multiple]').val(['option1', 'option2'])
css(prop: string | Record<string, string>, value?: string): DomProxyCollection
$$('button').css('color', 'red')
$$('button').css({ color: 'red', backgroundColor: 'blue' })
addStyleSheet(cssString: string): DomProxyCollection
@scope
rule being automatically inserted, but that's still only in Chromium browsers.$$('button').addStyleSheet('button:hover { color: red }')
addClass(className: string): DomProxyCollection
Adds one or more classes to the elements.
To add multiple classes, separate them with commas.
Alternatively, you can just spread out an array.
Example: $$('.buttons').addClass('btn')
Example: $$('.buttons').addClass('btn', 'btn-primary')
Example:
const classes = ["btn", "btn-primary"]
$$(".buttons").addClass(...classes)
removeClass(className: string): DomProxyCollection
Removes one or more classes from the elements.
To remove multiple classes, separate them with commas.
Alternatively, you can just spread out an array.
Example: $$('.buttons').removeClass('btn')
Example: $$('.buttons').removeClass('btn', 'btn-primary')
Example:
const classes = ["btn", "btn-primary"]
$$(".buttons").removeClass(...classes)
toggleClass(className: string, value?: boolean): DomProxyCollection
$$('.buttons').toggleClass('btn')
const mediumScreen = window.matchMedia("(min-width: 768px)").matches
$$(".buttons").toggleClass("btn", mediumScreen)
set(attr: string | Record<string, string>, value?: string): DomProxyCollection
Sets an attribute on the elements.
If the first argument is an object, each key-value pair will be treated as an attribute name and value.
If the first argument is a string, it will be treated as the attribute name and the second argument will be treated as the attribute value.
If the value is undefined, it will be set to ""
, which is useful for boolean attributes like disabled or hidden.
Example: $$('button').set('disabled')
Example: $$('button').set('disabled', 'true')
Example: $$('button').set({ disabled: 'true', hidden: 'true' })
unset(attr: string): DomProxyCollection
$$('button').unset('disabled')
toggle(attr: string, value?: boolean): DomProxyCollection
Toggles an attribute on the elements.
If given a second argument, it will set the attribute if the second argument is truthy, and remove it if the second argument is falsy.
Example: $$('button').toggle('disabled')
Example:
const mediumScreen = window.matchMedia("(min-width: 768px)").matches
$$(".buttons").toggle("disabled", mediumScreen)
data(attr: string | { [key: string]: string}, value?: string): DomProxyCollection
Sets one or more data attributes on the elements.
If the first argument is an object, each key-value pair will be treated as a data attribute name and value.
If the first argument is a string, it will be treated as the data attribute name and the second argument will be treated as the data attribute value.
Example: $$('button').data('id', '123')
Example: $$('button').data({ id: '123', name: 'myButton' })
attach(...children: (HTMLElement | DomProxy)[]): DomProxyCollection
sanitize
option to false
.$$('button').attach('<span>Click me!</span>')
$$('button').attach($('.container'), { position: 'prepend' })
$$('button').attach([$('.container'), '<span>Click me!</span>'], { position: 'before' })
$$('button').attach('<image src="https://github.com/jazzypants1989/jessquery/raw/main/x" onerror="alert(\'hacked!\')">')
// No XSS attack here!$$('button').attach('<image src="https://github.com/jazzypants1989/jessquery/raw/main/x" onerror="alert(\'hacked!\')">', { sanitize: false })
// XSS attack here!cloneTo(parentSelector: string, options?: { position: string; all: boolean }): DomProxyCollection
Clone of the elements to a new parent element in the DOM. The original elements remain in their current location. If you want to move the elements instead of cloning them, use moveTo
.
The position can be:
'append' (default): Adds the children to the end of the parent.
'prepend': Adds the children to the beginning of the parent.
'before': Adds the children before the parent.
'after': Adds the children after the parent.
The all option will clone the elements into each new parent in the collection. If the all option is not passed, only the first parent in the collection will be used.
Example: $$('div').cloneTo('.target')
// Clones and places inside first .target element (default behavior)
Example: $$('div').cloneTo('.target', { position: 'after' })
// Clones and places after first .target element
Example: $$('div').cloneTo('.target', { all: true })
// Clones and places inside all .target elements
Example: $$('div').cloneTo('.target', { all: true, position: 'before' })
// Clones and places before all .target elements
moveTo(parentSelector: string, options?: { position: string }): DomProxyCollection
cloneTo
.$$('div').moveTo('.target')
// Moves elements inside first .target element (default behavior)$$('div').moveTo('.target', { position: 'before' })
// Moves elements before first .target element$$('div').moveTo('.target', { position: 'after' })
// Moves elements after first .target elementbecome(replacement: HTMLElement | Array
The become method is used to replace a single element with a different element from elsewhere in the DOM.
Under the hood, it utilizes the native replaceWith
method but adds extra layers of functionality.
The replacement can be a simple HTMLElement, an array of HTMLElements, or another DomProxy instance.
Mode:
clone (default) - This makes a copy of the replacement element to use for the DomProxy. This clone includes the element, its attributes, and all its child nodes, but does not include event listeners. The original element is left untouched.
move - This moves the replacement element to the original element's position. The original element is removed from the DOM. This is the same as calling replaceWith
directly.
Example: $$('div').become(newElement, {mode: "move"})
Expectation: Replaces div with newElement, literally moving it to the original div's position.
Example: $$('div').become(newElement, {mode: "clone"})
Expectation: Replaces div with a deep clone of newElement, leaving the original newElement untouched.
Example: $$('.buttons').become($$('.otherButtons'))
Expectation: Takes another DomProxy as the replacement. The first element of the DomProxy is chosen for the replacement.
purge(): DomProxyCollection
$$('.buttons').purge()
send(options: { url?: string, json?: boolean, event?: Event, serializer?: (elements) => void } & FetchOptions) => DomProxyCollection
Sends HTTP requests using each of the current elements as the body of the requests unless otherwise specified.
This will not cache or dedupe the fetches-- you should probably be careful with this.
None of the options are required-- not even the URL.
If you do not provide a URL the method will:
First, look to see if it's in a form with an action property and use that.
If it can't find that, it will look to see if the element is a button with a formaction property and use that.
If it can't find that, it will try to see if the element is part of a form that has an action property and use that.
Finally, if it can't find anything else, it will use the current URL.
Unless the body
option is provided, it will be created automatically based on the element type:
If it's a form, the entire form will be serialized using the formData API unless a custom serializer is provided.
If it's an input, textarea, or select, the value will be used.
If it isn't a form or one of the above elements, we will check to see if the element has a form property or one can be found with the closest
method. If so, we will serialize the form using the formData API unless a custom serializer is provided.
If none of the above, the element's textContent will be used.
If the json
option is set to true, the request will be sent as JSON and the response will be parsed as JSON.
Otherwise, the request will be sent as FormData and the response will be parsed as text.
The serializer option can be used to provide a custom function to serialize the form. It will be passed the element as an argument and should return the serialized form data.
This will probably break if you don't return a FormData object, but I haven't tested it.
Example: $$('button').send({ url: '/api/submit' })
Example: $$('button').send({ url: '/api/submit', method: 'GET' })
Example: $$('button').send({ url: '/api/submit', json: true })
do(fn: (el: DomProxy) => Promise
$$('button').do(async (el) => { // The elements are passed as an argument const response = await fetch('/api') const data = await response.json() el.text(data.message) // All the methods are still available })
defer(fn: (el: DomProxy) => void): DomProxyCollection
Schedules a function for deferred execution on the elements.
This will push the operation to the very end of the internal event loop.
Usually, everything will happen in sequence anyways. Given the predictability of each queue, defer
has limited use cases and should be used sparingly. The whole point of JessQuery is to make things predictable, so you should just put the function at the end of the chain if you can.
This only becomes a problem if you set up an event listener using the same variable that has lots of queued behavior-- especially calls to the wait method. Just wrap the wait call and everything after it in defer to ensure that event handlers don't get stuck behind these in the queue.
defer
will capture the elements at the time of the call, so this should not be mixed with context switching methods like parent
or pickAll
.
Honestly, I'm not sure if this even makes much sense. I just spent a bunch of time building a crazy queue system, and I feel like I need to expose it. If you have any ideas for how to make this more useful, please open an issue or PR.
Example:
const buttons = $$(".buttons")
buttons
.text("this won't do anything for a second because of the wait call")
.on("click", () => buttons.text("clicked"))
.wait(1000)
//but if we wrap the wait call in defer, the events will not be queued behind it
buttons
.text("this will be immediately responsive due to the defer call")
.defer((el) => el.wait(1000).text("Yay!"))
// THIS ONLY OCCURS BECAUSE THE SAME VARIABLE IS USED FOR THE EVENT LISTENER AND THE CHAIN
$$(".buttons").on("click", () =>
$$(".buttons").text("clicked").wait(1000)
) // NO PROBLEM HERE
``
fromJSON(url: string, transformFunc: (el: DomProxy, json: any) => void, options?: FetchOptions): DomProxyCollection
Fetches a JSON resource from the provided URL and applies a transformation function which uses the fetched JSON and the proxy's target element as arguments.
The transform function can be used to set the text, html, or any other property of the element.
The options object can be used define a plethora of options defined in FetchOptions.
These options extend the Request interface, so you can use all of the normal fetch options as well. (e.g., method, headers, etc.)
Example:
$$(".item").fromJSON("/api/data", (element, json) => {
element.text(json.value)
})
$$(".item").fromJSON("/api/data", (element, json) => {
element.html(`<span>${json.description}</span>`)
})
$$('.news-item').fromJSON('/api/news-item', (element, json) => {
{ title, summary } = json;
element.html(`<h1>${title}</h1>
<p>${summary}</p>`);
},
{
error: 'Failed to load news item',
onWait: () => `<h1>Loading news item...</h1>`,
onSuccess: () => console.log('News item loaded')
}
fromHTML(url: string, options?: FetchOptions): DomProxyCollection
$$('.template').fromHTML('/template.html')
$$('.update').fromHTML('/update.html', { fallback: 'Loading update...', error: 'Failed to load update!' })
$$('.content').fromHTML('/malicious-content.html', { sanitize: false })
fromStream(url: string, options?: { sse?: boolean; add?: boolean; toTop?: boolean; error?: string; onError?: () => void; onWait?: () => void; waitTime?: number; runScripts?: boolean; sanitize?: boolean; onSuccess?: (data: any) => void }): DomProxyCollection
fromHTML
and fromJSON
methods for normal streams, but with a few caveats for SSEs.onSuccess
callback will be invoked after each message is received, and it will receive the last event as an argument.add
and onTop
, which determine how the new data is added to the existing content.$$('.content').fromStream('/api/data', { sanitize: false })
<-- Only for trusted sources!$$('.liveFeed').fromStream('/api/live', { sse: true, add: true, onSuccess: (data) => console.log('New data received:', data) })
transition(keyframes: Keyframe[] | PropertyIndexedKeyframes, options: KeyframeAnimationOptions): DomProxyCollection
animate
method instead.$$('.buttons').transition([{ opacity: 0 }, { opacity: 1 }], { duration: 1000 })
wait(ms: number): DomProxyCollection
animate
method instead.defer
which will allow the events to skip ahead in the queue.wait
will always reliably hold up a queue when used in the middle of a chain, but each method that contains the element as an argument will operate on a different queue so wait()
calls inside of them will not be respected in subsequent methods on the parent chain.do
, fromJSON
, and if
, will respect wait
calls held within them for their own individual queues, but they will not hold up the queue of the parent chain.wait
inside the callback for the method, you must continue the chain inside. Otherwise, the chain will continue immediately.$$('buttons').wait(1000)
testButtons
.on("click", () => {
testButtons.text(`Clicked ${++count} times`)
})
.wait(2000)
.css({ color: "red" }) // BAD! The click handler will not register for 2 seconds!
testButtons
.on("click", () => {
testButtons.text(`Clicked ${++count} times`)
})
.defer((el) => el.wait(2000).css({ color: "red" })) // Good! The click handler will register immediately!
testButtons
.on("click", () => {
$(".testButton").text(`Clicked ${++count} times`)
})
.wait(2000)
.css({ color: "red" }) // Less good, but still better!
// You made a new proxy for no reason, but at least the user can click the button!
testButtons
.do((el) => el.wait(2000).css({ color: "green" }).wait(2000))
.css({ color: "red" })
// This will turn red, then green. NOT GREEN, THEN RED!
next(): DomProxyCollection
$$('button').css('color', 'red').next().css('color', 'blue')
prev(): DomProxyCollection
$$('button').css('color', 'red').prev().css('color', 'blue')
first(): DomProxyCollection
$$('button').css('color', 'red').first().css('color', 'blue')
last(): DomProxyCollection
$$('button').css('color', 'red').last().css('color', 'blue')
parent(): DomProxyCollection
$$('button').css('color', 'red').parent().css('color', 'blue')
ancestor(ancestorSelector: string): DomProxyCollection
closest
API under the hood.$$('.buttons').css('color', 'red').ancestor('.container').css('color', 'blue')
pick(subSelector: string): DomProxyCollection
$$('.container').css('color', 'red').pick('.buttons').css('color', 'blue')
pickAll(subSelector: string): DomProxyCollection
$$('.container').css('color', 'red').pickAll('.buttons').css('color', 'blue')
siblings(): DomProxyCollection
$$('button').css('color', 'red').siblings().css('color', 'blue')
kids(): DomProxyCollection
$$('.container').css('color', 'red').kids().css('color', 'blue')
if: (options: { is: (el: DomProxyCollection
Executes conditional logic on the elements based on their current state.
The is
option is a function that receives the elements as an argument and returns a boolean.
The then
option is a function that receives the elements as an argument and is executed if the is
function returns true.
The or
option is a function that receives the elements as an argument and is executed if the is
function returns false.
Example:
$$(".buttons").if({
is: (el) => el.hasClass("active"),
then: (el) => el.text("Active"),
or: (el) => el.text("Inactive"),
})
takeWhile: (predicate: (el: DomProxyCollection
Filters the current elements in the proxy based on a predicate.
The predicate is a function that receives the elements as an argument and returns a boolean.
The predicate will be executed on each element in the proxy in order. If the predicate returns true, the element will be kept. If the predicate returns false, the element will be removed.
It will run on the elements in the order they were added to the proxy unless the reverse
argument is set to true. In that case, it will run in reverse order. Usually, this will correspond to position in the DOM, but it's not guaranteed.
It's essential to use this method with caution as it can empty the proxy if the current elements do not match the predicate.
The refresh()
method can be used to restore the proxy to its original state.
This will throw an error if the proxy was created as "fixed" (with a second argument of true).
For simple, conditional logic, use the if
method instead.
Example:
$$(".buttons")
.kids()
.takeWhile((el) => el.hasClass("active"))
.css("color", "red")
// will do nothing if the buttons do not have the active class
refresh(): DomProxyCollection
$$('button').next().css('color', 'blue').refresh().css('color', 'green')
onError?: () => void
onError
callback is provided.Failed to load ${type}
(e.g., Failed to load HTML
)onSuccess?: () => void
fromStream()
and the sse
option is set to true, this will receive the last event as an argument.onWait?: () => void
A callback to execute while the fetch is pending.
This will not run for 250ms by default, but you can change that by setting the waitTime
option.
The error message to display if the fetch fails.
If you provide an onError callback, the error message will be ignored.
waitTime?: number
onWait
callback.retries?: number
retryDelay?: number
runScripts?: boolean
sanitize?: boolean
Sanitizer?:
sse?: boolean
fromStream()
add?: boolean
toTop?: boolean
Finally, all of the normal Request options are available as well.
If you have any ideas for new features or improvements, feel free to open an issue or a PR. I'm always open to suggestions! I started this as a bit of a joke, but I think it turned into something pretty useful. I'm sure there are a lot of things that could be improved, so I welcome any and all feedback.