AmoebeLabs / flex-horseshoe-card

Flexible Horseshoe card for Home Assistant Lovelace UI. A card with a flexible layout, a horseshoe-like donut graph, multiple entities or attributes, graphics and animations!
278 stars 43 forks source link

Entity without an attribute is not rendered if it is not the first entity in card #92

Open tolga-orhon opened 2 months ago

tolga-orhon commented 2 months ago

Summary When an entity without an attribute is used in the card as the first entity, it is rendered normally. If an entity without an attribute is used in the card as not a first entity and if the first entity is using an attribute, then the entity is not rendered on the card.

Steps to reproduce

  1. Create two template sensors, one with an attribute and one without
  - sensor:
      - name: "Sensor with no value attribute"
        state: 30
  - sensor:
      - name: "Sensor with value attribute"
        state: 20
        attributes:
          value: 20
  1. Add a cart with both entities, using the entity with no attribute first:

    type: custom:flex-horseshoe-card
    entities:
    - entity: sensor.sensor_with_no_value_attribute
    - entity: sensor.sensor_with_value_attribute
    attribute: value
    show:
    horseshoe_style: fixed
    layout:
    states:
    - id: 0
      entity_index: 0
      xpos: 50
      ypos: 45
      styles:
        - font-size: 3.5em;
    - id: 1
      entity_index: 1
      xpos: 50
      ypos: 70
      styles:
        - font-size: 1.5em;
    horseshoe_scale:
    min: 7
    max: 35
    color_stops:
    '0': '#1B0F52'
  2. Observe that this works as expected: image

  3. Change the order of entities in card definition:

    type: custom:flex-horseshoe-card
    entities:
    - entity: sensor.sensor_with_value_attribute
    attribute: value
    - entity: sensor.sensor_with_no_value_attribute
    show:
    horseshoe_style: fixed
    layout:
    states:
    - id: 0
      entity_index: 0
      xpos: 50
      ypos: 45
      styles:
        - font-size: 3.5em;
    - id: 1
      entity_index: 1
      xpos: 50
      ypos: 70
      styles:
        - font-size: 1.5em;
    horseshoe_scale:
    min: 7
    max: 35
    color_stops:
    '0': '#1B0F52'
  4. Observe that the second entity is not rendered: image

Reason for the bug In file flex-horseshoe-card.js the following code block exists in the definition of 'hass' method:

      // Update state strings and check for changes.
      // Only if changed, continue and force render
      var value;
      var index = 0;
      var attrSet = false;
      var newStateStr;
      for (value of this.config.entities) {
        this.entities[index] = hass.states[this.config.entities[index].entity];

        // Get attribute state if specified and available
        if (this.config.entities[index].attribute) {
          if (this.entities[index].attributes[this.config.entities[index].attribute]) {
            newStateStr = this._buildState(this.entities[index].attributes[this.config.entities[index].attribute], this.config.entities[index]);
            if (newStateStr != this.attributesStr[index]) {
              this.attributesStr[index] = newStateStr;
              entityHasChanged = true;
            }
            attrSet = true;
          }
        }
        if (!attrSet) {
          newStateStr = this._buildState(this.entities[index].state, this.config.entities[index]);
          if (newStateStr != this.entitiesStr[index]) {
            this.entitiesStr[index] = newStateStr;
            entityHasChanged = true;
          }
        }

        index++;
      }

In this code block, the default value for the variable attrSet is set as false outside of the for loop. If the first entity in the index has an attribute, then the value of attrSet variable is set to true in the first condition check. As variable is in the same scope, on the next iteration of the for loop, value of attrSet variable still evaluates to true thus bypasses the second conditional block, which supposed to render an entity without an attribute.

This also explains, why an entity without an attribute works if it is the first entity, as the value of attrSet variable evaluates to false in the first iteration.

Proposed Solution Set the value of attrSet variable to false inside of the loop, before the conditional blocks.

      // Update state strings and check for changes.
      // Only if changed, continue and force render
      var value;
      var index = 0;
      var attrSet = false;
      var newStateStr;
      for (value of this.config.entities) {
        attrSet = false;
        this.entities[index] = hass.states[this.config.entities[index].entity];

        // Get attribute state if specified and available
        if (this.config.entities[index].attribute) {
          if (this.entities[index].attributes[this.config.entities[index].attribute]) {
            newStateStr = this._buildState(this.entities[index].attributes[this.config.entities[index].attribute], this.config.entities[index]);
            if (newStateStr != this.attributesStr[index]) {
              this.attributesStr[index] = newStateStr;
              entityHasChanged = true;
            }
            attrSet = true;
          }
        }
        if (!attrSet) {
          newStateStr = this._buildState(this.entities[index].state, this.config.entities[index]);
          if (newStateStr != this.entitiesStr[index]) {
            this.entitiesStr[index] = newStateStr;
            entityHasChanged = true;
          }
        }

        index++;
      }