Flex-table-card

Does anybody know why an unavailable value is displayed as “undefinedundefinedundefined”, i.e. three times “undefined”?

I do not want to use strictbecause some values are available despite others not being available. Or is there an option I missed that allows omitting only “undefined” data?

EDIT: Found the workaround in the github issues and this thread, duh.
Just use

modify: if(x.length == 0){"-"}else{x}

It will replace all unavailable data with “-” (or whatever else you want)

1 Like

Is there a way of comparing the value of column 3 with column 2 of the same row?
I think you mentioned this in another answer in this thread and said it is not diretcly possible but in another answer you mentioned that the attributes should be available.

So am I correct in thinking that I cannot “read” the value of column x but I can read the attribute that is being used in column x and it will automatically be the one from the same row?

Because x is always already the attribute of an entity. So how would I read a different attribute of that same entity? I cannot go to the parent of x, I don’t think :thinking:

I think that is why you mention dicts here Flex-table-card - #135 by Ildar_Gabdullin?

So no way without creating a helper dict?

Formatters - duration and duration_h

Two of the formatters listed to the documentation are duration and duration_h.

https://github.com/custom-cards/flex-table-card/blob/447cc0c4842a8d88f42edd5ad61ec47dc9c41552/docs/config-ref.md

I can’t find any documentation on how these work or examples of how to use them, and try as I might, I can’t get them to work. It would be interesting to see what they do and if they are different than the the two “passed” formatters. Has anyone used them?

Looking at the raw Javascript source should tell you everything:

    duration(data) {
        let h = (data > 3600) ? Math.floor(data / 3600).toString() + ':' : '';
        let m = (data > 60) ? Math.floor((data % 3600) / 60).toString().padStart(2, 0) + ':' : '';
        let s = (data > 0) ? Math.floor((data % 3600) % 60).toString() : '';
        if (m) s = s.padStart(2, 0);
        return h + m + s;
    }
    duration_h(data) {
        let d = (data > 86400) ? Math.floor(data / 86400).toString() + 'd ' : '';
        let h = (data > 3600) ? Math.floor((data % 86400) / 3600) : ''
        h = (d) ? h.toString().padStart(2,0) + ':' : ((h) ? h.toString() + ':' : '');

        let m = (data > 60) ? Math.floor((data % 3600) / 60).toString().padStart(2, 0) + ':' : '';
        let s = (data > 0) ? Math.floor((data % 3600) % 60).toString() : '';
        if (m) s = s.padStart(2, 0);
        return d + h + m + s;
    }

Assume some entity has attr1 and attr2 attributes.
If some column is associated to attr1 - then X is a value of this attribute.
You cannot use attr2 to modify a value of this column.

Next, assume 2 columns are associated to attr1.
In both columns you may use X (which points to same attribute) to modify values.

The only way to use ANY attribute in a column - keeping (sub)attributes in a dict as it was suggested earlier.

I created a workaround table that is more or less working.

I have not yet figured out where the small gap is coming from.
I also have not figured out how to make sure the rows are aligned because if the header of table 1 is 3 lines and that of table 2 is 2 lines, the rows no longer align.

Summary
type: custom:layout-card
layout_type: grid
layout:
  grid-template-columns: 4fr 1fr
  grid-template-rows: auto
  grid-template-areas: |
    "main1 side1"
cards:
  - type: custom:flex-table-card
    view_layout:
      grid-area: main1
    entities:
      include: climate.*
    clickable: true
    columns:
      - name: ''
        data: friendly_name
        align: left
        modify: x.replace("eQ-3 ", "").replace(" Climate", "")
      - name: <div>Target Temperature<br>°C</div>
        data: temperature
        align: center
        modify: |-
          if (x.length == 0)
            "-"
          else
            if (parseInt(x) >= 4.5 )
              '<span style="color:white;"><b>' + x + '</span>'
            else
              '<span style="color:grey;"><i>' + "Off" + '</span>'
      - name: <div>Current Temperature<br>°C</div>
        data: current_temperature
        align: center
        modify: |-
          if (x.length == 0)
            '<span style="color:grey;"><i>' + "-" + '</span>'
          else
            x
      - name: <div>Target Humidity<br>%</div>
        data: current_humidity
        align: center
        modify: |-
          if (x.length == 0)
            '<span style="color:grey;"><i>' + "-" + '</span>'
          else
            x
    css:
      table+: 'padding: 16px 0px 16px 16px;'
      tbody tr+: 'user-select: text'
      tbody tr td:nth-child(1)+: 'min-width: 1fr;width: 1fr;white-space: nowrap;'
      tbody tr td:nth-child(2)+: 'min-width: 1fr;width: 1fr;'
      tbody tr td:nth-child(3)+: 'min-width: 1fr;width: 1fr;'
      tbody tr td:nth-child(4)+: 'min-width: 1fr;width: 1fr;'
      th+: 'border-bottom: 1px solid rgb(127,127,127);'
  - type: custom:flex-table-card
    view_layout:
      grid-area: side1
    entities:
      include:
        - sensor.eq3*valve
    clickable: true
    columns:
      - name: <div>Valve<br><br>%</div>
        data: state
        align: center
        modify: |-
          if (x.length == 0)
            "-"
          else
            if (x > 0 )
              '<span style="color:white;"><b>' + x + '</span>'
            else
              '<span style="color:grey;"><i>' + x + '</span>'
    css:
      table+: 'padding: 16px 16px 16px 0px;'
      tbody tr+: 'user-select: text'
      tbody tr td:nth-child(1)+: 'min-width: 1fr;width: 1fr;white-space: nowrap;'
      tbody tr td:nth-child(2)+: 'min-width: 1fr;width: 1fr;'
      tbody tr td:nth-child(3)+: 'min-width: 1fr;width: 1fr;'
      tbody tr td:nth-child(4)+: 'min-width: 1fr;width: 1fr;'
      th+: 'border-bottom: 1px solid rgb(127,127,127);'

I posted an enhancement request for those that do not follow on Github. Please take a look and comment on

Essentially I see this very similar (but different) to the formatters. Instead of canned functions that exist only within the core like CellFormatters are, I would think many folks would benefit from being able to create Javascript functions that can be used in “modify” and accept data, perform complex, user-written functions that are not common for anyone but the user (or perhaps to be shared with others that may use your implementation).

This is different than CellFormatters as they are burned into the core JS code and I would think external to that would be best. This is well beyond my JS capabilities for implementation but from what I did, it works perfect.

Comment, call me crazy, tell me how to do it differently … any/all answers welcome but chime in if you understand and think this is valuable.

As a note, I did manage to standardize this which requires one line change in the flex-table-card.js:

Step one, one minor change to the core JS (only the first few lines shown here and the change is adding the “import”):

"use strict";
// Import Plugins
import * as plugin from "./flex-table-card-plugins.js";
// VERSION info
var VERSION = "0.7.5";

This will import a separate JS file into a class named “plugin”. The file would be located in the same directory as “flex-table-card.js” and named “flex-table-card-plugins.js”. Of course you can change that name if you like.

Step two, put your function(s) in this file which will be imported. You add the keyword “export” in front of the function so that it is exported and available within the core JS. One example:

export function drawscore(place, score){
    var cellstring = '';
    if (place == 1 || place == '1T') {
      cellstring = '<div style="font-weight:bold; color: black">' + score + ' ' + '<span style="font-size:14px">' + place + '</span></div>'}
    else if (place == 2 || place == 3 || place == '2T' || place == '3T') {
      cellstring = '<div style="font-weight:normal; color:black;">' + score + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + place + '</span></div>'}
    else {
      cellstring = '<div style="font-weight:normal; color: #888;">' + score + ' ' + '<span style="font-size:14px">' + place + '</span></div>'}
    return cellstring;
}

Step 3, use them! They are in the namespace “plugin” referred to on the “import” line. To use one you would call it by adding the namespace in front of the function name. So the example above becomes:

        - name: Vault
          data: '[[attribute]]'
          modify: |
            plugin.drawscore(x.EventPlace1, x.EventScore1)
        - name: Bars
          data: '[[attribute]]'
          modify: |
            plugin.drawscore(x.EventPlace2, x.EventScore2)
        - name: Beam
          data: '[[attribute]]'
          modify: |
            plugin.drawscore(x.EventPlace3, x.EventScore3)
        - name: Floor
          data: '[[attribute]]'
          modify: |
            plugin.drawscore(x.EventPlace4, x.EventScore4)
        - name: AA
          data: '[[attribute]]'
          modify: |
            plugin.drawscore(x.AAPlace, x.AAScore)

Now, if the install of flex-table would create an empty JS “plugins” file and never wiped it out on updates, it would provide this enhancement. Otherwise, on updates you could always reimplement this by adding the one line in the core JS.

I can’t make this work. Such a simple example…! Can someone help? For sure many others of you use HACS, do you?

Aiming at:

Getting this:

With this:

                  - type: custom:flex-table-card
                    title: ''
                    clickable: true
                    entities:
                      include: sensor.hacs
                      exclude: null
                    columns:
                      - name: ' Verfügbare HACS-Updates'
                        data: repositories.display_name
                        prefix: '▶ '
                        suffix: ''
                        align: left
                        icon: hacs:hacs

According to flex-table-card/docs/example-cfg-data.md at 447cc0c4842a8d88f42edd5ad61ec47dc9c41552 · custom-cards/flex-table-card · GitHub one can

When accessing attributes, sometimes an attribute object will itself contain objects. In that case, you can access the lower object using dotted notation. eg: object1.object2.field1

But that does not work here…


Edit: solution @ Edit 2: Solution @ Flex-table-card - #167 by Ildar_Gabdullin

Difficult to test becuse I have no updates pending, but you might need to insert a 0.

1 Like

Oh wow, indeed - the 0 did the trick.
Interesting as I was trying similar things already (repositories[0|1].display_name, repositories.display_name[0|1] etc.).


Edit: this does not work. If there are two elements, using repositories.0.display_name really only lists exactly that first. In other words: I only get one row! So back to start, looking for a solution…


Edit 2: Solution @ Flex-table-card - #167 by Ildar_Gabdullin

1 Like

I was wondering if that could be done using a template for loop.
Do you know if the number of columns is available as information so the user does not need to define it?

I’m not getting the question. Please elaborate.

In you rexample (and I am using it like that), you need to define the width of each column separately. So if you have 20 columns you need to repeat the same line of code 20 times.
Maybe a for loop would achieve the same, i.e. something like

{% for i in range(1, number_of_columns) %}
      {% tbody tr td:nth-child(i)+: 'width: 1em' %}
{% endfor %} 

where the number of columns is maybe readable from the card somehow?

If there is a pattern, read up on CSS nth-child.

Example: set table full width, first column 2%, second column 15%, rest of the columns 4%

        table+: 'padding: 0px; width: 100%;'
        tbody tr td:first-child: 'width: 2%;'
        tbody tr td:nth-child(2): 'width: 15%;'
        tbody tr td:nth-child(n+3): 'width: 4%;'

Example: sets of two columns, one 2%, one 10.5%:

        table+: 'padding: 0px; width: 100%;'
        tbdoy tr td:nth-child(odd): 'width: 2%;'
        tbdoy tr td:nth-child(even): 'width: 10.5%;'
1 Like

Thank you, I was not aware that you could just write tbody tr td:nth-child(n)+: to select all.

What I am still failing to do is make each column as narrow as possible (with nowrap). So no matter how large the card itself is, keep each column to the absolute minimum width. Even tried setting width to 1% and it still makes the columns wider so the entire card is filled.
tbody tr td:nth-child(n)+: 'min-width: 1%;width: 1%;white-space: nowrap;'

I don;t think the “+” is needed. Did you try not setting a width on the table?

Does not seem to make a difference if I add the + or not, so I removed it now.

I have tried with and without defining widths and using table-layout: fixed and flex. USing the same code for both tables (valve is a second table.

    css:
      table: 'padding: 0px 0px 0px 0px;'
      tbody tr: 'user-select: text'
      tbody tr td:first-child: 'width: 1%;white-space: nowrap;'
      tbody tr td:nth-child(n+2): 'width: 1%;'

So far nothing worked.

No, in my example I specified a width only for a few 1st columns.
You may define width (or whatever) explicitly to each needed element - or use nth-child() for mass-defining.

1 Like

I think the problem may be the combination of custom:layout-card with the flex-table.

I cannot seem to get it to work properly, i.e. have two tables next to each other without padding/margins.

Example code

Summary
type: custom:layout-card
view_layout:
  grid-area: dashboard3
layout_type: grid
layout:
  grid-template-columns: auto auto
  grid-template-rows: auto
  grid-template-areas: |
    "main1 main2"
cards:
  - type: custom:flex-table-card
    view_layout:
      grid-area: main1
    entities:
      include: climate.*
    sort_by: friendly_name
    clickable: true
    columns:
      - name: ''
        data: friendly_name
        align: left
        modify: x.replace("eQ-3 ", "").replace(" Climate", "")
      - name: <div>Target<br>Temperature<br>°C</div>
        data: temperature
        align: center
        modify: |-
          if (x.length == 0)
            "-"
          else
            if (parseInt(x) >= 4.5 )
              '<span style="color:white;"><b>' + x + '</span>'
            else
              '<span style="color:grey;"><i>' + "Off" + '</span>'
      - name: <div>Current<br>Temperature<br>°C</div>
        data: current_temperature
        align: center
        modify: |-
          if (x.length == 0)
            '<span style="color:grey;"><i>' + "-" + '</span>'
          else
            x
      - name: <div>Target<br>Humidity<br>%</div>
        data: current_humidity
        align: center
        modify: |-
          if (x.length == 0)
            '<span style="color:grey;"><i>' + "-" + '</span>'
          else
            x
    css:
      table: 'padding: 0px 0px 0px 0px;'
      tbody tr: 'user-select: text'
      tbody tr td:first-child: 'width: auto;white-space: nowrap;'
      tbody tr td:nth-child(n+2): 'width: auto;white-space: nowrap;'
      th: 'border-bottom: 1px solid rgb(127,127,127);'
  - type: custom:flex-table-card
    view_layout:
      grid-area: main2
    entities:
      include:
        - sensor.eq3*valve
    sort_by: friendly_name
    clickable: true
    columns:
      - name: ''
        data: friendly_name
        align: center
        modify: x.replace("eQ-3 ", "").replace(" Valve", "")
        hidden: true
      - name: <div>Valve<br><br>%</div>
        data: state
        align: center
        modify: |-
          if (x.length == 0)
            "-"
          else
            if (x > 0 )
              '<span style="color:white;">' + x + '</span>'
            else
              '<span style="color:grey;"><i>' + x + '</span>'
    css:
      table: 'padding: 0px 0px 0px 0px;'
      tbody tr: 'user-select: text'
      tbody tr td:first-child: 'width: auto;white-space: nowrap;'
      tbody tr td:nth-child(n+2): 'width: auto;white-space: nowrap;'
      th: 'border-bottom: 1px solid rgb(127,127,127);'

Results in

Looks like minimum column space used but card then not filled correctly.

Alternatively, card filled but then columns not minimum space.
Guess that is the choice. Entire card filled correctly or sub-card filled correctly.

Edit: this does not work. If there are two elements, using repositories.0.display_name really only lists exactly that first. In other words: I only get one row! So back to start, looking for a solution…