Conditional Row Style

I need some help. I have a perspective table that I want a specific row to flash when the Status column is "Active". For some reason, my transform code is not working. Could someone review it and see what I may be missing? I do know that my "flashingRow" style class works; I have put it into the row's style in the property editor, and I can get all the rows to flash. However, when I try to make it conditional, it doesn't work. I do have rows striped enabled. But I have tried with that disabled as well. Any help would be greatly appreciated.

def transform(self, value, quality, timestamp):
	headers = system.dataset.getColumnHeaders(value)
	styled_data = []
	
	for rowIndex in range(value.getRowCount()):
	    row_data = {}
	    for colIndex in range(value.getColumnCount()):
	        col_name = headers[colIndex]
	        row_data[col_name] = value.getValueAt(rowIndex, colIndex)
	
	    # Add the row-level 'style' key
	    if row_data.get("Status") == "Active":
	        row_data["style"] = {"classes": "flashingRow"} 
	    else:
	        row_data["style"] = {}
	
	    styled_data.append(row_data)
	
	return styled_data

Post a few lines of the data array so we can see what is returned. Maybe add a non-existent class to the else code so we'll get something. e.g.,
row_data["style"] = {"classes": "nonFlashingRow"}


According to [Feedback] NEW Perspective Table - #30 by ynejati you seem to be styling it correctly.

Rows can now be configured with their own styles.

    { 
        "value": {   // The rows value. 
            "city": "Folsom" 
            "country": "United States", 
            "population": 1000000 
        }, 
        "style": {  // Custom row styles.
            "backgroundColor": "#F7901D",
            "classes": "some-class"
        }
    } 

Thank you for your help. Here is the print log of the file:

INFO   | jvm 1    | 2025/06/28 11:52:20 | I [StyledDataLogger              ] [11:52:20.469]: Row 0: {u'Status': u'Active', u'Duration': 4.25, u'EndOfDownTime': None, u'StartOfDownTime': 2025-06-28 07:37:22.493, 'style': {'classes': 'flashingRow'}} view=Page/PressPage@C, component=root/DownTime Table, project-name=CDC_MES_1, target=props.data
INFO   | jvm 1    | 2025/06/28 11:52:20 | I [StyledDataLogger              ] [11:52:20.472]: Row 1: {u'Status': u'Complete', u'Duration': 5.98, u'EndOfDownTime': 2025-06-28 06:00:00.023, u'StartOfDownTime': 2025-06-28 00:01:00.017, 'style': {}} view=Page/PressPage@C, component=root/DownTime Table, project-name=CDC_MES_1, target=props.data

Then I added the nonexistent class:

INFO   | jvm 1    | 2025/06/28 12:00:24 | I [StyledDataLogger              ] [12:00:24.715]: Row 0: {u'Status': u'Active', u'Duration': 4.38, u'EndOfDownTime': None, u'StartOfDownTime': 2025-06-28 07:37:22.493, 'style': {'classes': 'flashingRow'}} view=Page/PressPage@C, component=root/DownTime Table, project-name=CDC_MES_1, target=props.data
INFO   | jvm 1    | 2025/06/28 12:00:24 | I [StyledDataLogger              ] [12:00:24.715]: Row 1: {u'Status': u'Complete', u'Duration': 5.98, u'EndOfDownTime': 2025-06-28 06:00:00.023, u'StartOfDownTime': 2025-06-28 00:01:00.017, 'style': {'classes': 'nonFlashingRow'}} view=Page/PressPage@C, component=root/DownTime Table, project-name=CDC_MES_1, target=props.data

I would thought I would have seen some type of error.

Extracting the data from your logger, it looks OK.

Row 0: {
    u'Status': u'Active', 
    u'Duration': 4.25, 
    u'EndOfDownTime': None, 
    u'StartOfDownTime': 2025-06-28 07:37:22.493, 
    'style': {'classes': 'flashingRow'}
} 
Row 1: {
    u'Status': u'Complete', 
    u'Duration': 5.98, 
    u'EndOfDownTime': 2025-06-28 06:00:00.023, 
    u'StartOfDownTime': 2025-06-28 00:01:00.017, 
    'style': {}
} 
  1. When you expand the data property and look at data[0] and data[1] what do you see?
  2. Where is your code being applied? Normally we would do a query binding (or similar) on props.data to get the raw data and then run your script in a Script Transform on the original binding. Is that what you are doing?

I would have thought you could simplify your code to something like,

for row in value:
	if row("Status") == "Active":
		style = {"classes": "flashingRow"} 
	else:
		style = {}
	styled_data.append({"value": row, "style": style)

	return styled_data

but I could be wrong.

I can not look at the data coming in on data 0 or data 1 since the table is only part of a view. My data is coming in a dataset, could that be a problem? I did try it using a JSON, but it didn't work that way either, but I may have missed something.

Yes, I am using the binding on the table.prop.data using a query to pull the data, then the code I presented is a transform script. I didn't think this would be such a challenge. :slight_smile:

I'm almost 100% certain that this is the issue. Datasets don't care for the dict you're providing for the style; it doesn't get set properly. You'll need to convert the dataset to a list of dicts instead

Thank you for your help. This morning, I changed the dataset to JSON and redid the code. Still not flashing.

	
	styled_data = []
	
	for row in value:
	    
	    if row.get("Status") == "Active":
	        row["style"] = {"classes": "flashingRow"}
	    else:
	        row["style"] = {}
	
	    
	    system.util.getLogger("StyledDataLogger").infof("Row: %s", row)
	
	    styled_data.append(row)
	
	return styled_data
INFO   | jvm 1    | 2025/06/29 08:26:47 | I [StyledDataLogger              ] [08:26:47.424]: Row: {"StartOfDownTime":1751203140580,"EndOfDownTime":null,"Duration":0.12,"Status":"Active","style":{"classes":"flashingRow"}} view=Page/PressPage@C, project-name=CDC_MES_1, component=root/DownTime Table, target=props.data

So everyone can see the upstream process, here is the query that I use to pull the data from the table,

SELECT 
    PressDownTime.StartOfDownTime,
    PressDownTime.EndOfDownTime,
    CAST(SUM(DATEDIFF(MINUTE, PressDownTime.StartOfDownTime, 
        ISNULL(PressDownTime.EndOfDownTime, GETDATE()))
    ) / 60.0 AS DECIMAL(10,2)) AS Duration,
    CASE 
        WHEN PressDownTime.EndOfDownTime IS NULL THEN 'Active'
        ELSE 'Complete'
    END AS Status
FROM 
    PressDownTime
WHERE 
    PressDownTime.PressID = :PressIDPass
    AND PressDownTime.StartOfDownTime >= :PressDateRangePastPass
    AND ISNULL(PressDownTime.EndOfDownTime, GETDATE()) <= :PressDateRangeFuturePass
GROUP BY 
    PressDownTime.PressID, 
    PressDownTime.StartOfDownTime, 
    PressDownTime.EndOfDownTime;

Any help will be greatly appreciated.

Right-click on the table's props.data, copy and paste it here as code. We only need a few array entries, so you can truncate it (keeping the closing brackets so that it's valid JSON) to keep the post short.

I'm not sure if this is what you intended for me to do. This is the code from the binding. Is this what you are asking me to post?

{
  "type": "query",
  "config": {
    "polling": {
      "enabled": true,
      "rate": "120"
    },
    "designerUseLimit": false,
    "queryPath": "MachineHours/PressDownTimeByDateTable",
    "parameters": {
      "PressDateRangePastPass": "{../From Date.props.value}",
      "PressDateRangeFuturePass": "{../To Date.props.value}",
      "PressIDPass": "{session.custom.MachineID}"
    }
  },
  "transforms": [
    {
      "code": "\tlogger \u003d system.util.getLogger(\"StyledDataLogger\")\n\t\n\theaders \u003d system.dataset.getColumnHeaders(value)\n\tstyled_data \u003d []\n\t\n\tfor rowIndex in range(value.getRowCount()):\n\t    row_data \u003d {}\n\t    for colIndex in range(value.getColumnCount()):\n\t        col_name \u003d headers[colIndex]\n\t        row_data[col_name] \u003d value.getValueAt(rowIndex, colIndex)\n\t\n\t    # Add the row-level \u0027style\u0027 key\n\t    if row_data.get(\"Status\") \u003d\u003d \"Active\":\n\t        row_data[\"style\"] \u003d {\"classes\": \"flashingRow\"}\n\t    else:\n\t        row_data[\"style\"] \u003d {\"classes\": \"nonFlashingRow\"}\n\t\n\t    styled_data.append(row_data)\n\t    logger.info(\"Row {}: {}\".format(rowIndex, row_data))\n\t\n\treturn styled_data",
      "type": "script"
    }
  ]
}

No, we want to see what was returned by the binding, not the binding itself.

Sorry, now I understand. Here is the data. There are only two rows being it's the weekend.

[
  {
    "Status": "Active",
    "EndOfDownTime": null,
    "StartOfDownTime": {
      "$": [
        "ts",
        192,
        1751214906261
      ],
      "$ts": 1751214816243
    },
    "Duration": 0.02,
    "style": {
      "classes": "flashingRow"
    }
  },
  {
    "Status": "Complete",
    "EndOfDownTime": {
      "$": [
        "ts",
        192,
        1751214906261
      ],
      "$ts": 1751108400023
    },
    "StartOfDownTime": {
      "$": [
        "ts",
        192,
        1751214906261
      ],
      "$ts": 1751086860017
    },
    "Duration": 5.98,
    "style": {
      "classes": "nonFlashingRow"
    }
  }
]

Simplifying one of your row's data for legibility (by removing the time-related columns) you've got,

  {
    "Status": "Active",
    "Duration": 0.02,
    "style": {
      "classes": "flashingRow"
    }
  }

That doesn't follow the format I showed in post #2. You need to arrange yours like this,

  {
    "value": {
        "Status": "Active",
        "Duration": 0.02
    },
    "style": {
      "classes": "flashingRow"
    }
  }

That's why my post #4 has the line,
styled_data.append({"value": row, "style": style)
It shoves the whole row of data down a level into the row's value property. This is necessary whenever you introduce a style. (Examine the data proprty for the default Table component and see how 'Folsom' is handled - although that's just for a single cell.

1 Like

Yes, that was it. Thank you for your help, I was chasing that for a few days. I changed the code to:

styled_data = []

for rowIndex in range(value.getRowCount()):
    row_data = {}
    for colIndex in range(value.getColumnCount()):
        col_name = value.getColumnName(colIndex)
        row_data[col_name] = value.getValueAt(rowIndex, colIndex)


    if row_data.get("Status") == "Active":
        styled_row = {
            "value": row_data,
            "style": { "classes": "flashingRow" }
        }
    else:
        styled_row = {
            "value": row_data,
            "style": {}
        }

    styled_data.append(styled_row)

return styled_data

Good stuff. You can refactor:

styled_data = []

for row_data in value:
    if row_data.get("Status") == "Active":   # missing .get edited in.
        styled_data.append({
            "value": row_data,
            "style": { "classes": "flashingRow" }
        })
    else:
         styled_data.append(row_data)

return styled_data

Untested!

Missing a .get:
row_data.get('Status')

return [
	{
		"value": row,
		'style': {'classes': "flashingRow"} if row.get('status') == "Active" else {}
	} for row in value
]

Thanks, Pascal. I don't think I've ever needed to use .get.
As usual, you've compacted it more than I could!

When the dictionary might not have that key, .get() returns None instead of throwing a key error.

(Of course, you might really need it if you get the case of the key wrong.)