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

```
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://seanime.gitbook.io/seanime-extensions/plugins/ui/user-interface/dom.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
