Flex-table-card

Is it possible to have a common header for columns? This is what I have right now:

image

type: custom:flex-table-card
entities:
  include:
    - sensor.ignacios_devices
columns:
  - name: Personal Phone Location
    data: personal_phone_location
  - name: Personal Phone Battery
    data: personal_phone_battery
    suffix: '%'
  - name: Wallet Location
    data: wallet_location
  - name: Work Phone Location
    data: work_phone_location
  - name: Work Phone Battery
    data: work_phone_battery
    suffix: '%'
  - name: Badge
    data: badge_location

I would like to add a row that for the first two columns says Personal Phone, and for columns 4 and 5 says Work phone.

Have you considered to read the thread? It was described.

1 Like

I consider it, and actually did it before posting. Alas, I clearly cannot read. Thanks for the help!

1st post → styling → styling a header row

image

Thanks. I will need to learn some css to figure out how to do this.

How to set a fixed width for a column

Assume we have 2 tables:

And these tables both have similar 4 columns - “x_1” … “x_4”.
Ofc I would like to have them placed horizontally aligned!

Can be done by setting a fixed width for these columns:

tbody tr td:nth-child(1)+: 'width: 50px;'
tbody tr td:nth-child(2)+: 'width: 80px;'
tbody tr td:nth-child(3)+: 'width: 110px;'
tbody tr td:nth-child(4)+: 'width: 140px;'

But this may not work in iOS (depends on a number of columns & their widths):

This is a working code:

tbody tr td:nth-child(1)+: 'min-width: 50px;width: 50px;'
tbody tr td:nth-child(2)+: 'min-width: 80px;width: 80px;'
tbody tr td:nth-child(3)+: 'min-width: 110px;width: 110px;'
tbody tr td:nth-child(4)+: 'min-width: 140px;width: 140px;'

i.e. we need to specify “width” & “min-width”:

P.S. Probably it depends not only a iOS - it probably depends on a viewport.
Not a css expert, using “try - fail - repeat - succeed” approach.

1 Like

I was very happy with the addition of the sorting on columns however I see an issue that I am not sure how to resolve. If you use HTML markup in the modify command, the sorting seems to be based on the HTML markup and not some value.

Example:

        - name: SOS
          data: '[[attribute]]'
          modify: |-
            if (x[8] > 3 ){
                '<div style="background-color:lightgreen;text-align:center">' + x[8] + '</div>'; }
            else if (x[8] < 3) {
                '<div style="background-color:lightcoral;text-align:center"">' + x[8] + '</div>'; }
            else {
                '<div style=";text-align:center">' + x[8] +'</div>'}

Now the values for x[8] can be 2,3 or 4 really (this is the data). If I just output x[8] the auto sorting is fine, but using the above template and sorting the column I get this:

As you can see 4 … then 2 … then 3 because the sorting is not based on the value of x[8], it must be based on something that aligns with the HTML code in the cell to get specific colors.

Am I wrong? If not, then the “sort” for a column click should be separate from the “modify” for that column.

For completeness, that is from a decluttering template so it is used like this:

      - type: custom:decluttering-card
        template: sos_settings
        variables:
          - title: NHL Strength of Schedule
          - entity: sensor.hockey_sos
          - attribute: pageProps.dataRows

So x is “dataRows” and I want to sort by x[8] when I click the sort column and not by:

<div style="background-color:lightgreen">4</div>

If this is “how it is” then I will post an enhancement request which should allow one to specifiy what to sort-by when you click the sortable header.

Now I could add:

sort_by: pageProps.dataRows-

And add a hidden column for the first column, but the instant someone sorts the column, you can never return back to that view (so that is not the answer). I could create a non-hidden column just for that original sort and display nothing in it but that is a total kludge also.

Updated KLUDGE Workaround (put the number as the first entry in the cell and set display:none):

          modify: |-
            if (x[8] > 3 ){
                '<div style="display:none;">'+ x[8] + '</div>' + '<div style="background-color:lightgreen;text-align:center">' + x[8] + '</div>'; }
            else if (x[8] < 3) {
                '<div style="display:none;">'+ x[8] + '</div>' + '<div style="background-color:lightcoral;text-align:center"">' + x[8] + '</div>'; }
            else {
                '<div style="display:none;">'+ x[8] + '</div>' + '<div style=";text-align:center">' + x[8] +'</div>'}

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)

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?