Skip to content

Technical Notes

Ryan Slominski edited this page Aug 19, 2020 · 54 revisions

Rendering

Native web technologies are extremely flexible and there are multiple ways in which EDM screens could be rendered in a web browser. Modern web standards support both immediate mode rendering via the HTML 5 Canvas and retained mode rendering via both HTML and SVG elements. The wedm implementation uses retained mode rendering with HTML and SVG to leverage as much existing web scaffolding/infrastructure as possible such as the ability to style via Cascading Style Sheet (CSS) rules and interact and respond to events on elements via JavaScript. Another reason: The lead developer is very familiar with manipulating the Document Object Model (DOM), the scene graph / retained object model, and not as proficient with manipulating a canvas or creating a custom system to track and manage the state of objects represented on the canvas. The wedm implementation in a nutshell: each EDM object is converted to either an HTML div or an embedded SVG element and positioned in an absolute layout. An HTML div is used for widgets with text to leverage the excellent text handling in HTML and shapes are modeled/rendered using SVG as it provides a natural fit.

Fonts

Fonts are platform dependent (Each computer make and model has its own set of installed fonts). This creates challenges with providing properly sized screen text on a screen of absolutely positioned elements viewed from differing computers. This problem exists with EDM itself, but is generally not noticed too much as most EDM installations are used in a local network with computers of similar build. Remote viewing of EDM screens in a web browser results in a much more diverse set of client computers including mobile devices and therefore a much more diverse set of client fonts. To overcome this wedm dynamically resizes all fonts on the client web browser at runtime to fit within their bounding box. An alternative that could be adopted in the future with much more coordination and re-working of screens is to agree to only use a specific set of fonts and ensure they are installed on all clients. This could be extended to the web since custom fonts can be supplied to web pages. However, a major limitation is that most fonts installed on operating systems are licensed and cannot be shared on other devices so everyone would have to agree to use open fonts.

Mouse Events

The EDM mouse event model differs from the model in web browsers. In EDM mouse events are propagated to all elements in a stack of elements at a given point. In JavaScript mouse event handling depends on how the element is positioned and only the top most absolutely positioned element receives mouse events; events are only propagated up a direct hierarchy to parent elements (or down to child elements depending on bubble vs capture setting) if their position is static (default / normal document flow). To overcome this wedm top most element event handlers must search for elements underneath and manually propagate mouse events to these other elements. EDM screen designers at JLab like to stack buttons on each other so that what appears like clicking one button is actually clicking multiple. Also, stacking Related Displays with the "invisible" attribute is a common way to allow clicking on a shape to result in a new screen or screen menu.

Invisible Elements

There are many ways to make elements invisible in HTML / SVG. However, in EDM invisible elements still respond to mouse events. This means we cannot use style rules such as "display: none" or "visibility: hidden". Instead we must set the border, background, and foreground color to fully transparent.

Stacking Order

In both EDM and HTML / SVG the order the element appears in the document from top to bottom determines the stack order. However, EDM will move an element to the top of the stack each time it redraws an element (i.e. redraw ignores stack order). This includes drawing the outline around a control widget such as a Related Display or button element on hover or each time a PV update results in a new background, border, or other graphical change. This always draw on top behavior remains an outstanding difference. One possible approach to match EDM behavior is to move the widget position in the DOM each time an update occurs to ensure it is drawn on top. This is fairly costly though and must take into account nested groups. Another option is to increment the z-index on each update to be more than everything else (global shared z-index counter), which would need to be aware of the max z-index of 2147483647 and be prepared to reset all widget z-indexes when rolling back around, and also would need to be aware of nested groups. In practice this discrepancy hasn't been a problem on the screens at JLab for anything other than hover outlines so a simpler partial solution is used to handle this one case: on hover the :before CSS selector is used to insert a psudo div inside the widget that is then positioned via z-index above everything else. This psudo div is assigned rules to show an outline and allow mouse events to pass through. In other words now having a button hidden below a rectangle will result in an outline on hover around the rectangle without showing the button.

CALC Expressions

The ability to execute expressions can be accomplished in JavaScript using the eval statement, but the expression syntax differs between EDM and JavaScript. To overcome this EDM CALC expressions are transformed via character substitution to JavaScript friendly expressions. The following substitutions are currently used:

EDM JavaScript
= ==
# !=
and &
or |
abs Math.abs
min Math.min
max Math.max

Colors

Colors in EDM are specified as an index into a color palette stored in a color file (colors.list). Each EDM color is defined as either a static RGB or as a color rule (expression). Since the RGB colors on the web have a depth of 256 and EDM has a depth of 56K (or optionally 256) a conversion generally must take place. Since colors are dynamically being evaluated and assigned the color file is translated to a set of JavaScript objects so that the script running on the client browser can quickly access the colors.

Color Rules

Color rules are handled similar to CALC expressions in that character substitution is used to prepare the expression for evaluation using JavaScript eval. However, the color rule operands are implicit and the format is of a conditional statement. In order for the rules to be evaluated explicit operands are inserted and the expression is converted to a JavaScript switch statement.

Widgets

An EDL file is parsed and the widgets within are translated to either an HTML or SVG representation. This initial translation is done by creating a Java object representation of each widget. Many widgets share similar attributes and rendering rules so a class hierarchy exists (using inheritance). The job of the Java widget objects are to (1) parse the set of traits from the EDL file and (2) generate HTML / SVG that contains all of the information needed by the client for the widget. After the web page completes loading all HTML / SVG widgets in the document a JavaScript PvObserver object is created for each widget which requires PV Monitoring. The objects are responsible for handling PV updates such as visibility, alarm state, etc. Inheritance is used to share common functionality with JavaScript objects as well. Each JavaScript object is mapped to any PVs that the widget is concerned with. This way when an update for a PV is received the map can be consulted to see which widgets are interested in the update and notified.

LOC Variables

During web page initialization when JavaScript objects are created for each WEDM widget LOC (Local) variables are handled specially to ensure they aren't sent to the epics2web server requesting EPICS monitoring, but instead are added to a JavaScript collection of known local variables. Mapping of LOC variables to widgets is handled just like with EPICS PVs so that when a LOC variable is updated (perhaps via button press) then all widgets interested can be notified. LOC variables are stored with their LOC\\ prefix to allow mixing them in the same collections and maps containing EPICS PVs, which always have their EPICS\\ prefix stripped off if originally present.

Macros

HTTP URL parameters are used for EDM macros (a.k.a. symbols / screen parameters). Macros are passed in the URL in the form:

$(<key>)=<value>

This ensures that there are no namespace collisions with other URL parameters; for example the already used parameter name "edl" could not be used as a macro name otherwise. It also means the search and replace is ready to go without further manipulation since a dollar sign then the name surrounded in parentheses is the form the keys are stored in EDL. The server does the search and replace just before sending the HTML response to the client browser. The URL parameters are encoded so the dollar sign becomes %24.

Screen Cache

Parsing an EDL file and translating it to HTML every time a screen is requested can be time consuming. A caching layer is implemented and screens are stored in memory and simply returned on subsequent requests. Currently the last modified date of the EDL file is used to determine if the screen has been modified and if so the cached version is cleared. A possible improvement is to use the File Change Watch Service to determine if a cached screen needs to be rebuilt. The Java NIO WatchService uses OS mechanisms when available, but network file systems (NFS) don't seem to be supported.

Extensibility

WEDM follows the client-server architecture of the web and each widget has both a server and client side representation. WEDM is designed to be extensible and there is a widget interface on both sides. The high level steps to add a new widget:

  1. Create a Java Class that implements the Java WEDMWidget interface
  2. Create a JavaScript object that extends the JavaScript PvObserver object*

*only if your widget needs to monitor PVs, otherwise skip.

In order to include your new widget objects (each in a new file) you'll need to:

  1. Include your Java class file in the classpath (drop it in the lib directory)
  2. Drop your new JavaScript file into the resources/widget/edmClassName directory (and drop a CSS file too with any styles)
  3. Update the wedm.properties configuration file to map your new Java Class to the EDM object name (links to JavaScript and CSS files are also determined by the keys in this file)

You don't need to recompile the existing code, but you do need to repackage the application with your new code since Java web applications are distributed as a single WAR file. See Build and Package.

Server Side

The server application is implemented in Java and runs in a Java Application server such as Tomcat. A request to generate an EDM screen is serviced in two stages: (1) parsing the file, and (2) rendering the HTML. During parsing the application reads a configuration file that maps EDM objects to Java objects. Each Java object must implement the WEDMWidget interface, which contains two methods:

public void parseTraits(Map<String, String> traits, ScreenProperties properties);
public String toHtml(String indent, Point translation);

The application parsing stage generates key-value traits for each widget and passes them to the widget in the parseTraits method along with the ScreenProperties object, which contains default colors for the screen and also has a reference to the ColorPalette object, which is the generated by parsing the colors.list file. A utility class named TraitParser provides static methods for parsing common traits such as an EDLColor, EDLFont, float, int, etc. In order to pass a Map of key-value pairs an assumption is made that an EDL object definition is defined between "beginObject" and "endObject" and consists of a set of key-value pairs with each line containing one pair except for multi-line values which are delimited by braces. This assumption seems to work for all but one known old version of ActiveLine, in which nested braces break the multi-line values - this special case is detected and dealt with.

After everything is parsed and Java widget objects are created the rendering stage is initiated and the toHtml method is invoked on each widget. The indent parameter is useful for generating easy to read (nicely indented) HTML. During a production build the indent may be an empty string to reduce generated screen size. The translation Point is necessary because some container widgets such as a Symbol will wish to have child widgets rendered in a translated location.

A widget abstract class named CoreWidget implements this interface and in practice it will likely be more useful to extend this class than implement the interface because the CoreWidget parses many of the common attributes such as x and y location on the screen and height and width dimensions.

Client Side

Since there are no classes in JavaScript inheritance is done via prototypes and instances of objects using "new" automatically share the prototype functions. In order to handle PV monitoring a base object named PvObserver exists. The base PvObserver object has a function for updates and for metadata info:

handleUpdate();
handleInfo();

Updates are subdivided into types based on PV usage. Each widget can define a custom PvObserver extension object, which can override the functions they are interested in. The base PvObserver object provides core handling of many of the types. The PV update notification functions are:

handleControlUpdate()  
handleVisibilityUpdate()  
handleAlarmUpdate()
handleColorUpdate()
handleIndicatorUpdate()  
handleLimitUpdate()

In order for a new PvObserver extensions to inherit the prototypes from parent objects they must be defined after the parent objects are defined. The base object PvObserver will always be defined before any extensions due to script include ordering, but extensions that extend other extensions have no guarantee of script include order. This ordering requirement is handled by wrapping all extension definitions in a function and registering it with the wedm core, which will invoke it in the proper order based on specified dependency (of which there can only be one - multiple inheritance is not allowed). The method to register an observer init function is:

jlab.wedm.initPvObserver(observer, dependency);

There is a "PvObserver.refresh()" function that can be overridden. This function is called each time a hidden widget becomes visible again. This can happen to a widget if it is embedded in a PictureInPicture widget, which may only show one screen out of many at a time. This refresh method allows widgets which require positioning based on dimensions to operate properly as elements with "display: none" have no size. The MotifSlider is an example - without this the slider knob is not positioned correctly upon becoming visible again.

Resizing text only works on widgets that are visible. Therefore by convention every widget must be visible initially. After the text resize function executes all post-resize initialization code can be executed. This post resize init code can hide widgets. The callback functions for post resize init can be pushed into the array:

jlab.wedm.initFuncs

Build and Package

A Gradle build file is provided and the target "war" will compile and package the application for deployment in a Java application server. However, during development it is often useful to take advantage of the compile-on-save and automatic re-deployment of the application provided by an IDE such as Netbeans or Eclipse. The project follows the standard project organization and can be easily imported so that an IDE build file / process can be utilized. The standard build and packaging provided by an IDE will result in code that has non-optimal performance, but which can be easily debugged. The war target provided by the WEDM Gradle build file is optimized for production deployments by consolidating the many widget JavaScript files and Stylesheets to a single file for scripts and a single file for styles. These files are then further optimized by minifying them.

Future Performance Considerations

  • Embedded: The embedded widget has the ability to act like a deck of cards where there are many screens, but only the top one is visible. Currently all embedded screens in a set are parsed and rendered and then hidden behind the top one when the parent screen is requested. Also, all PV monitoring is enabled on all screens. There could be a benefit if the embedded screens were obtained lazily (on-demand) instead of all up front. It might also be beneficial if the hidden embedded screens are not monitoring EPICS and instead monitor only when they become visible.
  • Images: Currently the image widget has the image data loaded on the server and packaged with the main screen as a data URI. This could be improved by instead providing a reference to the image file on the server as this would allow the web browser to asynchronously fetch the image for faster perceived page load times. Perhaps more importantly the external image file could be cached for actually faster page load times.
  • Color File: Currently each screen request results in a response containing all colors and color rules. It would be better to provide a reference to an external file containing this information (it would be an external JavaScript file) so that it could be cached. We would have to consider what happens if someone updates the color file though (perhaps we use middle ground - conditional cache where browser asks if file is modified each time instead of extremes of always transferring latest version or always caching blindly without knowing if it changed).

Misc

  • EDM has been reverse engineered to create WEDM as no specification document exists. This means widgets I have not encountered won't work. There is currently no provision in WEDM for widget versions either.
  • A border specified to have width 0 in the EDL is often rendered non-zero in EDM. This may have to do with the EDM box model, which needs further investigation (border included in widget size or not, etc).
  • A widget with a color rule and no color PV (or alarm PV) results in the first color of the rule, not the default color in the rule.
  • EDL files must use UNIX line endings or EDM barfs; wedm doesn't care either way
  • An EDM Button widget with local variable must be of type enum and assumes exactly two states. However, a MessageButton can have any type. Further, the seemingly undocumented LOC enum declaration is of the form:
LOC\\<name>=<type>:<initial value>,<first label>,<second label>