# JMRI Remote Panels

JMRI displays panels via a web browser at the /panel relative URL.

Remote Panel URLs are:

/panel
  Without any additional parameters, /panel lists the open JMRI panels, as shown in the screen shot above (panels may be hidden in PanelPro, but they must be loaded to show up).
  Clicking or tapping on one of the listed panel names will open a functioning panel.
/panel?name=<type>/<name>
  Displays an open, oparable panel. <type> is one of: Panel, ControlPanel, Switchboard or Layout.
/panel/<type>/<name>?format=xml
  Retrieves a portable XML representation of the panel. <type> is one of: Panel, ControlPanel, Switchboard or Layout.

Panel redisplay requires a modern browser with HTML5 support (Chrome, FireFox, Safari, IE9).

## Technical Example: Behind the scenes of Web Server Panels

This chapter shows an overview of how JMRI Web Server operates between the JMRI application and the Web Browser to demonstrate what code is involved, using Panels as an example.

In part A we show at how a panel initially shows up in the browser of a (remote) user logged on to JMRI Web Server.

Part B focuses on the opposing flow of information. You will discover how user action in the browser is picked up all the way back to JMRI and even to the layout.

Some definitions

- Web Server displays panels, roster engines, tables and Operations in the web browser, using a mix of XML, Javascript, jdom, json, jQuery and CSS.
- Each of these categories is displayed in a separate frame, accessible in the browser via tabs at the top edge of the browser window: Panels, Throttle, Operations, Tables:
- Each category has its own JavaScript code file, e.g., `/web/js/Panel.js`, plus supporting "Servlets" to connect to JMRI, as documented in the Web Server Javadoc. (Note: The code is run from `/web/js/`, but the real source code is stored in `/web/ts/` to allow the use of compiled TypeScript)

### A. From JMRI to the browser

We assume JMRI and Web Server are running and the user has a browser pointing to `http://localhost:12080` as set in JMRI Web Server preferences.
Configuration and current state of panel items is handled by the `jmri/jmrit/display/xPanelEditor.java` code. This means that creating a panel and items like Turnouts connected to the layout, and listening for changes has been completed first in the JMRI application. A graphical control panel needs to be loaded in JMRI for it to show up in the JMRI web browser drop down (it is OK if a panel is hidden in JMRI UI).

1. To open a panel the User uses the mouse to click the "Panel" combo on the menu bar.
2. The web browser responds by asking Web Server to produce the panel by sending a GET request together with a `/panel/"name"` URL.
3. This starts the `/web/js/panel.js` JavaScript code.
4. The main method in `panel.js` is `$(document).ready(function() {` (around line 2000).
   It ends with a call to the `requestPanelXML(panelName);` method:
   `
   141 var requestPanelXML = function(panelName) {
   142 $.ajax({
   143 type: "GET",
   144 url: "/panel/" + panelName + "?format=xml", [...]
   145 success: function(data, textStatus, jqXHR) {
   146 processPanelXML(data, textStatus, jqXHR);
   147 setTitle($gPanel["name"]); } [...]
   `
   Here, line 142 sends an AJAX (Asynchronous JavaScript And XML) `GET XMLHttpRequest`code> for the panel to be retrieved by its name to the JMRI PanelServlet.
5. The connection between web server and JMRI categories is handled by servlets in `JMRI/java/src/jmri/web/servlet/`, pieces of Java code interacting with the web server. The extended HttpServlet `jmri/web/servlet/panel/AbstractPanelServlet.java` links the web server requests to the information kept in Java. The `doGet()` HttpServer message calls in `panel.js` to answer the request.
6. In the case of Switchboards the `/jmri/web/servlet/panel/SwitchboardServlet.java` contains methods such as:
   - `String getPanelType()`
   - `String getXmlPanel(String name)`
   When you want to develop new features on web panels, that second method needs to be expanded to include them when pulling new information from SwitchboardEditor and individual BeanSwitches placed on that panel:
   `
   40 protected String getXmlPanel(String name) {
   41 SwitchboardEditor editor = (SwitchboardEditor) getEditor(name);
   42 Element panel = new Element("panel");
   43 panel.setAttribute("shape", editor.getSwitchShape());
   44 Element color = new Element("backgroundColor");
   45 color.setAttribute("red", Integer.toString(editor.getBackgroundColor().getRed()));
   46 [...] 47 panel.addContent(color);
   `
   Line 45 allows us to later from `panel.js` call e.g.,
   `979 $("#panel-area").css({"background-color": "rgb(" + $widget.red + "," + $widget.green + "," + $widget.blue + ")"});`
7. From the Java code running JMRI, specific information is written out to an xml "snapshot" for both the panel (JFrame) and the individual objects placed on that panel, so called 'Positionables':
   - The complete panel is "stored" by the `store(object)` method in `jmri/jmrit/display/switchboardEditor/configurexml/SwitchboardEditorXml.java`:
     `
     41 public Element store(Object o) {
     42 SwitchboardEditor p = (SwitchboardEditor) o;
     43 Element panel = new Element("switchboardeditor");
     44 JFrame frame = p.getTargetFrame();
     45 panel.setAttribute("name", "" + frame.getTitle());
     46 panel.setAttribute("class", "jmri.jmrit.display.switchboardEditor.configurexml.SwitchboardEditorXml");
     [...]
     `
     followed by a for-each loop:
     `
     for (BeanSwitch sub : _switches) {
     try {
     Element e = ConfigXmlManager.elementFromObject(sub);
     e.setAttribute("label", sub.getNameString());
     `
   - Additional properties of all BeanSwitches on the Switchboard panel are exported in a for-each loop in `jmri/jmrit/display/switchboardEditor/configurexml/BeanSwitchXml.java`:
     `
     public Element store(Object o) {
     BeanSwitch bs = (BeanSwitch) o;
     Element element = new Element("beanswitch");
     element.setAttribute("label", bs.getNameString());
     [...]
     }
     `
   The result is an xml `out.outputString(doc)` in reply to the web server request.
8. To process the xml response returned for the `requestPanelXML` command, the JavaScript code in `/web/js/panel.js` contains the function
   `processPanelXML($returnedData, $success, $xhr)` (around line 160)
   that renders from the xml data in `$xml` an object-based web panel html, ready to forward to the browser and display client-side using CSS and HTML5.
   Among a lot of other properties, the background color is set from the `panel` attribute:
   `
   $("#panel-area").css({backgroundColor: $gPanel.backgroundcolor});
   `
   where `$()` invokes jQuery as a selector (a function that returns a set of elements found in the DOM of the web page).
   After the attributes of the main panel such as color, size and position are copied from the xml, all individual elements in the panel xml are processed in a for-each loop, building a persistent array of `$widget` arrays:
   `
   236 $panel.contents().each(
   237 function() {
   238 var $widget = new Array();
   `
   In such an array the attributes of each (positionable) item are copied in 1-on-1 from the xml, filling the basic widget web object:
   `244 $(this.attributes).each(function() {
   245 $widget[this.name] = this.value;
   246 });
   `
9. Next, every `$widget` array if enriched by extra fields, either using attributes shared on the panel, like
   `666 var $cr = $gPanel.turnoutcirclesize * SIZE;`
   with attributes of one of the elements: `1039 $widget['text8'] = $(this).find('inconsistentText').attr('text');`
   or with copies of other elements already present in the $widget array, for example:
   `
   case "beanswitch" :
   $widget['name'] = $widget.label;`
   which creates a 'name' item in the array and copies in the `label` item.
10. Next, the HTML is written:
    `
    $("#panel-area").append("<div id=" + $widget.id + "r class='" +
    $widget.classes + "' " + $hoverText + "></div>");
    `
11. The complete HTML "page" is sent out to user's screen as a structured but basically static visual, composed of graphical "objects" that will be updated via the server as needed at a later point in time when the status of one such item changes inside JMRI.
12. Next, `panel.js` starts up a process to send and listens for changes to each panel element (called "nodes" in xml speak).
    The nodes in our example panel are called BeanSwitches. SensorIcons and LayoutTracks are similar nodes. Each element is assigned a `$widget` variable in `/web/js/panel.js`, to interact with the browser document object model (DOM) HTML5+ entities via jdom (Java-based document object model for XML), jQuery and JavaScript Object Notation (JSON).
    To be able to keep a link to every graphical element displayed on the browser we mainly use `<div/>`s that the server will feed to the user's browser and using jQuery can be contacted by their unique ID.
13. From that moment, `panel.js` follows interaction in `java.src.jmrit.display.xPanelEditor` and the separate PositionableItems like LayoutTurnout, CPE SensorIcon and Switchboard Beanswitch.
14. Now that the `$(document).ready(function()` main method is running, it will listen for changes, and depending on the type (object) will start `updateWidgets(name, state, data)`, which in turn calls `setWidgetState(widgetId, state, data)` to update the shape displayed on screen, e.g., when a sensor on the layout changes state.

What to Code:

1. Describe how to get your specific stuff out of JMRI and into the web server:
   - Shared: create the `store(panel)` method
   - Per item: add code in a `for-each` element loop in the same method
2. Describe how to fill the `$widgets` for your items in the `processPanelXML()` function.

Where to Code:

1. In `/jmri/jmrit/display/xEditor/configureXml/xEditorXml.java` + similar files for any special classes your panel type relies on.
2. In `/web/js/panel.js`

### B. From the browser to JMRI

Note: Web click events are only enabled if a panel is configured (in JMRI xPanelEditor) to allow control.

1. To perform regular click-handling, a `mouseup()` function is hooked up to widgets in `/web/js/panel.js` (around line 1120):
   - a state toggle function for all non-momentary clickable widgets:
     `$('.clickable:not(.momentary)').bind(UPEVENT, $handleClick);
     `
   - Momentary widgets go active on `mousedown`, and inactive on `mouseup`
2. Sending commands to the JMRI items under control, and listening for changes to those items in JMRI, is handled by the JSON WebSocket Server.
   - User interaction in the browser is picked up via `UserClicked(object)` and GET/POST HTTP messages.
   - When a `mouseClickEvent` is heard, the `$handleClick(e)` function in `panel.js` calls `sendElementChange($widget.jsonType, $widget.systemName, $newState)`, resulting in `jmri.setObject(type, name, state)` forwarding to the JMRI system.
3. The `$handleClick(e)` function in `panel.js` (around line 1160) updates the widgets involved:
   - Text based widgets are told how to change the <div> item that's already displayed on screen: fetch them by their ID, update their contents e.g., show an ON label instead of OFF.
   - Icon-based widget are redrawn to reflect changes to state or occupancy by drawing on the graphical "canvas"
   - It is also possible to swap styles on a div using CSS, for example to change the style of a widget from "css2" to a new style called "css4" (where 4 represents the `int` value for Inactive and Thrown):
     `var $reDrawIcon = function($widget) {
     $('div#' + $id).css($widget['css' + $newState]);
     `
   The actual CSS styling code for the element is taken from `web/css/panel.css` file by he user's browser.
4. Via `sendElementChange` (around line 1750) a call is made to `jmri.setObject(type, name, state);`, where `jmri` represents the JMRI WebSocket on this server
5. `setObject()` in `/web/js/jquery.jmri.js` jQuery JavaScript Library instructs JMRI to set for example a Light to "On" via a Post message:
   `
   jmri.setLight = function (name, state) {
   jmri.socket.send("light", { name: name, state: state }, 'post');
   }
   `
6. If properly connected, on the layout a light will start to light.

What to Code: Specify the response to user action by adding an `if(...)` block to the `$setWidgetState(id, newState, data)` function (near line 1570).

Where to Code: `$handleClick()` function in `jmri/web/js/panel.js`

### Conclusion

We hope the above example will help new developers understand which parts are required to create and maintain web display for a panel type, and how each part is connected to display stuff from JMRI in the web browser, and back.
As always, start small and check often!