Iāve discovered a funny thingā¦
tl;dr Wrap config-template-card
with card-templater
and put sun.sun
in the entities
list ā it will work like magic without manually listing entities xDDD
The goal: a dynamic list of lights, grouped by their areas. I want a vertical-stack
card containing multiple entities
cards, each representing an area. And I want it ALL to be dynamic, including the areas.
My first attempt was with the custom:card-templater
ā I wanted to replace the cards
key of vertical-stack
with cards_template
and thus generate the the list of cards with Jinja templates, but Iāve run into an issueā¦
So, Iāve decided to try custom:config-template-card
, so I can generate vertical-stack
ās cards
list using the Javascript templates. Iāve managed to do that! But, unfortunately Iād also need to generate the config-template-card
ās entities
key, and that canāt be doneā¦
We need to go deeper Final try: Wrap config-template-card
with card-templater
and generate config-template-card
ās entities
by using a Jinja template in form of the entities_template
keyā¦ ACTUALLY NO NEED!
After I wrapped config-template-card
with card-templater
, listing ANY existing entity inside the config-template-card
ās entities
list ā WORKS. I donāt why. It just does xDDD Every state is updated correctly.
# THIS WORKS
type: custom:card-templater
card:
type: custom:config-template-card
entities:
# ANY existing entity will work,
# doesn't need to be one of the used in the JS generated content
- sun.sun
card:
type: vertical-stack
cards: |-
${ generate_cards() }
The whole code to display a dynamic list of lights grouped by areas:
type: custom:card-templater
card:
type: custom:config-template-card
entities:
- sun.sun
card:
type: vertical-stack
cards: |-
${
const hass = this.hass;
function getEntityDomain(entity) {
return entity.entity_id.split('.')[0];
}
function getEntityAreaIdWithDeviceInheritance(entity) {
return entity.area_id || hass.devices[entity.device_id].area_id;
}
function getEntityAreaWithDeviceInheritance(entity) {
const areaId = getEntityAreaIdWithDeviceInheritance(entity);
if (undefined === areaId) {
return undefined;
}
return hass.areas[areaId];
}
let areasWithLights = {};
for (const [, entity] of Object.entries(hass.entities)) {
if ('light' === getEntityDomain(entity)) {
const area = getEntityAreaWithDeviceInheritance(entity);
if (area) {
if (!areasWithLights[area.area_id]) {
areasWithLights[area.area_id] = {
'area': area,
'entities': [],
};
}
areasWithLights[area.area_id].entities.push(entity);
}
}
}
let cards = [];
for (const [areaId, areaAndEntities] of Object.entries(areasWithLights)) {
cards.push({
'type': 'entities',
'state_color': 'true',
'show_header_toggle': 'true',
'title': areaAndEntities.area.name,
'entities': areaAndEntities.entities.map(entity => {
return {
'entity': entity.entity_id,
'secondary_info': 'last-changed'
};
})
});
}
cards = cards.sort((card1, card2) => new Intl.Collator().compare(card1.title, card2.title));
cards;
}
The result:
- everything is dynamic ā areas and lights belonging to them
- everything is updated properly ā without the wrapping hack, the buttons would be āstuckā
- iām listing only lights which belong to any area
- iām listing only areas which actually contain lights