Perspective flash syncing

I user a perspective table control to house a custom alarm list. The customer wants the alarms to be flashing when unacknowledged. When items get added to this list, the flashing gets off sync. I did put a little delay in the flashing that lets them sync if they are all drawn at about the same time, but if seems to work only part of the time.

Any tricks to getting the flashing on a page to sync?

To my knowledge, if you want synchronization, you will need to use some kind of global time source and instead of styles base directly on the properties.

Is being out of sync a deal breaker for the client? My opinion, it draws attention more to the issue.

Far as I know, not a deal breaker, but...

In Vision, I would user a timer, not completely sure an option for Perspective.

Vision could offload some of the processing to the client. That doesn't happen in Perspective.

Just an idea ...

  • Apply that style class to the table background so that it flashes by default.
  • Let the unacknowledged alarm rows have a transparent background.
  • Everything else should have an opaque background.

You might have trouble if there are only a few rows on a long table. Try setting props.virtualized : false and position.basis : auto to have the table auto-size to its contents. (I haven't tried it.)

Avoid black on red.

Also have a look at High Performance HMI. Tell your customer that s/he's asking for a "low performance HMI" from the last century.

I would create an expression tag, This is on for 1 second and off for 1 second.

toMillis(now(1000)) % 2000 < 1000

You could also do this on the view and a custom property.

This is an extraordinarily bad idea for Perspective, as those changing values have to be assembled and sent from the gateway to the many individual places you would use these in your Perspective UIs. For every change.

Perfectly fine idea for Vision.

Well Phil if you want something global what other option do you have?

If it's just the view, then use a custom property. But if everything has to sync what other choice do you have?

1 Like

I understand high Perf HMI. Main issue here is they wanted it exactly like their existing HMI so their training / documentation changes would be minimal. This is a regulated line, and the screen layout / format has passed PHMSA audit and they're comfortable with it.

Lots of things I would do different, but they don't want it to be different...

Maybe put the animation on the alarm table itself and use a selector so that only the UnAck'd parts would animate? Not sure how this would work in the various browsers.

Add the animation to the stylesheet, but only applying to elements that are a member of SyncBlink (the whole table) and Unacked (rows that have not been ack'd).

stylesheet.css


.psc-SyncBlink .psc-Unacked {
  animation-name: psc-BlinkAnimation-anim;
  animation-delay: 0s;
  animation-direction: alternate;
  animation-duration: 2s;
  animation-fill-mode: both;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

Then define a couple of dummy styles to apply to the alarm table and the rowStyles:

.psc-SyncBlink {
}

.psc-Unacked {
}

Create the real style which isn't tied to anything but controls what is animated:

Apply the styles to the alarm status table:

[
  {
    "type": "ia.display.alarmstatustable",
    "version": 0,
    "props": {
      "rowStyles": {
        "activeUnacked": {
          "base": {
            "classes": "Unacked"
          }
        },
        "clearUnacked": {
          "base": {
            "classes": "Unacked"
          }
        }
      },
      "style": {
        "classes": "SyncBlink"
      }
    },
    "meta": {
      "name": "AlarmStatusTable"
    },
    "position": {
      "grow": 1,
      "shrink": 0,
      "basis": "400px"
    },
    "custom": {}
  }
]

The difficulty is that the element style settings will override the BlinkAnimation, so you can only animate things that aren't set by the alarm row (e.g., font-weight didn't animate for me).

It is possible that they will still get out of sync, in which case you could do something expensive (reset the style/animation based on a memory tag every few seconds) or pick a prime number for the duration (so that they will never be in sync).

1 Like

It's very possible that I'm missing something and this is a terrible approach, but here goes nothing.

There is the is() function in CSS that I like to take advantage of when trying to do some out of the box design work in Perspective. In this case, as a quick example, create two Styles one called "Blink_Trigger" and the other "Blink_Red". You don't need to set any attributes in these styles, you can just leave them empty. Then in your stylesheet resource add this:

:is(.psc-Blink_Trigger) .psc-Blink_Red {
	background-color: red;
}

Paste this view into the project:

{
  "custom": {},
  "params": {},
  "props": {
    "defaultSize": {
      "height": 529
    }
  },
  "root": {
    "children": [
      {
        "events": {
          "component": {
            "onRowClick": {
              "config": {
                "script": "\tif self.props.data[event.rowIndex].city.style.classes \u003d\u003d \"\":\n\t\tself.props.data[event.rowIndex].city.style.classes \u003d \"Blink_Red\"\n\t\tself.props.data[event.rowIndex].country.style.classes \u003d \"Blink_Red\"\n\t\tself.props.data[event.rowIndex].population.style.classes \u003d \"Blink_Red\"\n\telse:\n\t\tself.props.data[event.rowIndex].city.style.classes \u003d \"\"\n\t\tself.props.data[event.rowIndex].country.style.classes \u003d \"\"\n\t\tself.props.data[event.rowIndex].population.style.classes \u003d \"\"\n"
              },
              "scope": "G",
              "type": "script"
            }
          }
        },
        "meta": {
          "name": "Table"
        },
        "position": {
          "height": 400,
          "width": 652,
          "x": 54,
          "y": 42
        },
        "props": {
          "data": [
            {
              "city": {
                "align": "center",
                "editable": true,
                "justify": "left",
                "style": {
                  "classes": ""
                },
                "value": "Folsom"
              },
              "country": {
                "style": {
                  "classes": ""
                },
                "value": "United States"
              },
              "population": {
                "style": {
                  "classes": ""
                },
                "value": 77271
              }
            },
            {
              "city": {
                "align": "center",
                "editable": true,
                "justify": "left",
                "style": {
                  "classes": "Blink_Red"
                },
                "value": "Folsom"
              },
              "country": {
                "style": {
                  "classes": "Blink_Red"
                },
                "value": "United States"
              },
              "population": {
                "style": {
                  "classes": "Blink_Red"
                },
                "value": 77271
              }
            },
            {
              "city": {
                "align": "center",
                "editable": true,
                "justify": "left",
                "style": {
                  "classes": ""
                },
                "value": "Folsom"
              },
              "country": {
                "style": {
                  "classes": ""
                },
                "value": "United States"
              },
              "population": {
                "style": {
                  "classes": ""
                },
                "value": 77271
              }
            },
            {
              "city": {
                "align": "center",
                "editable": true,
                "justify": "left",
                "style": {
                  "classes": ""
                },
                "value": "Folsom"
              },
              "country": {
                "style": {
                  "classes": ""
                },
                "value": "United States"
              },
              "population": {
                "style": {
                  "classes": ""
                },
                "value": 77271
              }
            },
            {
              "city": {
                "align": "center",
                "editable": true,
                "justify": "left",
                "style": {
                  "classes": "Blink_Red"
                },
                "value": "Folsom"
              },
              "country": {
                "style": {
                  "classes": "Blink_Red"
                },
                "value": "United States"
              },
              "population": {
                "style": {
                  "classes": "Blink_Red"
                },
                "value": 77271
              }
            }
          ],
          "selection": {
            "data": [
              {
                "city": "Folsom",
                "country": "United States",
                "population": 77271
              }
            ],
            "selectedColumn": "country",
            "selectedRow": 2
          }
        },
        "type": "ia.display.table"
      }
    ],
    "meta": {
      "name": "root"
    },
    "propConfig": {
      "props.style.classes": {
        "binding": {
          "config": {
            "expression": "if(\n\tgetSecond(now(1000)) % 2,\n\t\"Blink_Trigger\",\n\t\"\"\n)"
          },
          "type": "expr"
        }
      }
    },
    "props": {
      "style": {}
    },
    "type": "ia.container.coord"
  }
}

This view has a binding on the root component's style.classes property to swap the class between empty and Blink_Trigger style class every second. This then results in the :is() function in the stylesheet resource alternating between setting and unsetting the Blink_Red style class background-color attribute to red. I added a script to the table for clicking on the rows as a quick way of setting/removing the Blink_Red class from each row to show that the color flashing remains in sync regardless of timing.

Unless I'm missing something, this shouldn't have a huge impact on the server since there is just the one expression that is having to make the round trip every second. The actual blinking is left up to CSS/your browser to handle the load - I wouldn't think this would be anything too crazy, but I'm far from a web browser expert...if someone knows that this will cause problems, please let me know and I'll gladly edit this post.

In the end, this example will look like this:

3 Likes

Thanks everyone. This is starting to look way too involved. I think I'm going to wait until they say they can't live without it. Shouldn't be too much of an issue if they keep up with their acknowledgements.

They're also very sensitive to screen draw performance, so I sure don't want to do anything that could affect that.

Thanks again!

This stackoverflow answer gives an interesting idea for using the delay property but calculated to sync up with a multiple of the animation duration.

Not sure what the best way would be to implement this in Perspective, maybe JS injection?

Is there a way today to perform flash syncing on multiple objects without resorting to an expression tag which then triggers a border or something ?

I'd rather use a css class with an animation as I assume this will be way less consuming than using the expression tag but this unfortunately does not naturally sync...

Obviously an expression tag used in an expression binding is the worst way of doing this as it will keep going back and forth to the gateway, but this is the only way I can think of that will work

EDIT: FWIW I've found a solution but this involves using JS Markdown Injection..

Have a look at Document: getAnimations() method - Web APIs | MDN