Useful CSS Stuff

Alternative Alarm Status Table Styling - Use Pills for Priority and State columns

Disclaimer: I haven’t tested any of this all that thoroughly yet!

I had a request from a customer to change the alarm status table styles. This was a bit of fun (and headache).

This requires CSS and a bit of JS injection (required for the splitting of the State values).

It has a number of parts:

  1. Alarm Status Table component which contains specific rowStyles

    Alarm Status Table - Copy JSON and Paste into a Flex Container
    [
      {
        "type": "ia.display.alarmstatustable",
        "version": 0,
        "props": {
          "refreshRate": 2000,
          "toolbar": {
            "enableShelvedTab": false,
            "enableFilter": false,
            "toggleableFilter": false,
            "enablePreFilters": false,
            "enableConfiguration": false
          },
          "filters": {
            "active": {
              "priorities": {
                "diagnostic": true
              }
            }
          },
          "rowStyles": {
            "activeUnacked": {
              "base": {
                "backgroundColor": "white",
                "color": "hsl(0 0% 5%)",
                "maxHeight": "3rem",
                "minHeight": "3rem"
              },
              "priorities": {
                "low": {
                  "backgroundColor": "white"
                },
                "medium": {
                  "backgroundColor": "white"
                },
                "high": {
                  "backgroundColor": "white"
                },
                "critical": {
                  "backgroundColor": "white"
                }
              }
            },
            "activeAcked": {
              "base": {
                "backgroundColor": "white",
                "color": "hsl(0 0% 5%)",
                "fontWeight": "normal",
                "maxHeight": "3rem",
                "minHeight": "3rem"
              },
              "priorities": {
                "low": {
                  "backgroundColor": "white"
                },
                "medium": {
                  "backgroundColor": "white"
                },
                "high": {
                  "backgroundColor": "white"
                },
                "critical": {
                  "backgroundColor": "white"
                }
              }
            },
            "clearUnacked": {
              "base": {
                "backgroundColor": "white",
                "fontWeight": "lighter",
                "color": "hsl(0 0% 70%)",
                "maxHeight": "3rem",
                "minHeight": "3rem"
              },
              "priorities": {
                "diagnostic": {
                  "backgroundColor": "white",
                  "color": "hsl(0 0% 70%)"
                },
                "low": {
                  "backgroundColor": "white"
                },
                "medium": {
                  "backgroundColor": "white"
                },
                "high": {
                  "backgroundColor": "white"
                },
                "critical": {
                  "backgroundColor": "white"
                }
              }
            },
            "clearAcked": {
              "base": {
                "backgroundColor": "white",
                "color": "hsl(0 0% 70%)",
                "fontWeight": "lighter",
                "maxHeight": "3rem",
                "minHeight": "3rem"
              },
              "priorities": {
                "diagnostic": {
                  "color": "hsl(0 0% 70%)"
                },
                "low": {
                  "backgroundColor": "white"
                },
                "medium": {
                  "backgroundColor": "white"
                },
                "high": {
                  "backgroundColor": "white"
                },
                "critical": {
                  "backgroundColor": "white"
                }
              }
            }
          },
          "dateFormat": "DD MMM yyyy HH:mm z",
          "activeSortOrder": [
            "activeTime"
          ],
          "columns": {
            "active": {
              "activeTime": {
                "sort": "descending",
                "width": 230,
                "strictWidth": true,
                "order": 3
              },
              "displayPath": {
                "width": 350,
                "strictWidth": true
              },
              "priority": {
                "sort": "none",
                "width": 160,
                "strictWidth": true,
                "order": 0
              },
              "state": {
                "sort": "none",
                "width": 320,
                "strictWidth": true,
                "order": 4
              },
              "source": {
                "enabled": false,
                "width": 240,
                "order": 3
              },
              "name": {
                "enabled": false,
                "order": 2
              }
            }
          },
          "columnsAssociated": {
            "active": [
              {
                "field": "Description",
                "enabled": true,
                "width": "",
                "strictWidth": false,
                "sort": "none",
                "order": 2
              }
            ]
          }
        },
        "meta": {
          "name": "AlarmStatusTable"
        },
        "position": {
          "grow": 1,
          "basis": "400px"
        },
        "custom": {}
      }
    ]
    
  2. Markdown Javascript injector component which converts the State column text into separate components and adds them into a flex

    Markdown JS Injector (State-only mods) - Copy JSON and Paste into the View containing the AST
    [
      {
        "type": "ia.display.markdown",
        "version": 0,
        "props": {
          "style": {
            "flex": "--neutral-40"
          },
          "markdown": {
            "escapeHtml": false
          }
        },
        "meta": {
          "name": "JSInject_ConvertStateColToPillBoxes"
        },
        "position": {
          "shrink": 0,
          "basis": 0
        },
        "custom": {
          "inlineJavascript": "(() => {\n    const styleAlarmStates = () => {\n        document.querySelectorAll(\".ia_table__cell[data-column-id=state]:not(.ia_table__head__header__cell) .content > div\").forEach(el => {\n            if (el.querySelector(\".custom_alarmStatusTable__stateCol_pill\"))\n                return;\n            const text = el.textContent.trim();\n            if (!text)\n                return;\n            const parts = text.split(\",\").map(s => s.trim());\n            const classMap = {\n                \"Active\": \"custom_alarmStatusTable__stateCol_active\",\n                \"Cleared\": \"custom_alarmStatusTable__stateCol_cleared\",\n                \"Acknowledged\": \"custom_alarmStatusTable__stateCol_ack\",\n                \"Unacknowledged\": \"custom_alarmStatusTable__stateCol_unack\"\n            };\n            const pills = parts.map(part => {\n                const stateClass = classMap[part] || \"\";\n                return \"<span class=\\\"custom_alarmStatusTable__stateCol_pill \" + stateClass + \"\\\">\" + part + \"</span>\";\n            }).join(\"\");\n            el.innerHTML = \"<div class=\\\"custom_alarmStatusTable__stateCol_container\\\">\" + pills + \"</div>\";\n        });\n    };\n    const setupObserver = () => {\n        let rafId;\n        const observer = new MutationObserver(() => {\n            if (rafId)\n                return;\n            rafId = requestAnimationFrame(() => {\n                styleAlarmStates();\n                rafId = null;\n            });\n        });\n        const tableContainer = document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\");\n        if (tableContainer) {\n            styleAlarmStates();\n            observer.observe(tableContainer, {\n                childList: true,\n                attributes: true,\n                subtree: true,\n                attributeFilter: [\"title\", \"style\"]\n            });\n        }\n    };\n    setupObserver();\n    if (!document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n        const waitObserver = new MutationObserver(() => {\n            if (document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n                waitObserver.disconnect();\n                setupObserver();\n            }\n        });\n        waitObserver.observe(document.body, {\n            childList: true,\n            subtree: true\n        });\n    }\n})();\n"
        },
        "propConfig": {
          "props.source": {
            "binding": {
              "config": {
                "struct": {
                  "script": "{this.custom.inlineJavascript}"
                },
                "waitOnAll": true
              },
              "transforms": [
                {
                  "code": "\tcode =  \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n\t\n\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \")\n\t\n\treturn code",
                  "type": "script"
                }
              ],
              "type": "expr-struct"
            }
          }
        }
      }
    ]
    
  3. CSS to add to the Advanced Stylesheet

    Stylesheet CSS to define the: alarm priority colours and the state pill styles
    /* === Setup the standard colour variables ================================================================================= */
    
    :root {
    	/** Format: --c-<area>-<prop><-modifier>, where 'c' means 'colour' **/
    	
    	/* Alarm Priority Colours */
    	--c-alarm-critical:   hsl(5 95% 65%); /* red */
    	--c-alarm-critical-text:   white;
    	
    	--c-alarm-high:       hsl(30, 98%, 55%); /* orange */
    	--c-alarm-high-text:       white;
    	
    	--c-alarm-medium:     hsl(50, 98%, 71%); /* yellow */
    	--c-alarm-medium-text:     hsl(0, 0%, 5%);
    	
    	--c-alarm-low:        hsl(200, 88%, 51%); /* blue */
    	--c-alarm-low-text:        white;
    	
    	--c-alarm-diagnostic: hsl(0, 0%, 75%); /* grey */
    	--c-alarm-diagnostic-text: white;
    }
    
    /* === Alarm Status Table Styles =================================================================== */
    
    /* Allow cell text to word wrap */
    .alarmStatusTable .ia_table__cell .content div {
    	white-space: pre-wrap;
    }
    
    /* Make the Priority column show a pill-box with the priority colour in the background */
    .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title]){
    	width: 7rem;
    	border-radius: 50vh;
    	height: 1.7rem;
    	padding: 0.2rem 0.5rem;
    	text-align: center;
    	font-weight: normal;
    	text-transform: uppercase;
    	justify-content: center !important;
    	align-items: center;
    }
    .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Critical"]) {
    	background-color: var(--c-alarm-critical);
    	color: var(--c-alarm-critical-text);
    }
    .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="High"]) {
    	background-color: var(--c-alarm-high);
    	color: var(--c-alarm-high-text);
    }
    .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Medium"]) {
    	background-color: var(--c-alarm-medium);
    	color: var(--c-alarm-medium-text);
    }
    .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Low"]) {
    	background-color: var(--c-alarm-low);
    	color: var(--c-alarm-low-text);
    }
    .alarmStatusTable .ia_table__cell[data-column-id="priority"] .content:has([title="Diagnostic"]) {
    	background-color: var(--c-alarm-diagnostic);
    	color: var(--c-alarm-diagnostic-text);
    }
    
    
    /* defines the flex container housing the two state pills */
    .custom_alarmStatusTable__stateCol_container {
      display: flex;
      gap: 0.3rem;
      align-items: center;;
    }
    /* styles for the pill containers housing the active/cleared and ack/unack states */
    .custom_alarmStatusTable__stateCol_pill {
      border-top-left-radius: 50vh;
      border-top-right-radius: 50vh;
      border-bottom-left-radius: 50vh;
      border-bottom-right-radius: 50vh;
      margin: ;height: 150%;
      padding: 0.2rem 0.5rem;
      text-transform: uppercase;
    }
    /* styles for the ack state */
    .custom_alarmStatusTable__stateCol_ack {
      background-color: #C0C1BE;
      color: hsl(0 0% 5%);
      font-weight: normal;
    }
    /* styles for the unack state */
    .custom_alarmStatusTable__stateCol_unack {
      background-color: #FFC700;
      color: hsl(0 0% 5%);
      font-weight: normal;
    }
    /* styles for the active state */
    .custom_alarmStatusTable__stateCol_active {
      background-color: #98E737;
      color: hsl(0 0% 5%);
      font-weight: normal;
    }
    /* styles for the cleared state */
    .custom_alarmStatusTable__stateCol_cleared {
      background-color: #C0C1BE;
      color: hsl(0 0% 5%);
      font-weight: normal;
    }
    

If you just want to import a single View, here’s the project export in 8.3.2. You’ll still need to copy in the styling into the adv. stylesheet though.

AlarmStatusTable-AlternateStyling.zip (44.2 KB)

They also wanted the datetime formatted differently as well, in case you want this too. If so, replace the Markdown above with this version:

Markdown JS Injector (State & Datetime mods) - Copy JSON and Paste into the View containing the AST
[
  {
    "type": "ia.display.markdown",
    "version": 0,
    "props": {
      "style": {
        "flex": "--neutral-40"
      },
      "markdown": {
        "escapeHtml": false
      }
    },
    "meta": {
      "name": "JSInject_ConvertStateColToPillBoxes"
    },
    "position": {
      "shrink": 0,
      "basis": 0
    },
    "custom": {
      "inlineJavascript": "(() => {\n    const styleAlarmStates = () => {\n        document.querySelectorAll(\".ia_table__cell[data-column-id=state]:not(.ia_table__head__header__cell) .content > div\").forEach(el => {\n            if (el.querySelector(\".custom_alarmStatusTable__stateCol_pill\"))\n                return;\n            const text = el.textContent.trim();\n            if (!text)\n                return;\n            const parts = text.split(\",\").map(s => s.trim());\n            const classMap = {\n                \"Active\": \"custom_alarmStatusTable__stateCol_active\",\n                \"Cleared\": \"custom_alarmStatusTable__stateCol_cleared\",\n                \"Acknowledged\": \"custom_alarmStatusTable__stateCol_ack\",\n                \"Unacknowledged\": \"custom_alarmStatusTable__stateCol_unack\"\n            };\n            const pills = parts.map(part => {\n                const stateClass = classMap[part] || \"\";\n                return \"<span class=\\\"custom_alarmStatusTable__stateCol_pill \" + stateClass + \"\\\">\" + part + \"</span>\";\n            }).join(\"\");\n            el.innerHTML = \"<div class=\\\"custom_alarmStatusTable__stateCol_container\\\">\" + pills + \"</div>\";\n        });\n    };\n\n    const formatDateTimeCells = () => {\n        document.querySelectorAll('.alarmStatusTable .ia_table__cell:not(.ia_table__head__header__cell) .content > div').forEach(div => {\n            const title = div.getAttribute('title');\n            const datetimeRegex = /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}[+-]\\d{4}$/;\n            \n            if (title && datetimeRegex.test(title) && !div.querySelector('div[style*=\"flex\"]')) {\n                const text = div.textContent.trim();\n                const parts = text.split(' ');\n                \n                if (parts.length >= 5) {\n                    const date = parts.slice(0, 3).join(' ').trim();\n                    const time = parts.slice(3).join(' ').trim();\n                    \n                    div.innerHTML = `\n                        <div style=\"display: flex; flex-direction: column; gap: 2px; align-items: flex-end;\">\n                            <div style=\"font-weight: bold;\">${date}</div>\n                            <div style=\"font-size: 0.9em; color: #666;\">${time}</div>\n                        </div>\n                    `;\n                }\n            }\n        });\n    };\n\n    const setupObserver = () => {\n        let rafId;\n        const observer = new MutationObserver(() => {\n            if (rafId)\n                return;\n            rafId = requestAnimationFrame(() => {\n                styleAlarmStates();\n                formatDateTimeCells();\n                rafId = null;\n            });\n        });\n        const tableContainer = document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\");\n        if (tableContainer) {\n            styleAlarmStates();\n            formatDateTimeCells();\n            observer.observe(tableContainer, {\n                childList: true,\n                attributes: true,\n                subtree: true,\n                attributeFilter: [\"title\", \"style\"]\n            });\n        }\n    };\n    setupObserver();\n    if (!document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n        const waitObserver = new MutationObserver(() => {\n            if (document.querySelector(\".ReactVirtualized__Grid__innerScrollContainer\")) {\n                waitObserver.disconnect();\n                setupObserver();\n            }\n        });\n        waitObserver.observe(document.body, {\n            childList: true,\n            subtree: true\n        });\n    }\n})();"
    },
    "propConfig": {
      "props.source": {
        "binding": {
          "config": {
            "struct": {
              "script": "{this.custom.inlineJavascript}"
            },
            "waitOnAll": true
          },
          "transforms": [
            {
              "code": "\tcode =  \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n\t\n\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \")\n\t\n\treturn code",
              "type": "script"
            }
          ],
          "type": "expr-struct"
        }
      }
    }
  }
]

13 Likes