Custom Page: Difference between revisions

From MidasWiki
Jump to navigation Jump to search
(Document main user-facing control functions)
 
(144 intermediate revisions by 4 users not shown)
Line 2: Line 2:


= Links =
= Links =
{{mhttpdpages3|[[Custom Page Features]]|[[New Custom Pages (2017)]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}
{{mhttpdpages3|[[Custom Page Features]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}


= Purpose =
= Purpose =
A user-created [[mhttpd]] Custom Web Page accessible from the [[Status Page]] allows the user additional flexibility. For example, a custom page may present the essential parameters of the controlled experiment in a more compact way. A custom page may even replace the default [[Status Page]].  
A user-created [[mhttpd]] Custom Web Page accessible from the side menu allows the user additional flexibility. For example, a custom page may present the essential parameters of the controlled experiment in a more compact way. A custom page may even replace the default [[Status Page]].


= Introduction =
= Introduction =
Custom web pages provide the user with a means of creating secondary user-created web page(s) activated within the standard MIDAS web interface. These custom pages usually display ODB parameters or data to the user. They can contain specific links to the ODB so the user may also input information relevant to the experiment. Users create Custom Pages when the standard pages do not meet their requirements completely.
Custom web pages provide the user with a means of creating secondary user-created web page(s) activated within the standard MIDAS web interface. These custom pages usually display ODB parameters or data to the user. They can contain specific links to the ODB so the user may also input information relevant to the experiment. Users create Custom Pages when the standard pages do not meet their requirements completely.
We note that MIDAS has provided a number of different ways of providing custom pages over the years.  A new scheme of custom pages making use of modern HTML5 techniques has been introduced in 2017. This page will mostly only be providing documentation for the new scheme of custom pages.


= Examples of Custom Pages =
= Examples of Custom Pages =
Line 14: Line 16:
Click on the thumbnails to enlarge.
Click on the thumbnails to enlarge.


[[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]]
{|
|-
| [[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]] || '''Example 1'''
This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of "fills" and "labels". Open valves are represented as green circles, closed valves as red circles. If, for example, an open valve is clicked, the valve closes, and the circle turns red (provided the user successfully supplied the correct password).
|-
| [[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]] || '''Example 2'''
Many MIDAS experiments work with ROOT based analyzers today. One problem is that the graphical output of the root analyzer can only be seen through the X server and not through the web. At the MEG experiment, this problem was solved in an elegant way: The ROOT analyzer runs in the background, using a "virtual" X server called Xvfb. It plots its output (several panels) normally using this X server, then saves this panels every ten seconds into GIF files. These GIF files are then served through mhttpd using a custom page. The output is shown in Figure 2.


;Example 1
The buttons on the left sides are actually HTML buttons on that custom page overlaid to the GIF image, which in this case shows one of the 800 PMT channels digitized at 1.6 GSPS. With these buttons one can cycle through the different GIF images, which then automatically update ever ten seconds. Of course it is not possible to feed interaction back to the analyzer (i.e. the waveform cannot be fitted interactively) but for monitoring an experiment in production mode this tool is extremely helpful, since it is seamlessly integrated into mhttpd. All the magic is done with JavaScript, and the buttons are overlaid on the graphics using CSS with absolute positioning. The analysis ratio on the top right is also done with JavaScript accessing the required information from the ODB.  
:This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of "fills" and "labels". Open valves are represented as green circles, closed valves as red circles. If, for example, an open valve is clicked, the valve closes, and the circle turns red (provided the user successfully supplied the correct password).


[[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]]
For details using Xvfb server, please contact Ryu Sawada <sawada@icepp.s.u-tokyo.ac.jp>.
;Example 2
|-
:Many MIDAS experiments work with ROOT based analyzers today. One problem is that the graphical output of the root analyzer can only be seen through the X server and not through the web. At the MEG experiment, this problem was solved in an elegant way: The ROOT analyzer runs in the background, using a "virtual" X server called Xvfb. It plots its output (several panels) normally using this X server, then saves this panels every ten seconds into GIF files. These GIF files are then served through mhttpd using a custom page. The output is shown in Figure 2.
| [[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]] || '''Example 3'''
This custom page from the Deap Experiment (Figure 3) allows the users to easily set individual channels, or a group of channels, or all channels of the SCB modules to a particular value.  
|}


The buttons on the left sides are actually HTML buttons on that custom page overlaid to the GIF image, which in this case shows one of the 800 PMT channels digitized at 1.6 GSPS. With these buttons one can cycle through the different GIF images, which then automatically update ever ten seconds. Of course it is not possible to feed interaction back to the analyzer (i.e. the waveform cannot be fitted interactively) but for monitoring an experiment in production mode this tool is extremely helpful, since it is seamlessly integrated into mhttpd. All the magic is done with JavaScript, and the buttons are overlaid on the graphics using CSS with absolute positioning. The analysis ratio on the top right is also done with JavaScript accessing the required information from the ODB.  
= Access a Custom Page from the Regular MIDAS pages =
Access to a Custom Page is set up through the [[/Custom ODB tree]] (see [[/Custom ODB tree#Keys in the /Custom tree|custom-link]]). This associates a custom page file on the disk with a menu item on the left navigation bar. Clicking on the resulting link will display that custom page.
 
Often a custom page requires resources such as *.css (stylesheets) or *.js (javascript) files. It is convenient to store all such files with the custom page file (*.html) in
a particular directory, e.g. /home/expt/online/custom. By creating an ODB key /Custom/Path, all the custom page files and resources can be served easily from this directory.
See [[Custom Page Features#Resource files]] for more information.
 
If the key  {{Odbpath|path=/Custom/myPage&}}  (see Note) is created, e.g.
odbedit> ls /Custom
    Path                              /home/expt/online/custom
    myPage&                          mypage.html
 
the custom link on the left navigation bar will be <code>myPage</code> and the URL for the resulting custom page will be of the form <code>http://myhost.mydomain:myport/cmd=?Custom&page=myPage</code> (see also [[mhttpd#usage]]). 
Clicking on <code>myPage</code> will display the custom page in the same window.
 
;Note
: Without the "&" symbol in the key name, the page would appear in a new window. See [[/Custom ODB tree#Key names|Key names]] for more information.
 
If an experiment used many custom pages, the menu on the left side can get pretty long. To avoid that, custom pages can be structured in submenus. Simply put a custom page in an ODB subdirectory, and it will appear in a separate submenu, e.g.
 
odbedit> ls -r /Custom
    Path                              /home/expt/online/custom
    myPage&                          mypage.html
    Calorimeter
        HV                            hv.html
        Rates                        rates.html
    Baeam
        Beamline                      beamline.html
        Accelerator                  accel.html
 
The pages then include the subdirectory in the URL, like
 
http://localhost:8080?cmd=custom&page=beam/Beamline
 
Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as
 
  <body class="mcss" onload="mhttpd_init('Calorimeter/HV');">
 
in order to keep the submenu open after you select the custom page.
 
= How to write a custom page =
A custom page is usually written in a combination of HTML and Javascript. It can contain any of the features described below. A [[Mjsonrpc | Javascript MjsonRPC Library]] has been written to provide access to the ODB and other functions.
 
In what follows, we describe a scheme for writing custom pages with the set of modb* javascript functions.  The advantages of using these modb* javascript functions are
 
* modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.
* modb* functions ensure that all the periodic updates of the ODB value (and other MIDAS information) are done in a single MjsonRPC batch call, which should ensure optimal page loading speed.
* modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.
 
It is also possible for users to write their custom pages using only the underlying [[Mjsonrpc | MjsonRPC library]] calls, but they then need to ensure on their own that the page loading remains efficient.  In particular, if you combine the standard MIDAS navigation bars (described in next section) with your own periodic MjsonRPC calls then you will probably need at least two separate periodic RPC calls to populate the custom page (instead of one call).  It will also require more coding to implement the custom page with only MjsonRPC calls.
 
== How to use the standard MIDAS navigation bars on your custom page ==
 
If you want to have your custom page use the same header and navigation bars as the standard MIDAS pages, you need to use the following syntax
 
<pre>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="midas.css">
  <script src="controls.js"></script>
  <script src="midas.js"></script>
  <script src="mhttpd.js"></script>
  <title>myPage</title>
</head>
 
<body class="mcss" onload="mhttpd_init('myPage');">
 
<!-- header and side navigation will be filled in mhttpd_start -->
<div id="mheader"></div>
<div id="msidenav"></div>
 
<div id="mmain">
ADD YOUR HTML/JS  CODE here...
</div>
</pre>
 
The call <code>mhttpd_init('myPage')</code> is executed when the page is loaded, and  <code>myPage</code> is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage.
This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying [[Mjsonrpc | javascript libraries]].
 
= modb* Javascript scheme =
 
The general scheme of the custom page scheme is to write <code>&lt;div class="modb..."&gt;&lt;/div&gt;</code> or <code>&lt;span class="modb..."&gt;&lt;/span&gt;</code> tags with special class names, most of them starting with "modb..." ("MIDAS-ODB"). Use a <code>&lt;div&gt;</code> tag if you want the element to appear in a separate line, and use the <code>&lt;span&gt;</code> tag if you want to display the element in-line. The following description uses only <code>&lt;div&gt;</code> tags, but all of them can be changed to <code>&lt;span&gt;</code>.
 
All HTML tags with "modb..." names are scanned by the <code>mhttp_init('name')</code> function upon page load, and their inner contents are replaced by the requested ODB value or some graphics. The contents are then updated regularly. Updates are once per second by default. This can be changed by passing a second argument to <code>mhttpd_init('name', interval)</code> where "interval" is in milliseconds. You can add or remove "modb..." elements at any time using javascript, and the new elements will be "discovered" automatically during the next update.
 
== modbset(path, value) ==
 
To set values in the ODB, the midas JavaScript function mjsonrpc_db_paste() is usually called. This function is implemented as a JavaScript promise, which lets you chain several request in order to change values inside the ODB in a certain order. If that functionality is not required, the simplified modbset() function can be called, which also implements standard error handling. Two versions of this function exist, one which accepts a single ODB path and a single value, and one which accepts an array of ODB paths and values:
 
<code>
modbset("odb path", value)
</code>
 
<code>
modbset(["odb path1", "odb path2", ...], [value1, value2, ...])
</code>
 
These functions are typically used by custom JavaScript code, like when some value in an experiment exceeds some limit and some action has to be taken like to close a valve. If the call fails (like if mhttpd is dead), a window with an error description is shown.
 
== modb ==
 
This special HTML div tag (abbreviation stands for Midas ODB) <code>&lt;div class="modb" data-odb-path="/Some/Path" onchange="func()"&gt;</code> can be used to call a user-defined function func() if a value in the ODB changes. This function must be defined inline or in a separate &lt;script&gt;...&lt;/script&gt; section, and can execute any operation, such as opening a dialog box, hiding/unhiding parts of the custom page, or changing colors and styles of page elements.
 
The current value of the ODB entry is available inside the "onchange" function as '''this.value'''. Following tag will call a function which logs the current run number in the JavaScript console window:
<br /><br />
<code>&lt;div class="modb" data-odb-path="/Runinfo/Run number" onchange="func(this.value)"&gt;
 
&lt;script&gt;function func(value) {
console.log(value);
}&lt;/script&gt;
</code>
<br /><br />
If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to '''this.value''' as a JavaSctipt object such as
<br /><br />
<code>&lt;div class="modb" data-odb-path="/Runinfo" onchange="func(this.value)"&gt;
 
&lt;script&gt;function func(value) {
console.log(value["run number"]);
}&lt;/script&gt;
<br />
</code>
<br /><br />
Note that ODB entries are mapped to JavaScript objects without change. So if an ODB entry name contains a blank, it must be accessed via the JS square bracket '''value["run number"]''' as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as '''value.state''' for /Runinfo/State for example.
 
== modbvalue ==
 
This special HTML div tag (abbreviation stands for "Midas ODB VALUE")
<br /><br />
<code>&lt;div class="modbvalue" data-odb-path="/Some/Path"&gt;&lt;/div&gt;</code>
<br /><br />
is now automatically replaced by the value in the ODB found at the given path and updated regularly as described above Following options are valid for this tag:
 
{| class="wikitable"
|+ Table 1: List of valid options for modbvalue tag
|-
! Option !! Example !! Meaning
|-
| data-name || class="modbvalue" || Tells the framework to replace this tag with an ODB value
|-
| data-odb-path || data-odb-path = "/Runinfo/Run number" || Path to the value in the ODB
|-
| data-odb-editable || data-odb-editable="1" || If set, the value is not only shown, but is also clickable for in-line editing. Hitting return send the new value to the ODB.
|-
| data-format || data-format="f3" || Specify format of data shown. See Table 2 below for options.
|-
| data-size || data-size="8" || Specify size (in chars) of edit box if one modifies the value. Default is 10.
|-
| data-formula || data-formula="2*x+3" || Specify an optional formula to process with the current ODB value stored in x
|-
| data-validate || data-validate="func" || Specify an optional validation function which gets called before submitting data (see below)
|}
 
=== Validation ===
 
Before a modified value is submitted to the ODB, an optional validation function can be called via the <code>data-validate</code> option. The function
will be called with the current value and a reference to the current modbvalue element. If the function returns <code>false</code>, then the value
is not sent to the ODB.
 
Following example shows a function which just rejects the submission of values above 1000:
 
  &lt;div class="modbvalue" ... data-odb-editable="1" data-validate="my_validate"&gt;&lt;/div&gt;
 
  &lt;script src="controls.js"&gt;&lt;/script&gt; &lt;!-- needed of dlgAlert() --&gt;
  &lt;script&gt;
  function my_validate(value, element) {
    if (value > 1000) {
        dlgAlert("Value cannot be above 1000");
        return false;
    }
    return true;
  }
  &lt;/script&gt;
 
Following function corrects the return value to 1000 if it's above 1000:
 
  &lt;script&gt;
  function my_validate2(value, element) {
    if (value > 1000) {
        element.childNodes[0].value = 1000;
    return true;
  }
  &lt;/script&gt;
 
=== Confirmation ===
 
Before a modified value is submitted to the ODB, a confirmation dialog box can be displayed to let the user confirm the change before it is actually written to the ODB.
This is done with the option <code>data-confirm=&lt;string&gt;</code>. A dialog box is then shown with the <code>&lt;string&gt;</code> as the main text and two buttons with "OK" and "Cancel".
Hitting "OK" finally writes the value to the ODB, hitting "Cancel" keeps the old value.
 
Following example shows an example:
 
  &lt;div class="modbvalue" ... data-odb-editable="1" data-confirm="Are you sure to change the value?"&gt;&lt;/div&gt;
 
Following dialog box is then showed:
 
[[File:Confirm.png|frame|left|Optional confirm dialog]]
 
<br clear=all>
 
=== Formatting ===
 
Table 2 below lists the format specifiers supported with a modbvalue tag <code>data-format="..."</code>:
 
{| class="wikitable"
|+ Table 2: Format specifiers for modbvalue tag <code>data-format="..."</code>.
|-
! Option !! Valid for* !! Example !! Meaning
|-
| %d || int || 1234 || Shows a number in decimal encoding
|-
| %t || int, float || 1,234,567 || Shows a number in decimal encoding with commas as thousands separator
|-
| %x || int || 0x4D2 || Shows a number in hexadecimal encoding
|-
| %b || int || 10011010010b|| Shows a number in binary encoding. Options t, d, x, b can be combined, like <code>data-format="%d / %x"</code> to produce <code>1234 / 0x4D2</code>
|-
| %f&lt;x&gt; || float || 1.234 || Shows a floating point number with &lt;x&gt; digits after the decimal. A value of f0 shows only the integer part.
|-
| %p&lt;x&gt; || float || 1.23 || Shows a floating point number with &lt;x&gt; significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012
|-
| %e&lt;x&gt; || float || 1.23e-05 || Shows a floating point number with &lt;x&gt; digits after the decimal in exponential format. Useful for small number such as pressures.
|-
| T= %f1 C || float || T= 25.4 C || Combination of text and float value is possible.
|-
| %d / %x / %b || int || 17 / 0x11 / 10001b|| Same integer value in different formats
|}
 
* Note that valid for "int" means all integral ODB types (regardless of size or signed/unsigned) and valid for "float" means both float and double.
 
== modbbutton ==
 
This tag generates a push-button which can set a certain ODB entry to a specific value. To set the "Run number" to 100, one can use the following tag:
<br /><br />
<code>&lt;button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="100" data-validate="my_validate"&gt;[Button Text]&lt;/button&gt;</code>
 
 
The optional <code>data-validate</code> function can be used to prevent pressing the button under certain circumstances. If the validate function returns false, the value is not written to the ODB, similarly than for modbvalue tag.
 
== modbbox ==
 
This tag generates a rectangular box which changes color according to a value in the ODB. If the value is nonzero or true (for booleans), the '''data-color''' is used, otherwise the '''data-background-color''' is used
<br /><br />
<code>&lt;div class="modbbox" data-odb-path="/Logger/Write Data" data-formula="x > 0" style="width: 30px; height: 30px; border: 1px solid black" data-color="lightgreen" data-background-color="red"&gt;&lt;/div&gt;</code>
 
Optionally, a <code>data-formula</code> can be specified. The formula sees the ODB value in the variable <code>x</code>, and can do any boolean operation. If the result of this is true, then the box gets the <code>data-color</code>, otherwise the <code>data-background-color</code>.
Examples for these formulas are <code>x > 10</code> for a comparison or <code>x & 1</code> which will do a bitwise AND operation and is true only for odd numbers.
 
== modbcheckbox ==
 
This tag generates a check box which can set a certain ODB entry to true or false. To set the "Write data" flag for the logger true or false, one can use the following tag:
<br /><br />
<code>&lt;input type="checkbox" class="modbcheckbox" data-odb-path="/Logger/Write data" data-validate="my_validate" /&gt;</code>
<br /><br />
If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional <code>data-validate</code> function can be used to prevent changing a value under certain circumstances. If the validate function returns false, the value is not written to the ODB, similarly than for <code>modbvalue</code> tag.
 
== modbselect ==
 
This tag generates a drop down box with certain valued which can be sent to a value in the ODB. Following example shows a drop-down box with three different values. When selected, they are sent to the ODB under <code>/Runinfo/Run number</code>.
 
  &lt;select class="modbselect" data-odb-path="/Runinfo/Run number"&gt;
    &lt;option value="1"&gt;1&lt;/option&gt;
    &lt;option value="5"&gt;5&lt;/option&gt;
    &lt;option value="10"&gt;10&lt;/option&gt;
  &lt;/select&gt;
 
Instead of specifying the valid options in the javascript code, you can also specify them in the ODB, and populate the drop-down with those values. Specify <code>data-auto-options="1"</code> to enable this behaviour. For ODB key <code>x</code>, you will need to create an ODB entry called <code>Options x</code> in the same directory; this "options" key should be a list, with each element in the list being an allowable option.
 
  <!-- Will read options from "/Equipment/Example/Settings/Options Something" in this case -->
  &lt;select class="modbselect" data-odb-path="/Equipment/Example/Settings/Something" data-auto-options="1"&gt;
  &lt;/select&gt;
 
The benefit of the <code>data-auto-options="1"</code> approach is that the same options will be shown on the regular ODB browser webpage.
 
Note that these options are only a convenience for the user interface - there is no strict enforcement in the ODB itself! Power-users are able to set other values via C++/Python/Javascript/odbedit etc.
 
== modbhbar ==
 
The following tag:
<br /><br />
<code>&lt;div class="modbhbar" style="width: 500px; height: 18px; color: lightgreen;" data-odb-path="/Runinfo/Run number" data-max-value="10" &gt;&lt;/div&gt;</code>
<br /><br />
shows a horizontal bar with a total length of 500px. Depending on the ODB value {{Odbpath|path=Run number}}. If {{Odbpath|path=Run number}} is 10, then the bar is filled all the way to the right, if {{Odbpath|path=Run number}} is 5, the bar is only filled halfway. Following options are possible:
 
{| class="wikitable"
|-
! Setting !! Meaning !! Required
|-
| style="width: 500px" || Total width of the horizontal bar || Yes
|-
| style="height: 18px" || Height of the horizontal bar || Yes
|-
| style="color: red" || Color of horizontal bar || Transparent if not present
|-
| style="background-color: red" || Background color of horizontal bar || Transparent if not present
|-
| data-odb-path || ODB path of value being displayed || Yes
|-
| data-min-value || Left limit of bar range || 0 if not present
|-
| data-max-value || Right limit of bar range || 1 if not present
|-
| data-log || Logarithmic display || No
|-
| data-print-value || If "1", data value is shown as text overlay || No
|-
| data-format || Specify format of data shown. See Table 2 above for options || No
|-
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No
|}
 
== mhaxis ==
 
A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.
 
The following tag:
<br /><br />
<code>&lt;div class="mhaxis" style="width: 500px; height: 22px;" data-min-value="0" data-max-value="10" &gt;&lt;/div&gt;</code>
<br /><br />
shows a horizontal axis next to the bar. Following options are possible:
 
{| class="wikitable"
|-
! Setting !! Meaning !! Required
|-
| style="width: 500px" || Total width of the axis, must match the width of the horizontal bar || Yes
|-
| style="height: 18px" || Height of the axis, must be enough to display labels  || Yes
|-
| style="vertical-align: top" || Must be "top" if the axis is below the bar || "bottom" if not given
|-
| data-min-value || Left limit of axis range || Yes
|-
| data-max-value || Right limit of axis range || Yes
|-
| data-log || Logarithmic display || No
|}
 
== modbvbar ==
 
Same as <code>modbhbar</code>, except the bar grows vertically instead of horizontally.
 
== mvaxis ==
 
Same as <code>mhaxis</code>, except the axis is shown vertically instead of horizontally.
 
== modbthermo ==
 
The following tag:
<br /><br />
<code>&lt;div class="modbthermo" style="width: 30px; height: 100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30" data-color="blue" data-print-value="1" &gt;&lt;/div&gt;</code>
<br /><br />
shows a vertical thermometer ranging from -10 to 30. Depending on the ODB value {{Odbpath|path=Run number}}. The run number was chosen instead of a real temperature since this ODB variable exists in all midas installations by default, so it's good for testing. Following options are possible:
 
{| class="wikitable"
|-
! Setting !! Meaning !! Required
|-
| style="width: 30px" || Width of the thermometer || Yes
|-
| style="height: 100px" || Total height of the thermometer || Yes
|-
| data-odb-path || ODB path of value being displayed || Yes
|-
| data-max-value || Upper range || Yes
|-
| data-min-value || Lower range || 0 if not present
|-
| data-color || Color of thermometer || Black if not present
|-
| data-background-color || Color of thermometer background || Transparent if not present
|-
| data-print-value || If "1", data value is shown below the thermometer || No
|-
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No
|-
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No
|}
 
== modbgauge ==
 
The following tag:
<br /><br />
<code>&lt;div class="modbgauge" style="width: 100px; height: 50px;" data-odb-path="/Runinfo/Run number" data-min-value="0" data-max-value="10" data-color="darkgreen"&gt;&lt;/div&gt;</code>
<br /><br />
shows a circular gauge ranging from 0 to 10. Depending on the ODB value "Run number". Following options are possible:
 
{| class="wikitable"
|-
! Setting !! Meaning !! Required
|-
| style="width: 100px" || Width of the gauge || Yes
|-
| style="height: 50px" || Total height of the gauge || Yes
|-
| data-odb-path || ODB path of value being displayed || Yes
|-
| data-max-value || Upper range || Yes
|-
| data-min-value || Lower range || 0 if not present
|-
| data-color || Color of gauge || Black if not present
|-
| data-background-color || Color of gauge background || Transparent if not present
|-
| data-print-value || If "1", data value is shown below the gauge || No
|-
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No
|-
| data-scale || If "1", the min and max values of the range are shown below the gauge || No
|-
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No
|}
 
If the gauge scale is not shown, the gauge height should be half the gauge width. If the scale is shown, 15px must be added to the height.
 
== Changing properties of controls dynamically ==
 
All custom controls can be configured to call a user's function when the control is first set up, or when the value changes. This is done by specifying the '''onload''' and/or '''onchange''' functions.
 
* '''onload''' is called only once, when the control's value is first read from the ODB.
* '''onchange''' is called each time the value in the ODB changes.
 
The onload/onchange functions have access to the current value and can change any of the parameters of the control. The following callback for example changes the color of a thermometer to red if the value is above 30 and to blue if it is below:
 
<code>onchange="this.dataset.color=this.value > 30?'red':'blue';"</code>
 
onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:
 
<pre>
<script>
function check_therm(elem) {
  if (elem.value > 30) {
    elem.dataset.color = "red";
  } else {
    elem.dataset.color = "blue";
  }
};
</script>
 
....
 
<div class="modbthermo" style="width: 30px; height: 100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30" data-color="blue" data-print-value="1" onchange="check_therm(this)"></div>
 
</pre>
 
Other example use cases include showing/hiding other elements on a webpage based on whether an modbcheckbox is checked or not, or temporarily changing the background color of an element to highlight that a value has changed.
 
If you want the same function to be called for both onload and onchange, you could set <code>onload="onchange()"</code> and have the "real" function in onchange.
 
== Changing values of indicators programmatically ==
 
Usually, custom controls are directly linked to values in the ODB. Sometimes it is however necessary to combine several ODB values into a single indicator, like if you want to show the difference of two ODB values.
 
For such cases, the <code>data-odb-path</code> attribute can be removed and the the display value can be changed via JavaScript code like following:
 
<pre>
 
<div id="mythermo" class="modbthermo" data-min-value="-10" data-max-value="30"></div>
 
...
  let t = document.getElementById("mythermo");
  t.setValue(15);
...
 
</pre>
 
= mjshistory =
 
Custom pages can contain one or more specific history panels usually shown on the "History" page. This makes it easy to combine current readings of values together with the history of these values.
 
To enable interactive history panels, following lines have to be added to your custom page:
 
Inisde the <head> tag:
 
  <script src="mhistory.js"></script>
 
Inside the <body> tag:
 
  <body ... onload="mhistory_init();">
 
The following tag:
 
  &lt;div class="mjshistory" data-group="<group>" data-panel="<panel>" style="width: 320px; height: 200px;" &gt;&lt;/div&gt;
 
shows a history panel defined in the ODB under /History/Display/&lt;group&gt;/&lt;panel&gt; (replace &lt;group&gt;/&lt;panel&gt; with groups and panels from your experiment).
 
Following options are possible:
 
{| class="wikitable"
|-
! Setting !! Meaning !! Required
|-
| data-group || ODB group of history. Has to match a group under /History/Display || Yes
|-
| data-panel || ODB panel name of history. Has to match a panel name under /History/Display/&lt;group&gt;/ || Yes
|-
| data-scale || Time scale of history plot. Use 10m for 10 minutes and 5h for 5 hours. If not specified, the value from the ODB under /History/Display/&lt;group&gt;/&lt;panel&gt;/Timescale is used. || No
|-
| data-show-title || Flag to show or hide the title. Default is "1". || No
|-
| data-show-values || Flag to show or hide the variable labels with their current value. Default is "1". || No
|-
| data-show-axis || Flag to show or hide the X/Y axis. Default is "1". || No
|-
| data-show-menu-buttons || Flag to show or hide the menu buttons. Default is "1". || No
|-
| data-show-menu-buttons || Flag to show or hide the zoom buttons. Default is "1". || No
|-
| style="width: 320px" || Width of the history panel || No
|-
| style="height: 200px" || Height of the history panel || No
|-
| style="border: 1px solid black" || Border around the history panel || No
|}
 
If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.
 
In addition, it is possible to show a floating dialog box with a history panel. That might be useful if you show a single value on a custom page, and want to give users
the possibility to show the history of that variable. Just put a button next to the value and call '''mhistory_dialog(&lt;group&gt;, &lt;panel&gt;)''' from that button like:
 
  &lt;span class="modbvalue" data-odb-path="/Some/Path"&gt;&lt;/span&gt;
  <button onclick="mhistory_dialog('group','panel')"><img src="icons/activity.svg"></button>
 
The history panel will then be opened when the user clicks the button:
 
[[File:History dialog.png|frame|left|Floating history dialog]]
 
<br clear=all>
 
If one wants to avoid the definition of a history panel in the ODB, a "direct variable plot" can be done with the function '''mhistory_dialog_var(&lt;variable&gt;)''' where &lt;variable&gt; has the format like the variable definition in the ODB (e.g. "System:Trigger per sec."). Such a plot has some default parameters for the timescale etc., which can be overwritten by passing a parameter object to the function such as '''mhistory_dialog_var("System:Trigger per sec.", {"Timescale": "24h"});'''
 
A full example of a custom page with a history panel is shown below.
 
<pre>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="midas.css" type="text/css">
  <script src="midas.js"></script>
  <script src="mhttpd.js"></script>
  <script src="controls.js"></script>
  <script src="mhistory.js"></script>
</head>
<body onload="mhttpd_init('history_example'); mhistory_init();">
  <div id="mheader"></div>
  <div id="msidenav"></div> 
  <div id="mmain">
    <div class="mjshistory" data-group="EPICS" data-panel="Logging" style="width: 500px; height: 300px;" ></div>
  </div>
</body>
</html>
</pre>
 
= mplot =
 
Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated [[Custom plots with mplot]] page.
 
= Dialog, popup and modal boxes =
 
You can spawn a dialog box to either show a message to the user, ask for input, or require confirmation of an action. You can call all these functions from your own javascript code.
 
== Showing a message ==
 
The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an "Ok" button that the user may click to dismiss the message.
 
=== dlgMessage ===


For details using Xvfb server, please contact Ryu Sawada <sawada@icepp.s.u-tokyo.ac.jp>.
dlgMessage is the most customisable of the 3 functions. The signature is:


<pre>
dlgMessage(title, message, modal, error, callback, param)
</pre>


[[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]]
{| class="wikitable"
;Example 3
|-
:This custom page from the Deap Experiment (Figure 3) allows the users to easily set individual channels, or a group of channels, or all channels of the SCB modules to a particular value.  
! Parameter !! Type !! Meaning !! Required
|-
| title || string || Title of the dialog box || Yes
|-
| message || string (can be arbitrary HTML) || Main message content || Yes
|-
| modal || boolean || If true, will grey out the rest of the page and require the user to dismiss the alert box before they can continue interacting with the page. || No. Defaults to false
|-
| error || boolean || If true, will use a red background for the title || No. Defaults to false
|-
| callback || function || Function to be called with the user clicks the "Ok" button. The callback function will be called with just a single paramter. || No. Defaults to not calling a callback function.
|-
| param || anything || Parameter that will be passed to the `callback` function || No. Defaults to undefined.
|}
   
   
Some example invocations are:
<pre>
dlgMessage("Message title", "Message text");
dlgMessage("It's an error!", "<b>Something is wrong!!!</b>", true, true);
dlgMessage("Callback example", "Testing", true, false, my_message_cb, "some_param");
function my_message_cb(param) {
  alert("Message dismissed! The param was " + param);
}
</pre>
=== dlgAlert ===
dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:
<pre>
dlgAlert(message, callback)
// The above is equivalent to:
dlgMessage("Message", message, true, true, callback)
</pre>
=== dlgWait ===
dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:
<pre>
dlgWait(time_in_seconds, message)
</pre>
== Asking for input ==
=== dlgQuery ===
dlgQuery asks the user to respond to a question. The dialog shows a message, an input field for the user to type in, and Ok/Cancel buttons. The user's response is sent to a callback function that you must implement yourself. The signature is:
<pre>
dlgQuery(message, value, callback, param)
</pre>
{| class="wikitable"
|-
! Parameter !! Type !! Meaning !! Required
|-
| message || string (can be arbitrary HTML) || The question to show the user || Yes
|-
| value || string || Default value to pre-populate the answer field with || Yes (but can be empty string)
|-
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the "Cancel" button, or their answer if they clicked the "Ok" button. || Yes
|-
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.
|}
Example usage is:
<pre>
dlgQuery("Enter a value:", "my default value", my_query_cb, "some_other_param")
function my_query_cb(value, param) {
  if (value === false) {
      // User clicked the Cancel button
  } else {
      alert('Value is ' + value + ', param is ' + param);
  }
}
// where 'param' can also be ommitted.
</pre>
=== dlgConfirm ===
dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:
<pre>
dlgConfirm(message, callback, param)
</pre>
{| class="wikitable"
|-
! Parameter !! Type !! Meaning !! Required
|-
| message || string (can be arbitrary HTML) || The question to show the user || Yes
|-
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the "Cancel" button, or true if they clicked the "Ok" button. || Yes
|-
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.
|}
Example usage is:
<pre>
dlgConfirm("Are you sure you wish to do that?", my_confirm_cb, "some_other_param")
function my_confirm_cb(value, param) {
  if (value === false) {
      alert("User clicked Cancel! Param was " + param);
  } else {
      alert("User clicked Ok! Param was " + param);
  }
}
// where 'param' can also be ommitted.
</pre>
= Complete Example =
The following code shows an example page (contained in {{Filepath|path=resources/a_example.html}} in the MIDAS distribution) of a custom page implementing most of the new features. You activate this page by putting in the ODB:
<pre>
/Custom
  Path    /midas/resources
  Test    a_example.html
</pre>
== Code ==
The file '''a_example.html''' contains the following code:
<pre>
<!DOCTYPE html>
<html class="mcss">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="midas.css">
  <script src="controls.js"></script>
  <script src="midas.js"></script>
  <script src="mhttpd.js"></script>
  <title>Example</title>
  <style>
      .mtable td { padding: 10px; }
  </style>
</head>
<body class="mcss" onload="mhttpd_init('Example');">
<!-- header and side navigation will be filled in mhttpd_init -->
<div id="mheader"></div>
<div id="msidenav"></div>
<div id="mmain">
  <table class="mtable">
      <tr>
        <th colspan="2" class="mtableheader">Status</th>
      </tr>
      <tr>
        <td style="width: 200px;">
            Run number:
        </td>
        <td>
            <div class="modbvalue" data-odb-path="/Runinfo/Run number" data-odb-editable="1"></div>
        </td>
      </tr>
      <tr>
        <td>
            Last run start:
        </td>
        <td>
            <div class="modbvalue" data-odb-path="/Runinfo/Start time"></div>
        </td>
      </tr>
      <tr>
        <td>
            Last run stop:
        </td>
        <td>
            <div class="modbvalue" data-odb-path="/Runinfo/Stop time"></div>
        </td>
      </tr>
      <tr>
        <td>
            Check box:
        </td>
        <td>
            <!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) -->
            <input type="checkbox" class="modbcheckbox" data-odb-path="/Logger/Write data"></input>
            <div class="modb" data-odb-path="/Logger/Write data" onchange="dlgAlert('Flag has changed');"></div>
        </td>
      </tr>
      <tr>
        <td>
            Color box:
        </td>
        <td>
            <!-- box changes color according to /Logger/Write data -->
            <div class="modbbox" style="width: 30px; height: 30px;" data-odb-path="/Logger/Write data"
                data-color="lightgreen" data-background-color="red"></div>
        </td>
      </tr>
      <tr>
        <td>
            Horizontal bars:
        </td>
        <td>
            <div class="modbhbar" style="width:300px;height:20px;color:orange;" data-odb-path="/Runinfo/Run number"
                data-max-value="10" data-print-value="1"></div><br />
            <div class="mhaxis" style="width:500px;height:22px;" data-min-value="0" data-max-value="10"></div>
            <div class="modbhbar" style="width: 500px; height: 18px;color:lightblue" data-odb-path="/Runinfo/Run number"
                data-max-value="10"></div><br />
            <div class="modbhbar" style="width: 200px; height: 10px;color:lightgreen;background-color:white"
                data-odb-path="/Runinfo/Run number" data-min-value="0.1" data-max-value="10" data-log="1"></div>
            <div class="mhaxis" style="width:200px;height:22px;vertical-align:top;" data-min-value="0.1"
                data-max-value="10" data-line="0" data-log="1"></div>
        </td>
      </tr>
      <tr>
        <td>
            Vertical bars:
        </td>
        <td>
            <span class="mvaxis" style="width:100px;height:200px;text-align:right;" data-min-value="0" data-max-value="20"></span><span class="modbvbar"
                  style="width:20px;height:200px;color:yellow;" data-odb-path="/Runinfo/Run number"
                  data-min-value="0" data-max-value="20"></span>
            <span class="modbvbar" style="width:10px;height:200px;vertical-align:top;color:red" data-odb-path="/Runinfo/Run number" data-min-value="0.1"
                  data-max-value="10" data-log="1"></span><span class="mvaxis" style="width:100px;height:200px;text-align:left;" data-min-value="0.1"
                                                                data-max-value="10" data-log="1"></span>
        </td>
      </tr>


      <tr>
        <td>
            Thermometer:
        </td>
        <td>
            <div class="modbthermo" style="width:30px;height:100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                data-color="darkgreen"></div>
            <div class="modbthermo" style="width:60px;height:100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                data-color="blue" data-scale="1"
                onchange="this.dataset.color=this.value > 9?'red':'blue';"></div>
            <div class="modbthermo" style="width:30px;height:100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                data-color="blue" data-background-color="white" data-value="1"></div>
        </td>
      </tr>


<div style="clear: both"></div> <!-- clear wraparound after thumbnail -->
      <tr>
        <td>
            Gauges:
        </td>
        <td>
            <div class="modbgauge" style="width:100px;height:50px;" data-odb-path="/Runinfo/Run number" data-min-value="0" data-max-value="10"
                data-color="darkgreen" data-background-color="lightgrey" ></div>
            <div class="modbgauge" style="width:100px;height:65px;" data-odb-path="/Runinfo/Run number" data-min-value="0" data-max-value="10"
                data-color="red" data-value="1" data-scale="1"></div>
        </td>
      </tr>
 
      <tr>
        <td colspan="2" style="text-align: center;">
            <!-- div around image with "relative" position as anchor for labels and bars -->
            <div style="position:relative;width:300px;margin:auto">


              <img src="tank.gif"> <!-- background image of tank -->


              <!-- label next to valve -->
              <div class="modbvalue" data-odb-path="/Runinfo/Run number" data-odb-editable="1"
                    style="position:absolute;top:157px;left:288px;"></div>


= Access a Custom Page from the Regular MIDAS pages =
              <!-- vertical bar inside tank, render red if value > 9 -->
Access to a Custom Page is set up through the [[/Custom ODB tree]] (see [[/Custom ODB tree#Keys in the /Custom tree|custom-link]]). This associates a custom page file on the disk with a button on the status page. Clicking on the resulting [[/Custom ODB tree#Custom-Button|custom-button]] on the [[mhttpd]] [[Status Page]] will display that custom page.
              <div class="modbvbar" style="position:absolute;top:80px;left:10px;width:104px;height:170px;"
 
                    data-odb-path="/Runinfo/Run number" data-max-value="11" data-color="green"
For example, if the key  {{Odbpath|path=/Custom/myPage&}}  (see Note) is created, and the contents contains the path of a custom page file on the disk, e.g.
                    onchange="this.firstChild.style.backgroundColor=(this.value > 9)?'red':'green';"></div>
odbedit> ls /Custom/myPage&
    myPage&                          /home/midas/online/expt/custom/test.html
the custom-button will be {{Button|name=myPage}} and the URL for the resulting custom page will be of the form  http://myhost.mydomain:myport/CS/myPage (see also [[mhttpd#usage]]). 
Pressing {{Button|name=myPage}} will display the custom page in the same window.


;Note
              <!-- thermometer inside tank -->
: Without the "&" symbol in the key name, the page would appear in a new window. See [[/Custom ODB tree#Key names|Key names]] for more information.
              <div class="modbthermo" style="position:absolute;top:140px;left:20px;width:20px;height:100px;"
                    data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                    data-color="blue" data-value="1"></div>


= How to write a custom page =
            </div>
A custom page is usually written in a combination of HTML and Javascript. It can contain any of the [[Custom Page Features]], e.g. include images with labelled with ODB values and areas coloured according to ODB values using fills. A Javascript Library has been written to provide access to the ODB and other functions. A custom page is usually of the form:
        </td>
      </tr>


<div style="text-align:left; background-color:floralwhite;color: seagreen; font-style:italic;">  
      <tr>
<html><br>
        <td colspan="2" style="text-align: center;">
<head><br>
            <!-- three buttons to change an ODB entry (run number in this example) -->
<title>My Custom Page Title</title><br>
            <button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="1">Set run
<meta http-equiv="Refresh" content="30"> <span style="color:black;font-style:normal;"> &lt;!-- see [[Custom Page Features#Page refresh|Page Refresh]] --&gt; </span> <br>
              number to 1
<script src='mhttpd.js'>  <span style="color:black;font-style:normal;">  &lt;!-- see [[#MIDAS Javascript Library]] --&gt; </span><br>
            </button>
</script><br>
            <button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="5">Set run
</head><br>
              number to 5
<body><br>
            </button>
<form name="form1" method="Get" action="/CS/MyPage&"> <span style="color:black; font-style:normal"> &lt;!-- see [[#Notes|note]] below --&gt; </span> <br>
            <button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="10">Set run
............<span style="color:floralwhite;">spacerspacer</span>  <span style="color:black; font-style:normal"> &lt;!-- program statements may include any of the [[#List of Features available on custom pages|available features]]  --&gt; </span> <br>
              number to 10
</form><br>
            </button>
</body><br>
        </td>
</html><br>
      </tr>
  </table>
</div>
</div>
<div id="Notes"></div>
;Notes
* Comments enclosed by HTML comment tags ( &lt;!-- --&gt; ) have been added for documentation purposes only 
* The HTML '''<form>''' tag has the ''action'' set to part of the URL ([[#Access a Custom Page|see above]]), i.e. "/CS/" followed by the ''custom-link''. The special character "&" (if present) is ignored. The '''<form>''' tag may not be necessary if only using Javascript library calls.


The [[#Demo Custom Page]] can be used as a template for many of the custom page features, as can the examples using the [[#MIDAS Javascript Library]].
</body>
</html>
</pre>


= Other Features available on custom pages =
which results in the page shown in Figure 1 below:


See  <big>[[Custom Page Features]]</big>.
[[File:Custom17.png|frame|left|Figure 1  Example custom page using most features]]
 
<div style="clear: both"></div> <!-- clear wraparound after thumbnail -->


= Old custom page feature =
= Old custom page feature =

Latest revision as of 12:37, 23 September 2024


Links


Purpose

A user-created mhttpd Custom Web Page accessible from the side menu allows the user additional flexibility. For example, a custom page may present the essential parameters of the controlled experiment in a more compact way. A custom page may even replace the default Status Page.

Introduction

Custom web pages provide the user with a means of creating secondary user-created web page(s) activated within the standard MIDAS web interface. These custom pages usually display ODB parameters or data to the user. They can contain specific links to the ODB so the user may also input information relevant to the experiment. Users create Custom Pages when the standard pages do not meet their requirements completely.

We note that MIDAS has provided a number of different ways of providing custom pages over the years. A new scheme of custom pages making use of modern HTML5 techniques has been introduced in 2017. This page will mostly only be providing documentation for the new scheme of custom pages.

Examples of Custom Pages

Click on the thumbnails to enlarge.

Figure 1: MEG Gas System
Example 1

This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of "fills" and "labels". Open valves are represented as green circles, closed valves as red circles. If, for example, an open valve is clicked, the valve closes, and the circle turns red (provided the user successfully supplied the correct password).

Figure 2: ROOT Analyzer (MEG Experiment)
Example 2

Many MIDAS experiments work with ROOT based analyzers today. One problem is that the graphical output of the root analyzer can only be seen through the X server and not through the web. At the MEG experiment, this problem was solved in an elegant way: The ROOT analyzer runs in the background, using a "virtual" X server called Xvfb. It plots its output (several panels) normally using this X server, then saves this panels every ten seconds into GIF files. These GIF files are then served through mhttpd using a custom page. The output is shown in Figure 2.

The buttons on the left sides are actually HTML buttons on that custom page overlaid to the GIF image, which in this case shows one of the 800 PMT channels digitized at 1.6 GSPS. With these buttons one can cycle through the different GIF images, which then automatically update ever ten seconds. Of course it is not possible to feed interaction back to the analyzer (i.e. the waveform cannot be fitted interactively) but for monitoring an experiment in production mode this tool is extremely helpful, since it is seamlessly integrated into mhttpd. All the magic is done with JavaScript, and the buttons are overlaid on the graphics using CSS with absolute positioning. The analysis ratio on the top right is also done with JavaScript accessing the required information from the ODB.

For details using Xvfb server, please contact Ryu Sawada <sawada@icepp.s.u-tokyo.ac.jp>.

Figure 3: SCB Setup (Deap Experiment)
Example 3

This custom page from the Deap Experiment (Figure 3) allows the users to easily set individual channels, or a group of channels, or all channels of the SCB modules to a particular value.

Access a Custom Page from the Regular MIDAS pages

Access to a Custom Page is set up through the /Custom ODB tree (see custom-link). This associates a custom page file on the disk with a menu item on the left navigation bar. Clicking on the resulting link will display that custom page.

Often a custom page requires resources such as *.css (stylesheets) or *.js (javascript) files. It is convenient to store all such files with the custom page file (*.html) in a particular directory, e.g. /home/expt/online/custom. By creating an ODB key /Custom/Path, all the custom page files and resources can be served easily from this directory. See Custom Page Features#Resource files for more information.

If the key /Custom/myPage& (see Note) is created, e.g.

odbedit> ls /Custom
   Path                              /home/expt/online/custom
   myPage&                           mypage.html

the custom link on the left navigation bar will be myPage and the URL for the resulting custom page will be of the form http://myhost.mydomain:myport/cmd=?Custom&page=myPage (see also mhttpd#usage). Clicking on myPage will display the custom page in the same window.

Note
Without the "&" symbol in the key name, the page would appear in a new window. See Key names for more information.

If an experiment used many custom pages, the menu on the left side can get pretty long. To avoid that, custom pages can be structured in submenus. Simply put a custom page in an ODB subdirectory, and it will appear in a separate submenu, e.g.

odbedit> ls -r /Custom
   Path                              /home/expt/online/custom
   myPage&                           mypage.html
   Calorimeter
       HV                            hv.html
       Rates                         rates.html
   Baeam
       Beamline                      beamline.html
       Accelerator                   accel.html

The pages then include the subdirectory in the URL, like

http://localhost:8080?cmd=custom&page=beam/Beamline

Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as

 <body class="mcss" onload="mhttpd_init('Calorimeter/HV');">

in order to keep the submenu open after you select the custom page.

How to write a custom page

A custom page is usually written in a combination of HTML and Javascript. It can contain any of the features described below. A Javascript MjsonRPC Library has been written to provide access to the ODB and other functions.

In what follows, we describe a scheme for writing custom pages with the set of modb* javascript functions. The advantages of using these modb* javascript functions are

  • modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.
  • modb* functions ensure that all the periodic updates of the ODB value (and other MIDAS information) are done in a single MjsonRPC batch call, which should ensure optimal page loading speed.
  • modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.

It is also possible for users to write their custom pages using only the underlying MjsonRPC library calls, but they then need to ensure on their own that the page loading remains efficient. In particular, if you combine the standard MIDAS navigation bars (described in next section) with your own periodic MjsonRPC calls then you will probably need at least two separate periodic RPC calls to populate the custom page (instead of one call). It will also require more coding to implement the custom page with only MjsonRPC calls.

How to use the standard MIDAS navigation bars on your custom page

If you want to have your custom page use the same header and navigation bars as the standard MIDAS pages, you need to use the following syntax

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <link rel="stylesheet" href="midas.css">
   <script src="controls.js"></script>
   <script src="midas.js"></script>
   <script src="mhttpd.js"></script>
   <title>myPage</title>
</head>

<body class="mcss" onload="mhttpd_init('myPage');">

<!-- header and side navigation will be filled in mhttpd_start -->
<div id="mheader"></div>
<div id="msidenav"></div>

<div id="mmain">
 ADD YOUR HTML/JS  CODE here...
</div>

The call mhttpd_init('myPage') is executed when the page is loaded, and myPage is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage. This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying javascript libraries.

modb* Javascript scheme

The general scheme of the custom page scheme is to write <div class="modb..."></div> or <span class="modb..."></span> tags with special class names, most of them starting with "modb..." ("MIDAS-ODB"). Use a <div> tag if you want the element to appear in a separate line, and use the <span> tag if you want to display the element in-line. The following description uses only <div> tags, but all of them can be changed to <span>.

All HTML tags with "modb..." names are scanned by the mhttp_init('name') function upon page load, and their inner contents are replaced by the requested ODB value or some graphics. The contents are then updated regularly. Updates are once per second by default. This can be changed by passing a second argument to mhttpd_init('name', interval) where "interval" is in milliseconds. You can add or remove "modb..." elements at any time using javascript, and the new elements will be "discovered" automatically during the next update.

modbset(path, value)

To set values in the ODB, the midas JavaScript function mjsonrpc_db_paste() is usually called. This function is implemented as a JavaScript promise, which lets you chain several request in order to change values inside the ODB in a certain order. If that functionality is not required, the simplified modbset() function can be called, which also implements standard error handling. Two versions of this function exist, one which accepts a single ODB path and a single value, and one which accepts an array of ODB paths and values:

modbset("odb path", value)

modbset(["odb path1", "odb path2", ...], [value1, value2, ...])

These functions are typically used by custom JavaScript code, like when some value in an experiment exceeds some limit and some action has to be taken like to close a valve. If the call fails (like if mhttpd is dead), a window with an error description is shown.

modb

This special HTML div tag (abbreviation stands for Midas ODB) <div class="modb" data-odb-path="/Some/Path" onchange="func()"> can be used to call a user-defined function func() if a value in the ODB changes. This function must be defined inline or in a separate <script>...</script> section, and can execute any operation, such as opening a dialog box, hiding/unhiding parts of the custom page, or changing colors and styles of page elements.

The current value of the ODB entry is available inside the "onchange" function as this.value. Following tag will call a function which logs the current run number in the JavaScript console window:

<div class="modb" data-odb-path="/Runinfo/Run number" onchange="func(this.value)">

<script>function func(value) { console.log(value); }</script>

If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to this.value as a JavaSctipt object such as

<div class="modb" data-odb-path="/Runinfo" onchange="func(this.value)">

<script>function func(value) { console.log(value["run number"]); }</script>


Note that ODB entries are mapped to JavaScript objects without change. So if an ODB entry name contains a blank, it must be accessed via the JS square bracket value["run number"] as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as value.state for /Runinfo/State for example.

modbvalue

This special HTML div tag (abbreviation stands for "Midas ODB VALUE")

<div class="modbvalue" data-odb-path="/Some/Path"></div>

is now automatically replaced by the value in the ODB found at the given path and updated regularly as described above Following options are valid for this tag:

Table 1: List of valid options for modbvalue tag
Option Example Meaning
data-name class="modbvalue" Tells the framework to replace this tag with an ODB value
data-odb-path data-odb-path = "/Runinfo/Run number" Path to the value in the ODB
data-odb-editable data-odb-editable="1" If set, the value is not only shown, but is also clickable for in-line editing. Hitting return send the new value to the ODB.
data-format data-format="f3" Specify format of data shown. See Table 2 below for options.
data-size data-size="8" Specify size (in chars) of edit box if one modifies the value. Default is 10.
data-formula data-formula="2*x+3" Specify an optional formula to process with the current ODB value stored in x
data-validate data-validate="func" Specify an optional validation function which gets called before submitting data (see below)

Validation

Before a modified value is submitted to the ODB, an optional validation function can be called via the data-validate option. The function will be called with the current value and a reference to the current modbvalue element. If the function returns false, then the value is not sent to the ODB.

Following example shows a function which just rejects the submission of values above 1000:

 <div class="modbvalue" ... data-odb-editable="1" data-validate="my_validate"></div>
 <script src="controls.js"></script> <!-- needed of dlgAlert() -->
 <script>
 function my_validate(value, element) {
    if (value > 1000) {
       dlgAlert("Value cannot be above 1000");
       return false;
    }
    return true;
 }
 </script>

Following function corrects the return value to 1000 if it's above 1000:

 <script>
 function my_validate2(value, element) {
    if (value > 1000) {
       element.childNodes[0].value = 1000;
    return true;
 }
 </script>

Confirmation

Before a modified value is submitted to the ODB, a confirmation dialog box can be displayed to let the user confirm the change before it is actually written to the ODB. This is done with the option data-confirm=<string>. A dialog box is then shown with the <string> as the main text and two buttons with "OK" and "Cancel". Hitting "OK" finally writes the value to the ODB, hitting "Cancel" keeps the old value.

Following example shows an example:

 <div class="modbvalue" ... data-odb-editable="1" data-confirm="Are you sure to change the value?"></div>

Following dialog box is then showed:

Optional confirm dialog


Formatting

Table 2 below lists the format specifiers supported with a modbvalue tag data-format="...":

Table 2: Format specifiers for modbvalue tag data-format="...".
Option Valid for* Example Meaning
%d int 1234 Shows a number in decimal encoding
%t int, float 1,234,567 Shows a number in decimal encoding with commas as thousands separator
%x int 0x4D2 Shows a number in hexadecimal encoding
%b int 10011010010b Shows a number in binary encoding. Options t, d, x, b can be combined, like data-format="%d / %x" to produce 1234 / 0x4D2
%f<x> float 1.234 Shows a floating point number with <x> digits after the decimal. A value of f0 shows only the integer part.
%p<x> float 1.23 Shows a floating point number with <x> significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012
%e<x> float 1.23e-05 Shows a floating point number with <x> digits after the decimal in exponential format. Useful for small number such as pressures.
T= %f1 C float T= 25.4 C Combination of text and float value is possible.
%d / %x / %b int 17 / 0x11 / 10001b Same integer value in different formats
  • Note that valid for "int" means all integral ODB types (regardless of size or signed/unsigned) and valid for "float" means both float and double.

modbbutton

This tag generates a push-button which can set a certain ODB entry to a specific value. To set the "Run number" to 100, one can use the following tag:

<button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="100" data-validate="my_validate">[Button Text]</button>


The optional data-validate function can be used to prevent pressing the button under certain circumstances. If the validate function returns false, the value is not written to the ODB, similarly than for modbvalue tag.

modbbox

This tag generates a rectangular box which changes color according to a value in the ODB. If the value is nonzero or true (for booleans), the data-color is used, otherwise the data-background-color is used

<div class="modbbox" data-odb-path="/Logger/Write Data" data-formula="x > 0" style="width: 30px; height: 30px; border: 1px solid black" data-color="lightgreen" data-background-color="red"></div>

Optionally, a data-formula can be specified. The formula sees the ODB value in the variable x, and can do any boolean operation. If the result of this is true, then the box gets the data-color, otherwise the data-background-color. Examples for these formulas are x > 10 for a comparison or x & 1 which will do a bitwise AND operation and is true only for odd numbers.

modbcheckbox

This tag generates a check box which can set a certain ODB entry to true or false. To set the "Write data" flag for the logger true or false, one can use the following tag:

<input type="checkbox" class="modbcheckbox" data-odb-path="/Logger/Write data" data-validate="my_validate" />

If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional data-validate function can be used to prevent changing a value under certain circumstances. If the validate function returns false, the value is not written to the ODB, similarly than for modbvalue tag.

modbselect

This tag generates a drop down box with certain valued which can be sent to a value in the ODB. Following example shows a drop-down box with three different values. When selected, they are sent to the ODB under /Runinfo/Run number.

 <select class="modbselect" data-odb-path="/Runinfo/Run number">
   <option value="1">1</option>
   <option value="5">5</option>
   <option value="10">10</option>
 </select>

Instead of specifying the valid options in the javascript code, you can also specify them in the ODB, and populate the drop-down with those values. Specify data-auto-options="1" to enable this behaviour. For ODB key x, you will need to create an ODB entry called Options x in the same directory; this "options" key should be a list, with each element in the list being an allowable option.

 <select class="modbselect" data-odb-path="/Equipment/Example/Settings/Something" data-auto-options="1">
 </select>

The benefit of the data-auto-options="1" approach is that the same options will be shown on the regular ODB browser webpage.

Note that these options are only a convenience for the user interface - there is no strict enforcement in the ODB itself! Power-users are able to set other values via C++/Python/Javascript/odbedit etc.

modbhbar

The following tag:

<div class="modbhbar" style="width: 500px; height: 18px; color: lightgreen;" data-odb-path="/Runinfo/Run number" data-max-value="10" ></div>

shows a horizontal bar with a total length of 500px. Depending on the ODB value Run number. If Run number is 10, then the bar is filled all the way to the right, if Run number is 5, the bar is only filled halfway. Following options are possible:

Setting Meaning Required
style="width: 500px" Total width of the horizontal bar Yes
style="height: 18px" Height of the horizontal bar Yes
style="color: red" Color of horizontal bar Transparent if not present
style="background-color: red" Background color of horizontal bar Transparent if not present
data-odb-path ODB path of value being displayed Yes
data-min-value Left limit of bar range 0 if not present
data-max-value Right limit of bar range 1 if not present
data-log Logarithmic display No
data-print-value If "1", data value is shown as text overlay No
data-format Specify format of data shown. See Table 2 above for options No
data-formula Specify an optional formula to process with the current ODB value stored in x No

mhaxis

A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.

The following tag:

<div class="mhaxis" style="width: 500px; height: 22px;" data-min-value="0" data-max-value="10" ></div>

shows a horizontal axis next to the bar. Following options are possible:

Setting Meaning Required
style="width: 500px" Total width of the axis, must match the width of the horizontal bar Yes
style="height: 18px" Height of the axis, must be enough to display labels Yes
style="vertical-align: top" Must be "top" if the axis is below the bar "bottom" if not given
data-min-value Left limit of axis range Yes
data-max-value Right limit of axis range Yes
data-log Logarithmic display No

modbvbar

Same as modbhbar, except the bar grows vertically instead of horizontally.

mvaxis

Same as mhaxis, except the axis is shown vertically instead of horizontally.

modbthermo

The following tag:

<div class="modbthermo" style="width: 30px; height: 100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30" data-color="blue" data-print-value="1" ></div>

shows a vertical thermometer ranging from -10 to 30. Depending on the ODB value Run number. The run number was chosen instead of a real temperature since this ODB variable exists in all midas installations by default, so it's good for testing. Following options are possible:

Setting Meaning Required
style="width: 30px" Width of the thermometer Yes
style="height: 100px" Total height of the thermometer Yes
data-odb-path ODB path of value being displayed Yes
data-max-value Upper range Yes
data-min-value Lower range 0 if not present
data-color Color of thermometer Black if not present
data-background-color Color of thermometer background Transparent if not present
data-print-value If "1", data value is shown below the thermometer No
data-format Specifies format of temperature shown below gauge. See Table 2 for options. No
data-formula Specify an optional formula to process with the current ODB value stored in x No

modbgauge

The following tag:

<div class="modbgauge" style="width: 100px; height: 50px;" data-odb-path="/Runinfo/Run number" data-min-value="0" data-max-value="10" data-color="darkgreen"></div>

shows a circular gauge ranging from 0 to 10. Depending on the ODB value "Run number". Following options are possible:

Setting Meaning Required
style="width: 100px" Width of the gauge Yes
style="height: 50px" Total height of the gauge Yes
data-odb-path ODB path of value being displayed Yes
data-max-value Upper range Yes
data-min-value Lower range 0 if not present
data-color Color of gauge Black if not present
data-background-color Color of gauge background Transparent if not present
data-print-value If "1", data value is shown below the gauge No
data-format Specifies format of temperature shown below gauge. See Table 2 for options. No
data-scale If "1", the min and max values of the range are shown below the gauge No
data-formula Specify an optional formula to process with the current ODB value stored in x No

If the gauge scale is not shown, the gauge height should be half the gauge width. If the scale is shown, 15px must be added to the height.

Changing properties of controls dynamically

All custom controls can be configured to call a user's function when the control is first set up, or when the value changes. This is done by specifying the onload and/or onchange functions.

  • onload is called only once, when the control's value is first read from the ODB.
  • onchange is called each time the value in the ODB changes.

The onload/onchange functions have access to the current value and can change any of the parameters of the control. The following callback for example changes the color of a thermometer to red if the value is above 30 and to blue if it is below:

onchange="this.dataset.color=this.value > 30?'red':'blue';"

onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:

<script>
function check_therm(elem) {
  if (elem.value > 30) {
    elem.dataset.color = "red";
  } else {
    elem.dataset.color = "blue";
  }
};
</script>

....

<div class="modbthermo" style="width: 30px; height: 100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30" data-color="blue" data-print-value="1" onchange="check_therm(this)"></div>

Other example use cases include showing/hiding other elements on a webpage based on whether an modbcheckbox is checked or not, or temporarily changing the background color of an element to highlight that a value has changed.

If you want the same function to be called for both onload and onchange, you could set onload="onchange()" and have the "real" function in onchange.

Changing values of indicators programmatically

Usually, custom controls are directly linked to values in the ODB. Sometimes it is however necessary to combine several ODB values into a single indicator, like if you want to show the difference of two ODB values.

For such cases, the data-odb-path attribute can be removed and the the display value can be changed via JavaScript code like following:


<div id="mythermo" class="modbthermo" data-min-value="-10" data-max-value="30"></div>

...
  let t = document.getElementById("mythermo");
  t.setValue(15);
...

mjshistory

Custom pages can contain one or more specific history panels usually shown on the "History" page. This makes it easy to combine current readings of values together with the history of these values.

To enable interactive history panels, following lines have to be added to your custom page:

Inisde the <head> tag:

  <script src="mhistory.js"></script>

Inside the <body> tag:

  <body ... onload="mhistory_init();">

The following tag:

  <div class="mjshistory" data-group="<group>" data-panel="<panel>" style="width: 320px; height: 200px;" ></div>

shows a history panel defined in the ODB under /History/Display/<group>/<panel> (replace <group>/<panel> with groups and panels from your experiment).

Following options are possible:

Setting Meaning Required
data-group ODB group of history. Has to match a group under /History/Display Yes
data-panel ODB panel name of history. Has to match a panel name under /History/Display/<group>/ Yes
data-scale Time scale of history plot. Use 10m for 10 minutes and 5h for 5 hours. If not specified, the value from the ODB under /History/Display/<group>/<panel>/Timescale is used. No
data-show-title Flag to show or hide the title. Default is "1". No
data-show-values Flag to show or hide the variable labels with their current value. Default is "1". No
data-show-axis Flag to show or hide the X/Y axis. Default is "1". No
data-show-menu-buttons Flag to show or hide the menu buttons. Default is "1". No
data-show-menu-buttons Flag to show or hide the zoom buttons. Default is "1". No
style="width: 320px" Width of the history panel No
style="height: 200px" Height of the history panel No
style="border: 1px solid black" Border around the history panel No

If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.

In addition, it is possible to show a floating dialog box with a history panel. That might be useful if you show a single value on a custom page, and want to give users the possibility to show the history of that variable. Just put a button next to the value and call mhistory_dialog(<group>, <panel>) from that button like:

  <span class="modbvalue" data-odb-path="/Some/Path"></span>
  <button onclick="mhistory_dialog('group','panel')"><img src="icons/activity.svg"></button>

The history panel will then be opened when the user clicks the button:

Floating history dialog


If one wants to avoid the definition of a history panel in the ODB, a "direct variable plot" can be done with the function mhistory_dialog_var(<variable>) where <variable> has the format like the variable definition in the ODB (e.g. "System:Trigger per sec."). Such a plot has some default parameters for the timescale etc., which can be overwritten by passing a parameter object to the function such as mhistory_dialog_var("System:Trigger per sec.", {"Timescale": "24h"});

A full example of a custom page with a history panel is shown below.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="midas.css" type="text/css">
  <script src="midas.js"></script>
  <script src="mhttpd.js"></script>
  <script src="controls.js"></script>
  <script src="mhistory.js"></script>
</head>
<body onload="mhttpd_init('history_example'); mhistory_init();">
  <div id="mheader"></div>
  <div id="msidenav"></div>  
  <div id="mmain">
    <div class="mjshistory" data-group="EPICS" data-panel="Logging" style="width: 500px; height: 300px;" ></div>
  </div>
</body>
</html>

mplot

Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated Custom plots with mplot page.

Dialog, popup and modal boxes

You can spawn a dialog box to either show a message to the user, ask for input, or require confirmation of an action. You can call all these functions from your own javascript code.

Showing a message

The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an "Ok" button that the user may click to dismiss the message.

dlgMessage

dlgMessage is the most customisable of the 3 functions. The signature is:

dlgMessage(title, message, modal, error, callback, param)
Parameter Type Meaning Required
title string Title of the dialog box Yes
message string (can be arbitrary HTML) Main message content Yes
modal boolean If true, will grey out the rest of the page and require the user to dismiss the alert box before they can continue interacting with the page. No. Defaults to false
error boolean If true, will use a red background for the title No. Defaults to false
callback function Function to be called with the user clicks the "Ok" button. The callback function will be called with just a single paramter. No. Defaults to not calling a callback function.
param anything Parameter that will be passed to the `callback` function No. Defaults to undefined.

Some example invocations are:

dlgMessage("Message title", "Message text");
dlgMessage("It's an error!", "<b>Something is wrong!!!</b>", true, true);
dlgMessage("Callback example", "Testing", true, false, my_message_cb, "some_param");

function my_message_cb(param) {
   alert("Message dismissed! The param was " + param);
}

dlgAlert

dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:

dlgAlert(message, callback)

// The above is equivalent to:
dlgMessage("Message", message, true, true, callback)

dlgWait

dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:

dlgWait(time_in_seconds, message)

Asking for input

dlgQuery

dlgQuery asks the user to respond to a question. The dialog shows a message, an input field for the user to type in, and Ok/Cancel buttons. The user's response is sent to a callback function that you must implement yourself. The signature is:

dlgQuery(message, value, callback, param)
Parameter Type Meaning Required
message string (can be arbitrary HTML) The question to show the user Yes
value string Default value to pre-populate the answer field with Yes (but can be empty string)
callback function Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the "Cancel" button, or their answer if they clicked the "Ok" button. Yes
param anything Parameter that will be passed to the `callback` function as the 2nd parameter No. Defaults to undefined.

Example usage is:

dlgQuery("Enter a value:", "my default value", my_query_cb, "some_other_param")

function my_query_cb(value, param) {
   if (value === false) {
      // User clicked the Cancel button
   } else {
      alert('Value is ' + value + ', param is ' + param);
   }
}

// where 'param' can also be ommitted.

dlgConfirm

dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:

dlgConfirm(message, callback, param)
Parameter Type Meaning Required
message string (can be arbitrary HTML) The question to show the user Yes
callback function Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the "Cancel" button, or true if they clicked the "Ok" button. Yes
param anything Parameter that will be passed to the `callback` function as the 2nd parameter No. Defaults to undefined.

Example usage is:

dlgConfirm("Are you sure you wish to do that?", my_confirm_cb, "some_other_param")

function my_confirm_cb(value, param) {
   if (value === false) {
      alert("User clicked Cancel! Param was " + param);
   } else {
      alert("User clicked Ok! Param was " + param);
   }
}

// where 'param' can also be ommitted.

Complete Example

The following code shows an example page (contained in resources/a_example.html in the MIDAS distribution) of a custom page implementing most of the new features. You activate this page by putting in the ODB:

/Custom
  Path     /midas/resources
  Test     a_example.html

Code

The file a_example.html contains the following code:

<!DOCTYPE html>
<html class="mcss">
<head>
   <meta charset="UTF-8">
   <link rel="stylesheet" href="midas.css">
   <script src="controls.js"></script>
   <script src="midas.js"></script>
   <script src="mhttpd.js"></script>
   <title>Example</title>

   <style>
      .mtable td { padding: 10px; }
   </style>
</head>

<body class="mcss" onload="mhttpd_init('Example');">

<!-- header and side navigation will be filled in mhttpd_init -->
<div id="mheader"></div>
<div id="msidenav"></div>

<div id="mmain">
   <table class="mtable">
      <tr>
         <th colspan="2" class="mtableheader">Status</th>
      </tr>
      <tr>
         <td style="width: 200px;">
            Run number:
         </td>
         <td>
            <div class="modbvalue" data-odb-path="/Runinfo/Run number" data-odb-editable="1"></div>
         </td>
      </tr>
      <tr>
         <td>
            Last run start:
         </td>
         <td>
            <div class="modbvalue" data-odb-path="/Runinfo/Start time"></div>
         </td>
      </tr>
      <tr>
         <td>
            Last run stop:
         </td>
         <td>
            <div class="modbvalue" data-odb-path="/Runinfo/Stop time"></div>
         </td>
      </tr>
      <tr>
         <td>
            Check box:
         </td>
         <td>
            <!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) -->
            <input type="checkbox" class="modbcheckbox" data-odb-path="/Logger/Write data"></input>

            <div class="modb" data-odb-path="/Logger/Write data" onchange="dlgAlert('Flag has changed');"></div>
         </td>
      </tr>
      <tr>
         <td>
            Color box:
         </td>
         <td>
            <!-- box changes color according to /Logger/Write data -->
            <div class="modbbox" style="width: 30px; height: 30px;" data-odb-path="/Logger/Write data"
                 data-color="lightgreen" data-background-color="red"></div>
         </td>
      </tr>
      <tr>
         <td>
            Horizontal bars:
         </td>
         <td>
            <div class="modbhbar" style="width:300px;height:20px;color:orange;" data-odb-path="/Runinfo/Run number"
                 data-max-value="10" data-print-value="1"></div><br />

            <div class="mhaxis" style="width:500px;height:22px;" data-min-value="0" data-max-value="10"></div>
            <div class="modbhbar" style="width: 500px; height: 18px;color:lightblue" data-odb-path="/Runinfo/Run number"
                 data-max-value="10"></div><br />

            <div class="modbhbar" style="width: 200px; height: 10px;color:lightgreen;background-color:white"
                 data-odb-path="/Runinfo/Run number" data-min-value="0.1" data-max-value="10" data-log="1"></div>
            <div class="mhaxis" style="width:200px;height:22px;vertical-align:top;" data-min-value="0.1"
                 data-max-value="10" data-line="0" data-log="1"></div>
         </td>
      </tr>
      <tr>
         <td>
            Vertical bars:
         </td>
         <td>
            <span class="mvaxis" style="width:100px;height:200px;text-align:right;" data-min-value="0" data-max-value="20"></span><span class="modbvbar"
                  style="width:20px;height:200px;color:yellow;" data-odb-path="/Runinfo/Run number"
                  data-min-value="0" data-max-value="20"></span>
            <span class="modbvbar" style="width:10px;height:200px;vertical-align:top;color:red" data-odb-path="/Runinfo/Run number" data-min-value="0.1"
                  data-max-value="10" data-log="1"></span><span class="mvaxis" style="width:100px;height:200px;text-align:left;" data-min-value="0.1"
                                                                data-max-value="10" data-log="1"></span>
         </td>
      </tr>

      <tr>
         <td>
            Thermometer:
         </td>
         <td>
            <div class="modbthermo" style="width:30px;height:100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                 data-color="darkgreen"></div>
            <div class="modbthermo" style="width:60px;height:100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                 data-color="blue" data-scale="1"
                 onchange="this.dataset.color=this.value > 9?'red':'blue';"></div>
            <div class="modbthermo" style="width:30px;height:100px;" data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                 data-color="blue" data-background-color="white" data-value="1"></div>
         </td>
      </tr>

      <tr>
         <td>
            Gauges:
         </td>
         <td>
            <div class="modbgauge" style="width:100px;height:50px;" data-odb-path="/Runinfo/Run number" data-min-value="0" data-max-value="10"
                 data-color="darkgreen" data-background-color="lightgrey" ></div>
            <div class="modbgauge" style="width:100px;height:65px;" data-odb-path="/Runinfo/Run number" data-min-value="0" data-max-value="10"
                 data-color="red" data-value="1" data-scale="1"></div>
         </td>
      </tr>

      <tr>
         <td colspan="2" style="text-align: center;">
            <!-- div around image with "relative" position as anchor for labels and bars -->
            <div style="position:relative;width:300px;margin:auto">

               <img src="tank.gif"> <!-- background image of tank -->

               <!-- label next to valve -->
               <div class="modbvalue" data-odb-path="/Runinfo/Run number" data-odb-editable="1"
                    style="position:absolute;top:157px;left:288px;"></div>

               <!-- vertical bar inside tank, render red if value > 9 -->
               <div class="modbvbar" style="position:absolute;top:80px;left:10px;width:104px;height:170px;"
                    data-odb-path="/Runinfo/Run number" data-max-value="11" data-color="green"
                    onchange="this.firstChild.style.backgroundColor=(this.value > 9)?'red':'green';"></div>

               <!-- thermometer inside tank -->
               <div class="modbthermo" style="position:absolute;top:140px;left:20px;width:20px;height:100px;"
                    data-odb-path="/Runinfo/Run number" data-min-value="-10" data-max-value="30"
                    data-color="blue" data-value="1"></div>

            </div>
         </td>
      </tr>

      <tr>
         <td colspan="2" style="text-align: center;">
            <!-- three buttons to change an ODB entry (run number in this example) -->
            <button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="1">Set run
               number to 1
            </button>
            <button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="5">Set run
               number to 5
            </button>
            <button class="modbbutton" class="mbutton" data-odb-path="/Runinfo/Run number" data-odb-value="10">Set run
               number to 10
            </button>
         </td>
      </tr>
   </table>
</div>

</body>
</html>

which results in the page shown in Figure 1 below:

Figure 1 Example custom page using most features

Old custom page feature

There are a number of deprecated custom page features, which can be seen here: Old Custom Page Features.