Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Add ability to define "personal" Cellformatters #125

Open
kbrown01 opened this issue Dec 27, 2023 · 11 comments
Open

[Feature Request] Add ability to define "personal" Cellformatters #125

kbrown01 opened this issue Dec 27, 2023 · 11 comments

Comments

@kbrown01
Copy link

kbrown01 commented Dec 27, 2023

While the code for Cellformatters is great, there are many highly custom uses that are not really something that should mess with the core JS. One should be able to drop in some file where personal CellFormatters can be defined and used withn their installation without breaking the core installation.

Example, take this:

- name: Vault
          data: '[[attribute]]'
          modify: >
            if (x.EventPlace1 == 1 || x.EventPlace1 == '1T') {
              '<div style="font-weight:bold; color: black">' + x.EventScore1 + ' ' + '<span style="font-size:14px">' + x.EventPlace1 + '</span></div>'}
            else if (x.EventPlace1 == 2 || x.EventPlace1 == 3 || x.EventPlace1
            == '2T' || x.EventPlace1 == '3T') {
              '<div style="font-weight:normal; color:black;">' + x.EventScore1 + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + x.EventPlace1 + '</span></div>'}
            else {
              '<div style="font-weight:normal; color: #888;">' + x.EventScore1 + ' ' + '<span style="font-size:14px">' + x.EventPlace1 + '</span></div>'}
        - name: Bars

No one want that code but me and possibly a few others that may display gymnastics statistics. The raw data is such that that modify is repeated for Event/Score 1 - 7 (think Floor, Bars, Vault, Beam, ...) and also All Around score.

It would be much more valuable to write that simply as:

modify: "eventscore(x.EventPlace1, x.EventScore1)"

or

modify: "eventscore(x.AAPlace,x.AAScore)"

I have yet to test but I assume I could jut hack my formatter into the base class and test it but it would not stand through updates.

@kbrown01
Copy link
Author

kbrown01 commented Dec 27, 2023

I would guess there is no real way to do this or I am missing something. It looks like "fmt" only takes the name of the CellFormatter and hence no way to pass in anything as it just operates on "data" which is "x". From code:

        this.data = this.raw_data.map((raw, idx) => {
            let x = raw;
            let cfg = col_cfgs[idx];
						
						let fmt = new CellFormatters();
            if (cfg.fmt) {
                x = fmt[cfg.fmt](x);
                if (fmt.failed)
                   x = null;
            }

That is unfortunate as it would considerably clean up most evert single flex table I have. This all have customizations like this, consider this for NHL Hockey repeated one for every day of the week:

modify: >-
            if( (x[1] ) && ( parseFloat(2*x[1].team.wins +
            x[1].team.overtimeLosses) < parseFloat(2*x[0].team.wins +
            x[0].team.overtimeLosses )) ) {
                '<ha-icon icon="mdi:arrow-up" style="color:green;">';}
            else if( (x[1] ) && ( parseFloat(2*x[1].team.wins +
            x[1].team.overtimeLosses) > parseFloat(2*x[0].team.wins +
            x[0].team.overtimeLosses )) ) {
                '<ha-icon icon="mdi:arrow-down" style="color:red;">';}
            else if (! x[1]) {' ';} else {'<ha-icon icon="mdi:arrow-all"
            style="color:silver;">';}

Or worse, this for 16 statistics x 4 sports (NHL, NFL, MLB, NBA) which is 48 times:

        - name: <div>PTS</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'PTS').displayValue

@ildar170975
Copy link
Contributor

Assume I have a table with 10 columns.
Each column has some tricky “modify”.
And same formula for “modify” is used in other 10 tables in 10 files.

And what? The formula is placed inside some yaml file which is called by include.
ZERO repeating code)))

@kbrown01
Copy link
Author

kbrown01 commented Dec 27, 2023

Interesting, but I would guess I would need an example because if you look those are not the same exact code. They differ by an index/name (like EventScore1 and EventScore2 .... for the different apperatus ) or x.stats.find(y=>y.abbreviation == 'PTS').displayValue and x.stats.find(y=>y.abbreviation == 'TS').displayValue and x.stats.find(y=>y.abbreviation == 'FS').displayValue ...

They are different. I do not see how YAML includes solve this. To me YAML includes say copy this entre thing here. But if it is different in each case, then I need 10 different includes which is no better than what it is right now. Perhaps I am missing something though so an example of how one piece that selects different fields but has the same logic would be great.

Maybe a more complete example, here is the code for all the modifies for that table:

        - name: Vault
          data: '[[attribute]]'
          modify: >
            if (x.EventPlace1 == 1 || x.EventPlace1 == '1T') {
              '<div style="font-weight:bold; color: black">' + x.EventScore1 + ' ' + '<span style="font-size:14px">' + x.EventPlace1 + '</span></div>'}
            else if (x.EventPlace1 == 2 || x.EventPlace1 == 3 || x.EventPlace1
            == '2T' || x.EventPlace1 == '3T') {
              '<div style="font-weight:normal; color:black;">' + x.EventScore1 + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + x.EventPlace1 + '</span></div>'}
            else {
              '<div style="font-weight:normal; color: #888;">' + x.EventScore1 + ' ' + '<span style="font-size:14px">' + x.EventPlace1 + '</span></div>'}
        - name: Bars
          data: '[[attribute]]'
          modify: >
            if (x.EventPlace2 == 1 || x.EventPlace2 == '1T') {
              '<div style="font-weight:bold; color: black">' + x.EventScore2 + ' ' + '<span style="font-size:14px">' + x.EventPlace2 + '</span></div>'}
            else if (x.EventPlace2 == 2 || x.EventPlace2 == 3 || x.EventPlace2
            == '2T' || x.EventPlace2 == '3T') {
              '<div style="font-weight:normal; color:black;">' + x.EventScore2 + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + x.EventPlace2 + '</span></div>'}
            else {
              '<div style="font-weight:normal; color: #888;">' + x.EventScore2 + ' ' + '<span style="font-size:14px">' + x.EventPlace2 + '</span></div>'}
        - name: Beam
          data: '[[attribute]]'
          modify: >
            if (x.EventPlace3 == 1|| x.EventPlace3 == '1T') {
              '<div style="font-weight:bold; color: black">' + x.EventScore3 + ' ' + '<span style="font-size:14px">' + x.EventPlace3 + '</span></div>'}
            else if (x.EventPlace3 == 2 || x.EventPlace3 == 3 || x.EventPlace3
            == '2T' || x.EventPlace3 == '3T') {
              '<div style="font-weight:normal; color:black;">' + x.EventScore3 + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + x.EventPlace3 + '</span></div>'}
            else {
              '<div style="font-weight:normal; color: #888;">' + x.EventScore3 + ' ' + '<span style="font-size:14px">' + x.EventPlace3 + '</span></div>'}
        - name: Floor
          data: '[[attribute]]'
          modify: >
            if (x.EventPlace4 == 1|| x.EventPlace4 == '1T') {
              '<div style="font-weight:bold; color: black">' + x.EventScore4 + ' ' + '<span style="font-size:14px">' + x.EventPlace4 + '</span></div>'}
            else if (x.EventPlace4 == 2 || x.EventPlace4 == 3 || x.EventPlace4
            == '2T' || x.EventPlace4 == '3T') {
              '<div style="font-weight:normal; color:black;">' + x.EventScore4 + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + x.EventPlace4 + '</span></div>'}
            else {
              '<div style="font-weight:normal; color: #888;">' + x.EventScore4 + ' ' + '<span style="font-size:14px">' + x.EventPlace4 + '</span></div>'}
        - name: AA
          data: '[[attribute]]'
          modify: >
            if (x.AAPlace == 1|| x.AAPlace == '1T') {
              '<div style="font-weight:bold; color: black">' + x.AAScore + ' ' + '<span style="font-size:14px">' + x.AAPlace + '</span></div>'}
            else if (x.AAPlace == 2 || x.AAPlace == 3 || x.AAPlace == '2T' ||
            x.AAPlace == '3T') {
              '<div style="font-weight:normal; color:black;">' + x.AAScore + ' ' + '<span style="font-size:14px;font-weight:bold;color:black">' + x.AAPlace + '</span></div>'}
            else {
              '<div style="font-weight:normal; color: #888;">' + x.AAScore + ' ' + '<span style="font-size:14px">' + x.AAPlace + '</span></div>'}

Note the each one differs by multiple attributes used.

What I thought would be easy though I cannot figure out. It would be something like I just have a separate JS file and load it (I have done that, it loaded on page load). I put a function in that file that should be accessible from anywhere I like. But If I test in the JS console, it cannot find my function no matter what I try. This to me would be the generic no code solution but I guess I do not understand how to inject a custom JS file and make those functions I define in it globally available.

Example in a file riocrest-inject.js

"use strict";
// VERSION info
var VERSION = "0.0.1";
function bingo()
{
    console.log("I am injected by Kevin");
    return "bango";
}
console.info(`%c Rio Crest Inject %c Version ${VERSION} `, "font-weight: bold; color: #000; background: #aeb", "font-weight: bold; color: #000; background: #ddd");

Does it inject? Yes, I can see the message in the info console. And I see it loaded in the page.

<script async="" type="module" src="http://192.168.1.245:8123/hacsfiles/riocrest-inject/riocrest-inject.js"></script>

In the console ...

bingo()
window.bingo()
window.parent.bingo()

All "There is no function named bingo"

If someone has a pointer as to how I could write a function that would be known then I could do as I like without the "enhancement" because I should be able to do then:

modify: >
    myscorefunction(x.EventPlace1, x.EventScore1)
   ...
modify: >
    myscorefunction(x.EventPlace2, x.EventScore2)
   ...
modify: >
    myscorefunction(x.AAPlace, x.AAScore)

That is much more manageable and understood to me. But if YAML includes can do this, I am all for it.

@kbrown01
Copy link
Author

kbrown01 commented Dec 28, 2023

To add to this as an update, I did the following:

Changed the flex-table JS to add a function:

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;
}

Then I changed that whole mess previous posted above with repetitive but different modifies to the following easy code:

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

It works perfectly.

Now, a better implementation is not in hacking the core JS (with custom functions) but using some include and being able to load them and use them. Keep your personal JS function library. That is beyond my skills but to me that would be a super enhancement.

@kbrown01
Copy link
Author

kbrown01 commented Dec 29, 2023

OK, for anyone following this, I have done something that I believe minimizes changes to core and provides great flexibility. I used import to import a file that can contain a set of functions.

Step one, one minor change to the core JS (only the first few lines shown here and only one is important -- 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:

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.

@JeffCrum1
Copy link

JeffCrum1 commented Dec 29, 2023

I think this is a great request. I'd certainly be willing to do any testing needing done during development.

People have been doing some amazing things with the flex table card and I think this could take it over the top.

It can't be an actual !include as the HA front end does not support it. Oh, I see @kbrown01 has already covered this!

@kbrown01
Copy link
Author

kbrown01 commented Dec 29, 2023

Yes. The real question is in implementation for the code owners. I would suggest it creates an empty "plugins" file to not get 404 errors on the import. BUT never overwrite that file on update. No sure how this is handled in Home Assistant world and hence beyond me. I know one could do it by creating a separate folder as the path to the import file is variable. On first install, create the card folder + JS and the Plugin folder and blank JS.

On subsequent updates, only update the card folder and do not touch the plugin folder.

I know how to do it "by hand" but I suspect it needs to be solved "generically" (as it should).

@kenwiens
Copy link

I support this. Great idea.

@daringer
Copy link
Collaborator

daringer commented Jan 2, 2024

The approach here is indeed quite nice.
Could you try making this conditional, which would be far nicer compared to a dummy file ? Means to first check for existance and then import, if this is not possible just set up an empty plugin object...

@kbrown01
Copy link
Author

kbrown01 commented Jan 2, 2024

@daringer I have not tried. Possibly a try-catch. I can test soon. I was hoping the code owner would take that on.

@kbrown01
Copy link
Author

kbrown01 commented Jan 2, 2024

I would also note that import works on classes too and IMHO cellformatter code should be handled this same way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants