PyPositron Docs

ElementList Class

The ElementList class represents a collection of DOM elements in PyPositron. It's returned by methods like getElementsByClassName(), getElementsByTagName(), and querySelectorAll(). ElementList provides a Python-like interface for working with multiple elements at once.

Overview

ElementList acts like a Python list but contains DOM elements. It allows you to:

  • Access elements by index
  • Iterate through all elements
  • Get the count of elements
  • Apply operations to multiple elements efficiently

Properties

  • length -> int: The number of elements in the list. This property provides the count of elements contained in the ElementList.

Methods

  • __getitem__(index: int) -> Element: Retrieves an element by index. Raises IndexError if index is out of range.
  • __len__() -> int: Returns the number of elements in the list. This allows you to use Python's built-in len() function.
  • __iter__() -> Iterator[Element]: Allows iteration over the elements in the list using Python's for-loop syntax.

Usage Examples

Basic Element Access

def main(ui):
    # Get all buttons in the document
    buttons = ui.document.getElementsByClassName("btn")
    
    # Access by index
    if len(buttons) > 0:
        first_button = buttons[0]
        first_button.innerText = "First Button"
    
    # Check if elements exist
    if buttons.length > 0:
        print(f"Found {buttons.length} buttons")
    
    # Get the last button
    if len(buttons) > 0:
        last_button = buttons[len(buttons) - 1]
        last_button.style.backgroundColor = "red"

Iterating Through Elements

def main(ui):
    # Get all paragraph elements
    paragraphs = ui.document.getElementsByTagName("p")
    
    # Iterate through all paragraphs
    for i, paragraph in enumerate(paragraphs):
        paragraph.innerText = f"This is paragraph {i + 1}"
        paragraph.style.fontSize = "14px"
        paragraph.style.margin = "10px 0"
    
    # Alternative iteration with index
    for i in range(len(paragraphs)):
        paragraph = paragraphs[i]
        if i % 2 == 0:
            paragraph.style.backgroundColor = "#f0f0f0"

Bulk Operations

def main(ui):
    # Get all input fields
    inputs = ui.document.getElementsByTagName("input")
    
    def clear_all_inputs():
        for input_field in inputs:
            input_field.value = ""
            input_field.style.borderColor = "#ddd"
    
    def validate_all_inputs():
        all_valid = True
        for input_field in inputs:
            if input_field.value.strip() == "":
                input_field.style.borderColor = "red"
                all_valid = False
            else:
                input_field.style.borderColor = "green"
        return all_valid
    
    # Apply styles to all inputs
    for input_field in inputs:
        input_field.style.padding = "8px"
        input_field.style.border = "1px solid #ddd"
        input_field.style.borderRadius = "4px"
    
    # Add event listeners to all inputs
    for input_field in inputs:
        def create_handler(field):
            def on_change():
                if field.value.strip():
                    field.style.borderColor = "green"
                else:
                    field.style.borderColor = "red"
            return on_change
        
        input_field.addEventListener("change", create_handler(input_field))
    
    # Clear button
    clear_btn = ui.document.getElementById("clearBtn")
    if clear_btn:
        clear_btn.addEventListener("click", clear_all_inputs)
    
    # Validate button
    validate_btn = ui.document.getElementById("validateBtn")
    if validate_btn:
        validate_btn.addEventListener("click", lambda: print("Valid:", validate_all_inputs()))

Working with Forms

def main(ui):
    # Get all form elements
    forms = ui.document.getElementsByTagName("form")
    
    for form in forms:
        # Get all inputs within each form
        form_inputs = form.getElementsByTagName("input")
        form_selects = form.getElementsByTagName("select")
        form_textareas = form.getElementsByTagName("textarea")
        
        def create_form_handler(current_form):
            def handle_submit():
                # Collect all form data
                data = {}
                
                # Process inputs
                inputs = current_form.getElementsByTagName("input")
                for input_field in inputs:
                    if input_field.getAttribute("name"):
                        data[input_field.getAttribute("name")] = input_field.value
                
                # Process selects
                selects = current_form.getElementsByTagName("select")
                for select in selects:
                    if select.getAttribute("name"):
                        data[select.getAttribute("name")] = select.value
                
                # Process textareas
                textareas = current_form.getElementsByTagName("textarea")
                for textarea in textareas:
                    if textarea.getAttribute("name"):
                        data[textarea.getAttribute("name")] = textarea.value
                
                print("Form submitted with data:", data)
                return False  # Prevent default submission
            
            return handle_submit
        
        form.addEventListener("submit", create_form_handler(form))

Dynamic List Management

def main(ui):
    container = ui.document.getElementById("itemContainer")
    
    def add_new_item(text):
        # Create item element
        item = ui.document.createElement("div")
        item.className = "list-item"
        item.innerText = text
        item.style.padding = "10px"
        item.style.margin = "5px 0"
        item.style.backgroundColor = "#f9f9f9"
        item.style.border = "1px solid #ddd"
        item.style.borderRadius = "4px"
        
        # Add remove button
        remove_btn = ui.document.createElement("button")
        remove_btn.innerText = "Remove"
        remove_btn.style.marginLeft = "10px"
        remove_btn.style.padding = "5px 10px"
        remove_btn.style.backgroundColor = "#dc3545"
        remove_btn.style.color = "white"
        remove_btn.style.border = "none"
        remove_btn.style.borderRadius = "3px"
        remove_btn.style.cursor = "pointer"
        
        def remove_item():
            item.remove()
            update_item_numbers()
        
        remove_btn.addEventListener("click", remove_item)
        item.appendChild(remove_btn)
        container.appendChild(item)
        
        update_item_numbers()
    
    def update_item_numbers():
        # Get all current items
        items = container.getElementsByClassName("list-item")
        for i, item in enumerate(items):
            # Update the text to show item number
            text_content = item.innerText.split(" - ")[0] if " - " in item.innerText else item.innerText
            if text_content.endswith("Remove"):
                text_content = text_content[:-6].strip()  # Remove "Remove" button text
            item.childNodes[0].nodeValue = f"Item {i + 1} - {text_content}"
    
    def remove_all_items():
        items = container.getElementsByClassName("list-item")
        # Convert to regular list to avoid issues with live collection
        items_to_remove = []
        for item in items:
            items_to_remove.append(item)
        
        for item in items_to_remove:
            item.remove()
    
    # Add some initial items
    for i in range(3):
        add_new_item(f"Sample item {i + 1}")
    
    # Add button to create new items
    add_btn = ui.document.getElementById("addBtn")
    if add_btn:
        add_btn.addEventListener("click", lambda: add_new_item("New item"))
    
    # Add button to remove all items
    clear_btn = ui.document.getElementById("clearAllBtn")
    if clear_btn:
        clear_btn.addEventListener("click", remove_all_items)

Filtering and Searching

def main(ui):
    # Get all items that can be filtered
    all_items = ui.document.getElementsByClassName("filterable-item")
    search_input = ui.document.getElementById("searchInput")
    
    def filter_items(search_term):
        search_term = search_term.lower()
        visible_count = 0
        
        for item in all_items:
            item_text = item.innerText.lower()
            if search_term in item_text:
                item.style.display = "block"
                visible_count += 1
            else:
                item.style.display = "none"
        
        # Update results counter
        results_counter = ui.document.getElementById("resultsCounter")
        if results_counter:
            results_counter.innerText = f"Showing {visible_count} of {len(all_items)} items"
    
    def handle_search():
        filter_items(search_input.value)
    
    # Add search functionality
    if search_input:
        search_input.addEventListener("input", handle_search)
        
        # Initial setup
        for item in all_items:
            item.style.transition = "opacity 0.3s ease"
    
    # Category filtering
    category_buttons = ui.document.getElementsByClassName("category-filter")
    
    for button in category_buttons:
        def create_category_handler(category_btn):
            def handle_category_filter():
                category = category_btn.getAttribute("data-category")
                
                # Reset all button styles
                for btn in category_buttons:
                    btn.style.backgroundColor = "#f8f9fa"
                    btn.style.color = "#495057"
                
                # Highlight active button
                category_btn.style.backgroundColor = "#007bff"
                category_btn.style.color = "white"
                
                # Filter items
                if category == "all":
                    for item in all_items:
                        item.style.display = "block"
                else:
                    for item in all_items:
                        item_category = item.getAttribute("data-category")
                        if item_category == category:
                            item.style.display = "block"
                        else:
                            item.style.display = "none"
            
            return handle_category_filter
        
        button.addEventListener("click", create_category_handler(button))

Batch Styling

def main(ui):
    def apply_theme_to_elements(elements, theme):
        if theme == "dark":
            for element in elements:
                element.style.backgroundColor = "#333"
                element.style.color = "#fff"
                element.style.borderColor = "#555"
        elif theme == "light":
            for element in elements:
                element.style.backgroundColor = "#fff"
                element.style.color = "#333"
                element.style.borderColor = "#ddd"
        elif theme == "accent":
            for element in elements:
                element.style.backgroundColor = "#007bff"
                element.style.color = "#fff"
                element.style.borderColor = "#0056b3"
    
    # Apply themes to different element groups
    buttons = ui.document.getElementsByClassName("btn")
    cards = ui.document.getElementsByClassName("card")
    inputs = ui.document.getElementsByTagName("input")
    
    # Theme switcher
    theme_selector = ui.document.getElementById("themeSelector")
    
    def change_theme():
        selected_theme = theme_selector.value
        
        apply_theme_to_elements(buttons, selected_theme)
        apply_theme_to_elements(cards, selected_theme)
        
        # Special handling for inputs
        if selected_theme == "dark":
            for input_field in inputs:
                input_field.style.backgroundColor = "#444"
                input_field.style.color = "#fff"
                input_field.style.borderColor = "#666"
        else:
            for input_field in inputs:
                input_field.style.backgroundColor = "#fff"
                input_field.style.color = "#333"
                input_field.style.borderColor = "#ddd"
    
    if theme_selector:
        theme_selector.addEventListener("change", change_theme)

Working with Live Collections

ElementList represents a "live" collection, meaning it automatically updates when the DOM changes:

def main(ui):
    # Get all elements with class "dynamic"
    dynamic_elements = ui.document.getElementsByClassName("dynamic")
    
    print(f"Initial count: {len(dynamic_elements)}")  # e.g., 3
    
    # Add a new element with the same class
    new_element = ui.document.createElement("div")
    new_element.className = "dynamic"
    ui.document.body.appendChild(new_element)
    
    print(f"After adding: {len(dynamic_elements)}")  # Now 4!
    
    # The collection automatically includes the new element
    for element in dynamic_elements:
        element.style.border = "1px solid red"  # All 4 elements get the border

Error Handling

def main(ui):
    elements = ui.document.getElementsByClassName("my-class")
    
    # Always check if elements exist
    if len(elements) > 0:
        # Safe to access first element
        first_element = elements[0]
        first_element.innerText = "Found!"
    else:
        print("No elements found with class 'my-class'")
    
    # Safe iteration (handles empty collections)
    for element in elements:
        element.style.color = "blue"
    
    # Index bounds checking
    try:
        fifth_element = elements[4]  # May throw IndexError
        fifth_element.innerText = "Fifth element"
    except IndexError:
        print("No fifth element found")

Best Practices

  1. Check collection length: Always verify that elements exist before accessing them by index.

  2. Use iteration for bulk operations: When applying the same operation to multiple elements, use for-loops rather than accessing by index.

  3. Be aware of live collections: Remember that ElementList updates automatically when the DOM changes.

  4. Cache element references: If you're accessing the same element multiple times, store it in a variable.

  5. Handle empty collections gracefully: Always account for the possibility that no elements match your query.

  6. Use appropriate selection methods: Choose the most specific selection method for better performance:

    • getElementById() for single elements by ID
    • getElementsByClassName() for elements by class
    • querySelectorAll() for complex CSS selectors

Common Patterns

Processing Form Data

def collect_form_data(form):
    data = {}
    inputs = form.getElementsByTagName("input")
    
    for input_field in inputs:
        name = input_field.getAttribute("name")
        if name:
            data[name] = input_field.value
    
    return data

Batch Event Handling

def add_click_handlers(elements, handler):
    for element in elements:
        element.addEventListener("click", handler)

Conditional Styling

def highlight_errors(elements, validation_func):
    for element in elements:
        if not validation_func(element.value):
            element.style.borderColor = "red"
        else:
            element.style.borderColor = "green"

Related Classes

  • Document: Contains methods that return ElementList objects
  • Element: Individual elements contained in ElementList
  • Style: CSS styling for individual elements