-
-
Save thomasloven/1de8c62d691e754f95b023105fe4b74b to your computer and use it in GitHub Desktop.
// Simplest possible custom card | |
// Does nothing. Doesn't look like anything | |
class MyCustomCard extends HTMLElement { | |
setConfig(config) { | |
// The config object contains the configuration specified by the user in ui-lovelace.yaml | |
// for your card. | |
// It will minimally contain: | |
// config.type = "custom:my-custom-card" | |
// `setConfig` MUST be defined - and is in fact the only function that must be. | |
// It doesn't need to actually DO anything, though. | |
// Note that setConfig will ALWAYS be called at the start of the lifetime of the card | |
// BEFORE the `hass` object is first provided. | |
// It MAY be called several times during the lifetime of the card, e.g. if the configuration | |
// of the card is changed. | |
} | |
set hass(hass) { | |
// Whenever anything updates in Home Assistant, the hass object is updated | |
// and passed out to every card. If you want to react to state changes, this is where | |
// you do it. If not, you can just ommit this setter entirely. | |
// Note that if you do NOT have a `set hass(hass)` in your class, you can access the hass | |
// object through `this.hass`. But if you DO have it, you need to save the hass object | |
// manually, thusly: | |
this._hass = hass; | |
} | |
getCardSize() { | |
// The height of your card. Home Assistant uses this to automatically | |
// distribute all cards over the available columns. | |
// This is actually optional. If not present, the cardHeight is assumed to be 1. | |
return 1; | |
} | |
} | |
// This registers the card class as a custom element that can be included in lovelace by | |
// type: custom:my-custom-card | |
customElements.define('my-custom-card', MyCustomCard); | |
/* To use this card: | |
- Put this file in <config>/www/my-custom-card.js | |
- Add "/local/my-custom-card.js" to your lovelace resources | |
- Refresh your browser | |
- Add a new lovelace card and set its configuration to: | |
type: custom:my-custom-card | |
*/ |
// A custom card that actually looks like something | |
// Not like something good, mind you, but *something* at least. | |
class MyCustomCard2 extends HTMLElement { | |
setConfig(config) { | |
this._config = config; | |
// Example configuration: | |
// | |
// type: custom:my-custom-card2 | |
// entity: light.bed_light | |
// | |
if(!config.entity) { | |
// If no entity was specified, this will display a red error card with the message below | |
throw new Error('You need to define an entity'); | |
} | |
// Make sure this only runs once | |
if(!this.setupComplete) { | |
// A ha-card element should be the base of all cards | |
// Best practice, and makes themes and stuff work | |
const card = document.createElement("ha-card"); | |
// At this point, we don't necessarily know anything about the current state | |
// of anything, but we can set up the general structure of the card. | |
this.nameDiv = document.createElement("div"); | |
card.appendChild(this.nameDiv); | |
this.button = document.createElement("button"); | |
this.button.addEventListener("click", () => this.buttonClicked()); | |
card.appendChild(this.button); | |
this.appendChild(card); | |
this.setupComplete = true; | |
} | |
} | |
set hass(hass) { | |
this._hass = hass; | |
// Update the card in case anything has changed | |
if(!this._config) return; // Can't assume setConfig is called before hass is set | |
const stateObj = hass.states[this._config.entity]; | |
if(!stateObj) return; // This could be handled more gracefully | |
this.nameDiv.innerHTML = stateObj.attributes.friendly_name || stateObj.entity_id; | |
this.button.innerHTML = stateObj.state === "on" ? "Turn off" : "Turn on"; | |
} | |
buttonClicked() { | |
const stateObj = this._hass.states[this._config.entity]; | |
// Sanity checking is left as an exercise for the reader | |
const service = stateObj.state === "on" ? "turn_off" : "turn_on"; | |
// Turn on or off the light | |
// https://developers.home-assistant.io/docs/frontend_data#hasscallservicedomain-service-data | |
this._hass.callService("light", service, {entity_id: this._config.entity}); | |
} | |
} | |
customElements.define('my-custom-card2', MyCustomCard2); |
// A version of the same custom card which uses Lit (https://lit.dev) like the core lovelace cards do. | |
// Lit is imported from a CDN here, but it can also be bundled with your card with webpack or rollup or the like | |
import { html, LitElement } from "https://unpkg.com/lit?module"; | |
class MyCustomCard3 extends LitElement { | |
// This will make parts of the card rerender when this.hass or this._config is changed. | |
// this.hass is updated by Home Assistant whenever anything happens in your system. | |
static get properties() { | |
return { | |
hass: {}, | |
_config: {}, | |
}; | |
} | |
setConfig(config) { | |
this._config = config; | |
} | |
// The render() function of a LitElement returns the HTML of your card, and any time one or the | |
// properties defined above are updated, the correct parts of the rendered html are magically | |
// replaced with the new values. Check https://lit.dev for more info. | |
render() { | |
if (!this.hass || !this._config) { | |
return html``; | |
} | |
const stateObj = this.hass.states[this._config.entity]; | |
if (!stateObj) { | |
return html` <ha-card>Unknown entity: ${this._config.entity}</ha-card> `; | |
} | |
// @click below is also LitElement magic | |
return html` | |
<ha-card> | |
<div>${stateObj.attributes.friendly_name || stateObj.entity_id}</div> | |
<button @click=${this.buttonClicked}> | |
Turn ${stateObj.state === "on" ? "off" : "on"} | |
</button> | |
</ha-card> | |
`; | |
} | |
buttonClicked() { | |
const stateObj = this.hass.states[this._config.entity]; | |
const service = stateObj.state === "on" ? "turn_off" : "turn_on"; | |
this.hass.callService("light", service, { entity_id: this._config.entity }); | |
} | |
} | |
customElements.define("my-custom-card3", MyCustomCard3); |
// A version of the same custom card that shows up in the card picker dialog and also has a graphical editor | |
// This also used Lit because it's easy, but everything can be done without that too. | |
import { html, LitElement } from "https://unpkg.com/lit?module"; | |
// First we need to make some changes to the custom card class | |
class MyCustomCard4 extends LitElement { | |
static getConfigElement() { | |
// Create and return an editor element | |
return document.createElement("my-custom-card-editor"); | |
} | |
static getStubConfig() { | |
// Return a minimal configuration that will result in a working card configuration | |
return { entity: "" }; | |
} | |
// The rest of MyCustomCard4 is exactly like MyCustomCard3 above | |
// ... | |
// | |
} | |
customElements.define("my-custom-card4", MyCustomCard4); | |
// Next we add our card to the list of custom cards for the card picker | |
window.customCards = window.customCards || []; // Create the list if it doesn't exist. Careful not to overwrite it | |
window.customCards.push({ | |
type: "my-custom-card4", | |
name: "My Custom Card", | |
description: "A cool custom card", | |
}); | |
// Finally we create and register the editor itself | |
class MyCustomCardEditor extends LitElement { | |
static get properties() { | |
return { | |
hass: {}, | |
_config: {}, | |
}; | |
} | |
// setConfig works the same way as for the card itself | |
setConfig(config) { | |
this._config = config; | |
} | |
// This function is called when the input element of the editor loses focus | |
entityChanged(ev) { | |
// We make a copy of the current config so we don't accidentally overwrite anything too early | |
const _config = Object.assign({}, this._config); | |
// Then we update the entity value with what we just got from the input field | |
_config.entity = ev.target.value; | |
// And finally write back the updated configuration all at once | |
this._config = _config; | |
// A config-changed event will tell lovelace we have made changed to the configuration | |
// this make sure the changes are saved correctly later and will update the preview | |
const event = new CustomEvent("config-changed", { | |
detail: { config: _config }, | |
bubbles: true, | |
composed: true, | |
}); | |
this.dispatchEvent(event); | |
} | |
render() { | |
if (!this.hass || !this._config) { | |
return html``; | |
} | |
// @focusout below will call entityChanged when the input field loses focus (e.g. the user tabs away or clicks outside of it) | |
return html` | |
Entity: | |
<input | |
.value=${this._config.entity} | |
@focusout=${this.entityChanged} | |
></input> | |
`; | |
} | |
} | |
customElements.define("my-custom-card-editor", MyCustomCardEditor); |
// If you don't want to rely on external resources, you will have to "bundle" the lit module into your card. | |
// One way to do this is using Browserify (https://browserify.org/) with the esmify plugin (https://github.com/mattdesl/esmify) | |
// This requires you to have npm installed. | |
// > npm install --save-dev browserify esmify | |
// > npm install lit browser-resolve | |
// Then you replace the import statement in the beginning of the file with | |
import { html, LitElement } from "lit"; | |
// and finally bundle your file with | |
// > browserify -p esmify my-custom-card-4-bundled.js > my-custom-card.js | |
// This should give you a file my-custom-card.js which you can load as a resource. |
Fixed. Thanks!
This is the best source of documentation I can find to get started with card development.
The latest update of any part of https://github.com/custom-cards is older than two years. I don't know how is to be estimated for the presence.
I am really wrapping my head around, why it is that difficult to find documentation how to do cards. Home Assistant is a major player in home automation.
// this.hass is updated by Home Assistant whenever anything happens in your system.
Right. I did check this with with a log statement. It renders, when I trigger a different entity.
Upon each update of any entity the card gets rendered. In 99% it's completely unrelated. And yes the official documentation suggests to do it this way. All cards get continuously re-rendered for no reason if they follow this approach.
I suggest a different approach. Put the relevant parts into single reactive states (That is Lit' terminology for internal reactive properties). Use set hass(hass)
to set them up, but never make hass
a public reactive property.
May be useful to include that the object you push to window.customCards
can include a documentationURL
key, which will generate a help link to that URL in the frontend card editor.
In my-custom-card2.js line 60 should start with "this._hass ..." instead of "hass. ...". :-).