getConfigForm() - configure editor for custom card

In #16142 the devs introduced getConfigForm() method which should help us develop custom cards with simple editors.
The pull request got merged, but the docs weren’t updated and there was no dev blog post about this new method.

Did anyone use this method to create an editor for custom card? I’m aware we can create a fully custom editor like the one shown in GitHub - custom-cards/boilerplate-card: A community driven blueprint for best practices, but according to linked PR description this method allows

Adding easier editor support for custom cards and custom tile features. It only need a form schema to generate the editor.

I found an example here: frontend/src/panels/lovelace/cards/hui-entity-card.ts at 497c6c35f10770dd1ddac353af8d150e0acceff4 · home-assistant/frontend · GitHub
frontend/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts at 497c6c35f10770dd1ddac353af8d150e0acceff4 · home-assistant/frontend · GitHub

but not sure if this is the minimal example to have an automatic editor for a custom card.

Below is my approach to create a simple card with editor, the card works, but the editor isn’t displayed

class ProgressBarCard extends HTMLElement {
  // Called whenever Home Assistant updates the state
  set hass(hass) {
    // Get the sensor state object
    const entityId = this.config.entity;
    const stateObj = hass.states[entityId];
    const newStateStr = stateObj ? stateObj.state : "unavailable";

    // If the state has not changed, do nothing.
    if (this._lastState === newStateStr) {
      return;
    }
    this._lastState = newStateStr;

    // Build the card DOM if it hasn't been built yet.
    if (!this.content) {

      this.innerHTML = `
        <ha-card class="overflow-hidden">
          <style>
            .overflow-hidden {
              overflow: hidden;
            }
            .progress-bar {
              margin: 0;
              padding: 0;
              background-color: var(--mdc-theme-primary,#6200ee);
              height: 12px;
              width: 0%;
              transition: width 0.3s ease;
            }
          </style>
              <div class="progress-bar"></div>
        </ha-card>
      `;
      this.progressBar = this.querySelector(".progress-bar");
    }
    
    // Parse the sensor value and compute progress percentage.
    const stateValue = stateObj ? parseFloat(stateObj.state) : 0;
    const min = parseFloat(this.config.min);
    const max = parseFloat(this.config.max);
    let progressPercent = 0;
    if (max > min) {
      progressPercent = ((stateValue - min) / (max - min)) * 100;
      progressPercent = Math.min(Math.max(progressPercent, 0), 100);
    }
    
    // Update only if the computed progress has changed significantly.
    if (this._lastProgressPercent !== progressPercent) {
      this._lastProgressPercent = progressPercent;
      this.progressBar.style.width = `${progressPercent}%`;
    }
  }

  // Called with the user-provided YAML configuration.
  setConfig(config) {
    if (!config.entity) {
      throw new Error("You need to define an entity");
    }
    // Set defaults for min and max if they are not provided.
    this.config = {
      min: 0,
      max: 100,
      ...config,
    };
    
    this._lastState = null;
    this._lastProgressPercent = null;
  }

  // This function is used by Home Assistant to calculate card height in masonry view.
  getCardSize() {
    return 3;
  }

  // For grid-based layouts (sections view), you can provide layout options.
  getLayoutOptions() {
    return {
      grid_rows: 1,
      grid_columns: 2,
      grid_min_rows: 3,
      grid_max_rows: 3,
    };
  }

  static getConfigForm() {
    // Define the form schema.
    const SCHEMA = [
      { name: "entity", required: true, selector: { entity: {} } },
      { name: "min", selector: { text: { type: "number" } } },
      { name: "max", selector: { text: { type: "number" } } },
    ];

    // A simple assertion function to validate the configuration.
    const assertConfig = (config) => {
      if (!config.entity || typeof config.entity !== "string") {
        throw new Error('Configuration error: "entity" must be a non-empty string.');
      }
      if (config.min !== undefined && isNaN(Number(config.min))) {
        throw new Error('Configuration error: "min" must be a valid number.');
      }
      if (config.max !== undefined && isNaN(Number(config.max))) {
        throw new Error('Configuration error: "max" must be a valid number.');
      }
    };

    // computeLabel returns a localized label for a schema item.
    const computeLabel = (schema, localize) => {
      return localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`);
    };

    return {
      schema: SCHEMA,
      assertConfig: assertConfig,
      computeLabel: computeLabel,
    };
  }

  // Provide default configuration when a new card is added.
  static getStubConfig() {
    return {
      entity: "sensor.example_progress",
      min: 0,
      max: 100
    };
  }
}

customElements.define("ha-progress-bar-card", ProgressBarCard);

// Register the card in Home Assistant’s custom card registry.
window.customCards = window.customCards || [];
window.customCards.push({
  type: "ha-progress-bar-card",
  name: "Progress Bar Card",
  description: "A custom card to show progress (for example, for a 3D printer)",
});

I think what you have there looks roughly like what I would expect, I think you’re pretty close.

What do you see on the screen when editing the card?

@karwosts I get the same windows as without getConfigForm function.

I’m extending HTMLElement, no Lit involved.

I copied in your card and it worked instantly for me with no changes. Make sure you’re not caching a stale version.

1 Like

I thought that hitting Ctrl+F5 would fix the issue, but I had to go to Manage resources and add ?v=2 to my JS link, only then the UI editor showed up.

/hacsfiles/ha-progress-bar-card/ha-progress-bar-card.js?v=2

Thank you for checking!

Yeah that’s not enough. When doing development, it’s helpful to go tick this box in browser developer tools (e.g. this is Chrome)

image

Then as long as devtools is open no files will be cached by the browser.

1 Like