> For the complete documentation index, see [llms.txt](https://seanime.gitbook.io/seanime-extensions/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/dom.md).

# DOM

## Core Methods

### onReady

Executes a callback when the DOM is ready.

**Parameters:**

* `callback`: Function to execute

**Example:**

```typescript
ctx.dom.onReady(() => {
  console.log("DOM is ready...")
})
```

### onMainTabReady

Executes a callback when the the main tab is ready or each time there is a new main tab.

It will run right after `onReady` .

{% hint style="info" %}
A "main tab" is the currently focused tab that sends and receives DOM events.
{% endhint %}

**Parameters:**

* `callback`: Function to execute

**Example:**

```typescript
ctx.dom.onMainTabReady(() => {
  console.log("Main tab 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:**

```typescript
// 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:**

```typescript
// 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:**

```typescript
// 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:**

```typescript
// 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:**

```typescript
// 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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
// ❌ 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:**

```typescript
// ✅ 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:**

```typescript
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 })
```

```
```
