DOM
API for DOM manipulation in Seanime plugins. Each DOM operation involves communication between the plugin and the browser, so understanding performance considerations is important.
Pitfall
Users having multiple tabs open can lead to unexpected behavior.
This happens because DOM manipulation events are broadcasted to all connected clients.
Core Methods
onReady
Executes a callback when the DOM is ready.
Parameters:
callback
: Function to execute
Example:
ctx.dom.onReady(() => {
console.log("DOM is ready...")
})
query
Queries the DOM for elements matching the selector.
Parameters:
selector
: CSS selector stringoptions
: (Optional)withInnerHTML
: Boolean - Include theinnerHTML
property in the matched elementsidentifyChildren
: Boolean - Assign IDs to all child elements
Returns: Promise<DOMElement[]>
Example:
// Get all episode cards
const episodeCards = await ctx.dom.query("[data-episode-card]", { withInnerHTML: true })
queryOne
Queries the DOM for a single element matching the selector.
Parameters:
selector
: CSS selector stringoptions
: Same as query()
Returns: Promise<DOMElement | null>
Example:
// Get the main container
const mainContainer = await ctx.dom.queryOne("#main-container")
// Get user profile with inner HTML
const userProfile = await ctx.dom.queryOne(".user-profile", { withInnerHTML: true })
observe
Observes changes to the DOM for elements matching the selector. Returns functions to stop observing and to manually refetch elements.
Parameters:
selector
: CSS selector stringcallback
: Function(elements: DOMElement[]) => voidoptions
: Same as query()
Returns: [stopObserving: () => void, refetch: () => void]
Example:
// Observe when new content cards are added to the page
const [stopObserving, refetchCards] = ctx.dom.observe(".content-card", async (cards) => {
console.log(`Found ${cards.length} content cards`)
// Process each card
for (const card of cards) {
const title = await card.queryOne(".card-title")
if (title) {
console.log(`Card title: ${await title.getText()}`)
}
}
})
// Later, to stop observing:
stopObserving()
// To manually trigger a refresh:
refetchCards()
createElement
Creates a new DOM element.
Parameters:
tagName
: HTML tag name
Returns: Promise
Example:
// Create a new div element
const newDiv = await ctx.dom.createElement("div")
newDiv.setAttribute("class", "custom-element")
newDiv.setText("This is a dynamically created element")
// Create a button
const newButton = await ctx.dom.createElement("button")
newButton.setText("Click me")
asElement
Returns a DOM element object from an element ID. Useful when using identifyChildren option.
Parameters:
elementId
: String ID of the element
Returns: DOMElement
Example:
// When using with identifyChildren
const container = await ctx.dom.queryOne("#container", {
withInnerHTML: true,
identifyChildren: true
})
// Parse HTML locally
const $ = LoadDoc(container.innerHTML)
const buttonId = $(".action-button").attr("id")
// Get a reference to the actual DOM element
const button = ctx.dom.asElement(buttonId)
button.setText("New Button Text")
DOM Element Methods
Content Methods
getText()
Gets the text content of the element.
Returns: Promise
Example:
const titleElement = await ctx.dom.queryOne(".title")
const titleText = await titleElement.getText()
console.log(`Title: ${titleText}`)
setText(text)
Sets the text content of the element.
Parameters:
text
: String to set as text content
Example:
const statusElement = await ctx.dom.queryOne(".status")
statusElement.setText("Active")
getAttribute(name)
Gets the value of an attribute.
Parameters:
name
: Attribute name
Returns: Promise<string | null>
Example:
const link = await ctx.dom.queryOne("a.resource-link")
const href = await link.getAttribute("href")
console.log(`Resource URL: ${href}`)
getAttributes()
Gets all attributes of the element.
Returns: Promise<Record<string, string>>
Example:
const image = await ctx.dom.queryOne("img.poster")
const attributes = await image.getAttributes()
console.log(`Image src: ${attributes.src}`)
console.log(`Image alt: ${attributes.alt}`)
setAttribute(name, value)
Sets the value of an attribute.
Parameters:
name
: Attribute namevalue
: Attribute value
Example:
const image = await ctx.dom.queryOne(".thumbnail")
image.setAttribute("src", "https://example.com/new-image.jpg")
image.setAttribute("alt", "Updated thumbnail image")
removeAttribute(name)
Removes an attribute.
Parameters:
name
: Attribute name
Example:
const button = await ctx.dom.queryOne(".disabled-button")
button.removeAttribute("disabled")
hasAttribute(name)
Checks if the element has an attribute.
Parameters:
name
: Attribute name
Returns: Promise
Example:
const form = await ctx.dom.queryOne("form")
const isSubmitted = await form.hasAttribute("data-submitted")
if (isSubmitted) {
console.log("Form was already submitted")
}
Style Methods
setStyle(property, value)
Sets a style property on the element.
Parameters:
property
: CSS property namevalue
: Property value
Example:
const spoilerText = await ctx.dom.queryOne(".spoiler")
spoilerText.setStyle("filter", "blur(5px)")
spoilerText.setStyle("cursor", "pointer")
getStyle(property?)
Gets the style of the element.
Parameters:
property
: (Optional) Property name
Returns: Promise<string | Record<string, string>>
Example:
const element = await ctx.dom.queryOne(".styled-element")
// Get single property
const color = await element.getStyle("color")
// Get all styles
const allStyles = await element.getStyle()
removeStyle(property)
Removes a style property.
Parameters:
property
: CSS property name
Example:
const spoilerText = await ctx.dom.queryOne(".spoiler")
// Remove blur effect when clicked
spoilerText.removeStyle("filter")
hasStyle(property)
Checks if the element has a style property set.
Parameters:
property
: CSS property name
Returns: Promise
Example:
const element = await ctx.dom.queryOne(".target")
const hasTransition = await element.hasStyle("transition")
if (!hasTransition) {
element.setStyle("transition", "opacity 0.3s ease")
}
getComputedStyle(property)
Gets the computed style of the element.
Parameters:
property
: CSS property name
Returns: Promise
Example:
const box = await ctx.dom.queryOne(".box")
const actualWidth = await box.getComputedStyle("width")
console.log(`Box actual width: ${actualWidth}`)
CSS Class Methods
addClass(className)
Adds a class to the element.
Parameters:
className
: CSS class name
Example:
const card = await ctx.dom.queryOne(".card")
card.addClass("highlighted")
card.addClass("selected")
hasClass(className)
Checks if the element has a class.
Parameters:
className
: CSS class name
Returns: Promise
Example:
const row = await ctx.dom.queryOne("tr")
const isActive = await row.hasClass("active")
if (!isActive) {
row.addClass("active")
}
DOM Traversal and Manipulation
append(child)
Appends a child to the element.
Parameters:
child
: DOMElement to append
Example:
const container = await ctx.dom.queryOne(".container")
const newElement = await ctx.dom.createElement("div")
newElement.setText("New child element")
container.append(newElement)
before(sibling)
Inserts a sibling before the element.
Parameters:
sibling
: DOMElement to insert
Example:
const referenceElement = await ctx.dom.queryOne(".reference")
const newElement = await ctx.dom.createElement("div")
newElement.setText("Inserted before reference")
referenceElement.before(newElement)
after(sibling)
Inserts a sibling after the element.
Parameters:
sibling
: DOMElement to insert
Example:
const referenceElement = await ctx.dom.queryOne(".reference")
const newElement = await ctx.dom.createElement("div")
newElement.setText("Inserted after reference")
referenceElement.after(newElement)
remove()
Removes the element from the DOM.
Example:
const outdatedNotice = await ctx.dom.queryOne(".outdated-notice")
if (outdatedNotice) {
outdatedNotice.remove()
}
getParent(opts?)
Gets the parent of the element.
Parameters:
opts
: (Optional) Same options as query()
Returns: Promise<DOMElement | null>
Example:
const listItem = await ctx.dom.queryOne("li.active")
const list = await listItem.getParent()
console.log(`Parent element tag: ${list.tagName}`)
getChildren(opts?)
Gets the children of the element.
Parameters:
opts
: (Optional) Same options as query()
Returns: Promise<DOMElement[]>
Example:
const list = await ctx.dom.queryOne("ul.menu")
const listItems = await list.getChildren()
console.log(`Menu has ${listItems.length} items`)
query(selector)
Queries the DOM for elements that are descendants of this element and match the selector.
Parameters:
selector
: CSS selector string
Returns: Promise<DOMElement[]>
Example:
const articleBody = await ctx.dom.queryOne("article.blog-post")
const paragraphs = await articleBody.query("p")
console.log(`Article has ${paragraphs.length} paragraphs`)
queryOne(selector)
Queries the DOM for a single element that is a descendant of this element and matches the selector.
Parameters:
selector
: CSS selector string
Returns: Promise<DOMElement | null>
Example:
const card = await ctx.dom.queryOne(".card")
const title = await card.queryOne(".card-title")
const description = await card.queryOne(".card-description")
addEventListener(event, callback)
Adds an event listener to the element.
Parameters:
event
: Event name (e.g., "click")callback
: Function to call when the event occurs
Returns: Function to remove the event listener
Example:
const button = await ctx.dom.queryOne(".action-button")
const removeListener = button.addEventListener("click", (event) => {
console.log("Button clicked!")
})
// Later, to remove the listener:
removeListener()
Performance Best Practices
Minimize Roundtrips
Each async DOM method call represents a roundtrip between your plugin (on the server) and the browser. Minimize these for better performance.
Inefficient:
// ❌ Multiple sequential roundtrips
const items = await ctx.dom.query(".item")
for (const item of items) {
const title = await item.queryOne(".title")
const description = await item.queryOne(".description")
const image = await item.queryOne("img")
if (title) await title.setText("New Title")
if (description) await description.setText("New Description")
}
Efficient:
// ✅ Get all data at once, process locally
const items = await ctx.dom.query(".item", {
withInnerHTML: true,
identifyChildren: true
})
for (const item of items) {
const $ = LoadDoc(item.innerHTML)
// Access elements without additional roundtrips
const titleId = $(".title").attr("id")
const descriptionId = $(".description").attr("id")
const imageId = $("img").attr("id")
// Now make direct modifications
if (titleId) ctx.dom.asElement(titleId).setText("New Title")
if (descriptionId) ctx.dom.asElement(descriptionId).setText("New Description")
}
Use observe() Efficiently
When using observe(), apply performance optimizations to handle elements efficiently.
Example:
const [stopObserving, refetch] = ctx.dom.observe(".dynamic-content", async (elements) => {
// Use withInnerHTML and identifyChildren for efficient processing
for (const element of elements) {
const $ = LoadDoc(element.innerHTML)
// Process locally first
const buttonIds = $("button").map((i, el) => $(el).attr("id")).get()
// Then make direct DOM updates
for (const buttonId of buttonIds) {
if (buttonId) {
const button = ctx.dom.asElement(buttonId)
button.addEventListener("click", handleButtonClick)
}
}
}
}, { withInnerHTML: true, identifyChildren: true })
Last updated