eleva

Attr Plugin API Reference

Attr Plugin Complete API reference.

API

Attr

The main plugin object to install on your Eleva application.

import { Attr } from "eleva/plugins";

app.use(Attr, options);

Options

Property Type Default Description
enableAria boolean true When enabled, automatically handles ARIA attributes (aria-*)
enableData boolean true When enabled, automatically handles data attributes (data-*)
enableBoolean boolean true When enabled, intelligently handles boolean attributes based on truthy/falsy values
enableDynamic boolean true When enabled, detects and binds dynamic DOM properties

app.updateElementAttributes(oldElement, newElement)

Manually synchronize attributes from one element to another. This method is exposed on the app instance when the Attr plugin is installed.

/**
 * Update element attributes
 * @param {HTMLElement} oldElement - The element to update (modified in-place)
 * @param {HTMLElement} newElement - The reference element with desired attributes
 * @returns {void}
 */
app.updateElementAttributes(oldElement, newElement);

Example Usage

// Store reference to app for use in components
const app = new Eleva("MyApp");
app.use(Attr);

// Option 1: Use outside of component setup
const syncElements = () => {
  const oldEl = document.getElementById('source');
  const newEl = document.getElementById('target');
  app.updateElementAttributes(oldEl, newEl);
};

// Option 2: Pass app reference via closure
app.component("MyComponent", {
  setup({ signal }) {
    const updateAttributes = () => {
      const oldEl = document.getElementById('source');
      const newEl = document.getElementById('target');
      // app is available via closure (defined in outer scope)
      app.updateElementAttributes(oldEl, newEl);
    };

    return { updateAttributes };
  },
  template: () => `
    <div id="source" data-value="123" aria-label="Source">Source</div>
    <div id="target">Target</div>
    <button @click="updateAttributes">Sync Attributes</button>
  `
});

Note: The setup function receives ctx (context) as its argument, not this. To access the app instance inside setup, use a closure reference to app defined in the outer scope.


Uninstalling the Plugin

The Attr plugin provides an uninstall() method to completely remove it from an Eleva instance.

Attr.uninstall(app)

Removes the Attr plugin and restores the original renderer behavior.

import Eleva from "eleva";
import { Attr } from "eleva/plugins";

const app = new Eleva("MyApp");
app.use(Attr, {
  enableAria: true,
  enableData: true,
  enableBoolean: true,
  enableDynamic: true
});

// Use the plugin...
app.mount(document.getElementById("app"), "MyComponent");

// Later, to completely remove the Attr plugin:
Attr.uninstall(app);

// After uninstall:
// - app.updateElementAttributes (undefined)
// - Original renderer._patchNode() is restored
// - Plugin removed from registry

What Attr.uninstall() Does

  1. Restores original methods:
    • app.renderer._patchNode → restored to original
  2. Removes added properties:
    • app.updateElementAttributes
  3. Removes from plugin registry:
    • app.plugins.delete("attr")

When to Use

Uninstall Order (LIFO)

When using multiple plugins, uninstall in reverse order of installation:

// Installation order
app.use(Attr);
app.use(Store, { state: {} });
app.use(Router, { routes: [] });

// Uninstall in reverse order (LIFO)
await Router.uninstall(app);  // Last installed, first uninstalled
Store.uninstall(app);
Attr.uninstall(app);

Note: Attr’s uninstall() is synchronous (not async), unlike Router’s which is async.


Best Practices

1. Accessibility First

Always include appropriate ARIA attributes for interactive elements:

// Good - Accessible button
`<button
  aria-label="Close navigation menu"
  aria-expanded="${ctx.isMenuOpen.value}"
  @click="toggleMenu"
>
  <span aria-hidden="true">×</span>
</button>`

// Bad - No accessibility information
`<button @click="toggleMenu">×</button>`

2. Use Semantic HTML

Let HTML do the work before reaching for ARIA:

// Good - Semantic HTML
`<nav aria-label="Main navigation">
  <ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>`

// Avoid - ARIA overuse
`<div role="navigation" aria-label="Main navigation">
  <div role="list">
    <div role="listitem"><span role="link">Home</span></div>
  </div>
</div>`

3. Data Attribute Naming

Use consistent, descriptive data attribute names:

// Good - Clear, consistent naming
`<div
  data-user-id="${ctx.user.id}"
  data-user-role="${ctx.user.role}"
  data-is-active="${ctx.user.isActive}"
>`

// Bad - Inconsistent, unclear naming
`<div
  data-id="${ctx.user.id}"
  data-r="${ctx.user.role}"
  data-a="${ctx.user.isActive}"
>`

4. Boolean Attribute Clarity

Be explicit about boolean attribute conditions and use actual boolean values:

// Good - Boolean expression produces "true" or "false" string
`<button disabled="${ctx.isLoading.value || !ctx.isValid.value}">
  Submit
</button>`

// Good - Computed property returning boolean
const canSubmit = () => !isLoading.value && isValid.value;
`<button disabled="${!ctx.canSubmit()}">Submit</button>`

// Avoid - Complex inline logic
`<button disabled="${!(ctx.data.value && ctx.data.value.name && !ctx.errors.value.name)}">
  Submit
</button>`

// Important: Only these string values are recognized as truthy:
// - "true"
// - "" (empty string)
// - attribute name (e.g., disabled="disabled")

5. Performance Considerations

Minimize attribute updates for better performance:

// Good - Batch related state
const formState = signal({
  isSubmitting: false,
  isValid: true,
  errorMessage: ""
});

// Avoid - Many separate signals for related state
const isSubmitting = signal(false);
const isValid = signal(true);
const errorMessage = signal("");

Troubleshooting

Common Issues

Boolean Attribute Not Toggling

Problem: Boolean attribute doesn’t behave as expected.

// These work correctly with Attr plugin:
`<button disabled="${ctx.isDisabled.value}">`  // true/false signals work
`<button disabled="true">`   // attribute present
`<button disabled="false">`  // attribute removed
`<button disabled="">`       // attribute present (empty = true)

// These do NOT work as you might expect:
`<button disabled="1">`      // attribute REMOVED (not recognized as truthy)
`<button disabled="yes">`    // attribute REMOVED (not recognized as truthy)

Solution: The Attr plugin only recognizes "true", "" (empty), or matching attribute name (e.g., disabled="disabled") as truthy values. Use boolean signals that produce true/false strings.

ARIA Attributes Not Updating

Problem: ARIA attributes don’t reflect state changes.

// Check that you're using .value for signals
`aria-expanded="${ctx.isOpen}"`      // Wrong - missing .value
`aria-expanded="${ctx.isOpen.value}"` // Correct

Solution: Ensure you’re accessing the .value property of signals.

Data Attributes with Special Characters

Problem: Data attribute values contain quotes or special characters.

// Problem
`data-message="${ctx.message.value}"`  // message contains quotes

// Solution - Encode special characters
const safeMessage = () => encodeURIComponent(message.value);
`data-message="${ctx.safeMessage()}"`

Dynamic Property Not Binding

Problem: Input value doesn’t update when signal changes.

// Ensure two-way binding
`<input
  value="${ctx.inputValue.value}"
  @input="(e) => inputValue.value = e.target.value"
/>`

Solution: Implement both value binding and input event handler for two-way data flow.

Plugin Not Working

  1. Check installation order:
    const app = new Eleva("App");
    app.use(Attr);  // Must be before mount()
    app.component("MyComponent", { /* ... */ });
    app.mount(document.getElementById("app"), "MyComponent");
    
  2. Verify plugin is imported:
    import { Attr } from "eleva/plugins";
    // or
    const { Attr } = window.ElevaPlugins;
    
  3. Check for conflicting plugins: Some plugins may override attribute handling. Install Attr first.

Debugging Tips

// Log attribute updates
const DebugComponent = {
  setup({ signal }) {
    const value = signal("test");

    // Watch for changes (receives new value only)
    value.watch((newVal) => {
      console.log(`Value changed to: ${newVal}`);
    });

    return { value };
  },
  template({ value }) {
    return `<div data-debug="${value.value}">${value.value}</div>`;
  }
};

// Inspect element attributes
const el = document.querySelector('[data-debug]');
console.log('Attributes:', el.attributes);
console.log('Dataset:', el.dataset);
console.log('ARIA:', el.getAttribute('aria-label'));

Batching Tips & Gotchas

Eleva uses render batching via queueMicrotask to optimize performance. This means attribute updates happen asynchronously after signal changes.

1. Attribute Updates Are Batched

When signals change, attribute updates are batched with template re-renders:

// Both changes result in ONE DOM update
isDisabled.value = true;
ariaLabel.value = "Loading...";
// Attributes update together in next microtask

2. DOM Attributes Don’t Update Immediately

After changing a signal, attributes won’t reflect the change immediately:

isExpanded.value = true;
console.log(element.getAttribute('aria-expanded')); // Still "false"!

// Wait for batched update
isExpanded.value = true;
queueMicrotask(() => {
  console.log(element.getAttribute('aria-expanded')); // Now "true"
});

3. Tests May Need Delays

When testing attribute bindings, allow time for batched updates:

test("button becomes disabled", async () => {
  isLoading.value = true;

  // Wait for batched update
  await new Promise(resolve => queueMicrotask(resolve));

  expect(button.disabled).toBe(true);
  expect(button.getAttribute('aria-busy')).toBe('true');
});

4. Boolean Attributes and Batching

Boolean attribute toggling follows the same batching rules:

// These are batched together
disabled.value = true;
hidden.value = false;
checked.value = true;
// All three attributes update in one DOM operation

Summary

Key Features

Feature Description
ARIA Handling Automatic accessibility attribute management
Data Attributes Custom data storage on elements
Boolean Attributes Intelligent truthy/falsy handling
Dynamic Properties DOM property synchronization
Zero Config Works out of the box with sensible defaults
Selective Enable Configure which features to enable
Manual API updateElementAttributes() for advanced use cases

Plugin Statistics

For questions or issues, visit the GitHub repository.


See Also


← Back to Usage Patterns Back to Attr Overview Router Plugin →