Taps and handlers
A widget that only shows values is a good start. A widget that does something when you tap it is a control panel. There are two ways to make a part respond. You can fix what it does yourself with a handler, or you can mark it and let each tile’s owner choose.
Handlers
A handler is a short line attached to an element by an attribute whose name says when it runs:
| Attribute | Runs when |
|---|---|
data-hd-on-click | The element is tapped or clicked, or Enter or Space is pressed on a button. |
data-hd-on-dblclick | It is tapped twice quickly. |
data-hd-on-longpress | A finger or the mouse stays on it for half a second. |
data-hd-on-change | A control inside it, such as hd-slider or hd-toggle, changes. |
<button data-hd-on-click="toggle('onoff')">Switch</button><button data-hd-on-longpress="openDevice()">Details</button>The line is written in the same small language as expressions, but it calls actions. It is read by a parser and carried out by Tiledash. It is never run as JavaScript, so it can only do what the actions below allow.
Inline onclick attributes are always removed.
Only these four data-hd-on- names exist, and the editor tells you if a handler can’t be read.
Actions
An action is something a handler can do. Most of them take the device’s reading by its id, in quotes:
| Group | Actions |
|---|---|
| Devices | toggle, set, increment |
| Flows and moods | startFlow, setMood |
| Open | openHistory, openDevice, openCamera, navigate, openDashboard |
| This widget | setState, toggleState, runTap |
| Ask and tell | confirm, toast |
<button data-hd-on-click="toggle('onoff')">Switch</button><button data-hd-on-click="set('dim', 0.5)">Half</button><button data-hd-on-click="increment('target_temperature', 0.5)">Warmer</button><button data-hd-on-click="openHistory('measure_power', '24h')">Power</button>The device defaults to the tile’s own device.
To act on another device, give its id as the last argument.
Devices come in through settings: a device setting called charger gives settings.charger.id.
<button data-hd-on-click="set('evcharger_charging', false, settings.charger.id)">Stop</button>The actions reference lists every action with its arguments, and the Actions panel in the editor offers each as a snippet to put at the cursor.
Checked before they run
Whatever a handler asks for is checked before it reaches Homey:
- The device, flow or mood must be on this Homey.
- The reading must exist on the device, be changeable, and the device must be available.
- The value must fit: on or off for a switch, a number between the reading’s minimum and maximum (rounded to its step), or one of the choices of an enum.
A refused action changes nothing and says why in a message. A change that is allowed shows on the tile at once, and goes back if Homey refuses it.
Several steps, and asking first
Separate steps with a semicolon. A handler can run up to eight actions each time.
<button data-hd-on-click="startFlow(settings.boost.id); toast('Boost started')">Boost</button>confirm asks a yes or no question.
Joined to the next action with &&, that action runs only after a yes:
<button data-hd-on-click="confirm('Open the garage door?') && set('garagedoor_closed', false)"> Open the garage</button>A message passed to confirm or toast can be up to 200 characters.
What a handler can read
A handler can read these names and nothing else: device, cap, settings, devices, state, pending, item, flow, mood and, for a change handler, event.
event.value is the new value of the control that changed.
<hd-slider value="{{ cap.dim.value }}" data-hd-on-change="set('dim', event.value)"></hd-slider>Handlers don’t see now, prices, energy or other names of the template.
If a handler needs a value from the template, put it in the handler as text, with the quote filter so that it stays one piece of text:
{% for id in settings.shown %} <button data-hd-on-click="openHistory({{ id | quote }})">{{ cap[id].title }}</button>{% endfor %}A handler can be 500 characters long, with up to 120 parts.
Which handler runs
A tap goes to the innermost element that has a handler.
A control on the way (a button, a slider, a toggle, a dial, a field, or an element with a role such as button or tab) takes the tap for itself, and the handlers of the parts around it don’t run, so a slider inside a tappable tile doesn’t also trigger the tile.
A held press runs data-hd-on-longpress and swallows the click that follows.
A press that moves more than a few pixels is a scroll and not a long press.
Keeping state
A widget can remember small things for itself, such as which part is open.
setState stores a value under a name and draws the widget again, and toggleState flips a name between true and false.
The template reads them as state.<name>.
<button data-hd-on-click="toggleState('details')">Details</button>{% if state.details %} <p>Room: {{ cap.measure_temperature.value | temp: 1 }}</p>{% endif %}A name starts with a letter and continues with letters, digits and underscores, up to 32 characters.
A value is text, a number, true, false or null, and a widget can keep up to 32 of them.
State belongs to one tile on one screen and survives a reload of that screen.
Give setState a time in milliseconds, from 100 to 60,000, and the value goes back to null after that long and is not remembered:
<button data-hd-on-click="setState('flash', true, 2000)">Highlight for two seconds</button>Showing that something is on its way
While a command to the tile’s device is waiting for an answer, it is in pending, by reading.
pending.target_temperature is empty most of the time and holds { value } while a change is on its way, so a tile can say so.
This thermostat tile uses all of this: two buttons that change the target by half a degree, a tap on the number that toggles a detail line, and a word that says Sending while a change waits. The preview’s taps don’t reach a real Homey.
{% assign target = cap.target_temperature %}<div class="steps"> <button class="step" aria-label="Cooler" data-hd-on-click="increment('target_temperature', -0.5)">−</button> <button class="value" data-hd-on-click="toggleState('details')" aria-expanded="{% if state.details %}true{% else %}false{% endif %}"> <span class="big">{{ target.value | decimals: 1 }}°</span> <span class="hint">{% if pending.target_temperature %}Sending…{% else %}Target{% endif %}</span> </button> <button class="step" aria-label="Warmer" data-hd-on-click="increment('target_temperature', 0.5)">+</button></div>{% if state.details %} <p class="detail">Room: {{ cap.measure_temperature.value | temp: 1 }}</p>{% endif %}Taps the tile’s owner chooses
Sometimes you don’t know what a tap should do, because it depends on the home. Mark the part instead, and each tile’s owner picks what it does in the tile’s settings.
First declare a setting of the kind A tap in the editor’s Settings tab, or in its JSON view:
{ "type": "tap", "name": "lampTap", "label": "Tapping the lamp", "default": { "type": "toggle" }}Then mark the part with the setting’s name:
<div class="lamp" data-hd-tap="lampTap">…</div>On a tile’s Tap tab, the part now appears as Tapping the lamp, with a picker for what it does: nothing, switch it, show its history over a range, open the device, open a dashboard as a page or a popup, start a flow or set a mood.
Until the owner picks something, your default applies.
A default can be nothing, switch, history or open the device, because those work on any Homey.
A dashboard, a flow or a mood belongs to one Homey, so only the tile’s owner can choose one.
For a long press, make the setting’s gesture "longpress" and mark the part with data-hd-longpress.
A tap setting can say more about the part:
{ "type": "tap", "name": "powerTap", "label": "Tapping the power", "capability": "measure_power", "of": "charger", "default": { "type": "history", "range": "24h" }, "allow": ["none", "history", "device"]}capabilityis the reading the part shows, which switch and history use.ofis the name of a device setting whose device it belongs to. Left out, it is the tile’s own device.allowlimits what the owner can pick. Nothing is always allowed.
A part marked data-hd-tap that does something becomes a button for the keyboard and for screen readers, unless it already is one or has its own role.
A part whose tap is set to nothing stays inert, and a tap on it goes to whatever is around it.
Inside a loop, name the reading each part is for with data-hd-capability:
{% for id in settings.shown %} <div class="reading" data-hd-tap="readingTap" data-hd-capability="{{ id }}">{{ cap[id].text }}</div>{% endfor %}taps.lampTap is in the template only while that tap does something, so you can show a hint only when it is useful:
{% if taps.lampTap %}<span class="hint">Tap to switch</span>{% endif %}A part is either fixed by you or chosen by its owner, never both.
A part with data-hd-tap and data-hd-on-click is an error when you save, and so is a marker that names no tap setting.
A handler that wants to do what a tap setting says can call runTap('lampTap').
Controls that change a reading
The hd-slider, hd-toggle and hd-dial controls can be bound to a reading.
bind takes the whole reading, without .value:
<hd-slider bind="{{ cap.dim }}" label="Brightness"></hd-slider><hd-toggle bind="{{ settings.lamp.cap.onoff }}" label="Switch"></hd-toggle>A bound control shows the reading’s value, takes its minimum, maximum and step from the reading, and sends changes back. It shows a change at once, goes back if Homey refuses it, and checks the range. A control for a device that is unavailable shows its last value, disabled.
Writing a whole reading into an attribute, {{ cap.dim }}, renders a live reference to it, so any attribute that takes a value can follow any device.
The components reference says which attributes do.
When a change should do something else, use the explicit form.
value says what the control shows, and data-hd-on-change decides what a change does:
<hd-slider value="{{ cap.dim.value }}" data-hd-on-change="set('dim', event.value)"></hd-slider>Trying it out
The editor lists every handler call in a log under the preview: when it ran, which part, what it ran and what happened. By default, taps in the preview don’t reach your Homey. Turn on Send taps to my Homey when you preview with one of your own devices and want them to.