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.

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 string

  • options: (Optional)

    • withInnerHTML: Boolean - Include the innerHTML property in the matched elements

    • identifyChildren: 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 string

  • options: 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 string

  • callback: Function(elements: DOMElement[]) => void

  • options: 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 name

  • value: 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 name

  • value: 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