Widgets in widgets

Widgets in widgets

A room card can show a light, a thermostat and a media player, each drawn the way its own widget draws it. hd-widget does that: it draws another widget inside yours, as a full tile with its own frame, style and taps.

<hd-widget device="{{ settings.lamp.id }}" type="light.basic"></hd-widget>

The widget inside is called a child, and yours is its parent. A child fills the box of its hd-widget. It is a tile like any other, so a tap on it does what a tap on that tile does, and its history, style and the widget’s own look all work. A child keeps its own look: it doesn’t take on the style of the tile it sits in, so the parent’s background, accent colour, corners or padding don’t reach it. It follows the dashboard’s theme, and a style you give the child itself.

What it draws

You say what to draw with one of device, flow or mood, and which widget to draw it with with type.

AttributeWhat it does
deviceA device, usually {{ d.id }} of one in a list.
flowA flow, drawn as a button.
moodA mood, drawn as a button.
typeOne of Tiledash’s widgets by its type, such as light.basic. Left out, the widget that fits the device best.

The child gets that widget’s default settings. Nothing about it is stored: it is made up each time the parent is drawn.

type is one of Tiledash’s widgets: the built-in Device widget (generic.capabilities), or one now in the Community by the type it had before it moved there (light.basic is the Light). It has to be written out; it can’t be a value like {{ x }}, and it can’t be the type of the widget you are making, which would draw itself. The editor lists it as a problem, and a widget with such a problem can’t be saved. Leave it out and a light is drawn as a light, a thermostat as a thermostat, and a flow or a mood as a button.

Only what the widget’s settings chose

A widget can only draw what its tile was given: its own device, and the devices, flows and moods its settings chose. Name any other, say a device you typed in by its id, and nothing is drawn. A widget of yours that shows other people’s devices wouldn’t be safe to share otherwise.

So a room card starts with a setting that lets each tile choose its devices. Declare a device list, and loop over it:

{% for light in settings.lights %}  <hd-widget device="{{ light.id }}" type="light.basic"></hd-widget>{% endfor %}

In the settings, lights is a device that can hold a list ("multiple": true), as described in the settings reference. The picker of a tile’s settings then offers the devices that fit, and the card draws the ones that were chosen.

One level deep

A child can’t hold children of its own: an hd-widget inside a child draws nothing, and a widget with a Tiles setting can’t be placed inside another. A widget draws at most 128 widgets inside it; the rest draw nothing.

Give the element a size

hd-widget has no size of its own: it takes the room its parent’s style gives it, and the child fills that room. Without a height it is empty. Set one in the widget’s style, or put it in a grid or a row that has one:

.lights {  display: grid;  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));  gap: 8px;}hd-widget {  height: 150px;}

The child chooses its look from its size in pixels, as any tile does: a small box gets the small look of its widget, an icon, a bar, a pill or a column (see Styling). Every widget has a smallest size it still looks right at, its size.min (the Light widget’s is two columns by four rows): draw it in a box smaller than that and its parts can run into each other. Give the element at least that, or choose a widget made for small spaces.

Only drawn and live while in view

The child is only drawn while it can be seen: on screen (with about a screen’s height to spare, so it is there before you scroll to it), and not on a tab that isn’t showing. When it leaves the view it is taken away, and when it comes back it is drawn again with the values the tile already has.

That is what keeps a room card with dozens of devices light. It also means you can’t count on a child being there: write into the parent what the card has to say, and leave the device’s own controls to the child.

A child doesn’t pass taps up to its parent, so tapping a child doesn’t trigger the tap parts around it. Swiping across the hd-tabs of the parent doesn’t start on a child’s own control, such as a slider.

Tiles your users place

A room card can leave it to its owner what is inside it. Declare a Tiles setting and the tile’s settings get a place to add tiles of any widget (a light, a thermostat, a camera, a widget of yours) and put them on a grid inside the card. Those tiles are stored with the dashboard, each with its own settings, style and When to show, and your template draws them.

{ "type": "tiles", "name": "rooms", "label": "Tiles", "layout": "grid" }

Read the setting as settings.rooms, and draw each tile with tile="{{ t.id }}":

<div  class="grid"  style="--columns: {{ settings.rooms.columns }}; --rows: {{ settings.rooms.rows }}">  {% for t in settings.rooms.tiles %}    <hd-widget      tile="{{ t.id }}"      style="--x: {{ t.x }}; --y: {{ t.y }}; --w: {{ t.w }}; --h: {{ t.h }}"    ></hd-widget>  {% endfor %}</div>
.grid {  display: grid;  grid-template-columns: repeat(var(--columns), 1fr);  grid-template-rows: repeat(var(--rows), 1fr);  gap: 8px;  height: 100%;}hd-widget {  grid-column: calc(var(--x) + 1) / span var(--w);  grid-row: calc(var(--y) + 1) / span var(--h);}

A tile is always a value from the setting, like the t.id above. An id typed in would only work for one tile, so the editor lists it as a problem. The tile is drawn as the widget its owner chose, with the settings and style the owner gave it; type doesn’t apply.

What the setting holds

settings.rooms has the grid and the tiles on it:

FieldWhat it is
columnsHow many cells wide your widget is on the dashboard’s Desktop arrangement.
rowsHow many cells high it is.
tilesThe tiles, in reading order: top to bottom, then left to right.

Each tile has these fields:

FieldWhat it is
idWhat <hd-widget tile="…"> draws it by.
nameIts name: the widget’s, or its device’s.
iconIts icon name, or null.
kindWhat it draws: device, flow, mood or free.
x, yWhere its top left cell is on the grid, counting from 0.
w, hHow many cells wide and high it is.
showsWhen it shows, in words, or null when always.

The grid is your widget’s own cells: as many columns as it is cells wide, as many rows as high. The owner places the tiles on it on the Desktop arrangement, and on every other screen size the grid is scaled to your widget’s box, so the tiles keep their places. A tile the owner left outside the grid, because the widget was made smaller, stays in the list. Draw it where you can; the editor marks it for its owner.

Tiles that don’t show right now, because of their When to show, and tiles whose device, flow or mood is gone from the Homey, are left out of the list. They come back by themselves when they show again, so a card with tiles for the evening needs no code of its own.

Pages

Add "groups": true and the owner can sort the tiles into pages, each with a name, an icon and its own When to show. The setting then has groups instead of tiles; each page has id, label, icon, shows and its own tiles (read the same way as above). hd-tabs fits pages well:

<hd-tabs label="Rooms">  {% for page in settings.rooms.groups %}    <hd-tab name="{{ page.id }}" label="{{ page.label }}">      <div class="grid" style="--columns: {{ settings.rooms.columns }}; --rows: {{ settings.rooms.rows }}">        {% for t in page.tiles %}          <hd-widget tile="{{ t.id }}" style="--x: {{ t.x }}; --y: {{ t.y }}; --w: {{ t.w }}; --h: {{ t.h }}"></hd-widget>        {% endfor %}      </div>    </hd-tab>  {% endfor %}</hd-tabs>

Pages come in the owner’s order, and a page that isn’t showing now is left out, with its tiles. Tiles that were placed before the owner made pages, or whose page was removed, are on a first page named after your setting (“Tiles” above), so nothing disappears. Only the tabs that are shown are drawn and kept live, as described below.

The built-in Stack is this template with a few settings for how its tabs look and turn.

Limits

  • A widget holds at most 128 tiles in all its Tiles settings together, and max can lower that for one setting.
  • A setting with pages has at most 12 pages.
  • A dashboard holds at most 200 tiles of its own, and 1000 in all, counting the tiles inside widgets and their pages.
  • kinds says what the owner can add: device, flow, mood and free, the widgets that need no device, flow or mood. A widget with a Tiles setting can’t be added inside another.
  • Tiles one level deep: a tile inside your widget can’t hold tiles itself.
  • Only the tiles in view are drawn and followed live, so a card with a hundred tiles on several pages stays light.

More than one Tiles setting

A widget can declare several Tiles settings, say a row of buttons and a grid of rooms. Each reads settings.<name> with the tiles the owner put into that one, and each has its own window to arrange them, named after its label. A tile of one setting is never in another’s list.

The preview

The editor’s preview has no dashboard with tiles in it, so it makes some up for every Tiles setting: three tiles of demo devices (a lamp, a climate sensor and a power meter), and two pages of three and two tiles when the setting has pages. They are drawn as the widgets those devices get on a dashboard, so you can see the layout before anyone has placed a tile.

A room card

This widget draws a title and a tile for each light its tile chose. Its settings are a text, title, and a device list, lights, that needs onoff.

<header>  <h3>{{ settings.title }}</h3></header><div class="lights">  {% for light in settings.lights %}    <hd-widget device="{{ light.id }}" type="light.basic"></hd-widget>  {% endfor %}</div>
:host {  display: flex;  flex-direction: column;  gap: 8px;}.lights {  flex: 1;  display: grid;  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));  grid-auto-rows: 170px;  gap: 8px;  overflow-y: auto;}

The widget editor’s preview draws the children from the devices it previews, yours, your test devices or its demo ones, so you see the card as you type. Tiles of a Tiles setting come from demo tiles it makes up.

The components reference lists the attributes of hd-widget.