diff --git a/docs/frontend/custom-ui/creating-custom-panels.md b/docs/frontend/custom-ui/creating-custom-panels.md index 49a9ff25..4e4e4a31 100644 --- a/docs/frontend/custom-ui/creating-custom-panels.md +++ b/docs/frontend/custom-ui/creating-custom-panels.md @@ -2,7 +2,7 @@ title: "Creating custom panels" --- -Panels are pages that show information within Home Assistant and can allow controlling it. Panels are linked from the sidebar and rendered full screen. They have real-time access to the Home Assistant object via JavaScript. Examples of panels in the app are Map, Logbook and History. +Panels are pages that show information within Home Assistant and can allow controlling it. Panels are linked from the sidebar and rendered full screen. They have real-time access to the Home Assistant object via JavaScript. Examples of panels in the app are Lovelace, Map, Logbook and History. Besides components registering panels, users can also register panels using the `panel_custom` component. This allows users to quickly build their own custom interfaces for Home Assistant. diff --git a/docs/frontend/custom-ui/lovelace-custom-card.md b/docs/frontend/custom-ui/lovelace-custom-card.md index de7db5c6..3eb3e8e3 100644 --- a/docs/frontend/custom-ui/lovelace-custom-card.md +++ b/docs/frontend/custom-ui/lovelace-custom-card.md @@ -2,66 +2,27 @@ title: "Lovelace: Custom Cards" --- -[Lovelace](https://www.home-assistant.io/lovelace/) is our new approach to defining your user interface for Home Assistant. We offer a lot of built-in cards, but you're not just limited to the ones that we decided to include in the Lovelace UI. You can build and use your own! - -## API - -You define your custom card as a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements). It's up to you to decide how to render your DOM inside your element. You can use Polymer, Angular, Preact or any other popular framework (except for React – [more info on React here](https://custom-elements-everywhere.com/#react)). - -```js -const element = document.createElement('some-custom-card'); -``` - -Home Assistant will call `setConfig(config)` when the configuration changes (rare). If you throw an exception if the configuration is invalid, Lovelace will render an error card to notify the user. - -```js -try { - element.setConfig(config); -} catch (err) { - showErrorCard(err.message, config); -} -``` - -Home Assistant will set the `hass` property when the state of Home Assistant changes (frequent). Whenever the state changes, the component will have to update itself to represent the latest state. - -```js -element.hass = hass; -``` - -Your card can define a `getCardSize` method that returns the size of your card as a number or a promise that will resolve to a number. A height of 1 is equivalent to 50 pixels. This will help Home Assistant distribute the cards evenly over the columns. A card size of `1` will be assumed if the method is not defined. - -```js -if ('getCardSize' in element) { - return element.getCardSize(); -} else { - return 1; -} -``` - -Since some element can be lazy loaded, if you want to get the card size of another element, you should first check it is defined. - -```js - return customElements - .whenDefined(element.localName) - .then(() => element.getCardSize()); -``` - -Your card can define a `getConfigElement` method that returns a custom element for editing the user configuration. Home Assistant will display this element in the card editor in Lovelace. +[Lovelace](https://www.home-assistant.io/lovelace/) is our approach to defining your user interface for Home Assistant. We offer a lot of built-in cards, but you're not just limited to the ones that we decided to include in the Lovelace UI. You can build and use your own! ## Defining your card +This is a basic example to show what's possible. + Create a new file in your Home Assistant config dir as `/www/content-card-example.js` and put in the following contents: ```js class ContentCardExample extends HTMLElement { + // Whenever the state changes, a new `hass` object is set. Use this to + // update your content. set hass(hass) { + // Initialize the content if it's not there yet. if (!this.content) { - const card = document.createElement('ha-card'); - card.header = 'Example card'; - this.content = document.createElement('div'); - this.content.style.padding = '0 16px 16px'; - card.appendChild(this.content); - this.appendChild(card); + this.innerHTML = ` + +
+
+ `; + this.content = this.querySelector('div'); } const entityId = this.config.entity; @@ -75,6 +36,8 @@ class ContentCardExample extends HTMLElement { `; } + // The user supplied configuration. Throw an exception and Lovelace will + // render an error card. setConfig(config) { if (!config.entity) { throw new Error('You need to define an entity'); @@ -96,7 +59,7 @@ customElements.define('content-card-example', ContentCardExample); In our example card we defined a card with the tag `content-card-example` (see last line), so our card type will be `custom:content-card-example`. And because you created the file in your `/www` directory, it will be accessible in your browser via the url `/local/` (if you have recently added the www folder you will need to re-start Home Assistant for files to be picked up). -[Add a resource to your Lovelace configuration](/docs/frontend/custom-ui/registering-resources) with URL `/local/content-card-example.js` and type `module`. +Add a resource to your Lovelace configuration with URL `/local/content-card-example.js` and type `module` ([resource docs](/docs/frontend/custom-ui/registering-resources)). You can then use your card in your Lovelace configuration: @@ -109,9 +72,29 @@ views: entity: input_boolean.switch_tv ``` +## API + +Custom cards are defined as a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements). It's up to you to decide how to render your DOM inside your element. You can use Polymer, Angular, Preact or any other popular framework (except for React – [more info on React here](https://custom-elements-everywhere.com/#react)). + +Home Assistant will call `setConfig(config)` when the configuration changes (rare). If you throw an exception if the configuration is invalid, Lovelace will render an error card to notify the user. + +Home Assistant will set the `hass` property when the state of Home Assistant changes (frequent). Whenever the state changes, the component will have to update itself to represent the latest state. + +Your card can define a `getCardSize` method that returns the size of your card as a number or a promise that will resolve to a number. A height of 1 is equivalent to 50 pixels. This will help Home Assistant distribute the cards evenly over the columns. A card size of `1` will be assumed if the method is not defined. + +Since some elements can be lazy loaded, if you want to get the card size of another element, you should first check it is defined. + +```js + return customElements + .whenDefined(element.localName) + .then(() => element.getCardSize()); +``` + +Your card can define a `getConfigElement` method that returns a custom element for editing the user configuration. Home Assistant will display this element in the card editor in Lovelace. + ## Advanced example -Resources to load in Lovelace can be imported as a JS script, an HTML import or as a JS module import. Below is an example of a custom card using JS modules that does all the fancy things. +Resources to load in Lovelace are imported as a JS module import. Below is an example of a custom card using JS modules that does all the fancy things. ![Screenshot of the wired card](/img/en/frontend/lovelace-ui-custom-card-screenshot.png) diff --git a/docs/frontend/custom-ui/lovelace-custom-strategy.md b/docs/frontend/custom-ui/lovelace-custom-strategy.md new file mode 100644 index 00000000..eff03b35 --- /dev/null +++ b/docs/frontend/custom-ui/lovelace-custom-strategy.md @@ -0,0 +1,184 @@ +--- +title: "Lovelace: Custom Strategies" +--- + +_Introduced in Home Assistant 2021.5._ + +Strategies are JavaScript functions that generate Lovelace configurations. When a user has not created a Lovelace configuration yet, an auto-generated dashboard is shown. That configuration is generated using a built-in strategy. + +It's possible for developers to create their own strategies to generate dashboards. Strategies can use all of Home Assistant's data and the user's Lovelace configuration to create something new. + +A strategy can be applied to the whole configuration or to a specific view. + +Strategies are defined as a custom element in a JavaScript file, and included [via Lovelace resources](./registering-resources.md). Home Assistant will call static functions on the class instead of rendering it as a custom element. + +## Dashboard Strategies + +A dashboard strategy is responsible for generating a full Lovelace configuration. This can either be from scratch, or based on an existing Lovelace configuration that is passed in. + +An info object is passed to the strategy with information: + +| Key | Description +| -- | -- +| `config` | User supplied Lovelace configuration, if any. +| `hass` | The Home Assistant object. +| `narrow` | If the current user interface is rendered in narrow mode or not. + +```ts +class StrategyDemo { + + static async generateDashboard(info) { + + return { + title: "Generated Lovelace", + views: [ + { + "cards": [ + { + "type": "markdown", + "content": `Generated at ${(new Date).toLocaleString()}` + } + ] + } + ] + }; + + } + +} + +customElements.define("ll-strategy-my-demo", StrategyDemo); +``` + +Use the following Lovelace configuration to use this strategy: + +```yaml +strategy: + type: custom:my-demo +views: [] +``` + +## View Strategies + +A view strategy is responsible for generating the configuration of a specific Lovelace view. The strategy is invoked when the user opens the specific view. + +An info object is passed to the strategy with information: + +| Key | Description +| -- | -- +| `view` | View configuration. +| `config` | Lovelace configuration. +| `hass` | The Home Assistant object. +| `narrow` | If the current user interface is rendered in narrow mode or not. + +```ts +class StrategyDemo { + + static async generateView(info) { + + return { + "cards": [ + { + "type": "markdown", + "content": `Generated at ${(new Date).toLocaleString()}` + } + ] + }; + + } + +} + +customElements.define("ll-strategy-my-demo", StrategyDemo); +``` + +Use the following Lovelace configuration to use this strategy: + +```yaml +views: +- strategy: + type: custom:my-demo +``` + +## Full Example + +Strategies are structured such that a single class can provide both a dashboard and view strategy implementations. + +It's recommended for a dashboard strategy to leave as much work to be done to the view strategies. That way the dashboard will show up for the user as fast as possible. This can be done by having the dashboard generate a Lovelace configuration with views that rely on it's own strategy. + +Below example will create a view per area, with each view showing all entities in that area in a grid. + +```ts +class StrategyDemo { + + static async generateDashboard(info) { + // Query all data we need. We will make it available to views by storing it in strategy options. + const [areas, devices, entities] = await Promise.all([ + info.hass.callWS({ type: "config/area_registry/list" }), + info.hass.callWS({ type: "config/device_registry/list" }), + info.hass.callWS({ type: "config/entity_registry/list" }), + ]); + + // Each view is based on a strategy so we delay rendering until it's opened + return { + views: areas.map((area) => ({ + strategy: { + type: "custom:my-demo", + options: { area, devices, entities }, + }, + title: area.name, + path: area.area_id, + })), + }; + } + + static async generateView(info) { + const { area, devices, entities } = info.view.strategy.options; + + const areaDevices = new Set(); + + // Find all devices linked to this area + for (const device of devices) { + if (device.area_id === area.area_id) { + areaDevices.add(device.id); + } + } + + const cards = []; + + // Find all entities directly linked to this area + // or linked to a device linked to this area. + for (const entity of entities) { + if ( + entity.area_id + ? entity.area_id === area.area_id + : areaDevices.has(entity.device_id) + ) { + cards.push({ + type: "button", + entity: entity.entity_id, + }); + } + } + + return { + cards: [ + { + type: "grid", + cards, + }, + ], + }; + } +} + +customElements.define("ll-strategy-my-demo", StrategyDemo); +``` + +Use the following Lovelace configuration to use this strategy: + +```yaml +strategy: + type: custom:my-demo +views: [] +``` diff --git a/docs/frontend/custom-ui/lovelace-custom-view.md b/docs/frontend/custom-ui/lovelace-custom-view.md index 729aef8e..bbb72ff6 100644 --- a/docs/frontend/custom-ui/lovelace-custom-view.md +++ b/docs/frontend/custom-ui/lovelace-custom-view.md @@ -1,8 +1,8 @@ --- -title: "Lovelace: Custom Views" +title: "Lovelace: Custom View Layout" --- -Custom View Layouts allow developers and users to customize the way that Cards and Badges are displayed in a view by loading the layout and specifying the type on the view. You are now not limited to our built-in sorting methods. +By default Home Assistant will try to show the cards in a masonry layout (like Pinterest). A Custom View Layout allows developers to override this and define the layout mechanism (like a grid). ## API @@ -24,6 +24,7 @@ interface LovelaceViewElement { Cards and Badges will be created and maintained by the core code and given to the custom view. The custom views are meant to load the cards and badges and display them in a customized layout. ## Example + (note: this example does not have all of the properties but the necessities to show the example) ```js @@ -31,7 +32,7 @@ class MyNewView extends LitElement { setConfig(_config) {} static get properties() { - return { + return { cards: {type: Array, attribute: false} }; } @@ -51,9 +52,7 @@ And you can define this element in the Custom Element Registry just as you would customElements.define("my-new-view", MyNewView); ``` -You can find an example of this in our default view: `Masonry View` Located here: [frontend/src/panels/lovelace/views/hui-masonry-view.ts](https://github.com/home-assistant/frontend/blob/master/src/panels/lovelace/views/hui-masonry-view.ts) - -A user who downloads and installs your new Custom View can then use it via editing the YAML configuration of their view to be: +A custom view can be used by adding the following to the definition of your Lovelace view: ```yaml - title: Home View @@ -62,6 +61,8 @@ A user who downloads and installs your new Custom View can then use it via editi cards: [...] ``` +The Lovelace default masonry view is an example of a layout element. ([source](https://github.com/home-assistant/frontend/blob/master/src/panels/lovelace/views/hui-masonry-view.ts)). + ## Store Custom Data If your view requires data to persist at a card level, there is a `view_layout` in the card configuration that can be used to store information. Example: Key, X and Y coordinates, width and height, etc. This can be useful when you need to store the location or dimensions of a card for your view. diff --git a/docs/frontend/custom-ui/registering-resources.md b/docs/frontend/custom-ui/registering-resources.md index 7332c2ed..58c4fe6e 100644 --- a/docs/frontend/custom-ui/registering-resources.md +++ b/docs/frontend/custom-ui/registering-resources.md @@ -2,10 +2,22 @@ title: "Registering Resources" --- -Resources can be registered from the "Resources" tab of the Lovelace Configuration UI. +If you want to extend the Home Assistant Lovelace interface with custom cards, strategies or views you need to load external resources. -![Screenshot of the Resources tab, found at the top of the Lovelace Configuration UI](/img/en/frontend/lovelace-ui-resources-tab.png) +The first step is to make it accessible for the Home Assistant frontend. This is done by creating a new directory in your config folder called `www`. Create this directory and restart Home Assistant. + +Once restarted, you can put files in this directory. Each file will be accessible without authentication via the UI at `/local`. + +The next step is to register these resources with the Lovelace interface. This is done by navigating to the Lovelace resources page by following below link: + +[![Open your Home Assistant instance and show your Lovelace resources.](https://my.home-assistant.io/badges/lovelace_resources.svg)](https://my.home-assistant.io/redirect/lovelace_resources/) + +:::note This tab is only available when the active user's profile has "advanced mode" enabled. +::: + +![Screenshot of the Resources tab, found at the top of the Lovelace Configuration UI](/img/en/frontend/lovelace-ui-resources-tab.png) + ![Screenshot of the Advanced Mode selector found on the Profile page](/img/en/frontend/lovelace-ui-profile-advanced-mode.png) diff --git a/sidebars.js b/sidebars.js index 6f318484..3938224b 100644 --- a/sidebars.js +++ b/sidebars.js @@ -69,6 +69,7 @@ module.exports = { label: "Custom UI", items: [ "frontend/custom-ui/lovelace-custom-card", + "frontend/custom-ui/lovelace-custom-strategy", "frontend/custom-ui/lovelace-custom-view", "frontend/custom-ui/creating-custom-panels", "frontend/custom-ui/registering-resources",