<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://daq00.triumf.ca/MidasWiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Rudzki</id>
	<title>MidasWiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://daq00.triumf.ca/MidasWiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Rudzki"/>
	<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php/Special:Contributions/Rudzki"/>
	<updated>2026-04-17T11:15:01Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.39.6</generator>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3546</id>
		<title>Alarm System</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3546"/>
		<updated>2025-08-05T09:37:33Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Alarm triggering Slack notifications */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[/Alarms ODB tree]]&lt;br /&gt;
* [[Alarms Page]]&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
MIDAS provides an alarm system, which by default is turned off. When the alarm system is [[/Alarms ODB tree#Alarm system active|activated]] and an alarm condition is detected, alarm messages are sent by the system which appear as an alarm banner on the [[Status Page|mhttpd status page]], and as a message on any windows running [[odbedit]] clients. The alarm system is flexible and can be extensively customized for each experiment&lt;br /&gt;
using the [[Alarms Page|mhttpd Alarms Page]] or [[odbedit]]. &lt;br /&gt;
&lt;br /&gt;
The alarm system is built-in and part of the main experiment scheduler. This means no separate task is necessary to benefit from the alarm system. Its setup and activation is done through the &#039;&#039;&#039;[[/Alarms ODB tree]]&#039;&#039;&#039;. The alarm system includes several other features such as sequencing and control of the experiment. The alarm capabilities are:&lt;br /&gt;
* Alarm setting on any ODB variable against a threshold parameter.&lt;br /&gt;
* Alarm triggered by evaluated condition&lt;br /&gt;
* Selection of Alarm check frequency&lt;br /&gt;
* Selection of Alarm trigger frequency&lt;br /&gt;
* Customization alarm scheme; under this scheme multiple choices of alarm type can be selected&lt;br /&gt;
* Selection of alarm message destination (to system message log or to elog)&lt;br /&gt;
* email or SMS alerts can be sent&lt;br /&gt;
* Alarm triggered when a Program is not running &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Implementation of the MIDAS Alarm System =&lt;br /&gt;
&lt;br /&gt;
The alarm system source code is [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
Alarms are checked inside [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html alarm.c::al_check()]. This function is called by cm_yield() every 10 seconds and by rpc_server_thread(), also every 10 seconds. For remote MIDAS clients, their al_check() issues an RPC_AL_CHECK RPC call into the MIDAS server utility [[mserver]], where rpc_server_dispatch() calls the local al_check(). As result, all alarm checks run inside a process directly attached to the local MIDAS shared memory (inside a local client or inside an mserver process for a remote client). Each and every MIDAS client runs the alarm checks. To prevent race conditions between different MIDAS clients, access to al_check() is serialized using the ALARM semaphore. Inside al_check(), alarms are triggered using &lt;br /&gt;
[http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_trigger_alarm()], which in turn calls al_trigger_class(). Inside al_trigger_class(), the alarm is recorded into an elog or into [[Message System|midas.log]] using cm_msg(MTALK).&lt;br /&gt;
&lt;br /&gt;
Special note should be made of the ODB setting [[/Alarms ODB tree#System message interval|system message interval]], which has a surprising effect - after an alarm is recorded into system messages (using cm_msg(MTALK)), no record is made of any subsequent alarms until the time interval set by this variable elapses. With default value of 60 seconds, after one alarm, no more alarms are recorded for 60 seconds. Also, because all the alarms are checked at the same time, &#039;&#039;&#039;only the first&#039;&#039;&#039; triggered alarm will be recorded.&lt;br /&gt;
&lt;br /&gt;
As of alarm.c rev 4683, {{Odbpath|path=/Alarms/System message interval}} is set to 0 ensures that every alarm is recorded into the [[Message System#MIDAS Log file|MIDAS log file]]. (In previous revisions, this setting may still miss some alarms).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;Alarm class&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
= Alarms structure =&lt;br /&gt;
The [[/Alarms ODB tree]] structure is split into 2 sections:&lt;br /&gt;
*&amp;quot;Alarms&amp;quot; which define the condition to be tested. The user can create as many [[/Alarms ODB tree#Alarms subtree|Alarms]] as desired, but each must be one of the four defined [[#Alarm Types|Alarm types]] .&lt;br /&gt;
*&amp;quot;Classes&amp;quot; which define the action to be taken when the alarm occurs. Two Classes (Alarm and Warning) are defined by default. The user can add more [[/Alarms ODB tree#Classes subtree|Classes]] as desired.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In order to make the system flexible, each alarm class may perform different actions when an alarm is given. For example, it may&lt;br /&gt;
* write a system message (see [[/Alarms ODB tree#Write System Message|Write System Message]])&lt;br /&gt;
* write to the elog (see [[/Alarms ODB tree#Write Elog Message|Write Elog Message]])&lt;br /&gt;
* stop the run (see [[/Alarms ODB tree#Stop run|Stop run]])&lt;br /&gt;
* spawn a detached script listed in the ODB variable [[/Alarms ODB tree#Execute command|Execute command]]. This feature is used when an Alarm triggers Email or SMS alerts (see [[#Alarm triggering Email or SMS alerts|example]]).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm Types =&lt;br /&gt;
&lt;br /&gt;
The four available Alarm Types are shown in Table 1. They are defined in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/midas_8h.html&amp;amp;nbsp midas.h]. The alarm type is entered into the [[/Alarms ODB tree#Type|Type]] key.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: white;&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Table 1 : Defined Alarm Types&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Alarm Type&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | INT value&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Explanation&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot;  |Internal alarms&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: normal;&amp;quot; | AT_INTERNAL&lt;br /&gt;
|1&lt;br /&gt;
|Trigger on internal (program) alarm setting through the use of the al_...() functions. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Program alarms&lt;br /&gt;
|AT_PROGRAM&lt;br /&gt;
|2&lt;br /&gt;
|Triggered on condition of the state of the defined task (i.e. program not running)&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Evaluated alarms&lt;br /&gt;
|AT_EVALUATED&lt;br /&gt;
|3&lt;br /&gt;
|Triggered by ODB value on given arithmetical condition. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Periodic alarms&lt;br /&gt;
|AT_PERIODIC&lt;br /&gt;
|4&lt;br /&gt;
|Triggered by timeout condition defined in the alarm setting. &lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Program Alarm ==&lt;br /&gt;
&lt;br /&gt;
Program (or rather &amp;quot;Program not running&amp;quot;) alarms, when enabled, warn the user when a program is not running.&lt;br /&gt;
&lt;br /&gt;
Program alarms are enabled by setting the ODB key [[/Programs ODB tree#Alarm class|/Programs/&amp;lt;client-name&amp;gt;/Alarm class]] to a valid Alarm class specified in the [[/Alarms ODB tree]]. The first time the alarm is triggered, an &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Alarms/Alarms/&amp;lt;client-name&amp;gt;&amp;lt;/span&amp;gt; subtree will be created automatically. The program alarm will not be visible in the [[Alarms Page]] until the alarm has triggered, and the subtree created.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
The alarm system periodically calls [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_check()]. This causes every client listed in the {{Odbpath|path=/Programs}} ODB tree to be tested using [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__cmfunctionc.html cm_exist()] to see if it is running. If the client is not running, the time of first failure is recorded in the ODB key [[/Programs ODB tree#First failed|/Programs/&amp;lt;client-name&amp;gt;/First failed]].&lt;br /&gt;
&lt;br /&gt;
If the client has not been running for longer than the time set in ODB key [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]], a &amp;quot;Program not running&amp;quot; alarm is triggered (if enabled by [[/Programs ODB tree#Alarm class|Alarm class]]) and the program is restarted (if enabled by [[/Programs ODB tree#Auto restart|/Programs/&amp;lt;client-name&amp;gt;/Auto restart]] and a valid [[/Programs ODB tree#Start command|Start command]] is supplied).&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;not running&amp;quot; condition is tested every 10 seconds (each time al_check() is called), but the frequency of &#039;&#039;Program not running&#039;&#039; alarms can be reduced by increasing the value of the ODB key&lt;br /&gt;
[[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]&lt;br /&gt;
(default value 60 seconds). This can be useful if  [[/Alarms ODB tree#System message interval|System message interval]] in the specified alarm class subtree is set to zero.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Periodic Alarm ==&lt;br /&gt;
The periodic alarm is activated periodically according to the time in [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]. An example of a periodic alarm is &amp;quot;Demo Periodic&amp;quot; in the  [[/Alarms ODB tree#Example|example]].&lt;br /&gt;
&lt;br /&gt;
== Evaluated Alarm  ==&lt;br /&gt;
&lt;br /&gt;
Evaluated alarms require an &#039;&#039;alarm condition&#039;&#039; which is entered into the ODB key [[/Alarms ODB tree#Condition|Condition]] in the  [[/Alarms ODB tree#&amp;lt;alarm_name&amp;gt; subtree|&amp;lt;alarm_name&amp;gt; subtree]].&lt;br /&gt;
The condition may be simply a &#039;&#039;&#039;comparison&#039;&#039;&#039; between any ODB variable and a threshold parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Runinfo/Run number &amp;gt; 100&lt;br /&gt;
or it may be an &#039;&#039;&#039;evaluated condition&#039;&#039;&#039;. One can write conditions like&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[*] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[2-3] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[1,4,5-8,10] &amp;gt; 100&lt;br /&gt;
&lt;br /&gt;
to check all or certain values from an array. A dash means a range including both indices. If one array element fulfills the alarm condition, the alarm is triggered. In addition, bit-wise alarm conditions are possible, e.g.&lt;br /&gt;
&lt;br /&gt;
  /Equipment/Environment/Variables/Input[0] &amp;amp; 8&lt;br /&gt;
The alarm is triggered if bit #3 is set in Input[0].&lt;br /&gt;
&lt;br /&gt;
The value of an evaluated alarm is computed using al_evaluate_condition() in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
&lt;br /&gt;
Sometimes alarm trigger kind of accidentally on some analog input voltage if they are noise. In order to avoid this, the &amp;quot;Trigger count required&amp;quot; can be used. If this value contains a non-zero value of N, then the alarm system requires the alarm condition to be met N consecutive times until the alarm to be triggered.&lt;br /&gt;
&lt;br /&gt;
== Internal Alarm ==&lt;br /&gt;
These are triggered in a program using a call to &lt;br /&gt;
[https://daq.triumf.ca/~daqweb/doc/midas-devel/html/group__alfunctioncode.html al_trigger_alarm()]. See also description of al_trigger_alarm() sequence  [[#Implementation of the MIDAS Alarm System|above]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is nothing surprising in these alarms. Each alarm is checked with a time period set by ODB key [[ /Alarms ODB tree#Check interval|Check interval]] in the [[/Alarms ODB tree]].&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Email or SMS alerts =&lt;br /&gt;
&lt;br /&gt;
It is possible to have the MIDAS alarm system send email or SMS alerts to cell phones when alarms are triggered. This can be configured by defining an ODB alarm on a critical ODB parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Alarms/Liquid Level&lt;br /&gt;
 Active                   y&lt;br /&gt;
 Triggered                0 (0x0)&lt;br /&gt;
 Type                     3 (0x3)&lt;br /&gt;
 Check interval          60 (0x3C)&lt;br /&gt;
 Checked last    1227690148 (0x492D10A4)&lt;br /&gt;
 Trigger count            0 (0x0)&lt;br /&gt;
 Trigger count required   0 (0x0)&lt;br /&gt;
 Time triggered first    (empty)&lt;br /&gt;
 Time triggered last     (empty)&lt;br /&gt;
 Condition               /Equipment/Environment/Variables/Input[0] &amp;lt; 10&lt;br /&gt;
 Alarm Class             Level Alarm&lt;br /&gt;
 Alarm Message           Liquid Level is only %s&lt;br /&gt;
&lt;br /&gt;
In this example, the alarm triggers an alarm of class &amp;quot;Level Alarm&amp;quot;. This alarm class is defined as follows:&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Classes/Level Alarm&lt;br /&gt;
 Write system message    y&lt;br /&gt;
 Write Elog message      n&lt;br /&gt;
 System message interval 600 (0x258)&lt;br /&gt;
 System message last     0 (0x0)&lt;br /&gt;
 Execute command         /home/midas/level_alarm &#039;%s&#039;&lt;br /&gt;
 Execute interval        1800 (0x708)&lt;br /&gt;
 Execute last            0 (0x0)&lt;br /&gt;
 Stop run                n&lt;br /&gt;
 Display BGColor         red&lt;br /&gt;
 Display FGColor         black&lt;br /&gt;
&lt;br /&gt;
The key here is to call a script &amp;quot;level_alarm&amp;quot;, which can send emails. Use something like:&lt;br /&gt;
&lt;br /&gt;
 #/bin/csh&lt;br /&gt;
 echo $1 | mail -s \&amp;quot;Level Alarm\&amp;quot; your.name@domain.edu&lt;br /&gt;
 odbedit -c &#039;msg 2 level_alarm \&amp;quot;Alarm was sent to your.name@domain.edu\&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
The second command just generates a MIDAS system message for confirmation. Most cell phones (depends on the provider) have an email address. If you send an email there, it will be translated into a SMS message.&lt;br /&gt;
&lt;br /&gt;
The script file above can of course be more complicated. A perl script could be used that parses an address list, so other interested parties can register by adding his/her email address to that list. The script may also collects some other slow control variables (like pressure, temperature) and combine them into the SMS message.&lt;br /&gt;
&lt;br /&gt;
For very sensitive systems, having an alarm via SMS may not be sufficient, since the alarm system could be down (e.g. computer crash, network failure). In this case &#039;negative alarms&#039; can be used. For example, every 30 minutes the system may send an SMS with the current parameter values. If the expected message is not received, it may indicate that something in the MIDAS system is wrong.&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Slack notifications =&lt;br /&gt;
&lt;br /&gt;
A more modern way for notification is to use messengers apps such as Slack. To send alarms to Slack, do the following:&lt;br /&gt;
&lt;br /&gt;
* Go to https://api.slack.com/apps&lt;br /&gt;
* Create an App &amp;quot;MIDAS alarms&amp;quot;, select &amp;quot;From scratch&amp;quot;&lt;br /&gt;
* Name it &amp;quot;MIDAS alarm&amp;quot; or similar, select your workspace&lt;br /&gt;
* Click on &amp;quot;Add features and functionality&amp;quot;&lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Activate Incoming Webhooks&lt;br /&gt;
* Click on &amp;quot;Add New Webhook to Workspace&amp;quot;&lt;br /&gt;
* Select channel where alarms get posted, allow access&lt;br /&gt;
* Copy sample curl request and replace &amp;quot;Hello World&amp;quot; by &amp;quot;$1&amp;quot;, such as: &lt;br /&gt;
&lt;br /&gt;
 curl -X POST -H &#039;Content-type: application/json&#039; --data &amp;quot;{\&amp;quot;text\&amp;quot;:\&amp;quot;$1\&amp;quot;}&#039; https://hooks.slack.com/services/[xxx]/[yyy]/[zzz]&lt;br /&gt;
&lt;br /&gt;
(leave they keys xxx, yyy, zzz as shown on the web page).&lt;br /&gt;
&lt;br /&gt;
* Call the curl command from the alarm system by putting the above command under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
* Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Telegram notifications =&lt;br /&gt;
&lt;br /&gt;
A telegram bot needs to be created to post Midas alarms into telegram channels.&lt;br /&gt;
&lt;br /&gt;
* Go to https://web.telegram.org and login by scanning the QR-code with the telegram app on your phone&lt;br /&gt;
* Go to https://t.me/botfather&lt;br /&gt;
* Click on &amp;quot;open in web&amp;quot;&lt;br /&gt;
* A chat window will open. &lt;br /&gt;
* Send &amp;quot;/newbot&amp;quot; into the chat and assign name and username to your bot&lt;br /&gt;
* At the end of the bot creation a access token will be displayed. Save that access token.&lt;br /&gt;
* Post &amp;quot;/mybots&amp;quot; into the chat&lt;br /&gt;
* Select the bot that was just created&lt;br /&gt;
* Click on &amp;quot;Bot settings&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Allow Groups&amp;quot;&lt;br /&gt;
* Enable groups for the bot&lt;br /&gt;
* Click on &amp;quot;back to settings&amp;quot;&lt;br /&gt;
* Click on Channel Admin Rights&lt;br /&gt;
* Click on &amp;quot;Manage channel&amp;quot; (a checkmark should appear next to the text)&lt;br /&gt;
* Close this chat.&lt;br /&gt;
&lt;br /&gt;
* Add the created bot to a telegram group chat via searching for the bot name in &amp;quot;add members&amp;quot; (same as adding new People to the group)&lt;br /&gt;
* Now you need the chat_id of the same group chat. When you open the group chat in a webbrowser the URL should look like this https://web.telegram.org/a/#-1700000000 (where 1700000000 will be a different number for you)&lt;br /&gt;
* The group chat_id is then -1001700000000 (add -100 in front of the number in your URL)&lt;br /&gt;
* With the chat_id from above and the access token from the bot creation process you can post to the telegram group like this: (replace &amp;lt;chat_id&amp;gt; and &amp;lt;accessToken&amp;gt; with your numbers)&lt;br /&gt;
&lt;br /&gt;
  curl -s -X  POST &amp;quot;https://api.telegram.org/bot&amp;lt;accessToken&amp;gt;/sendMessage&amp;quot; -d chat_id=&amp;lt;chat_id&amp;gt; -d text=&amp;quot;🚨 Midas alarm triggered with message = $1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
* Create a shell script with this command&lt;br /&gt;
* Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
* Should you want to post the same alarm to multiple systems you can just separate scripts in &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; with a semicolon &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;;path/to/script2.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Mattermost notifications =&lt;br /&gt;
&lt;br /&gt;
First a mattermost bot needs to be created. To do this you need to be an Admin of the Team that the bot should post to.&lt;br /&gt;
&lt;br /&gt;
* In the drop down menu on the top left corner of the mattermost page click on &amp;quot;Integrations&amp;quot; (note: this field will be missing if you are not an Admin of the selected mattermost team) &lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Add Incoming Webhook&amp;quot;&lt;br /&gt;
* Give the bot a title and username (e.g. midasalarms) and select the channel to post to, click &amp;quot;save&amp;quot;&lt;br /&gt;
* Now a URL for the incoming webhook should be displayed (https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;). Copy that URL into the curl command in the following script:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   #!/bin/bash&lt;br /&gt;
   alarm_message=$1&lt;br /&gt;
   generate_post_data()&lt;br /&gt;
   {&lt;br /&gt;
     cat &amp;lt;&amp;lt;EOF&lt;br /&gt;
     {&lt;br /&gt;
     &amp;quot;text&amp;quot;: &amp;quot;@all Midas alarm triggered with message = $alarm_message&amp;quot;, &lt;br /&gt;
     &amp;quot;icon_emoji&amp;quot;:&amp;quot;:rotating_light:&amp;quot;, &lt;br /&gt;
     &amp;quot;username&amp;quot;:&amp;quot;Midas&amp;quot;}&lt;br /&gt;
   EOF&lt;br /&gt;
   }&lt;br /&gt;
   curl -i -X POST -H &#039;Content-Type: application/jsoni&#039; --data &amp;quot;$(generate_post_data)&amp;quot; https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[Category:Alarms]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3545</id>
		<title>Alarm System</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3545"/>
		<updated>2025-08-05T09:37:14Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Alarm triggering Mattermost notifications */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[/Alarms ODB tree]]&lt;br /&gt;
* [[Alarms Page]]&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
MIDAS provides an alarm system, which by default is turned off. When the alarm system is [[/Alarms ODB tree#Alarm system active|activated]] and an alarm condition is detected, alarm messages are sent by the system which appear as an alarm banner on the [[Status Page|mhttpd status page]], and as a message on any windows running [[odbedit]] clients. The alarm system is flexible and can be extensively customized for each experiment&lt;br /&gt;
using the [[Alarms Page|mhttpd Alarms Page]] or [[odbedit]]. &lt;br /&gt;
&lt;br /&gt;
The alarm system is built-in and part of the main experiment scheduler. This means no separate task is necessary to benefit from the alarm system. Its setup and activation is done through the &#039;&#039;&#039;[[/Alarms ODB tree]]&#039;&#039;&#039;. The alarm system includes several other features such as sequencing and control of the experiment. The alarm capabilities are:&lt;br /&gt;
* Alarm setting on any ODB variable against a threshold parameter.&lt;br /&gt;
* Alarm triggered by evaluated condition&lt;br /&gt;
* Selection of Alarm check frequency&lt;br /&gt;
* Selection of Alarm trigger frequency&lt;br /&gt;
* Customization alarm scheme; under this scheme multiple choices of alarm type can be selected&lt;br /&gt;
* Selection of alarm message destination (to system message log or to elog)&lt;br /&gt;
* email or SMS alerts can be sent&lt;br /&gt;
* Alarm triggered when a Program is not running &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Implementation of the MIDAS Alarm System =&lt;br /&gt;
&lt;br /&gt;
The alarm system source code is [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
Alarms are checked inside [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html alarm.c::al_check()]. This function is called by cm_yield() every 10 seconds and by rpc_server_thread(), also every 10 seconds. For remote MIDAS clients, their al_check() issues an RPC_AL_CHECK RPC call into the MIDAS server utility [[mserver]], where rpc_server_dispatch() calls the local al_check(). As result, all alarm checks run inside a process directly attached to the local MIDAS shared memory (inside a local client or inside an mserver process for a remote client). Each and every MIDAS client runs the alarm checks. To prevent race conditions between different MIDAS clients, access to al_check() is serialized using the ALARM semaphore. Inside al_check(), alarms are triggered using &lt;br /&gt;
[http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_trigger_alarm()], which in turn calls al_trigger_class(). Inside al_trigger_class(), the alarm is recorded into an elog or into [[Message System|midas.log]] using cm_msg(MTALK).&lt;br /&gt;
&lt;br /&gt;
Special note should be made of the ODB setting [[/Alarms ODB tree#System message interval|system message interval]], which has a surprising effect - after an alarm is recorded into system messages (using cm_msg(MTALK)), no record is made of any subsequent alarms until the time interval set by this variable elapses. With default value of 60 seconds, after one alarm, no more alarms are recorded for 60 seconds. Also, because all the alarms are checked at the same time, &#039;&#039;&#039;only the first&#039;&#039;&#039; triggered alarm will be recorded.&lt;br /&gt;
&lt;br /&gt;
As of alarm.c rev 4683, {{Odbpath|path=/Alarms/System message interval}} is set to 0 ensures that every alarm is recorded into the [[Message System#MIDAS Log file|MIDAS log file]]. (In previous revisions, this setting may still miss some alarms).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;Alarm class&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
= Alarms structure =&lt;br /&gt;
The [[/Alarms ODB tree]] structure is split into 2 sections:&lt;br /&gt;
*&amp;quot;Alarms&amp;quot; which define the condition to be tested. The user can create as many [[/Alarms ODB tree#Alarms subtree|Alarms]] as desired, but each must be one of the four defined [[#Alarm Types|Alarm types]] .&lt;br /&gt;
*&amp;quot;Classes&amp;quot; which define the action to be taken when the alarm occurs. Two Classes (Alarm and Warning) are defined by default. The user can add more [[/Alarms ODB tree#Classes subtree|Classes]] as desired.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In order to make the system flexible, each alarm class may perform different actions when an alarm is given. For example, it may&lt;br /&gt;
* write a system message (see [[/Alarms ODB tree#Write System Message|Write System Message]])&lt;br /&gt;
* write to the elog (see [[/Alarms ODB tree#Write Elog Message|Write Elog Message]])&lt;br /&gt;
* stop the run (see [[/Alarms ODB tree#Stop run|Stop run]])&lt;br /&gt;
* spawn a detached script listed in the ODB variable [[/Alarms ODB tree#Execute command|Execute command]]. This feature is used when an Alarm triggers Email or SMS alerts (see [[#Alarm triggering Email or SMS alerts|example]]).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm Types =&lt;br /&gt;
&lt;br /&gt;
The four available Alarm Types are shown in Table 1. They are defined in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/midas_8h.html&amp;amp;nbsp midas.h]. The alarm type is entered into the [[/Alarms ODB tree#Type|Type]] key.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: white;&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Table 1 : Defined Alarm Types&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Alarm Type&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | INT value&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Explanation&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot;  |Internal alarms&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: normal;&amp;quot; | AT_INTERNAL&lt;br /&gt;
|1&lt;br /&gt;
|Trigger on internal (program) alarm setting through the use of the al_...() functions. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Program alarms&lt;br /&gt;
|AT_PROGRAM&lt;br /&gt;
|2&lt;br /&gt;
|Triggered on condition of the state of the defined task (i.e. program not running)&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Evaluated alarms&lt;br /&gt;
|AT_EVALUATED&lt;br /&gt;
|3&lt;br /&gt;
|Triggered by ODB value on given arithmetical condition. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Periodic alarms&lt;br /&gt;
|AT_PERIODIC&lt;br /&gt;
|4&lt;br /&gt;
|Triggered by timeout condition defined in the alarm setting. &lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Program Alarm ==&lt;br /&gt;
&lt;br /&gt;
Program (or rather &amp;quot;Program not running&amp;quot;) alarms, when enabled, warn the user when a program is not running.&lt;br /&gt;
&lt;br /&gt;
Program alarms are enabled by setting the ODB key [[/Programs ODB tree#Alarm class|/Programs/&amp;lt;client-name&amp;gt;/Alarm class]] to a valid Alarm class specified in the [[/Alarms ODB tree]]. The first time the alarm is triggered, an &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Alarms/Alarms/&amp;lt;client-name&amp;gt;&amp;lt;/span&amp;gt; subtree will be created automatically. The program alarm will not be visible in the [[Alarms Page]] until the alarm has triggered, and the subtree created.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
The alarm system periodically calls [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_check()]. This causes every client listed in the {{Odbpath|path=/Programs}} ODB tree to be tested using [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__cmfunctionc.html cm_exist()] to see if it is running. If the client is not running, the time of first failure is recorded in the ODB key [[/Programs ODB tree#First failed|/Programs/&amp;lt;client-name&amp;gt;/First failed]].&lt;br /&gt;
&lt;br /&gt;
If the client has not been running for longer than the time set in ODB key [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]], a &amp;quot;Program not running&amp;quot; alarm is triggered (if enabled by [[/Programs ODB tree#Alarm class|Alarm class]]) and the program is restarted (if enabled by [[/Programs ODB tree#Auto restart|/Programs/&amp;lt;client-name&amp;gt;/Auto restart]] and a valid [[/Programs ODB tree#Start command|Start command]] is supplied).&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;not running&amp;quot; condition is tested every 10 seconds (each time al_check() is called), but the frequency of &#039;&#039;Program not running&#039;&#039; alarms can be reduced by increasing the value of the ODB key&lt;br /&gt;
[[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]&lt;br /&gt;
(default value 60 seconds). This can be useful if  [[/Alarms ODB tree#System message interval|System message interval]] in the specified alarm class subtree is set to zero.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Periodic Alarm ==&lt;br /&gt;
The periodic alarm is activated periodically according to the time in [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]. An example of a periodic alarm is &amp;quot;Demo Periodic&amp;quot; in the  [[/Alarms ODB tree#Example|example]].&lt;br /&gt;
&lt;br /&gt;
== Evaluated Alarm  ==&lt;br /&gt;
&lt;br /&gt;
Evaluated alarms require an &#039;&#039;alarm condition&#039;&#039; which is entered into the ODB key [[/Alarms ODB tree#Condition|Condition]] in the  [[/Alarms ODB tree#&amp;lt;alarm_name&amp;gt; subtree|&amp;lt;alarm_name&amp;gt; subtree]].&lt;br /&gt;
The condition may be simply a &#039;&#039;&#039;comparison&#039;&#039;&#039; between any ODB variable and a threshold parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Runinfo/Run number &amp;gt; 100&lt;br /&gt;
or it may be an &#039;&#039;&#039;evaluated condition&#039;&#039;&#039;. One can write conditions like&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[*] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[2-3] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[1,4,5-8,10] &amp;gt; 100&lt;br /&gt;
&lt;br /&gt;
to check all or certain values from an array. A dash means a range including both indices. If one array element fulfills the alarm condition, the alarm is triggered. In addition, bit-wise alarm conditions are possible, e.g.&lt;br /&gt;
&lt;br /&gt;
  /Equipment/Environment/Variables/Input[0] &amp;amp; 8&lt;br /&gt;
The alarm is triggered if bit #3 is set in Input[0].&lt;br /&gt;
&lt;br /&gt;
The value of an evaluated alarm is computed using al_evaluate_condition() in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
&lt;br /&gt;
Sometimes alarm trigger kind of accidentally on some analog input voltage if they are noise. In order to avoid this, the &amp;quot;Trigger count required&amp;quot; can be used. If this value contains a non-zero value of N, then the alarm system requires the alarm condition to be met N consecutive times until the alarm to be triggered.&lt;br /&gt;
&lt;br /&gt;
== Internal Alarm ==&lt;br /&gt;
These are triggered in a program using a call to &lt;br /&gt;
[https://daq.triumf.ca/~daqweb/doc/midas-devel/html/group__alfunctioncode.html al_trigger_alarm()]. See also description of al_trigger_alarm() sequence  [[#Implementation of the MIDAS Alarm System|above]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is nothing surprising in these alarms. Each alarm is checked with a time period set by ODB key [[ /Alarms ODB tree#Check interval|Check interval]] in the [[/Alarms ODB tree]].&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Email or SMS alerts =&lt;br /&gt;
&lt;br /&gt;
It is possible to have the MIDAS alarm system send email or SMS alerts to cell phones when alarms are triggered. This can be configured by defining an ODB alarm on a critical ODB parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Alarms/Liquid Level&lt;br /&gt;
 Active                   y&lt;br /&gt;
 Triggered                0 (0x0)&lt;br /&gt;
 Type                     3 (0x3)&lt;br /&gt;
 Check interval          60 (0x3C)&lt;br /&gt;
 Checked last    1227690148 (0x492D10A4)&lt;br /&gt;
 Trigger count            0 (0x0)&lt;br /&gt;
 Trigger count required   0 (0x0)&lt;br /&gt;
 Time triggered first    (empty)&lt;br /&gt;
 Time triggered last     (empty)&lt;br /&gt;
 Condition               /Equipment/Environment/Variables/Input[0] &amp;lt; 10&lt;br /&gt;
 Alarm Class             Level Alarm&lt;br /&gt;
 Alarm Message           Liquid Level is only %s&lt;br /&gt;
&lt;br /&gt;
In this example, the alarm triggers an alarm of class &amp;quot;Level Alarm&amp;quot;. This alarm class is defined as follows:&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Classes/Level Alarm&lt;br /&gt;
 Write system message    y&lt;br /&gt;
 Write Elog message      n&lt;br /&gt;
 System message interval 600 (0x258)&lt;br /&gt;
 System message last     0 (0x0)&lt;br /&gt;
 Execute command         /home/midas/level_alarm &#039;%s&#039;&lt;br /&gt;
 Execute interval        1800 (0x708)&lt;br /&gt;
 Execute last            0 (0x0)&lt;br /&gt;
 Stop run                n&lt;br /&gt;
 Display BGColor         red&lt;br /&gt;
 Display FGColor         black&lt;br /&gt;
&lt;br /&gt;
The key here is to call a script &amp;quot;level_alarm&amp;quot;, which can send emails. Use something like:&lt;br /&gt;
&lt;br /&gt;
 #/bin/csh&lt;br /&gt;
 echo $1 | mail -s \&amp;quot;Level Alarm\&amp;quot; your.name@domain.edu&lt;br /&gt;
 odbedit -c &#039;msg 2 level_alarm \&amp;quot;Alarm was sent to your.name@domain.edu\&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
The second command just generates a MIDAS system message for confirmation. Most cell phones (depends on the provider) have an email address. If you send an email there, it will be translated into a SMS message.&lt;br /&gt;
&lt;br /&gt;
The script file above can of course be more complicated. A perl script could be used that parses an address list, so other interested parties can register by adding his/her email address to that list. The script may also collects some other slow control variables (like pressure, temperature) and combine them into the SMS message.&lt;br /&gt;
&lt;br /&gt;
For very sensitive systems, having an alarm via SMS may not be sufficient, since the alarm system could be down (e.g. computer crash, network failure). In this case &#039;negative alarms&#039; can be used. For example, every 30 minutes the system may send an SMS with the current parameter values. If the expected message is not received, it may indicate that something in the MIDAS system is wrong.&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Slack notifications =&lt;br /&gt;
&lt;br /&gt;
A more modern way for notification is to use messengers apps such as Slack. To send alarms to Slack, do the following:&lt;br /&gt;
&lt;br /&gt;
* Go to https://api.slack.com/apps&lt;br /&gt;
* Create an App &amp;quot;MIDAS alarms&amp;quot;, select &amp;quot;From scratch&amp;quot;&lt;br /&gt;
* Name it &amp;quot;MIDAS alarm&amp;quot; or similar, select your workspace&lt;br /&gt;
* Click on &amp;quot;Add features and functionality&amp;quot;&lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Activate Incoming Webhooks&lt;br /&gt;
* Click on &amp;quot;Add New Webhook to Workspace&amp;quot;&lt;br /&gt;
* Select channel where alarms get posted, allow access&lt;br /&gt;
* Copy sample curl request and replace &amp;quot;Hello World&amp;quot; by &amp;quot;$1&amp;quot;, such as: &lt;br /&gt;
&lt;br /&gt;
 curl -X POST -H &#039;Content-type: application/json&#039; --data &amp;quot;{\&amp;quot;text\&amp;quot;:\&amp;quot;$1\&amp;quot;}&#039; https://hooks.slack.com/services/[xxx]/[yyy]/[zzz]&lt;br /&gt;
&lt;br /&gt;
(leave they keys xxx, yyy, zzz as shown on the web page).&lt;br /&gt;
&lt;br /&gt;
* Call the curl command from the alarm system by putting the above command under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Telegram notifications =&lt;br /&gt;
&lt;br /&gt;
A telegram bot needs to be created to post Midas alarms into telegram channels.&lt;br /&gt;
&lt;br /&gt;
* Go to https://web.telegram.org and login by scanning the QR-code with the telegram app on your phone&lt;br /&gt;
* Go to https://t.me/botfather&lt;br /&gt;
* Click on &amp;quot;open in web&amp;quot;&lt;br /&gt;
* A chat window will open. &lt;br /&gt;
* Send &amp;quot;/newbot&amp;quot; into the chat and assign name and username to your bot&lt;br /&gt;
* At the end of the bot creation a access token will be displayed. Save that access token.&lt;br /&gt;
* Post &amp;quot;/mybots&amp;quot; into the chat&lt;br /&gt;
* Select the bot that was just created&lt;br /&gt;
* Click on &amp;quot;Bot settings&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Allow Groups&amp;quot;&lt;br /&gt;
* Enable groups for the bot&lt;br /&gt;
* Click on &amp;quot;back to settings&amp;quot;&lt;br /&gt;
* Click on Channel Admin Rights&lt;br /&gt;
* Click on &amp;quot;Manage channel&amp;quot; (a checkmark should appear next to the text)&lt;br /&gt;
* Close this chat.&lt;br /&gt;
&lt;br /&gt;
* Add the created bot to a telegram group chat via searching for the bot name in &amp;quot;add members&amp;quot; (same as adding new People to the group)&lt;br /&gt;
* Now you need the chat_id of the same group chat. When you open the group chat in a webbrowser the URL should look like this https://web.telegram.org/a/#-1700000000 (where 1700000000 will be a different number for you)&lt;br /&gt;
* The group chat_id is then -1001700000000 (add -100 in front of the number in your URL)&lt;br /&gt;
* With the chat_id from above and the access token from the bot creation process you can post to the telegram group like this: (replace &amp;lt;chat_id&amp;gt; and &amp;lt;accessToken&amp;gt; with your numbers)&lt;br /&gt;
&lt;br /&gt;
  curl -s -X  POST &amp;quot;https://api.telegram.org/bot&amp;lt;accessToken&amp;gt;/sendMessage&amp;quot; -d chat_id=&amp;lt;chat_id&amp;gt; -d text=&amp;quot;🚨 Midas alarm triggered with message = $1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
* Create a shell script with this command&lt;br /&gt;
* Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
* Should you want to post the same alarm to multiple systems you can just separate scripts in &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; with a semicolon &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;;path/to/script2.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Mattermost notifications =&lt;br /&gt;
&lt;br /&gt;
First a mattermost bot needs to be created. To do this you need to be an Admin of the Team that the bot should post to.&lt;br /&gt;
&lt;br /&gt;
* In the drop down menu on the top left corner of the mattermost page click on &amp;quot;Integrations&amp;quot; (note: this field will be missing if you are not an Admin of the selected mattermost team) &lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Add Incoming Webhook&amp;quot;&lt;br /&gt;
* Give the bot a title and username (e.g. midasalarms) and select the channel to post to, click &amp;quot;save&amp;quot;&lt;br /&gt;
* Now a URL for the incoming webhook should be displayed (https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;). Copy that URL into the curl command in the following script:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   #!/bin/bash&lt;br /&gt;
   alarm_message=$1&lt;br /&gt;
   generate_post_data()&lt;br /&gt;
   {&lt;br /&gt;
     cat &amp;lt;&amp;lt;EOF&lt;br /&gt;
     {&lt;br /&gt;
     &amp;quot;text&amp;quot;: &amp;quot;@all Midas alarm triggered with message = $alarm_message&amp;quot;, &lt;br /&gt;
     &amp;quot;icon_emoji&amp;quot;:&amp;quot;:rotating_light:&amp;quot;, &lt;br /&gt;
     &amp;quot;username&amp;quot;:&amp;quot;Midas&amp;quot;}&lt;br /&gt;
   EOF&lt;br /&gt;
   }&lt;br /&gt;
   curl -i -X POST -H &#039;Content-Type: application/jsoni&#039; --data &amp;quot;$(generate_post_data)&amp;quot; https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
[[Category:Alarms]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3544</id>
		<title>Alarm System</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3544"/>
		<updated>2025-08-05T09:35:59Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Alarm triggering Email or SMS alerts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[/Alarms ODB tree]]&lt;br /&gt;
* [[Alarms Page]]&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
MIDAS provides an alarm system, which by default is turned off. When the alarm system is [[/Alarms ODB tree#Alarm system active|activated]] and an alarm condition is detected, alarm messages are sent by the system which appear as an alarm banner on the [[Status Page|mhttpd status page]], and as a message on any windows running [[odbedit]] clients. The alarm system is flexible and can be extensively customized for each experiment&lt;br /&gt;
using the [[Alarms Page|mhttpd Alarms Page]] or [[odbedit]]. &lt;br /&gt;
&lt;br /&gt;
The alarm system is built-in and part of the main experiment scheduler. This means no separate task is necessary to benefit from the alarm system. Its setup and activation is done through the &#039;&#039;&#039;[[/Alarms ODB tree]]&#039;&#039;&#039;. The alarm system includes several other features such as sequencing and control of the experiment. The alarm capabilities are:&lt;br /&gt;
* Alarm setting on any ODB variable against a threshold parameter.&lt;br /&gt;
* Alarm triggered by evaluated condition&lt;br /&gt;
* Selection of Alarm check frequency&lt;br /&gt;
* Selection of Alarm trigger frequency&lt;br /&gt;
* Customization alarm scheme; under this scheme multiple choices of alarm type can be selected&lt;br /&gt;
* Selection of alarm message destination (to system message log or to elog)&lt;br /&gt;
* email or SMS alerts can be sent&lt;br /&gt;
* Alarm triggered when a Program is not running &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Implementation of the MIDAS Alarm System =&lt;br /&gt;
&lt;br /&gt;
The alarm system source code is [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
Alarms are checked inside [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html alarm.c::al_check()]. This function is called by cm_yield() every 10 seconds and by rpc_server_thread(), also every 10 seconds. For remote MIDAS clients, their al_check() issues an RPC_AL_CHECK RPC call into the MIDAS server utility [[mserver]], where rpc_server_dispatch() calls the local al_check(). As result, all alarm checks run inside a process directly attached to the local MIDAS shared memory (inside a local client or inside an mserver process for a remote client). Each and every MIDAS client runs the alarm checks. To prevent race conditions between different MIDAS clients, access to al_check() is serialized using the ALARM semaphore. Inside al_check(), alarms are triggered using &lt;br /&gt;
[http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_trigger_alarm()], which in turn calls al_trigger_class(). Inside al_trigger_class(), the alarm is recorded into an elog or into [[Message System|midas.log]] using cm_msg(MTALK).&lt;br /&gt;
&lt;br /&gt;
Special note should be made of the ODB setting [[/Alarms ODB tree#System message interval|system message interval]], which has a surprising effect - after an alarm is recorded into system messages (using cm_msg(MTALK)), no record is made of any subsequent alarms until the time interval set by this variable elapses. With default value of 60 seconds, after one alarm, no more alarms are recorded for 60 seconds. Also, because all the alarms are checked at the same time, &#039;&#039;&#039;only the first&#039;&#039;&#039; triggered alarm will be recorded.&lt;br /&gt;
&lt;br /&gt;
As of alarm.c rev 4683, {{Odbpath|path=/Alarms/System message interval}} is set to 0 ensures that every alarm is recorded into the [[Message System#MIDAS Log file|MIDAS log file]]. (In previous revisions, this setting may still miss some alarms).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;Alarm class&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
= Alarms structure =&lt;br /&gt;
The [[/Alarms ODB tree]] structure is split into 2 sections:&lt;br /&gt;
*&amp;quot;Alarms&amp;quot; which define the condition to be tested. The user can create as many [[/Alarms ODB tree#Alarms subtree|Alarms]] as desired, but each must be one of the four defined [[#Alarm Types|Alarm types]] .&lt;br /&gt;
*&amp;quot;Classes&amp;quot; which define the action to be taken when the alarm occurs. Two Classes (Alarm and Warning) are defined by default. The user can add more [[/Alarms ODB tree#Classes subtree|Classes]] as desired.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In order to make the system flexible, each alarm class may perform different actions when an alarm is given. For example, it may&lt;br /&gt;
* write a system message (see [[/Alarms ODB tree#Write System Message|Write System Message]])&lt;br /&gt;
* write to the elog (see [[/Alarms ODB tree#Write Elog Message|Write Elog Message]])&lt;br /&gt;
* stop the run (see [[/Alarms ODB tree#Stop run|Stop run]])&lt;br /&gt;
* spawn a detached script listed in the ODB variable [[/Alarms ODB tree#Execute command|Execute command]]. This feature is used when an Alarm triggers Email or SMS alerts (see [[#Alarm triggering Email or SMS alerts|example]]).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm Types =&lt;br /&gt;
&lt;br /&gt;
The four available Alarm Types are shown in Table 1. They are defined in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/midas_8h.html&amp;amp;nbsp midas.h]. The alarm type is entered into the [[/Alarms ODB tree#Type|Type]] key.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: white;&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Table 1 : Defined Alarm Types&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Alarm Type&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | INT value&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Explanation&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot;  |Internal alarms&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: normal;&amp;quot; | AT_INTERNAL&lt;br /&gt;
|1&lt;br /&gt;
|Trigger on internal (program) alarm setting through the use of the al_...() functions. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Program alarms&lt;br /&gt;
|AT_PROGRAM&lt;br /&gt;
|2&lt;br /&gt;
|Triggered on condition of the state of the defined task (i.e. program not running)&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Evaluated alarms&lt;br /&gt;
|AT_EVALUATED&lt;br /&gt;
|3&lt;br /&gt;
|Triggered by ODB value on given arithmetical condition. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Periodic alarms&lt;br /&gt;
|AT_PERIODIC&lt;br /&gt;
|4&lt;br /&gt;
|Triggered by timeout condition defined in the alarm setting. &lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Program Alarm ==&lt;br /&gt;
&lt;br /&gt;
Program (or rather &amp;quot;Program not running&amp;quot;) alarms, when enabled, warn the user when a program is not running.&lt;br /&gt;
&lt;br /&gt;
Program alarms are enabled by setting the ODB key [[/Programs ODB tree#Alarm class|/Programs/&amp;lt;client-name&amp;gt;/Alarm class]] to a valid Alarm class specified in the [[/Alarms ODB tree]]. The first time the alarm is triggered, an &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Alarms/Alarms/&amp;lt;client-name&amp;gt;&amp;lt;/span&amp;gt; subtree will be created automatically. The program alarm will not be visible in the [[Alarms Page]] until the alarm has triggered, and the subtree created.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
The alarm system periodically calls [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_check()]. This causes every client listed in the {{Odbpath|path=/Programs}} ODB tree to be tested using [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__cmfunctionc.html cm_exist()] to see if it is running. If the client is not running, the time of first failure is recorded in the ODB key [[/Programs ODB tree#First failed|/Programs/&amp;lt;client-name&amp;gt;/First failed]].&lt;br /&gt;
&lt;br /&gt;
If the client has not been running for longer than the time set in ODB key [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]], a &amp;quot;Program not running&amp;quot; alarm is triggered (if enabled by [[/Programs ODB tree#Alarm class|Alarm class]]) and the program is restarted (if enabled by [[/Programs ODB tree#Auto restart|/Programs/&amp;lt;client-name&amp;gt;/Auto restart]] and a valid [[/Programs ODB tree#Start command|Start command]] is supplied).&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;not running&amp;quot; condition is tested every 10 seconds (each time al_check() is called), but the frequency of &#039;&#039;Program not running&#039;&#039; alarms can be reduced by increasing the value of the ODB key&lt;br /&gt;
[[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]&lt;br /&gt;
(default value 60 seconds). This can be useful if  [[/Alarms ODB tree#System message interval|System message interval]] in the specified alarm class subtree is set to zero.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Periodic Alarm ==&lt;br /&gt;
The periodic alarm is activated periodically according to the time in [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]. An example of a periodic alarm is &amp;quot;Demo Periodic&amp;quot; in the  [[/Alarms ODB tree#Example|example]].&lt;br /&gt;
&lt;br /&gt;
== Evaluated Alarm  ==&lt;br /&gt;
&lt;br /&gt;
Evaluated alarms require an &#039;&#039;alarm condition&#039;&#039; which is entered into the ODB key [[/Alarms ODB tree#Condition|Condition]] in the  [[/Alarms ODB tree#&amp;lt;alarm_name&amp;gt; subtree|&amp;lt;alarm_name&amp;gt; subtree]].&lt;br /&gt;
The condition may be simply a &#039;&#039;&#039;comparison&#039;&#039;&#039; between any ODB variable and a threshold parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Runinfo/Run number &amp;gt; 100&lt;br /&gt;
or it may be an &#039;&#039;&#039;evaluated condition&#039;&#039;&#039;. One can write conditions like&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[*] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[2-3] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[1,4,5-8,10] &amp;gt; 100&lt;br /&gt;
&lt;br /&gt;
to check all or certain values from an array. A dash means a range including both indices. If one array element fulfills the alarm condition, the alarm is triggered. In addition, bit-wise alarm conditions are possible, e.g.&lt;br /&gt;
&lt;br /&gt;
  /Equipment/Environment/Variables/Input[0] &amp;amp; 8&lt;br /&gt;
The alarm is triggered if bit #3 is set in Input[0].&lt;br /&gt;
&lt;br /&gt;
The value of an evaluated alarm is computed using al_evaluate_condition() in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
&lt;br /&gt;
Sometimes alarm trigger kind of accidentally on some analog input voltage if they are noise. In order to avoid this, the &amp;quot;Trigger count required&amp;quot; can be used. If this value contains a non-zero value of N, then the alarm system requires the alarm condition to be met N consecutive times until the alarm to be triggered.&lt;br /&gt;
&lt;br /&gt;
== Internal Alarm ==&lt;br /&gt;
These are triggered in a program using a call to &lt;br /&gt;
[https://daq.triumf.ca/~daqweb/doc/midas-devel/html/group__alfunctioncode.html al_trigger_alarm()]. See also description of al_trigger_alarm() sequence  [[#Implementation of the MIDAS Alarm System|above]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is nothing surprising in these alarms. Each alarm is checked with a time period set by ODB key [[ /Alarms ODB tree#Check interval|Check interval]] in the [[/Alarms ODB tree]].&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Email or SMS alerts =&lt;br /&gt;
&lt;br /&gt;
It is possible to have the MIDAS alarm system send email or SMS alerts to cell phones when alarms are triggered. This can be configured by defining an ODB alarm on a critical ODB parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Alarms/Liquid Level&lt;br /&gt;
 Active                   y&lt;br /&gt;
 Triggered                0 (0x0)&lt;br /&gt;
 Type                     3 (0x3)&lt;br /&gt;
 Check interval          60 (0x3C)&lt;br /&gt;
 Checked last    1227690148 (0x492D10A4)&lt;br /&gt;
 Trigger count            0 (0x0)&lt;br /&gt;
 Trigger count required   0 (0x0)&lt;br /&gt;
 Time triggered first    (empty)&lt;br /&gt;
 Time triggered last     (empty)&lt;br /&gt;
 Condition               /Equipment/Environment/Variables/Input[0] &amp;lt; 10&lt;br /&gt;
 Alarm Class             Level Alarm&lt;br /&gt;
 Alarm Message           Liquid Level is only %s&lt;br /&gt;
&lt;br /&gt;
In this example, the alarm triggers an alarm of class &amp;quot;Level Alarm&amp;quot;. This alarm class is defined as follows:&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Classes/Level Alarm&lt;br /&gt;
 Write system message    y&lt;br /&gt;
 Write Elog message      n&lt;br /&gt;
 System message interval 600 (0x258)&lt;br /&gt;
 System message last     0 (0x0)&lt;br /&gt;
 Execute command         /home/midas/level_alarm &#039;%s&#039;&lt;br /&gt;
 Execute interval        1800 (0x708)&lt;br /&gt;
 Execute last            0 (0x0)&lt;br /&gt;
 Stop run                n&lt;br /&gt;
 Display BGColor         red&lt;br /&gt;
 Display FGColor         black&lt;br /&gt;
&lt;br /&gt;
The key here is to call a script &amp;quot;level_alarm&amp;quot;, which can send emails. Use something like:&lt;br /&gt;
&lt;br /&gt;
 #/bin/csh&lt;br /&gt;
 echo $1 | mail -s \&amp;quot;Level Alarm\&amp;quot; your.name@domain.edu&lt;br /&gt;
 odbedit -c &#039;msg 2 level_alarm \&amp;quot;Alarm was sent to your.name@domain.edu\&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
The second command just generates a MIDAS system message for confirmation. Most cell phones (depends on the provider) have an email address. If you send an email there, it will be translated into a SMS message.&lt;br /&gt;
&lt;br /&gt;
The script file above can of course be more complicated. A perl script could be used that parses an address list, so other interested parties can register by adding his/her email address to that list. The script may also collects some other slow control variables (like pressure, temperature) and combine them into the SMS message.&lt;br /&gt;
&lt;br /&gt;
For very sensitive systems, having an alarm via SMS may not be sufficient, since the alarm system could be down (e.g. computer crash, network failure). In this case &#039;negative alarms&#039; can be used. For example, every 30 minutes the system may send an SMS with the current parameter values. If the expected message is not received, it may indicate that something in the MIDAS system is wrong.&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Slack notifications =&lt;br /&gt;
&lt;br /&gt;
A more modern way for notification is to use messengers apps such as Slack. To send alarms to Slack, do the following:&lt;br /&gt;
&lt;br /&gt;
* Go to https://api.slack.com/apps&lt;br /&gt;
* Create an App &amp;quot;MIDAS alarms&amp;quot;, select &amp;quot;From scratch&amp;quot;&lt;br /&gt;
* Name it &amp;quot;MIDAS alarm&amp;quot; or similar, select your workspace&lt;br /&gt;
* Click on &amp;quot;Add features and functionality&amp;quot;&lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Activate Incoming Webhooks&lt;br /&gt;
* Click on &amp;quot;Add New Webhook to Workspace&amp;quot;&lt;br /&gt;
* Select channel where alarms get posted, allow access&lt;br /&gt;
* Copy sample curl request and replace &amp;quot;Hello World&amp;quot; by &amp;quot;$1&amp;quot;, such as: &lt;br /&gt;
&lt;br /&gt;
 curl -X POST -H &#039;Content-type: application/json&#039; --data &amp;quot;{\&amp;quot;text\&amp;quot;:\&amp;quot;$1\&amp;quot;}&#039; https://hooks.slack.com/services/[xxx]/[yyy]/[zzz]&lt;br /&gt;
&lt;br /&gt;
(leave they keys xxx, yyy, zzz as shown on the web page).&lt;br /&gt;
&lt;br /&gt;
* Call the curl command from the alarm system by putting the above command under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Telegram notifications =&lt;br /&gt;
&lt;br /&gt;
A telegram bot needs to be created to post Midas alarms into telegram channels.&lt;br /&gt;
&lt;br /&gt;
* Go to https://web.telegram.org and login by scanning the QR-code with the telegram app on your phone&lt;br /&gt;
* Go to https://t.me/botfather&lt;br /&gt;
* Click on &amp;quot;open in web&amp;quot;&lt;br /&gt;
* A chat window will open. &lt;br /&gt;
* Send &amp;quot;/newbot&amp;quot; into the chat and assign name and username to your bot&lt;br /&gt;
* At the end of the bot creation a access token will be displayed. Save that access token.&lt;br /&gt;
* Post &amp;quot;/mybots&amp;quot; into the chat&lt;br /&gt;
* Select the bot that was just created&lt;br /&gt;
* Click on &amp;quot;Bot settings&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Allow Groups&amp;quot;&lt;br /&gt;
* Enable groups for the bot&lt;br /&gt;
* Click on &amp;quot;back to settings&amp;quot;&lt;br /&gt;
* Click on Channel Admin Rights&lt;br /&gt;
* Click on &amp;quot;Manage channel&amp;quot; (a checkmark should appear next to the text)&lt;br /&gt;
* Close this chat.&lt;br /&gt;
&lt;br /&gt;
* Add the created bot to a telegram group chat via searching for the bot name in &amp;quot;add members&amp;quot; (same as adding new People to the group)&lt;br /&gt;
* Now you need the chat_id of the same group chat. When you open the group chat in a webbrowser the URL should look like this https://web.telegram.org/a/#-1700000000 (where 1700000000 will be a different number for you)&lt;br /&gt;
* The group chat_id is then -1001700000000 (add -100 in front of the number in your URL)&lt;br /&gt;
* With the chat_id from above and the access token from the bot creation process you can post to the telegram group like this: (replace &amp;lt;chat_id&amp;gt; and &amp;lt;accessToken&amp;gt; with your numbers)&lt;br /&gt;
&lt;br /&gt;
  curl -s -X  POST &amp;quot;https://api.telegram.org/bot&amp;lt;accessToken&amp;gt;/sendMessage&amp;quot; -d chat_id=&amp;lt;chat_id&amp;gt; -d text=&amp;quot;🚨 Midas alarm triggered with message = $1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
* Create a shell script with this command&lt;br /&gt;
* Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
* Should you want to post the same alarm to multiple systems you can just separate scripts in &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; with a semicolon &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;;path/to/script2.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Mattermost notifications =&lt;br /&gt;
&lt;br /&gt;
First a mattermost bot needs to be created. To do this you need to be an Admin of the Team that the bot should post to.&lt;br /&gt;
&lt;br /&gt;
* In the drop down menu on the top left corner of the mattermost page click on &amp;quot;Integrations&amp;quot; (note: this field will be missing if you are not an Admin of the selected mattermost team) &lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Add Incoming Webhook&amp;quot;&lt;br /&gt;
* Give the bot a title and username (e.g. midasalarms) and select the channel to post to, click &amp;quot;save&amp;quot;&lt;br /&gt;
* Now a URL for the incoming webhook should be displayed (https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;). Copy that URL into the curl command in the following script:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   #!/bin/bash&lt;br /&gt;
   alarm_message=$1&lt;br /&gt;
   generate_post_data()&lt;br /&gt;
   {&lt;br /&gt;
     cat &amp;lt;&amp;lt;EOF&lt;br /&gt;
     {&lt;br /&gt;
     &amp;quot;text&amp;quot;: &amp;quot;@all Midas alarm triggered with message = $alarm_message&amp;quot;, &lt;br /&gt;
     &amp;quot;icon_emoji&amp;quot;:&amp;quot;:rotating_light:&amp;quot;, &lt;br /&gt;
     &amp;quot;username&amp;quot;:&amp;quot;Midas&amp;quot;}&lt;br /&gt;
   EOF&lt;br /&gt;
   }&lt;br /&gt;
   curl -i -X POST -H &#039;Content-Type: application/jsoni&#039; --data &amp;quot;$(generate_post_data)&amp;quot; https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
[[Category:Alarms]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3543</id>
		<title>Alarm System</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3543"/>
		<updated>2025-08-05T09:35:48Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Alarm triggering Email or SMS alerts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[/Alarms ODB tree]]&lt;br /&gt;
* [[Alarms Page]]&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
MIDAS provides an alarm system, which by default is turned off. When the alarm system is [[/Alarms ODB tree#Alarm system active|activated]] and an alarm condition is detected, alarm messages are sent by the system which appear as an alarm banner on the [[Status Page|mhttpd status page]], and as a message on any windows running [[odbedit]] clients. The alarm system is flexible and can be extensively customized for each experiment&lt;br /&gt;
using the [[Alarms Page|mhttpd Alarms Page]] or [[odbedit]]. &lt;br /&gt;
&lt;br /&gt;
The alarm system is built-in and part of the main experiment scheduler. This means no separate task is necessary to benefit from the alarm system. Its setup and activation is done through the &#039;&#039;&#039;[[/Alarms ODB tree]]&#039;&#039;&#039;. The alarm system includes several other features such as sequencing and control of the experiment. The alarm capabilities are:&lt;br /&gt;
* Alarm setting on any ODB variable against a threshold parameter.&lt;br /&gt;
* Alarm triggered by evaluated condition&lt;br /&gt;
* Selection of Alarm check frequency&lt;br /&gt;
* Selection of Alarm trigger frequency&lt;br /&gt;
* Customization alarm scheme; under this scheme multiple choices of alarm type can be selected&lt;br /&gt;
* Selection of alarm message destination (to system message log or to elog)&lt;br /&gt;
* email or SMS alerts can be sent&lt;br /&gt;
* Alarm triggered when a Program is not running &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Implementation of the MIDAS Alarm System =&lt;br /&gt;
&lt;br /&gt;
The alarm system source code is [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
Alarms are checked inside [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html alarm.c::al_check()]. This function is called by cm_yield() every 10 seconds and by rpc_server_thread(), also every 10 seconds. For remote MIDAS clients, their al_check() issues an RPC_AL_CHECK RPC call into the MIDAS server utility [[mserver]], where rpc_server_dispatch() calls the local al_check(). As result, all alarm checks run inside a process directly attached to the local MIDAS shared memory (inside a local client or inside an mserver process for a remote client). Each and every MIDAS client runs the alarm checks. To prevent race conditions between different MIDAS clients, access to al_check() is serialized using the ALARM semaphore. Inside al_check(), alarms are triggered using &lt;br /&gt;
[http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_trigger_alarm()], which in turn calls al_trigger_class(). Inside al_trigger_class(), the alarm is recorded into an elog or into [[Message System|midas.log]] using cm_msg(MTALK).&lt;br /&gt;
&lt;br /&gt;
Special note should be made of the ODB setting [[/Alarms ODB tree#System message interval|system message interval]], which has a surprising effect - after an alarm is recorded into system messages (using cm_msg(MTALK)), no record is made of any subsequent alarms until the time interval set by this variable elapses. With default value of 60 seconds, after one alarm, no more alarms are recorded for 60 seconds. Also, because all the alarms are checked at the same time, &#039;&#039;&#039;only the first&#039;&#039;&#039; triggered alarm will be recorded.&lt;br /&gt;
&lt;br /&gt;
As of alarm.c rev 4683, {{Odbpath|path=/Alarms/System message interval}} is set to 0 ensures that every alarm is recorded into the [[Message System#MIDAS Log file|MIDAS log file]]. (In previous revisions, this setting may still miss some alarms).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;Alarm class&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
= Alarms structure =&lt;br /&gt;
The [[/Alarms ODB tree]] structure is split into 2 sections:&lt;br /&gt;
*&amp;quot;Alarms&amp;quot; which define the condition to be tested. The user can create as many [[/Alarms ODB tree#Alarms subtree|Alarms]] as desired, but each must be one of the four defined [[#Alarm Types|Alarm types]] .&lt;br /&gt;
*&amp;quot;Classes&amp;quot; which define the action to be taken when the alarm occurs. Two Classes (Alarm and Warning) are defined by default. The user can add more [[/Alarms ODB tree#Classes subtree|Classes]] as desired.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In order to make the system flexible, each alarm class may perform different actions when an alarm is given. For example, it may&lt;br /&gt;
* write a system message (see [[/Alarms ODB tree#Write System Message|Write System Message]])&lt;br /&gt;
* write to the elog (see [[/Alarms ODB tree#Write Elog Message|Write Elog Message]])&lt;br /&gt;
* stop the run (see [[/Alarms ODB tree#Stop run|Stop run]])&lt;br /&gt;
* spawn a detached script listed in the ODB variable [[/Alarms ODB tree#Execute command|Execute command]]. This feature is used when an Alarm triggers Email or SMS alerts (see [[#Alarm triggering Email or SMS alerts|example]]).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm Types =&lt;br /&gt;
&lt;br /&gt;
The four available Alarm Types are shown in Table 1. They are defined in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/midas_8h.html&amp;amp;nbsp midas.h]. The alarm type is entered into the [[/Alarms ODB tree#Type|Type]] key.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: white;&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Table 1 : Defined Alarm Types&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Alarm Type&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | INT value&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Explanation&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot;  |Internal alarms&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: normal;&amp;quot; | AT_INTERNAL&lt;br /&gt;
|1&lt;br /&gt;
|Trigger on internal (program) alarm setting through the use of the al_...() functions. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Program alarms&lt;br /&gt;
|AT_PROGRAM&lt;br /&gt;
|2&lt;br /&gt;
|Triggered on condition of the state of the defined task (i.e. program not running)&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Evaluated alarms&lt;br /&gt;
|AT_EVALUATED&lt;br /&gt;
|3&lt;br /&gt;
|Triggered by ODB value on given arithmetical condition. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Periodic alarms&lt;br /&gt;
|AT_PERIODIC&lt;br /&gt;
|4&lt;br /&gt;
|Triggered by timeout condition defined in the alarm setting. &lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Program Alarm ==&lt;br /&gt;
&lt;br /&gt;
Program (or rather &amp;quot;Program not running&amp;quot;) alarms, when enabled, warn the user when a program is not running.&lt;br /&gt;
&lt;br /&gt;
Program alarms are enabled by setting the ODB key [[/Programs ODB tree#Alarm class|/Programs/&amp;lt;client-name&amp;gt;/Alarm class]] to a valid Alarm class specified in the [[/Alarms ODB tree]]. The first time the alarm is triggered, an &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Alarms/Alarms/&amp;lt;client-name&amp;gt;&amp;lt;/span&amp;gt; subtree will be created automatically. The program alarm will not be visible in the [[Alarms Page]] until the alarm has triggered, and the subtree created.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
The alarm system periodically calls [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_check()]. This causes every client listed in the {{Odbpath|path=/Programs}} ODB tree to be tested using [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__cmfunctionc.html cm_exist()] to see if it is running. If the client is not running, the time of first failure is recorded in the ODB key [[/Programs ODB tree#First failed|/Programs/&amp;lt;client-name&amp;gt;/First failed]].&lt;br /&gt;
&lt;br /&gt;
If the client has not been running for longer than the time set in ODB key [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]], a &amp;quot;Program not running&amp;quot; alarm is triggered (if enabled by [[/Programs ODB tree#Alarm class|Alarm class]]) and the program is restarted (if enabled by [[/Programs ODB tree#Auto restart|/Programs/&amp;lt;client-name&amp;gt;/Auto restart]] and a valid [[/Programs ODB tree#Start command|Start command]] is supplied).&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;not running&amp;quot; condition is tested every 10 seconds (each time al_check() is called), but the frequency of &#039;&#039;Program not running&#039;&#039; alarms can be reduced by increasing the value of the ODB key&lt;br /&gt;
[[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]&lt;br /&gt;
(default value 60 seconds). This can be useful if  [[/Alarms ODB tree#System message interval|System message interval]] in the specified alarm class subtree is set to zero.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Periodic Alarm ==&lt;br /&gt;
The periodic alarm is activated periodically according to the time in [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]. An example of a periodic alarm is &amp;quot;Demo Periodic&amp;quot; in the  [[/Alarms ODB tree#Example|example]].&lt;br /&gt;
&lt;br /&gt;
== Evaluated Alarm  ==&lt;br /&gt;
&lt;br /&gt;
Evaluated alarms require an &#039;&#039;alarm condition&#039;&#039; which is entered into the ODB key [[/Alarms ODB tree#Condition|Condition]] in the  [[/Alarms ODB tree#&amp;lt;alarm_name&amp;gt; subtree|&amp;lt;alarm_name&amp;gt; subtree]].&lt;br /&gt;
The condition may be simply a &#039;&#039;&#039;comparison&#039;&#039;&#039; between any ODB variable and a threshold parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Runinfo/Run number &amp;gt; 100&lt;br /&gt;
or it may be an &#039;&#039;&#039;evaluated condition&#039;&#039;&#039;. One can write conditions like&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[*] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[2-3] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[1,4,5-8,10] &amp;gt; 100&lt;br /&gt;
&lt;br /&gt;
to check all or certain values from an array. A dash means a range including both indices. If one array element fulfills the alarm condition, the alarm is triggered. In addition, bit-wise alarm conditions are possible, e.g.&lt;br /&gt;
&lt;br /&gt;
  /Equipment/Environment/Variables/Input[0] &amp;amp; 8&lt;br /&gt;
The alarm is triggered if bit #3 is set in Input[0].&lt;br /&gt;
&lt;br /&gt;
The value of an evaluated alarm is computed using al_evaluate_condition() in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
&lt;br /&gt;
Sometimes alarm trigger kind of accidentally on some analog input voltage if they are noise. In order to avoid this, the &amp;quot;Trigger count required&amp;quot; can be used. If this value contains a non-zero value of N, then the alarm system requires the alarm condition to be met N consecutive times until the alarm to be triggered.&lt;br /&gt;
&lt;br /&gt;
== Internal Alarm ==&lt;br /&gt;
These are triggered in a program using a call to &lt;br /&gt;
[https://daq.triumf.ca/~daqweb/doc/midas-devel/html/group__alfunctioncode.html al_trigger_alarm()]. See also description of al_trigger_alarm() sequence  [[#Implementation of the MIDAS Alarm System|above]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is nothing surprising in these alarms. Each alarm is checked with a time period set by ODB key [[ /Alarms ODB tree#Check interval|Check interval]] in the [[/Alarms ODB tree]].&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Email or SMS alerts =&lt;br /&gt;
&lt;br /&gt;
It is possible to have the MIDAS alarm system send email or SMS alerts to cell phones when alarms are triggered. This can be configured by defining an ODB alarm on a critical ODB parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Alarms/Liquid Level&lt;br /&gt;
 Active                   y&lt;br /&gt;
 Triggered                0 (0x0)&lt;br /&gt;
 Type                     3 (0x3)&lt;br /&gt;
 Check interval          60 (0x3C)&lt;br /&gt;
 Checked last    1227690148 (0x492D10A4)&lt;br /&gt;
 Trigger count            0 (0x0)&lt;br /&gt;
 Trigger count required   0 (0x0)&lt;br /&gt;
 Time triggered first    (empty)&lt;br /&gt;
 Time triggered last     (empty)&lt;br /&gt;
 Condition               /Equipment/Environment/Variables/Input[0] &amp;lt; 10&lt;br /&gt;
 Alarm Class             Level Alarm&lt;br /&gt;
 Alarm Message           Liquid Level is only %s&lt;br /&gt;
&lt;br /&gt;
In this example, the alarm triggers an alarm of class &amp;quot;Level Alarm&amp;quot;. This alarm class is defined as follows:&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Classes/Level Alarm&lt;br /&gt;
 Write system message    y&lt;br /&gt;
 Write Elog message      n&lt;br /&gt;
 System message interval 600 (0x258)&lt;br /&gt;
 System message last     0 (0x0)&lt;br /&gt;
 Execute command         /home/midas/level_alarm &#039;%s&#039;&lt;br /&gt;
 Execute interval        1800 (0x708)&lt;br /&gt;
 Execute last            0 (0x0)&lt;br /&gt;
 Stop run                n&lt;br /&gt;
 Display BGColor         red&lt;br /&gt;
 Display FGColor         black&lt;br /&gt;
&lt;br /&gt;
The key here is to call a script &amp;quot;level_alarm&amp;quot;, which can send emails. Use something like:&lt;br /&gt;
&lt;br /&gt;
 #/bin/csh&lt;br /&gt;
 echo $1 | mail -s \&amp;quot;Level Alarm\&amp;quot; your.name@domain.edu&lt;br /&gt;
 odbedit -c &#039;msg 2 level_alarm \&amp;quot;Alarm was sent to your.name@domain.edu\&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;).&lt;br /&gt;
The second command just generates a MIDAS system message for confirmation. Most cell phones (depends on the provider) have an email address. If you send an email there, it will be translated into a SMS message.&lt;br /&gt;
&lt;br /&gt;
The script file above can of course be more complicated. A perl script could be used that parses an address list, so other interested parties can register by adding his/her email address to that list. The script may also collects some other slow control variables (like pressure, temperature) and combine them into the SMS message.&lt;br /&gt;
&lt;br /&gt;
For very sensitive systems, having an alarm via SMS may not be sufficient, since the alarm system could be down (e.g. computer crash, network failure). In this case &#039;negative alarms&#039; can be used. For example, every 30 minutes the system may send an SMS with the current parameter values. If the expected message is not received, it may indicate that something in the MIDAS system is wrong.&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Slack notifications =&lt;br /&gt;
&lt;br /&gt;
A more modern way for notification is to use messengers apps such as Slack. To send alarms to Slack, do the following:&lt;br /&gt;
&lt;br /&gt;
* Go to https://api.slack.com/apps&lt;br /&gt;
* Create an App &amp;quot;MIDAS alarms&amp;quot;, select &amp;quot;From scratch&amp;quot;&lt;br /&gt;
* Name it &amp;quot;MIDAS alarm&amp;quot; or similar, select your workspace&lt;br /&gt;
* Click on &amp;quot;Add features and functionality&amp;quot;&lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Activate Incoming Webhooks&lt;br /&gt;
* Click on &amp;quot;Add New Webhook to Workspace&amp;quot;&lt;br /&gt;
* Select channel where alarms get posted, allow access&lt;br /&gt;
* Copy sample curl request and replace &amp;quot;Hello World&amp;quot; by &amp;quot;$1&amp;quot;, such as: &lt;br /&gt;
&lt;br /&gt;
 curl -X POST -H &#039;Content-type: application/json&#039; --data &amp;quot;{\&amp;quot;text\&amp;quot;:\&amp;quot;$1\&amp;quot;}&#039; https://hooks.slack.com/services/[xxx]/[yyy]/[zzz]&lt;br /&gt;
&lt;br /&gt;
(leave they keys xxx, yyy, zzz as shown on the web page).&lt;br /&gt;
&lt;br /&gt;
* Call the curl command from the alarm system by putting the above command under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Telegram notifications =&lt;br /&gt;
&lt;br /&gt;
A telegram bot needs to be created to post Midas alarms into telegram channels.&lt;br /&gt;
&lt;br /&gt;
* Go to https://web.telegram.org and login by scanning the QR-code with the telegram app on your phone&lt;br /&gt;
* Go to https://t.me/botfather&lt;br /&gt;
* Click on &amp;quot;open in web&amp;quot;&lt;br /&gt;
* A chat window will open. &lt;br /&gt;
* Send &amp;quot;/newbot&amp;quot; into the chat and assign name and username to your bot&lt;br /&gt;
* At the end of the bot creation a access token will be displayed. Save that access token.&lt;br /&gt;
* Post &amp;quot;/mybots&amp;quot; into the chat&lt;br /&gt;
* Select the bot that was just created&lt;br /&gt;
* Click on &amp;quot;Bot settings&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Allow Groups&amp;quot;&lt;br /&gt;
* Enable groups for the bot&lt;br /&gt;
* Click on &amp;quot;back to settings&amp;quot;&lt;br /&gt;
* Click on Channel Admin Rights&lt;br /&gt;
* Click on &amp;quot;Manage channel&amp;quot; (a checkmark should appear next to the text)&lt;br /&gt;
* Close this chat.&lt;br /&gt;
&lt;br /&gt;
* Add the created bot to a telegram group chat via searching for the bot name in &amp;quot;add members&amp;quot; (same as adding new People to the group)&lt;br /&gt;
* Now you need the chat_id of the same group chat. When you open the group chat in a webbrowser the URL should look like this https://web.telegram.org/a/#-1700000000 (where 1700000000 will be a different number for you)&lt;br /&gt;
* The group chat_id is then -1001700000000 (add -100 in front of the number in your URL)&lt;br /&gt;
* With the chat_id from above and the access token from the bot creation process you can post to the telegram group like this: (replace &amp;lt;chat_id&amp;gt; and &amp;lt;accessToken&amp;gt; with your numbers)&lt;br /&gt;
&lt;br /&gt;
  curl -s -X  POST &amp;quot;https://api.telegram.org/bot&amp;lt;accessToken&amp;gt;/sendMessage&amp;quot; -d chat_id=&amp;lt;chat_id&amp;gt; -d text=&amp;quot;🚨 Midas alarm triggered with message = $1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
* Create a shell script with this command&lt;br /&gt;
* Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
* Should you want to post the same alarm to multiple systems you can just separate scripts in &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; with a semicolon &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;;path/to/script2.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Mattermost notifications =&lt;br /&gt;
&lt;br /&gt;
First a mattermost bot needs to be created. To do this you need to be an Admin of the Team that the bot should post to.&lt;br /&gt;
&lt;br /&gt;
* In the drop down menu on the top left corner of the mattermost page click on &amp;quot;Integrations&amp;quot; (note: this field will be missing if you are not an Admin of the selected mattermost team) &lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Add Incoming Webhook&amp;quot;&lt;br /&gt;
* Give the bot a title and username (e.g. midasalarms) and select the channel to post to, click &amp;quot;save&amp;quot;&lt;br /&gt;
* Now a URL for the incoming webhook should be displayed (https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;). Copy that URL into the curl command in the following script:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   #!/bin/bash&lt;br /&gt;
   alarm_message=$1&lt;br /&gt;
   generate_post_data()&lt;br /&gt;
   {&lt;br /&gt;
     cat &amp;lt;&amp;lt;EOF&lt;br /&gt;
     {&lt;br /&gt;
     &amp;quot;text&amp;quot;: &amp;quot;@all Midas alarm triggered with message = $alarm_message&amp;quot;, &lt;br /&gt;
     &amp;quot;icon_emoji&amp;quot;:&amp;quot;:rotating_light:&amp;quot;, &lt;br /&gt;
     &amp;quot;username&amp;quot;:&amp;quot;Midas&amp;quot;}&lt;br /&gt;
   EOF&lt;br /&gt;
   }&lt;br /&gt;
   curl -i -X POST -H &#039;Content-Type: application/jsoni&#039; --data &amp;quot;$(generate_post_data)&amp;quot; https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
[[Category:Alarms]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3542</id>
		<title>Alarm System</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Alarm_System&amp;diff=3542"/>
		<updated>2025-08-05T08:53:49Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Alarm triggering Telegram notifications */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[/Alarms ODB tree]]&lt;br /&gt;
* [[Alarms Page]]&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
MIDAS provides an alarm system, which by default is turned off. When the alarm system is [[/Alarms ODB tree#Alarm system active|activated]] and an alarm condition is detected, alarm messages are sent by the system which appear as an alarm banner on the [[Status Page|mhttpd status page]], and as a message on any windows running [[odbedit]] clients. The alarm system is flexible and can be extensively customized for each experiment&lt;br /&gt;
using the [[Alarms Page|mhttpd Alarms Page]] or [[odbedit]]. &lt;br /&gt;
&lt;br /&gt;
The alarm system is built-in and part of the main experiment scheduler. This means no separate task is necessary to benefit from the alarm system. Its setup and activation is done through the &#039;&#039;&#039;[[/Alarms ODB tree]]&#039;&#039;&#039;. The alarm system includes several other features such as sequencing and control of the experiment. The alarm capabilities are:&lt;br /&gt;
* Alarm setting on any ODB variable against a threshold parameter.&lt;br /&gt;
* Alarm triggered by evaluated condition&lt;br /&gt;
* Selection of Alarm check frequency&lt;br /&gt;
* Selection of Alarm trigger frequency&lt;br /&gt;
* Customization alarm scheme; under this scheme multiple choices of alarm type can be selected&lt;br /&gt;
* Selection of alarm message destination (to system message log or to elog)&lt;br /&gt;
* email or SMS alerts can be sent&lt;br /&gt;
* Alarm triggered when a Program is not running &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Implementation of the MIDAS Alarm System =&lt;br /&gt;
&lt;br /&gt;
The alarm system source code is [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
Alarms are checked inside [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html alarm.c::al_check()]. This function is called by cm_yield() every 10 seconds and by rpc_server_thread(), also every 10 seconds. For remote MIDAS clients, their al_check() issues an RPC_AL_CHECK RPC call into the MIDAS server utility [[mserver]], where rpc_server_dispatch() calls the local al_check(). As result, all alarm checks run inside a process directly attached to the local MIDAS shared memory (inside a local client or inside an mserver process for a remote client). Each and every MIDAS client runs the alarm checks. To prevent race conditions between different MIDAS clients, access to al_check() is serialized using the ALARM semaphore. Inside al_check(), alarms are triggered using &lt;br /&gt;
[http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_trigger_alarm()], which in turn calls al_trigger_class(). Inside al_trigger_class(), the alarm is recorded into an elog or into [[Message System|midas.log]] using cm_msg(MTALK).&lt;br /&gt;
&lt;br /&gt;
Special note should be made of the ODB setting [[/Alarms ODB tree#System message interval|system message interval]], which has a surprising effect - after an alarm is recorded into system messages (using cm_msg(MTALK)), no record is made of any subsequent alarms until the time interval set by this variable elapses. With default value of 60 seconds, after one alarm, no more alarms are recorded for 60 seconds. Also, because all the alarms are checked at the same time, &#039;&#039;&#039;only the first&#039;&#039;&#039; triggered alarm will be recorded.&lt;br /&gt;
&lt;br /&gt;
As of alarm.c rev 4683, {{Odbpath|path=/Alarms/System message interval}} is set to 0 ensures that every alarm is recorded into the [[Message System#MIDAS Log file|MIDAS log file]]. (In previous revisions, this setting may still miss some alarms).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;Alarm class&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
= Alarms structure =&lt;br /&gt;
The [[/Alarms ODB tree]] structure is split into 2 sections:&lt;br /&gt;
*&amp;quot;Alarms&amp;quot; which define the condition to be tested. The user can create as many [[/Alarms ODB tree#Alarms subtree|Alarms]] as desired, but each must be one of the four defined [[#Alarm Types|Alarm types]] .&lt;br /&gt;
*&amp;quot;Classes&amp;quot; which define the action to be taken when the alarm occurs. Two Classes (Alarm and Warning) are defined by default. The user can add more [[/Alarms ODB tree#Classes subtree|Classes]] as desired.&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In order to make the system flexible, each alarm class may perform different actions when an alarm is given. For example, it may&lt;br /&gt;
* write a system message (see [[/Alarms ODB tree#Write System Message|Write System Message]])&lt;br /&gt;
* write to the elog (see [[/Alarms ODB tree#Write Elog Message|Write Elog Message]])&lt;br /&gt;
* stop the run (see [[/Alarms ODB tree#Stop run|Stop run]])&lt;br /&gt;
* spawn a detached script listed in the ODB variable [[/Alarms ODB tree#Execute command|Execute command]]. This feature is used when an Alarm triggers Email or SMS alerts (see [[#Alarm triggering Email or SMS alerts|example]]).&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm Types =&lt;br /&gt;
&lt;br /&gt;
The four available Alarm Types are shown in Table 1. They are defined in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/midas_8h.html&amp;amp;nbsp midas.h]. The alarm type is entered into the [[/Alarms ODB tree#Type|Type]] key.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: white;&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Table 1 : Defined Alarm Types&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Alarm Type&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | INT value&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;vertical-align: top; background-color: lavender; font-weight: bold;&amp;quot; | Explanation&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot;  |Internal alarms&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;vertical-align: top; background-color: white; font-weight: normal;&amp;quot; | AT_INTERNAL&lt;br /&gt;
|1&lt;br /&gt;
|Trigger on internal (program) alarm setting through the use of the al_...() functions. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Program alarms&lt;br /&gt;
|AT_PROGRAM&lt;br /&gt;
|2&lt;br /&gt;
|Triggered on condition of the state of the defined task (i.e. program not running)&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Evaluated alarms&lt;br /&gt;
|AT_EVALUATED&lt;br /&gt;
|3&lt;br /&gt;
|Triggered by ODB value on given arithmetical condition. &lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| style=&amp;quot;vertical-align: top; background-color: white; font-weight: bold;&amp;quot; |Periodic alarms&lt;br /&gt;
|AT_PERIODIC&lt;br /&gt;
|4&lt;br /&gt;
|Triggered by timeout condition defined in the alarm setting. &lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Program Alarm ==&lt;br /&gt;
&lt;br /&gt;
Program (or rather &amp;quot;Program not running&amp;quot;) alarms, when enabled, warn the user when a program is not running.&lt;br /&gt;
&lt;br /&gt;
Program alarms are enabled by setting the ODB key [[/Programs ODB tree#Alarm class|/Programs/&amp;lt;client-name&amp;gt;/Alarm class]] to a valid Alarm class specified in the [[/Alarms ODB tree]]. The first time the alarm is triggered, an &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Alarms/Alarms/&amp;lt;client-name&amp;gt;&amp;lt;/span&amp;gt; subtree will be created automatically. The program alarm will not be visible in the [[Alarms Page]] until the alarm has triggered, and the subtree created.&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
The alarm system periodically calls [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__alfunctioncode.html al_check()]. This causes every client listed in the {{Odbpath|path=/Programs}} ODB tree to be tested using [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/doc/html/group__cmfunctionc.html cm_exist()] to see if it is running. If the client is not running, the time of first failure is recorded in the ODB key [[/Programs ODB tree#First failed|/Programs/&amp;lt;client-name&amp;gt;/First failed]].&lt;br /&gt;
&lt;br /&gt;
If the client has not been running for longer than the time set in ODB key [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]], a &amp;quot;Program not running&amp;quot; alarm is triggered (if enabled by [[/Programs ODB tree#Alarm class|Alarm class]]) and the program is restarted (if enabled by [[/Programs ODB tree#Auto restart|/Programs/&amp;lt;client-name&amp;gt;/Auto restart]] and a valid [[/Programs ODB tree#Start command|Start command]] is supplied).&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;not running&amp;quot; condition is tested every 10 seconds (each time al_check() is called), but the frequency of &#039;&#039;Program not running&#039;&#039; alarms can be reduced by increasing the value of the ODB key&lt;br /&gt;
[[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]&lt;br /&gt;
(default value 60 seconds). This can be useful if  [[/Alarms ODB tree#System message interval|System message interval]] in the specified alarm class subtree is set to zero.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Periodic Alarm ==&lt;br /&gt;
The periodic alarm is activated periodically according to the time in [[/Programs ODB tree#Check interval|/Programs/&amp;lt;client-name&amp;gt;/Check interval]]. An example of a periodic alarm is &amp;quot;Demo Periodic&amp;quot; in the  [[/Alarms ODB tree#Example|example]].&lt;br /&gt;
&lt;br /&gt;
== Evaluated Alarm  ==&lt;br /&gt;
&lt;br /&gt;
Evaluated alarms require an &#039;&#039;alarm condition&#039;&#039; which is entered into the ODB key [[/Alarms ODB tree#Condition|Condition]] in the  [[/Alarms ODB tree#&amp;lt;alarm_name&amp;gt; subtree|&amp;lt;alarm_name&amp;gt; subtree]].&lt;br /&gt;
The condition may be simply a &#039;&#039;&#039;comparison&#039;&#039;&#039; between any ODB variable and a threshold parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Runinfo/Run number &amp;gt; 100&lt;br /&gt;
or it may be an &#039;&#039;&#039;evaluated condition&#039;&#039;&#039;. One can write conditions like&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[*] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[2-3] &amp;gt; 100&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
  /Equipment/HV/Variables/Input[1,4,5-8,10] &amp;gt; 100&lt;br /&gt;
&lt;br /&gt;
to check all or certain values from an array. A dash means a range including both indices. If one array element fulfills the alarm condition, the alarm is triggered. In addition, bit-wise alarm conditions are possible, e.g.&lt;br /&gt;
&lt;br /&gt;
  /Equipment/Environment/Variables/Input[0] &amp;amp; 8&lt;br /&gt;
The alarm is triggered if bit #3 is set in Input[0].&lt;br /&gt;
&lt;br /&gt;
The value of an evaluated alarm is computed using al_evaluate_condition() in [http://ladd00.triumf.ca/~daqweb/doc/midas-devel/html/alarm_8c_source.html alarm.c].&lt;br /&gt;
&lt;br /&gt;
Sometimes alarm trigger kind of accidentally on some analog input voltage if they are noise. In order to avoid this, the &amp;quot;Trigger count required&amp;quot; can be used. If this value contains a non-zero value of N, then the alarm system requires the alarm condition to be met N consecutive times until the alarm to be triggered.&lt;br /&gt;
&lt;br /&gt;
== Internal Alarm ==&lt;br /&gt;
These are triggered in a program using a call to &lt;br /&gt;
[https://daq.triumf.ca/~daqweb/doc/midas-devel/html/group__alfunctioncode.html al_trigger_alarm()]. See also description of al_trigger_alarm() sequence  [[#Implementation of the MIDAS Alarm System|above]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is nothing surprising in these alarms. Each alarm is checked with a time period set by ODB key [[ /Alarms ODB tree#Check interval|Check interval]] in the [[/Alarms ODB tree]].&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Email or SMS alerts =&lt;br /&gt;
&lt;br /&gt;
It is possible to have the MIDAS alarm system send email or SMS alerts to cell phones when alarms are triggered. This can be configured by defining an ODB alarm on a critical ODB parameter, e.g.&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Alarms/Liquid Level&lt;br /&gt;
 Active                   y&lt;br /&gt;
 Triggered                0 (0x0)&lt;br /&gt;
 Type                     3 (0x3)&lt;br /&gt;
 Check interval          60 (0x3C)&lt;br /&gt;
 Checked last    1227690148 (0x492D10A4)&lt;br /&gt;
 Trigger count            0 (0x0)&lt;br /&gt;
 Trigger count required   0 (0x0)&lt;br /&gt;
 Time triggered first    (empty)&lt;br /&gt;
 Time triggered last     (empty)&lt;br /&gt;
 Condition               /Equipment/Environment/Variables/Input[0] &amp;lt; 10&lt;br /&gt;
 Alarm Class             Level Alarm&lt;br /&gt;
 Alarm Message           Liquid Level is only %s&lt;br /&gt;
&lt;br /&gt;
In this example, the alarm triggers an alarm of class &amp;quot;Level Alarm&amp;quot;. This alarm class is defined as follows:&lt;br /&gt;
&lt;br /&gt;
 /Alarms/Classes/Level Alarm&lt;br /&gt;
 Write system message    y&lt;br /&gt;
 Write Elog message      n&lt;br /&gt;
 System message interval 600 (0x258)&lt;br /&gt;
 System message last     0 (0x0)&lt;br /&gt;
 Execute command         /home/midas/level_alarm &#039;%s&#039;&lt;br /&gt;
 Execute interval        1800 (0x708)&lt;br /&gt;
 Execute last            0 (0x0)&lt;br /&gt;
 Stop run                n&lt;br /&gt;
 Display BGColor         red&lt;br /&gt;
 Display FGColor         black&lt;br /&gt;
&lt;br /&gt;
The key here is to call a script &amp;quot;level_alarm&amp;quot;, which can send emails. Use something like:&lt;br /&gt;
&lt;br /&gt;
 #/bin/csh&lt;br /&gt;
 echo $1 | mail -s \&amp;quot;Level Alarm\&amp;quot; your.name@domain.edu&lt;br /&gt;
 odbedit -c &#039;msg 2 level_alarm \&amp;quot;Alarm was sent to your.name@domain.edu\&amp;quot;&#039;&lt;br /&gt;
&lt;br /&gt;
The second command just generates a MIDAS system message for confirmation. Most cell phones (depends on the provider) have an email address. If you send an email there, it will be translated into a SMS message.&lt;br /&gt;
&lt;br /&gt;
The script file above can of course be more complicated. A perl script could be used that parses an address list, so other interested parties can register by adding his/her email address to that list. The script may also collects some other slow control variables (like pressure, temperature) and combine them into the SMS message.&lt;br /&gt;
&lt;br /&gt;
For very sensitive systems, having an alarm via SMS may not be sufficient, since the alarm system could be down (e.g. computer crash, network failure). In this case &#039;negative alarms&#039; can be used. For example, every 30 minutes the system may send an SMS with the current parameter values. If the expected message is not received, it may indicate that something in the MIDAS system is wrong.&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Slack notifications =&lt;br /&gt;
&lt;br /&gt;
A more modern way for notification is to use messengers apps such as Slack. To send alarms to Slack, do the following:&lt;br /&gt;
&lt;br /&gt;
* Go to https://api.slack.com/apps&lt;br /&gt;
* Create an App &amp;quot;MIDAS alarms&amp;quot;, select &amp;quot;From scratch&amp;quot;&lt;br /&gt;
* Name it &amp;quot;MIDAS alarm&amp;quot; or similar, select your workspace&lt;br /&gt;
* Click on &amp;quot;Add features and functionality&amp;quot;&lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Activate Incoming Webhooks&lt;br /&gt;
* Click on &amp;quot;Add New Webhook to Workspace&amp;quot;&lt;br /&gt;
* Select channel where alarms get posted, allow access&lt;br /&gt;
* Copy sample curl request and replace &amp;quot;Hello World&amp;quot; by &amp;quot;$1&amp;quot;, such as: &lt;br /&gt;
&lt;br /&gt;
 curl -X POST -H &#039;Content-type: application/json&#039; --data &amp;quot;{\&amp;quot;text\&amp;quot;:\&amp;quot;$1\&amp;quot;}&#039; https://hooks.slack.com/services/[xxx]/[yyy]/[zzz]&lt;br /&gt;
&lt;br /&gt;
(leave they keys xxx, yyy, zzz as shown on the web page).&lt;br /&gt;
&lt;br /&gt;
* Call the curl command from the alarm system by putting the above command under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Telegram notifications =&lt;br /&gt;
&lt;br /&gt;
A telegram bot needs to be created to post Midas alarms into telegram channels.&lt;br /&gt;
&lt;br /&gt;
* Go to https://web.telegram.org and login by scanning the QR-code with the telegram app on your phone&lt;br /&gt;
* Go to https://t.me/botfather&lt;br /&gt;
* Click on &amp;quot;open in web&amp;quot;&lt;br /&gt;
* A chat window will open. &lt;br /&gt;
* Send &amp;quot;/newbot&amp;quot; into the chat and assign name and username to your bot&lt;br /&gt;
* At the end of the bot creation a access token will be displayed. Save that access token.&lt;br /&gt;
* Post &amp;quot;/mybots&amp;quot; into the chat&lt;br /&gt;
* Select the bot that was just created&lt;br /&gt;
* Click on &amp;quot;Bot settings&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Allow Groups&amp;quot;&lt;br /&gt;
* Enable groups for the bot&lt;br /&gt;
* Click on &amp;quot;back to settings&amp;quot;&lt;br /&gt;
* Click on Channel Admin Rights&lt;br /&gt;
* Click on &amp;quot;Manage channel&amp;quot; (a checkmark should appear next to the text)&lt;br /&gt;
* Close this chat.&lt;br /&gt;
&lt;br /&gt;
* Add the created bot to a telegram group chat via searching for the bot name in &amp;quot;add members&amp;quot; (same as adding new People to the group)&lt;br /&gt;
* Now you need the chat_id of the same group chat. When you open the group chat in a webbrowser the URL should look like this https://web.telegram.org/a/#-1700000000 (where 1700000000 will be a different number for you)&lt;br /&gt;
* The group chat_id is then -1001700000000 (add -100 in front of the number in your URL)&lt;br /&gt;
* With the chat_id from above and the access token from the bot creation process you can post to the telegram group like this: (replace &amp;lt;chat_id&amp;gt; and &amp;lt;accessToken&amp;gt; with your numbers)&lt;br /&gt;
&lt;br /&gt;
  curl -s -X  POST &amp;quot;https://api.telegram.org/bot&amp;lt;accessToken&amp;gt;/sendMessage&amp;quot; -d chat_id=&amp;lt;chat_id&amp;gt; -d text=&amp;quot;🚨 Midas alarm triggered with message = $1&amp;quot;&lt;br /&gt;
&lt;br /&gt;
* Create a shell script with this command&lt;br /&gt;
* Ensure the shell script is executable (e.g., &amp;lt;code&amp;gt;chmod +x script.sh&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
* Should you want to post the same alarm to multiple systems you can just separate scripts in &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; with a semicolon &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;;path/to/script2.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alarm triggering Mattermost notifications =&lt;br /&gt;
&lt;br /&gt;
First a mattermost bot needs to be created. To do this you need to be an Admin of the Team that the bot should post to.&lt;br /&gt;
&lt;br /&gt;
* In the drop down menu on the top left corner of the mattermost page click on &amp;quot;Integrations&amp;quot; (note: this field will be missing if you are not an Admin of the selected mattermost team) &lt;br /&gt;
* Select &amp;quot;Incoming Webhooks&amp;quot;&lt;br /&gt;
* Click on &amp;quot;Add Incoming Webhook&amp;quot;&lt;br /&gt;
* Give the bot a title and username (e.g. midasalarms) and select the channel to post to, click &amp;quot;save&amp;quot;&lt;br /&gt;
* Now a URL for the incoming webhook should be displayed (https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;). Copy that URL into the curl command in the following script:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
   #!/bin/bash&lt;br /&gt;
   alarm_message=$1&lt;br /&gt;
   generate_post_data()&lt;br /&gt;
   {&lt;br /&gt;
     cat &amp;lt;&amp;lt;EOF&lt;br /&gt;
     {&lt;br /&gt;
     &amp;quot;text&amp;quot;: &amp;quot;@all Midas alarm triggered with message = $alarm_message&amp;quot;, &lt;br /&gt;
     &amp;quot;icon_emoji&amp;quot;:&amp;quot;:rotating_light:&amp;quot;, &lt;br /&gt;
     &amp;quot;username&amp;quot;:&amp;quot;Midas&amp;quot;}&lt;br /&gt;
   EOF&lt;br /&gt;
   }&lt;br /&gt;
   curl -i -X POST -H &#039;Content-Type: application/jsoni&#039; --data &amp;quot;$(generate_post_data)&amp;quot; https://mattermost.gitlab.rlp.net/hooks/&amp;lt;token&amp;gt;&lt;br /&gt;
&lt;br /&gt;
- Call the shell script from the alarm system by putting &amp;lt;code&amp;gt;path/to/script.sh &#039;%1$s&#039;&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;/Alarms/Classes/All/Execute command&amp;lt;/code&amp;gt; into the ODB.&lt;br /&gt;
&lt;br /&gt;
[[Category:Alarms]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3533</id>
		<title>Custom Page Features</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3533"/>
		<updated>2025-05-12T13:44:09Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
* [[ODB Page]]&lt;br /&gt;
* [[Custom Page]]&lt;br /&gt;
* [[/Custom ODB tree]]&lt;br /&gt;
* [[Mhttpd.js|MIDAS Javascript library (mhttpd.js)]]&lt;br /&gt;
* [[mjsonrpc|MIDAS JSON RPC library functions (mjsonrpc)]]&lt;br /&gt;
* [[odbedit]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
;NOTE&lt;br /&gt;
: Since 2018, many of the features described here can now be implemented more simply using the  mod* JS functions (see [[Custom Page|Custom Web Page]]).&lt;br /&gt;
: &#039;&#039;&#039;If writing a new web page, it is strongly recommended you make use of the mod*JS features. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page describes some of the special features provided for use on a user-created [[Custom Page|Custom Web Page]] using the [[#The MIDAS Javascript Library]], which was the recommended way to write Custom Pages before the [[Custom Page#modb* Javascript scheme]]  was available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= The MIDAS Javascript Library =&lt;br /&gt;
The MIDAS Javascript Library [[mhttpd.js]] includes routines written in Javascript and AJAX to provide features useful for writers of [[Custom Page]]s, such as access to the ODB. Recently asynchronous [[mjsonrpc|JSON-RPC functions]] using [[mjsonrpc#Javascript client library|Promises]]  has been added (January 2016).&lt;br /&gt;
;NOTE&lt;br /&gt;
:To use functions in this library, it &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Many of the older routines (ODBGet, ODBSet) use &#039;&#039;&#039;synchronous&#039;&#039;&#039; RPC requests. With synchronous request, a second RPC request must wait until the first request is complete. This can significantly slow down web pages where many calls to ODBGet are made. For this reason, functions were developed that can be synchronous or &#039;&#039;&#039;asynchronous&#039;&#039;&#039; (e.g. ODBMCopy) that include a callback mechanism. The data from ODBMCopy can be formatted in [[mjsonrpc#JSON general information|JSON]]. &lt;br /&gt;
&lt;br /&gt;
Web developers are moving away from synchronous RPC requests, which are now deprecated [https://midas.triumf.ca/elog/Midas/1128]. Some modern browsers return a warning on a synchronous RPC request. &lt;br /&gt;
&lt;br /&gt;
A new group of JSON-RPC functions using  [[mjsonrpc#Javascript client library|Promises]] has been added to the MIDAS Javascript Library (January 2016). These are asynchronous only, and they also provide more functionality and have better error handling than the older AJAX calls, which are also inconsistent in how they handle Booleans. They also have much improved handling of reading/writing arrays to the ODB. Before 2018 it was recommended that all new Custom Pages are written using these JSON-RPC functions.  {{Important|text=Since 2018 the [[Custom Page#modb* Javascript scheme]] is recommended for new Custom Pages}}.&lt;br /&gt;
&lt;br /&gt;
The MIDAS Javascript Library routines requires an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). If using an older browser version, the asynchronous AJAX calls (e.g. ODBMCopy) should be used in order to avoid having to rewrite the web page at a later date to remove all synchronous calls.  &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using mjson-rpc asynchronous functions ==&lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], which (since January 2016) includes the [[mjsonrpc]] functions.  It is [[#The MIDAS Javascript Library|recommended]] that these functions are used for &#039;&#039;&#039;new&#039;&#039;&#039; pages rather than the older AJAX calls (see below). &lt;br /&gt;
&lt;br /&gt;
To run these functions you need&lt;br /&gt;
# to  [[mhttpd.js#include js lib|include the Javascript library in the HTML code]]&#039;&#039;&#039;&lt;br /&gt;
# to use an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). &lt;br /&gt;
&lt;br /&gt;
Examples showing how to read and write to the ODB can be found at [[mjsonrpc#examples]]. Also included are examples using arrays. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using asynchronous AJAX calls ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: It is recommended that  asynchronous MIDAS JSON-RPC functions are used for new pages rather than the older AJAX calls (see [[#Access the ODB using mjson-rpc asynchronous functions|above]]).&lt;br /&gt;
&lt;br /&gt;
[[#Example 2 : Asynchronous calls using Javascript and AJAX|Example 2]] uses the asynchronous call ODBMCopy() with callback to read the data. The data are converted into JSON format. In this case, when accessing the JSON data, &#039;&#039;the ODB Keynames must be in the same case as they are in the ODB&#039;&#039;, even though the case is ignored by the ODB. If you find this feature annoying, use [[#Access the ODB using mjson-rpc asynchronous functions|mjson-rpc function db_get_values()]] as all keynames are converted to lower case.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using synchronous AJAX requests ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: Synchronous requests are now deprecated (see [[#The MIDAS Javascript Library|above]]). New pages should be written [[#Using mjson-rpc asynchronous functions]].&lt;br /&gt;
&lt;br /&gt;
See [[#Example 3 : Synchronous Calls using Javascript and AJAX]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Access the ODB with HTML-style &amp;lt;span style=&amp;quot;color:seagreen font-style:italic&amp;quot;&amp;gt;&amp;lt;odb&amp;gt;&amp;lt;/span&amp;gt; tags =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: This method pre-dates the [[Mhttpd.js|MIDAS Javascript Library]]. It is recommended that the [[Mhttpd.js|MIDAS Javascript Library]] be used for ODB access.&lt;br /&gt;
&lt;br /&gt;
If Javascript (JS) is not available, the older HTML-style  {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are still available and provide limited functionality.&lt;br /&gt;
&lt;br /&gt;
The  HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag has been defined for read/write access to the ODB under HTML. The {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags.&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: rgb(255, 255, 255);&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Access to ODB from HTML&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | HTML ODB tag&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | Meaning&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot;&amp;gt;&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: black&amp;quot;  |Display ODB field (read only)&lt;br /&gt;
	&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=1 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (inline style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=2 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (popup style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: The Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; (documented in the [http://ladd00.triumf.ca/~daqweb/doc/midas-old/html/RC_customize_ODB.html#RC_Access_Control| OldMidas Document]) may not be working.&lt;br /&gt;
: Use the  [[/Experiment ODB tree#Security subtree|Web Password security]] instead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are included in the HTML code e.g.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
 Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt;&lt;br /&gt;
 Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt; &amp;lt;br&amp;gt;  Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[Custom Page#How to write a Custom Page|HTML Custom Page example]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= ODB RPC access =&lt;br /&gt;
The [[Mhttpd.js|MIDAS Javascript Library]] function ODBRpc() defined for RPC access.&lt;br /&gt;
&lt;br /&gt;
This permits buttons on MIDAS &amp;quot;custom&amp;quot; web pages to invoke RPC calls directly into user frontend programs, for example to turn hardware modules on or off.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= JSON support =&lt;br /&gt;
[[mjsonrpc#JSON general information|JSON]] support is provided with the [[Mhttpd.js|MIDAS Javascript Library]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Access to the MIDAS Menu buttons =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should follow the instructions there to include the standard MIDAS Menu buttons.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Access to the standard MIDAS Menu buttons can be provided with HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags of the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt; --&amp;gt;&lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt;}}&lt;br /&gt;
Valid values are the standard MIDAS Menu buttons, i.e. (Start, Pause, Resume, Stop, ODB, Elog, Alarms, History, Programs etc). The {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags must be declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags (see above).&lt;br /&gt;
&lt;br /&gt;
The following HTML fragment shows the inclusion of three of the standard buttons, giving access to the Main Status, ODB and Messages pages :&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
 &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; ...  &amp;lt;br&amp;gt; &amp;lt;/form&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
See also [[#Redirect]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Redirect =&lt;br /&gt;
&lt;br /&gt;
When buttons are included on a Custom Page, after pressing a button (e.g. the Start or Stop button) it may be desirable to return to the same custom page, rather than returning to the [[Status Page]].&lt;br /&gt;
&lt;br /&gt;
This can be done by including an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag with the attributes &#039;&#039;type&#039;&#039; set to &amp;quot;hidden&amp;quot; and &#039;&#039;name&#039;&#039; set to &amp;quot;redir&amp;quot;. This name (&amp;quot;redir&amp;quot;) is detected by [[Mhttpd]], causing a redirect to the specified custom link in the &#039;&#039;value&#039;&#039; attribute.&lt;br /&gt;
&lt;br /&gt;
For example, the following redirects the screen back to the custom page link   {{Odbpath|path=/Custom/my_custom_page&amp;amp;}} when buttons are pressed:&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;my_custom_page&amp;amp;&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[#Replace Status Page by a Custom page|Redirect when a Custom Page replaces the Status Page]].&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= CustomScript Buttons =&lt;br /&gt;
&lt;br /&gt;
[[/Customscript ODB tree#Customscript-button|CustomScript buttons]] can be provided on [[Custom Page|Custom Pages]]. These buttons are equivalent to optional &#039;&#039;&#039;script buttons&#039;&#039;&#039; on the [[Status Page]], which call a script to perform a particular action when the button is pressed. See [[/Script ODB tree#Script-button|script buttons]] for details. Customscript buttons can be set up through the [[/Customscript ODB tree]].&lt;br /&gt;
&lt;br /&gt;
Any key &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/CustomScript/test&amp;lt;/span&amp;gt; will appear as a customscript-button &lt;br /&gt;
&amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; on a custom page whose code includes an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag of the form:&lt;br /&gt;
{{Html|text=&amp;lt;input type=submit name=customscript value=&amp;quot;test&amp;quot;&amp;gt;}}&lt;br /&gt;
where the action of the button &amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; will be found in the &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/customscript/test&amp;lt;/span&amp;gt; subdirectory.&lt;br /&gt;
&lt;br /&gt;
After pressing a customscript-button, the &#039;&#039;type=submit&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} will cause a page reload. This will result in a switch to the [[Status Page]], unless a [[#redirect]] input tag is included to redirect back to the original custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customscript button without a page reload ==&lt;br /&gt;
To define a customscript button that does &#039;&#039;&#039;not&#039;&#039;&#039; cause a page reload, set the &#039;&#039;type&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} to &amp;quot;button&amp;quot;, i.e. &lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;customscript&amp;quot; value=&amp;quot;test&amp;quot; type=&amp;quot;button&amp;quot; onClick=cs_button(this.value)&amp;gt;  &amp;lt;!-- Customscript button does not reload page; no callback --&amp;gt;}}&lt;br /&gt;
and use &#039;&#039;&amp;quot;onClick&amp;quot;&#039;&#039; to call a function to [[#Send an Ajax Request]] instead. The following function sends an asynchronous request, with an optional callback.&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;script&amp;gt;}}&lt;br /&gt;
 &amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function cs_button(cmd, callback)&amp;lt;br&amp;gt;&lt;br /&gt;
{  // send a request to execute a custom script&lt;br /&gt;
:   var url;&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
:&lt;br /&gt;
:   url = ODBUrlBase&lt;br /&gt;
:   if(document.getElementById(&amp;quot;redir&amp;quot;) != null)&lt;br /&gt;
::     url +=  &#039;?redir=&#039;+document.getElementById(&#039;redir&#039;).value&lt;br /&gt;
:   url+=&#039;&amp;amp;customscript=&#039;+ encodeURIComponent(cmd);&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true); // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
:  if(callback!=undefined) {&lt;br /&gt;
::      request.onreadystatechange = function() {&lt;br /&gt;
:::         if (request.readyState == 4) { &lt;br /&gt;
::::            if (request.status == 200)&lt;br /&gt;
:::::                callback();&lt;br /&gt;
::::            else&lt;br /&gt;
:::::                alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:::            }&lt;br /&gt;
::         }&lt;br /&gt;
:  }&lt;br /&gt;
:  else { &lt;br /&gt;
::   if (request.status != 200)&lt;br /&gt;
:::       alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:	   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Resource files =&lt;br /&gt;
== MIDAS resource files ==&lt;br /&gt;
A number of resource files have been provided in the MIDAS packages under {{Filepath|path=../packages/midas/resources}}.  When including a MIDAS resource file, no key in  {{Odbpath|path=/Custom}} needs to be defined as the MIDAS resources directory is searched automatically. &lt;br /&gt;
&lt;br /&gt;
One such resource file is a condensed stylesheet {{File|name=midas.css}} for users who would like their custom pages to have a similar &amp;quot;look and feel&amp;quot; to that of the standard pages. To include the MIDAS stylesheet, in the HTML header, add &lt;br /&gt;
{{HtmlTag|tag=&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;}}&lt;br /&gt;
Other resource files can provide [[Custom Page#How to use the standard MIDAS navigation bars on your custom page|the standard MIDAS navigation bars]].&lt;br /&gt;
&lt;br /&gt;
== Custom resource files ==&lt;br /&gt;
It is often desirable to serve resource files (i.e. &#039;&#039;&#039;local files&#039;&#039;&#039; such as an external stylesheet, javascript files or images&#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039;) to a custom page. The following sections describe two alternative ways of serving resource files to a custom page:&lt;br /&gt;
* [[#Resource files served WITH /custom/path key defined|with  /custom/path key defined]]&lt;br /&gt;
* [[#Resource files served WITHOUT /custom/path key defined|without  /custom/path key defined]]&lt;br /&gt;
See also serving resources to a [[#Replace Status Page by a Custom Status page|Custom Status page]].&lt;br /&gt;
;NOTE&lt;br /&gt;
:  &#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039; To superimpose labels, bars or fills onto an image, the image cannot be served as a resource file. See [[#Image insertion]].&lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITH &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
When a number of resource files are required, it is convenient to place them in the same directory on the disk, and create a key  {{Odbpath|path=/Custom/Path}} to contain this directory. &lt;br /&gt;
&lt;br /&gt;
 $ ls  /home/midas/online/custom/&lt;br /&gt;
   custom_functions.js       custom_globals.js     custom_page.html    custom_stylesheet.css      test_image.png&lt;br /&gt;
With the  {{Odbpath|path=/Custom/Path}} key defined, [[/Custom ODB tree#Custom-Link|custom-links]] for individual resource files need not be created in the  {{Odbpath|path=/Custom/Path}} tree. The custom-link  {{Odbpath|path=custom_page&amp;amp;}} &#039;&#039;&#039;is&#039;&#039;&#039; required so that the custom page can be accessed from the [[/Custom ODB tree#Custom-Button|custom-button]]  {{Button|name=custom_page}} on the Status Page. The key name ends in the special character &amp;quot;&amp;amp;&amp;quot; so that it will open in the same window as the Status page (see [[/Custom ODB tree#Key names|key names]]).&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   path                           /home/midas/online/custom/&lt;br /&gt;
   custom_page&amp;amp;                   custom_page.html&lt;br /&gt;
 &lt;br /&gt;
A {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the file name) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_globals.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;custom_stylesheet.css&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;test_image.png&amp;quot;&amp;gt; }}&lt;br /&gt;
[[mhttpd]] then loads the resource file from the directory indicated by the Path key with the correct MIME type (see  [[/Custom ODB tree#Keys in the /Custom tree|Custom tree keys]] for supported MIME types). &lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITHOUT &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
Alternatively, resource files can be served to a [[Custom Page]] by creating a key for every resource file in the [[/Custom ODB tree]]. These keys must contain the full path of the file on the disk, and the key {{Odbpath|path=/Custom/Path}} must NOT be defined. The resource files can be in different directories on the disk. By defining the key names with an appropriate file extension, the resource files are served with the appropriate MIME types. The key names end in the special character &amp;quot;!&amp;quot; so that they will not appear on the Status page as custom-links (see [[/Custom ODB tree#Key names|key names]].&lt;br /&gt;
&lt;br /&gt;
Without the {{Odbpath|path=/Custom/Path}} key,  the links for the above example in the {{Odbpath|path=/Custom}} tree might look like&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   custom_page&amp;amp;                   /home/midas/online/custom/custom_page.html&lt;br /&gt;
   custom_functions.js!           /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   globals.js!                    /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   stylesheet.css!                /home/midas/online/stylesheets/custom_stylesheet.css&lt;br /&gt;
   image.png!                     /home/midas/images/test_image.png  &lt;br /&gt;
&lt;br /&gt;
assuming the resource files are now in the subdirectories indicated.&lt;br /&gt;
The {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the custom-link) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;globals.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;stylesheet.css!&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;image.png!&amp;quot;&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The resulting Demo custom page is shown in Figure 1, which can be compared with Figure 2 (no stylesheet). &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Figure 1  !! Figure 2&lt;br /&gt;
|-&lt;br /&gt;
| Demo Custom Page using MIDAS stylesheet || Demo Custom Page&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| [[File:Mhxcustom03.jpg|thumb|300px]] || [[File:Mhxcustom02.jpg|thumb|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=  Alias-Buttons and Hyperlinks =&lt;br /&gt;
Any hyperlink can easily be included on a [[Custom Page]] by using the standard HTML anchor {{HtmlTag|tag=&amp;lt;a...&amp;gt;}} tag, e.g.&lt;br /&gt;
{{Html|text=&amp;lt;a href=&amp;quot;http://ladd00.triumf.ca/~daqweb/doc/midas/html/&amp;quot;&amp;gt;Midas Help&amp;lt;/a&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Links on a custom page equivalent to [[/Alias ODB tree#Alias-Buttons|alias-buttons]] can also be made e.g.&lt;br /&gt;
{{Html|text=&amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.location.href=&#039;/Alias/alias&amp;amp;&#039;;&amp;quot;&amp;gt;alias&amp;lt;/button&amp;gt;}}&lt;br /&gt;
See the [[/Alias ODB tree]] for details.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Page refresh =&lt;br /&gt;
The following {{HtmlTag|tag=&amp;lt;meta...&amp;gt;}} tag included in the HTML header code will cause the whole custom page to refresh in 60 seconds :&lt;br /&gt;
{{Html|text=&amp;lt;meta http-equiv=&amp;quot;Refresh&amp;quot; content=&amp;quot;60&amp;quot;&amp;gt;}}&lt;br /&gt;
It is also possible to [[#Periodic update of parts of a custom page|periodically update parts]] of a custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Periodic update of parts of a custom page =&lt;br /&gt;
&lt;br /&gt;
The functionality of [[Mhttpd.js|ODBGet]] together with the window.setInterval() function&lt;br /&gt;
can be used to update parts of the web page periodically.&lt;br /&gt;
For example the Javascript fragment below contains a function which updates the current run number every 10 seconds in the background:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt; &amp;lt;!-- JS --&amp;gt;&lt;br /&gt;
window.setInterval(&amp;quot;Refresh()&amp;quot;, 10000);&amp;lt;br&amp;gt;&lt;br /&gt;
function Refresh() {&amp;lt;br&amp;gt;&lt;br /&gt;
:document.getElementById(&amp;quot;run_number&amp;quot;).innerHTML = ODBGet(&#039;/Runinfo/Run number&#039;);&amp;lt;br&amp;gt;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
The custom page has to &lt;br /&gt;
* [[Mhttpd.js#include js lib|include the MIDAS JS library]] to access ODBGet&lt;br /&gt;
* contain an element with id=&amp;quot;run_number&amp;quot;, such as&lt;br /&gt;
{{Html|text=&amp;lt;td id=&amp;quot;run_number&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Display last MIDAS message(s) =&lt;br /&gt;
&lt;br /&gt;
The message log (see [[Message System]]) can be accessed from a custom page using a call to the JavaScript library function [[Mhttpd.js|ODBGetMsg]] (provided the [[Mhttpd.js#include js lib|JS library is included]]).&lt;br /&gt;
&lt;br /&gt;
This allows the inclusion of the &amp;quot;Last Midas message&amp;quot; on a custom page, e.g.&lt;br /&gt;
{{JS|text=document.write(&#039;Last message:&#039;+ODBGetMsg(&amp;quot;midas&amp;quot;,0,1))}}&lt;br /&gt;
More messages may be displayed by increasing the third parameter to ODBGetMsg.&lt;br /&gt;
;Note&lt;br /&gt;
: Parameters were changed August 2015. See [[Mhttpd.js|ODBGetMsg]] for older versions.&lt;br /&gt;
: Coming soon - a [[mjsonrpc]] function&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Generate a message log entry =&lt;br /&gt;
&lt;br /&gt;
A custom page can generate a message to be sent to the MIDAS message log (see [[Message System]]).  A call to mjsonrpc_cm_msg() will generate a message if using the [[mjsonrpc]] functions. Otherwise, use the AJAX function [[Mhttpd.js|ODBGenerateMsg]]. To use these functions, the  [[Mhttpd.js#include js lib|JS library must be included]] in the html code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Checkboxes =&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New  (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should use [[Custom Page#modbcheckbox|modbcheckbox]] to create a checkbox.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;Br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function [[Mhttpd.js|ODBSet]] (provided the [[Mhttpd.js#include js lib|JS library is included]]) can be used when one clicks on a checkbox for example:&lt;br /&gt;
{{Html|text=&amp;lt;input  name=&amp;quot;box0&amp;quot;  type=&amp;quot;checkbox&amp;quot;  onClick=&amp;quot;ODBSet(my_path, this.checked?&#039;1&#039;:&#039;0&#039;)&amp;quot;&amp;gt;}}&lt;br /&gt;
If used as above, the state of the checkbox must be initialized when the page is loaded. This can be done with some JavaScript code called on initialization, e.g.&lt;br /&gt;
{{JS|text=document.form1.box0.checked= ODBGet(my_path));  // initialize to the correct value}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Replace Status Page by a Custom page =&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_status.jpg|thumbnail|left|Figure 3: ODB /Custom/Status custom-link to a custom status page]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a custom-link with the [[/Custom ODB tree#Key names|reserved key name]] &#039;&#039;&#039;Status&#039;&#039;&#039;  (not &amp;quot;Status&amp;amp;&amp;quot; or &amp;quot;Status!&amp;quot;) is created in the [[/Custom ODB tree]] (as shown in  Figure 3), then that custom page will &#039;&#039;&#039;replace the default Status Page&#039;&#039;&#039;. &lt;br /&gt;
  &lt;br /&gt;
Clicking on the {{Button|name=Status}} button on any of the sub-pages (e.g. [[ODB Page]], [[Programs Page]] etc.) will now return to the Custom Status Page. If there are buttons on the Custom Status page, you &#039;&#039;&#039;must&#039;&#039;&#039; include a  [[#Redirect]] statement of the form&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;../&amp;quot;&amp;gt;}}&lt;br /&gt;
or you will see the message &lt;br /&gt;
 Invalid custom page:NULL path&lt;br /&gt;
&lt;br /&gt;
If the Custom Status page includes [[#Resource files]] served on a regular custom page with a statement such as&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
to serve them in a Custom Status page, the statement would be&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&amp;lt;span style=&amp;quot;font-weight:bold&amp;quot;&amp;gt;/CS/&amp;lt;/span&amp;gt;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
In fact, this statement can be used in a regular custom page, as the &amp;quot;/CS/&amp;quot; is ignored in that case.&lt;br /&gt;
&lt;br /&gt;
To return to the default Status Page, delete the &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Custom/Status&amp;lt;/span&amp;gt; key. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Start, Stop and Check if a program is running =&lt;br /&gt;
There are [[mjsonrpc]] functions that implemented all three program management functions - start program, &lt;br /&gt;
stop program and &amp;quot;is running?&amp;quot; as JSON-RPC methods.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Send an Ajax request =&lt;br /&gt;
By sending an Ajax request from a custom page, you can make a button perform a specific function. &lt;br /&gt;
  &lt;br /&gt;
All functions in midas are controlled through special URLs. So the URL&lt;br /&gt;
 http://&amp;lt;host:port&amp;gt;/?cmd=Start&amp;amp;value=10&lt;br /&gt;
will start run #10.&lt;br /&gt;
&lt;br /&gt;
Although it is easier to use an HTML input statement to [[#Access to the MIDAS Menu buttons]], &lt;br /&gt;
to send an Ajax request, you can use the function &#039;&#039;XMLHttpRequestGeneric&#039;&#039; which is in the MIDAS Javascript library [[mhttpd.js]].&lt;br /&gt;
Then the HTML code would be&lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;start()&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
and in your JavaScript code add a function &#039;&#039;start()&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function start()&lt;br /&gt;
{&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
&lt;br /&gt;
:   url = &#039;?cmd=Start&amp;amp;value=10&#039;;&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true);  // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
See also [[#Access to the MIDAS Menu buttons]]. Another example with optional callback can be found in [[#Customscript button without a page reload]].&lt;br /&gt;
&lt;br /&gt;
This mechanism can be used for starting a particular program, see for example (see [https://midas.triumf.ca/elog/Midas/1046]). However, this functionality is now provided by [[mjsonrpc]] functions - see [[#Start, Stop and Check if a program is running]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Image insertion  =&lt;br /&gt;
An image can be loaded from the web using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}}, e.g.  &lt;br /&gt;
   {{Html|text=&amp;quot;TRIUMF logo&amp;quot; &amp;lt;img src=&amp;quot;https://lixenon.triumf.ca/InternalDocuments/Alice/figures/TRIUMF-logo.jpg/image_preview&amp;quot;&amp;gt;}}&lt;br /&gt;
or a MIDAS History image can be inserted into a custom page using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag of the following form:&lt;br /&gt;
 {{Html|text=blah&amp;lt;img src=&amp;quot;http://hostname.domain:port/HS/Meterdis.gif&amp;amp;scale=12h&amp;amp;width=300&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If the image file is on the local disk, it can be loaded as a &#039;&#039;&#039;resource file&#039;&#039;&#039;. The image can be of format .pdf, .jpg, .gif, .png. See loading [[#Resource files]].&lt;br /&gt;
&lt;br /&gt;
If you wish to superimpose features such as &#039;&#039;&#039;labels and fills&#039;&#039;&#039;, the image file cannot be served as a [[#Resource files|resource file]]. Instead, the image file must be in &#039;&#039;&#039;gif&#039;&#039;&#039; format, and must be included in the {{Odbpath|path=/Custom/images}} subtree (Figure 4). Image insertion into a Custom page will be illustrated using the Demo custom page shown in [[#MIDAS stylesheet|Figure 2]]. All the files required for this demo can be found in the MIDAS package at $MIDASSYS/examples/custom. If you do not wish to create&lt;br /&gt;
the keys yourself, proceed to [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
Make a [[/Custom ODB tree#Custom-Link|custom-link]] to the Demo custom page file &#039;&#039;myexpt.html&#039;&#039; , i.e.&lt;br /&gt;
 [local:js:S]/&amp;gt;ls /custom&lt;br /&gt;
    myexpt&amp;amp;                  /home/test/packages/midas/examples/custom/myexpt.html&lt;br /&gt;
 &lt;br /&gt;
To make the image &#039;&#039;myexpt.gif&#039;&#039; visible on the custom page, the path and filename of the image file must be defined in the   {{Odbpath|path=/Custom/images}} subtree. To do this, &lt;br /&gt;
create the  subtrees {{Odbpath|path=/Custom/images/myexpt.gif}} where the subtree name &amp;quot;myexpt.gif&amp;quot; is named for the image file you are going to use. Multiple images can be used, by creating multiple imagefile subtrees.&lt;br /&gt;
&lt;br /&gt;
In the imagefile subtree {{Odbpath|path=myexpt.gif}}, create the STRING key  {{Odbpath|path=Background}}, and set it to contain the path and name of the image file.  The tree structure should then look similar to Figure 4, minus the labels/bars/fill subtrees which will be added to the ODB later.&lt;br /&gt;
&lt;br /&gt;
The image must also be referenced in the custom HTML file &#039;&#039;myexpt.html&#039;&#039; in the &amp;quot;src&amp;quot; field of an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag, e.g. &lt;br /&gt;
{{Html|text=&amp;lt;img &#039;&#039;&#039;src=&amp;quot;myexpt.gif&amp;quot;&#039;&#039;&#039;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_images.jpg|thumbnail|left|Figure 4: /Custom/Images ODB Tree]]&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
Once the image is visible, enable [[#HTML mapping]], optionally [[#Display mouse position]] and proceed to  [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== HTML mapping ==&lt;br /&gt;
Note that if additional features such as active clickable areas and labels, bars and fills superimposed on the image are also required, HTML mapping must also be activated with the HTML {{HtmlTag|tag=&amp;lt;map...&amp;gt;}} tag and the &amp;quot;usemap&amp;quot; attribute of the HTML {{HtmlTag|tag=&amp;lt;img&amp;gt;}} tag&lt;br /&gt;
{{Html|text=   &amp;lt;map &#039;&#039;&#039;name=&amp;quot;myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;img src=&amp;quot;myexpt.gif&amp;quot; &#039;&#039;&#039;usemap=&amp;quot;#myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt;...&amp;lt;br&amp;gt; &amp;lt;/map&amp;gt;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Display mouse position ==&lt;br /&gt;
[[File:Cursor.png|thumbnail|left|Figure 5: MEG Gas System Custom Page showing cursor position]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When writing custom pages with large background images and labels and fills placed on that image, it is hard to figure out X and Y coordinates of the labels. This can now be simplified by using the function &#039;&#039;getMouseXY()&#039;&#039; in the development JavaScript built-in library [[develop.js]]. This function supplies the X,Y position of the cursor if an element of ID &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; is present. This JS library &#039;&#039;&#039;must be [[develop.js|included in the custom page]]&#039;&#039;&#039; in order to use it:&lt;br /&gt;
&lt;br /&gt;
Then, set the &amp;quot;id&amp;quot; attribute of the background HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag to &amp;quot;refimg&amp;quot;, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: &amp;lt;img  &#039;&#039;&#039;id=&amp;quot;refimg&amp;quot;&#039;&#039;&#039; src=&amp;quot;ebit_pc.gif&amp;quot; usemap=&amp;quot;#Custom1&amp;quot;&amp;gt;   &amp;lt;!-- name=&amp;quot;refimg&amp;quot; makes crosshairs appear --&amp;gt;&lt;br /&gt;
: &amp;lt;map name=&amp;quot;Custom1&amp;quot;&amp;gt;&lt;br /&gt;
: .....&lt;br /&gt;
: &amp;lt;/map&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; tag is present, the cursor changes into a crosshair, and its absolute and relative locations in respect to the reference image are shown in the status bar (Figure 5).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
==  Superimposing Labels, Bars and Fills onto a gif image ==&lt;br /&gt;
You can enhance your custom page by superimposing multiple features based on ODB variables onto an image (e.g. [[#Display mouse position|Figure 5]]), such as&lt;br /&gt;
&lt;br /&gt;
*    labels: &amp;quot;live&amp;quot; ODB values positioned in a particular location of the page&lt;br /&gt;
*    bars : &amp;quot;bar level&amp;quot; showing graphically ODB values such as levels or rate etc.&lt;br /&gt;
*    fills : &amp;quot;color level&amp;quot; where colour is used as the level indicator.&lt;br /&gt;
&lt;br /&gt;
Each entry (label/bar/fill) will have an ODB tree associated to it defining the ODB variable path, X/Y position, colour, etc. Each time the page is updated, the latest ODB value/level/rate will be shown based on the ODB parameter to which the label, bar or fill is linked - hence the term &amp;quot;live&amp;quot;. The overlay of the requested features is done onto the selected image file.&lt;br /&gt;
&lt;br /&gt;
This powerful new extension brings the [[mhttpd]] capability closer to other experimental web controllers similar to EPICS.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
:    Be sure to enable the feature to [[#Display mouse position]] in order to facilitate finding the X,Y positions of the various features.&lt;br /&gt;
: [[#HTML mapping]] must be activated for labels/bars/fills to work&lt;br /&gt;
&lt;br /&gt;
A Demo custom page showing labels, bars and fills superimposed on an image is shown in [[#MIDAS stylesheet|Figure 2]]. &lt;br /&gt;
All the files for this demo can be found in $MIDASSYS/examples/custom/. The file xcustom.odb contains the ODB keys required, including those to insert the image and superimpose the various labels, fills etc. This file can be loaded into the ODB with the  {{Odbedit cmd|cmd=load}}.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Labels ===&lt;br /&gt;
&lt;br /&gt;
 [[File:Mhcustom_label.jpg|thumbnail|left|Figure 6: /Custom/Images/Labels ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In order to include a readout of ODB values (i.e. labels), on the image, a further ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Labels}} must be created. Creating a subdirectory for a particular label i.e. {{Odbpath|path=&amp;lt;label name&amp;gt;}} in the   {{Odbpath|path=Labels}} subtree will, at the next custom web page refresh, cause the complete structure for that label to be created and filled with default values. Once the {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is created, the user fills the various keys as desired. See [[/Custom ODB tree#Labels subtree]] for details of the various fields. This procedure is repeated for all the labels required, using a unique {{Odbpath|path=&amp;lt;label name&amp;gt;}} subdirectory for each label. An example of a {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is shown in Figure 6. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Bars ===&lt;br /&gt;
 &lt;br /&gt;
[[File:Mhcustom_fill.jpg|thumbnail|left|Figure 7: /Custom/Images/Fills ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Bars can be superimposed on the image. Create  a new ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Bars}}. Create a subdirectory for a particular Bar ({{Odbpath|path=&amp;lt;bar name&amp;gt;}}) in the {{Odbpath|path=Bars}}  subdirectory. Refresh the web page and fill the various keys as desired.  See [[/Custom ODB tree#Bars subtree]] for details of the various fields.  Examples of a &amp;lt;bar-name&amp;gt; subtree is shown in Figure 7.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Fills ===&lt;br /&gt;
[[File:Mhcustom_bar.jpg|thumbnail|left|Figure 8: /Custom/Images/Bars ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Fills can be superimposed on the image. Create new ODB subdirectory   {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Fills}}. Create a subdirectory for a particular  Fill ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}) in the {{Odbpath|path=Fills}} subdirectory. Refresh the web page and fill the various keys as desired.  See  [[/Custom ODB tree#Fills subtree]] for details of the various fields.  Examples of a ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}  subtree is shown in Figure 8. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Mapping active areas onto the image ==&lt;br /&gt;
Provided [[#HTML mapping]] is activated, &amp;quot;clickable&amp;quot; areas can be created on the image.&lt;br /&gt;
&lt;br /&gt;
This can be done now with a new function like this:&lt;br /&gt;
{{Html|text= &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;40,200,100,300&amp;quot; alt=&amp;quot;Main Valve&amp;quot; href=&amp;quot;Custom1?cmd=Toggle&amp;amp;odb=/Equipment/Environment/Variables/Output[2]&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
This defines a clickable map on top of the custom image. The area(s) should match with some area(s) on the image, e.g. the box of a valve. Determining the co-ordinates of this area is simplified by using the Display mouse position feature.&lt;br /&gt;
&lt;br /&gt;
By clicking on this area, the supplied path to the ODB is used (in this case  {{Odbpath|path=/Equipment/Environment/Variables/Output[2]}}) and its value is toggled. If the valve value is then used in the image via a [[#Adding Fills|Fill]] statement to change the color of the valve, it can turn green or red depending on its state. This is illustrated in [[#Display mouse position|Figure 5]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Clicking an active area can also be made to open a new custom page, for example:&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;687,530, 890,648&amp;quot; alt=&amp;quot;Pump detail&amp;quot; href = &amp;quot;Pump!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;560,574,775,662&amp;quot; alt=&amp;quot;Buffer Tank detail&amp;quot; href = &amp;quot;BufferTank!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
where &#039;&#039;Pump!&#039;&#039; and &#039;&#039;BufferTank!&#039;&#039; are defined as links to custom pages in the [[/Custom ODB tree]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Edit boxes floating on top of a graphic ==&lt;br /&gt;
&lt;br /&gt;
An edit box can be placed on top of a graphic in a particular position by means of an HTML  &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/span&amp;gt; tag. Using the ODBEdit function from the Midas JS library [[Mhttpd.js]], the custom page code would look like this:&lt;br /&gt;
&amp;lt;!-- Complicated... have to use &amp;lt;pre&amp;gt; because of &amp;lt;div&amp;gt;, then a table to keep the background colour between &amp;lt;pre&amp;gt;s --&amp;gt;&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: floralwhite;&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;    &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
document.write(&#039;Run number: &#039;)&lt;br /&gt;
path=&#039;/runinfo/run number&#039;&lt;br /&gt;
rn = ODBGet(path)&lt;br /&gt;
document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(path)&amp;quot; &amp;gt;&#039;)  &lt;br /&gt;
document.write(rn)&lt;br /&gt;
document.write(&#039;&amp;lt;/a&amp;gt;&#039;);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same thing could be done with the HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag :&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;&lt;br /&gt;
Run number:  &amp;lt;odb src=&amp;quot;/Runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
For more examples of accessing the ODB with the Javascript library [[mhttpd.js]] look at the example experiment in the MIDAS package $MIDASSYS/examples/javascript1/example.html.&lt;br /&gt;
&lt;br /&gt;
== Example 1 : Asynchronous calls using mjsonrpc functions ==&lt;br /&gt;
See [[mjsonrpc#examples]]&lt;br /&gt;
&lt;br /&gt;
== Example 2 : Asynchronous calls using Javascript and AJAX ==&lt;br /&gt;
&lt;br /&gt;
Example 2 shows html code illustrating the use of ODBMCopy(), where innerHTML is used to display the data. On a page with images or a lot of data, innerHTML allows you to update variables without having to reload the whole page. In the example, a timer causes the page to update every 10s (reread the data). Often the ODB data for the whole page can be read by one asynchronous ODBMCopy() call, rather than scattering synchronous ODBGet() calls throughout the page.  &lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], included with the line &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;script src=&#039;mhttpd.js&#039;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;MyTitle&amp;lt;/title&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;script src=&#039;mhttpd.js&#039;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
var updatePeriod = 10000; // in msec &amp;lt;br&amp;gt;&lt;br /&gt;
var updateTimerId = 0; &amp;lt;br&amp;gt;&lt;br /&gt;
var counter=0; &amp;lt;br&amp;gt;&lt;br /&gt;
function update()  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:clearTimeout(updateTimerId); &amp;lt;br&amp;gt;&lt;br /&gt;
:      load(); &lt;br /&gt;
:      if (updatePeriod &amp;gt; 0) &lt;br /&gt;
:      updateTimerId = setTimeout(&#039;update()&#039;, updatePeriod); &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function load()  { &amp;lt;br&amp;gt;&lt;br /&gt;
: document.getElementById(&#039;LastUpdated&#039;).innerHTML = &amp;quot;Updating...&amp;quot; + new Date; &lt;br /&gt;
: var paths = [ &amp;quot;/Runinfo&amp;quot;, &amp;quot;/Experiment&amp;quot;]; &lt;br /&gt;
: var data_odb=ODBMCopy(paths, mcopy_callback, &amp;quot;json&amp;quot;) &lt;br /&gt;
: counter++; &lt;br /&gt;
: document.getElementById(&#039;counter&#039;).innerHTML = &#039;Counter: &#039;+ counter &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function mcopy_callback(data)  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:    var obj= JSON.parse(data);&lt;br /&gt;
: var runinfo=obj[0];&lt;br /&gt;
: document.getElementById(&#039;rn&#039;).innerHTML = &#039;Run Number =&#039;+ parseInt(runinfo[&amp;quot;Run number&amp;quot;]);&lt;br /&gt;
: document.getElementById(&#039;state&#039;).innerHTML  =&#039;Run State= &#039;+ runinfo.State;&lt;br /&gt;
: var experiment=obj[1];&lt;br /&gt;
: document.getElementById(&#039;name&#039;).innerHTML=&#039;Experiment name = &#039;+ experiment.Name &amp;lt;br&amp;gt; &lt;br /&gt;
}&amp;lt;br&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/head&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;body&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;b&amp;amp;gt;Javascript code using ODBMCopy with callback&amp;amp;lt;/b&amp;amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
The data on the page is updated every 10 sec using a timer&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;LastUpdated&amp;quot; &amp;amp;gt; Last updated: never&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;rn&amp;quot;&amp;amp;gt; Run Number : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;state&amp;quot;&amp;amp;gt; State : unknown&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;name&amp;quot;&amp;amp;gt; Experiment name : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;counter&amp;quot;&amp;amp;gt;Counter: zero  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/div&amp;gt;  &lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: if (updatePeriod &amp;gt; 0)&lt;br /&gt;
:  update(); &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Example 3 : Synchronous Calls using Javascript and AJAX ==&lt;br /&gt;
In the following example, JS functions ODBGet and  ODBEdit from the [[Mhttpd.js|MIDAS Javascript Library]] are used to access the ODB.  &lt;br /&gt;
;NOTE&lt;br /&gt;
# Synchronous calls are now deprecated (see [[#The MIDAS Javascript Library|above]]).&lt;br /&gt;
# The Javascript library &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;script&amp;gt;}}&lt;br /&gt;
{{JS|text=document.write (&#039;Experiment Name: &#039;+ ODBGet(&amp;quot;/Experiment/Name&amp;quot;)) &amp;lt;br&amp;gt; var alarm_path=&amp;quot;/alarms/Alarm system active&amp;quot;; &amp;lt;br&amp;gt; var alarm_active=ODBGet(alarm_path); &amp;lt;br&amp;gt; document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(alarm_path)&amp;quot; &amp;gt;&#039;+alarm_active+&#039;&amp;lt;/a&amp;gt;&#039;)}}&lt;br /&gt;
{{Html|text=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=File:SVGanimation.gif&amp;diff=3532</id>
		<title>File:SVGanimation.gif</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=File:SVGanimation.gif&amp;diff=3532"/>
		<updated>2025-05-12T13:38:48Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3531</id>
		<title>SVGs on Custom Pages</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3531"/>
		<updated>2025-05-12T13:38:31Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:SVGcustom_page.svg|thumbnail|none|Plain SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
== Custom Page HTML code ==&lt;br /&gt;
&lt;br /&gt;
[[File:SVGanimation.gif|thumbnail|none|Emptying the expansion volume displayed on an SVG-based custom page]]&lt;br /&gt;
&lt;br /&gt;
The custom page displays the SVG inside an &amp;lt;code&amp;gt;mtable&amp;lt;/code&amp;gt;.&lt;br /&gt;
The two valves turn green when open and red when closed.&lt;br /&gt;
This is done by retrieving the SVG element using &amp;lt;code&amp;gt;objVal = svgDoc.getElementById(valveID)&amp;lt;/code&amp;gt; and setting its fill color via &amp;lt;code&amp;gt;obj_Valve.style.fill = ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Sets of &amp;lt;code&amp;gt;modbbutton&amp;lt;/code&amp;gt; are used to change the associated ODB boolean variable. A confirmation dialog appears to prevent accidental valve operation.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The expansion volume shows its fill state both via a &amp;lt;code&amp;gt;modbvbar&amp;lt;/code&amp;gt; and by adjusting the height of the corresponding SVG object.&lt;br /&gt;
Depending on how the object is constructed in Inkscape, scaling and translating it can be non-trivial.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The example code below keeps the object&#039;s bottom edge fixed while scaling its height based on the fill level.&lt;br /&gt;
This means that only the y-dimension is scaled.&lt;br /&gt;
Since scaling also shifts the y-position, the original position and height must be retrieved first.&lt;br /&gt;
There may also be an additional scaling factor in the SVG, which is why the &amp;lt;code&amp;gt;updateVolume&amp;lt;/code&amp;gt; function first compares the &amp;lt;code&amp;gt;viewBox&amp;lt;/code&amp;gt; parameters.&lt;br /&gt;
Then, the position and dimensions are extracted from the transformation matrix defined in the XML.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== SVG XML code ==&lt;br /&gt;
&lt;br /&gt;
This is the XML code of the SVG example used in the custom page.&lt;br /&gt;
It is important to set the object IDs correctly, as they are used to access and manipulate elements via JavaScript.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;!-- Created with Inkscape (http://www.inkscape.org/) --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;svg&lt;br /&gt;
   width=&amp;quot;600&amp;quot;&lt;br /&gt;
   height=&amp;quot;750&amp;quot;&lt;br /&gt;
   viewBox=&amp;quot;0 0 600 750.00002&amp;quot;&lt;br /&gt;
   version=&amp;quot;1.1&amp;quot;&lt;br /&gt;
   id=&amp;quot;svg1&amp;quot;&lt;br /&gt;
   inkscape:version=&amp;quot;1.4.1 (unknown)&amp;quot;&lt;br /&gt;
   sodipodi:docname=&amp;quot;Midas_helium_example.svg&amp;quot;&lt;br /&gt;
   xmlns:inkscape=&amp;quot;http://www.inkscape.org/namespaces/inkscape&amp;quot;&lt;br /&gt;
   xmlns:sodipodi=&amp;quot;http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&amp;quot;&lt;br /&gt;
   xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;br /&gt;
   xmlns:svg=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;sodipodi:namedview&lt;br /&gt;
     id=&amp;quot;namedview1&amp;quot;&lt;br /&gt;
     pagecolor=&amp;quot;#ffffff&amp;quot;&lt;br /&gt;
     bordercolor=&amp;quot;#000000&amp;quot;&lt;br /&gt;
     borderopacity=&amp;quot;0.25&amp;quot;&lt;br /&gt;
     inkscape:showpageshadow=&amp;quot;2&amp;quot;&lt;br /&gt;
     inkscape:pageopacity=&amp;quot;0.0&amp;quot;&lt;br /&gt;
     inkscape:pagecheckerboard=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:deskcolor=&amp;quot;#d1d1d1&amp;quot;&lt;br /&gt;
     inkscape:document-units=&amp;quot;px&amp;quot;&lt;br /&gt;
     inkscape:zoom=&amp;quot;2.0661644&amp;quot;&lt;br /&gt;
     inkscape:cx=&amp;quot;423.974&amp;quot;&lt;br /&gt;
     inkscape:cy=&amp;quot;308.30073&amp;quot;&lt;br /&gt;
     inkscape:window-width=&amp;quot;1920&amp;quot;&lt;br /&gt;
     inkscape:window-height=&amp;quot;1009&amp;quot;&lt;br /&gt;
     inkscape:window-x=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:window-y=&amp;quot;32&amp;quot;&lt;br /&gt;
     inkscape:window-maximized=&amp;quot;1&amp;quot;&lt;br /&gt;
     inkscape:current-layer=&amp;quot;layer1&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;defs&lt;br /&gt;
     id=&amp;quot;defs1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;marker&lt;br /&gt;
       style=&amp;quot;overflow:visible&amp;quot;&lt;br /&gt;
       id=&amp;quot;marker2&amp;quot;&lt;br /&gt;
       refX=&amp;quot;0&amp;quot;&lt;br /&gt;
       refY=&amp;quot;0&amp;quot;&lt;br /&gt;
       orient=&amp;quot;auto-start-reverse&amp;quot;&lt;br /&gt;
       inkscape:stockid=&amp;quot;Triangle arrow&amp;quot;&lt;br /&gt;
       markerWidth=&amp;quot;1&amp;quot;&lt;br /&gt;
       markerHeight=&amp;quot;1&amp;quot;&lt;br /&gt;
       viewBox=&amp;quot;0 0 1 1&amp;quot;&lt;br /&gt;
       inkscape:isstock=&amp;quot;true&amp;quot;&lt;br /&gt;
       inkscape:collect=&amp;quot;always&amp;quot;&lt;br /&gt;
       preserveAspectRatio=&amp;quot;xMidYMid&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         transform=&amp;quot;scale(0.5)&amp;quot;&lt;br /&gt;
         style=&amp;quot;fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 5.77,0 -2.88,5 V -5 Z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path2&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/marker&amp;gt;&lt;br /&gt;
  &amp;lt;/defs&amp;gt;&lt;br /&gt;
  &amp;lt;g&lt;br /&gt;
     inkscape:label=&amp;quot;Gas system&amp;quot;&lt;br /&gt;
     inkscape:groupmode=&amp;quot;layer&amp;quot;&lt;br /&gt;
     id=&amp;quot;layer1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(4.056096,0,0,4.056096,-726.2293,-806.75454)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;rect&lt;br /&gt;
         style=&amp;quot;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.264635;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_outline&amp;quot;&lt;br /&gt;
         width=&amp;quot;45.862354&amp;quot;&lt;br /&gt;
         height=&amp;quot;58.905613&amp;quot;&lt;br /&gt;
         x=&amp;quot;267.93396&amp;quot;&lt;br /&gt;
         y=&amp;quot;308.49619&amp;quot;&lt;br /&gt;
         rx=&amp;quot;0&amp;quot;&lt;br /&gt;
         ry=&amp;quot;0&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_outline&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;g&lt;br /&gt;
         id=&amp;quot;Gas_bottle&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(3.6447445,0,0,3.4512234,112.28368,-106.71016)&amp;quot;&lt;br /&gt;
         style=&amp;quot;display:inline;mix-blend-mode:normal;fill:#ffffff;stroke-width:0.118799&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_bottle&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 45.466186,125.93812 v -1.31329 c 0,0 0.07542,-0.46399 0.475282,-0.46399 0.43403,0 0.541916,0.46399 0.541916,0.46399 v 1.31329&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11882&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;ccscc&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 44.681364,126.73032 c 0,0 0.09055,-0.94582 1.313641,-0.94582 1.120378,0 1.273201,0.94582 1.273201,0.94582 v 9.66595 h -2.586842 z&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11880&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;cscccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/g&amp;gt;&lt;br /&gt;
      &amp;lt;text&lt;br /&gt;
         xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-weight:bold;font-size:13.3333px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:3.77953&amp;quot;&lt;br /&gt;
         x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
         y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_title&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(0.49308498,0,0,0.49308498,179.04638,229.4703)&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
           sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
           style=&amp;quot;font-size:13.3333px;fill:#000000;fill-opacity:1;stroke-width:3.77953&amp;quot;&lt;br /&gt;
           x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
           y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
           id=&amp;quot;tspan96-8-4-0&amp;quot;&amp;gt;Gas cabinet&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2&amp;quot;&lt;br /&gt;
       d=&amp;quot;M 408.62454,498.40226 V 468.28588 H 288.15902 v 64.98024 H 93.771462 V 255.7319&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;cccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 220.44846,552.04894 v -37.8841 l -58.46319,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_valve&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;text&lt;br /&gt;
       xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
       style=&amp;quot;font-weight:bold;font-size:26.6666px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:7.55906&amp;quot;&lt;br /&gt;
       x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
       y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
       id=&amp;quot;Expansion_volume_title&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
         sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-size:26.6666px;fill:#000000;fill-opacity:1;stroke-width:7.55906&amp;quot;&lt;br /&gt;
         x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
         y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
         id=&amp;quot;tspan96-8-8&amp;quot;&amp;gt;Expansion volume&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;marker-end:url(#marker2)&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 251.67406,254.578 v 104.14268 h 97.7657 181.89378 V 251.47464&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(7.5560886,0,0,7.5560886,-161.95634,-143.19437)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 25.181199,26.590622 h 37.055527 l -2.746143,6.664982 2.885185,6.396771 -2.746137,6.65182 2.919945,6.490565 H 24.903107 l 2.798286,-6.490565 -2.937328,-6.813076 2.885185,-6.235515 z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path4823&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;ccccccccccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 27.691557,33.255604 H 59.644908&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3275&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 24.974876,39.556896 H 62.361591&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3277&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 27.950288,46.304195 h 31.69462&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3279&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 449.20856,377.30494 v -37.8841 l -58.4632,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_valve&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;/g&amp;gt;&lt;br /&gt;
&amp;lt;/svg&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3530</id>
		<title>SVGs on Custom Pages</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3530"/>
		<updated>2025-05-12T13:28:04Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* SVG XML code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:SVGcustom_page.svg|thumbnail|none|Figure 9: SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
== Custom Page HTML code ==&lt;br /&gt;
&lt;br /&gt;
The custom page displays the SVG inside an &amp;lt;code&amp;gt;mtable&amp;lt;/code&amp;gt;.&lt;br /&gt;
The two valves turn green when open and red when closed.&lt;br /&gt;
This is done by retrieving the SVG element using &amp;lt;code&amp;gt;objVal = svgDoc.getElementById(valveID)&amp;lt;/code&amp;gt; and setting its fill color via &amp;lt;code&amp;gt;obj_Valve.style.fill = ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Sets of &amp;lt;code&amp;gt;modbbutton&amp;lt;/code&amp;gt; are used to change the associated ODB boolean variable. A confirmation dialog appears to prevent accidental valve operation.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The expansion volume shows its fill state both via a &amp;lt;code&amp;gt;modbvbar&amp;lt;/code&amp;gt; and by adjusting the height of the corresponding SVG object.&lt;br /&gt;
Depending on how the object is constructed in Inkscape, scaling and translating it can be non-trivial.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The example code below keeps the object&#039;s bottom edge fixed while scaling its height based on the fill level.&lt;br /&gt;
This means that only the y-dimension is scaled.&lt;br /&gt;
Since scaling also shifts the y-position, the original position and height must be retrieved first.&lt;br /&gt;
There may also be an additional scaling factor in the SVG, which is why the &amp;lt;code&amp;gt;updateVolume&amp;lt;/code&amp;gt; function first compares the &amp;lt;code&amp;gt;viewBox&amp;lt;/code&amp;gt; parameters.&lt;br /&gt;
Then, the position and dimensions are extracted from the transformation matrix defined in the XML.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== SVG XML code ==&lt;br /&gt;
&lt;br /&gt;
This is the XML code of the SVG example used in the custom page.&lt;br /&gt;
It is important to set the object IDs correctly, as they are used to access and manipulate elements via JavaScript.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;!-- Created with Inkscape (http://www.inkscape.org/) --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;svg&lt;br /&gt;
   width=&amp;quot;600&amp;quot;&lt;br /&gt;
   height=&amp;quot;750&amp;quot;&lt;br /&gt;
   viewBox=&amp;quot;0 0 600 750.00002&amp;quot;&lt;br /&gt;
   version=&amp;quot;1.1&amp;quot;&lt;br /&gt;
   id=&amp;quot;svg1&amp;quot;&lt;br /&gt;
   inkscape:version=&amp;quot;1.4.1 (unknown)&amp;quot;&lt;br /&gt;
   sodipodi:docname=&amp;quot;Midas_helium_example.svg&amp;quot;&lt;br /&gt;
   xmlns:inkscape=&amp;quot;http://www.inkscape.org/namespaces/inkscape&amp;quot;&lt;br /&gt;
   xmlns:sodipodi=&amp;quot;http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&amp;quot;&lt;br /&gt;
   xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;br /&gt;
   xmlns:svg=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;sodipodi:namedview&lt;br /&gt;
     id=&amp;quot;namedview1&amp;quot;&lt;br /&gt;
     pagecolor=&amp;quot;#ffffff&amp;quot;&lt;br /&gt;
     bordercolor=&amp;quot;#000000&amp;quot;&lt;br /&gt;
     borderopacity=&amp;quot;0.25&amp;quot;&lt;br /&gt;
     inkscape:showpageshadow=&amp;quot;2&amp;quot;&lt;br /&gt;
     inkscape:pageopacity=&amp;quot;0.0&amp;quot;&lt;br /&gt;
     inkscape:pagecheckerboard=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:deskcolor=&amp;quot;#d1d1d1&amp;quot;&lt;br /&gt;
     inkscape:document-units=&amp;quot;px&amp;quot;&lt;br /&gt;
     inkscape:zoom=&amp;quot;2.0661644&amp;quot;&lt;br /&gt;
     inkscape:cx=&amp;quot;423.974&amp;quot;&lt;br /&gt;
     inkscape:cy=&amp;quot;308.30073&amp;quot;&lt;br /&gt;
     inkscape:window-width=&amp;quot;1920&amp;quot;&lt;br /&gt;
     inkscape:window-height=&amp;quot;1009&amp;quot;&lt;br /&gt;
     inkscape:window-x=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:window-y=&amp;quot;32&amp;quot;&lt;br /&gt;
     inkscape:window-maximized=&amp;quot;1&amp;quot;&lt;br /&gt;
     inkscape:current-layer=&amp;quot;layer1&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;defs&lt;br /&gt;
     id=&amp;quot;defs1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;marker&lt;br /&gt;
       style=&amp;quot;overflow:visible&amp;quot;&lt;br /&gt;
       id=&amp;quot;marker2&amp;quot;&lt;br /&gt;
       refX=&amp;quot;0&amp;quot;&lt;br /&gt;
       refY=&amp;quot;0&amp;quot;&lt;br /&gt;
       orient=&amp;quot;auto-start-reverse&amp;quot;&lt;br /&gt;
       inkscape:stockid=&amp;quot;Triangle arrow&amp;quot;&lt;br /&gt;
       markerWidth=&amp;quot;1&amp;quot;&lt;br /&gt;
       markerHeight=&amp;quot;1&amp;quot;&lt;br /&gt;
       viewBox=&amp;quot;0 0 1 1&amp;quot;&lt;br /&gt;
       inkscape:isstock=&amp;quot;true&amp;quot;&lt;br /&gt;
       inkscape:collect=&amp;quot;always&amp;quot;&lt;br /&gt;
       preserveAspectRatio=&amp;quot;xMidYMid&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         transform=&amp;quot;scale(0.5)&amp;quot;&lt;br /&gt;
         style=&amp;quot;fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 5.77,0 -2.88,5 V -5 Z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path2&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/marker&amp;gt;&lt;br /&gt;
  &amp;lt;/defs&amp;gt;&lt;br /&gt;
  &amp;lt;g&lt;br /&gt;
     inkscape:label=&amp;quot;Gas system&amp;quot;&lt;br /&gt;
     inkscape:groupmode=&amp;quot;layer&amp;quot;&lt;br /&gt;
     id=&amp;quot;layer1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(4.056096,0,0,4.056096,-726.2293,-806.75454)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;rect&lt;br /&gt;
         style=&amp;quot;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.264635;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_outline&amp;quot;&lt;br /&gt;
         width=&amp;quot;45.862354&amp;quot;&lt;br /&gt;
         height=&amp;quot;58.905613&amp;quot;&lt;br /&gt;
         x=&amp;quot;267.93396&amp;quot;&lt;br /&gt;
         y=&amp;quot;308.49619&amp;quot;&lt;br /&gt;
         rx=&amp;quot;0&amp;quot;&lt;br /&gt;
         ry=&amp;quot;0&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_outline&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;g&lt;br /&gt;
         id=&amp;quot;Gas_bottle&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(3.6447445,0,0,3.4512234,112.28368,-106.71016)&amp;quot;&lt;br /&gt;
         style=&amp;quot;display:inline;mix-blend-mode:normal;fill:#ffffff;stroke-width:0.118799&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_bottle&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 45.466186,125.93812 v -1.31329 c 0,0 0.07542,-0.46399 0.475282,-0.46399 0.43403,0 0.541916,0.46399 0.541916,0.46399 v 1.31329&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11882&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;ccscc&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 44.681364,126.73032 c 0,0 0.09055,-0.94582 1.313641,-0.94582 1.120378,0 1.273201,0.94582 1.273201,0.94582 v 9.66595 h -2.586842 z&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11880&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;cscccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/g&amp;gt;&lt;br /&gt;
      &amp;lt;text&lt;br /&gt;
         xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-weight:bold;font-size:13.3333px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:3.77953&amp;quot;&lt;br /&gt;
         x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
         y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_title&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(0.49308498,0,0,0.49308498,179.04638,229.4703)&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
           sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
           style=&amp;quot;font-size:13.3333px;fill:#000000;fill-opacity:1;stroke-width:3.77953&amp;quot;&lt;br /&gt;
           x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
           y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
           id=&amp;quot;tspan96-8-4-0&amp;quot;&amp;gt;Gas cabinet&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2&amp;quot;&lt;br /&gt;
       d=&amp;quot;M 408.62454,498.40226 V 468.28588 H 288.15902 v 64.98024 H 93.771462 V 255.7319&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;cccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 220.44846,552.04894 v -37.8841 l -58.46319,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_valve&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;text&lt;br /&gt;
       xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
       style=&amp;quot;font-weight:bold;font-size:26.6666px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:7.55906&amp;quot;&lt;br /&gt;
       x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
       y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
       id=&amp;quot;Expansion_volume_title&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
         sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-size:26.6666px;fill:#000000;fill-opacity:1;stroke-width:7.55906&amp;quot;&lt;br /&gt;
         x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
         y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
         id=&amp;quot;tspan96-8-8&amp;quot;&amp;gt;Expansion volume&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;marker-end:url(#marker2)&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 251.67406,254.578 v 104.14268 h 97.7657 181.89378 V 251.47464&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(7.5560886,0,0,7.5560886,-161.95634,-143.19437)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 25.181199,26.590622 h 37.055527 l -2.746143,6.664982 2.885185,6.396771 -2.746137,6.65182 2.919945,6.490565 H 24.903107 l 2.798286,-6.490565 -2.937328,-6.813076 2.885185,-6.235515 z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path4823&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;ccccccccccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 27.691557,33.255604 H 59.644908&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3275&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 24.974876,39.556896 H 62.361591&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3277&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 27.950288,46.304195 h 31.69462&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3279&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 449.20856,377.30494 v -37.8841 l -58.4632,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_valve&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;/g&amp;gt;&lt;br /&gt;
&amp;lt;/svg&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3529</id>
		<title>SVGs on Custom Pages</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3529"/>
		<updated>2025-05-12T13:27:03Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* SVG XML code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:SVGcustom_page.svg|thumbnail|none|Figure 9: SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
== Custom Page HTML code ==&lt;br /&gt;
&lt;br /&gt;
The custom page displays the SVG inside an &amp;lt;code&amp;gt;mtable&amp;lt;/code&amp;gt;.&lt;br /&gt;
The two valves turn green when open and red when closed.&lt;br /&gt;
This is done by retrieving the SVG element using &amp;lt;code&amp;gt;objVal = svgDoc.getElementById(valveID)&amp;lt;/code&amp;gt; and setting its fill color via &amp;lt;code&amp;gt;obj_Valve.style.fill = ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Sets of &amp;lt;code&amp;gt;modbbutton&amp;lt;/code&amp;gt; are used to change the associated ODB boolean variable. A confirmation dialog appears to prevent accidental valve operation.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The expansion volume shows its fill state both via a &amp;lt;code&amp;gt;modbvbar&amp;lt;/code&amp;gt; and by adjusting the height of the corresponding SVG object.&lt;br /&gt;
Depending on how the object is constructed in Inkscape, scaling and translating it can be non-trivial.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The example code below keeps the object&#039;s bottom edge fixed while scaling its height based on the fill level.&lt;br /&gt;
This means that only the y-dimension is scaled.&lt;br /&gt;
Since scaling also shifts the y-position, the original position and height must be retrieved first.&lt;br /&gt;
There may also be an additional scaling factor in the SVG, which is why the &amp;lt;code&amp;gt;updateVolume&amp;lt;/code&amp;gt; function first compares the &amp;lt;code&amp;gt;viewBox&amp;lt;/code&amp;gt; parameters.&lt;br /&gt;
Then, the position and dimensions are extracted from the transformation matrix defined in the XML.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== SVG XML code ==&lt;br /&gt;
&lt;br /&gt;
This is the XML code of the SVG example used in the custom page example.&lt;br /&gt;
It is important to set the IDs of object correctly.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;!-- Created with Inkscape (http://www.inkscape.org/) --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;svg&lt;br /&gt;
   width=&amp;quot;600&amp;quot;&lt;br /&gt;
   height=&amp;quot;750&amp;quot;&lt;br /&gt;
   viewBox=&amp;quot;0 0 600 750.00002&amp;quot;&lt;br /&gt;
   version=&amp;quot;1.1&amp;quot;&lt;br /&gt;
   id=&amp;quot;svg1&amp;quot;&lt;br /&gt;
   inkscape:version=&amp;quot;1.4.1 (unknown)&amp;quot;&lt;br /&gt;
   sodipodi:docname=&amp;quot;Midas_helium_example.svg&amp;quot;&lt;br /&gt;
   xmlns:inkscape=&amp;quot;http://www.inkscape.org/namespaces/inkscape&amp;quot;&lt;br /&gt;
   xmlns:sodipodi=&amp;quot;http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&amp;quot;&lt;br /&gt;
   xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;br /&gt;
   xmlns:svg=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;sodipodi:namedview&lt;br /&gt;
     id=&amp;quot;namedview1&amp;quot;&lt;br /&gt;
     pagecolor=&amp;quot;#ffffff&amp;quot;&lt;br /&gt;
     bordercolor=&amp;quot;#000000&amp;quot;&lt;br /&gt;
     borderopacity=&amp;quot;0.25&amp;quot;&lt;br /&gt;
     inkscape:showpageshadow=&amp;quot;2&amp;quot;&lt;br /&gt;
     inkscape:pageopacity=&amp;quot;0.0&amp;quot;&lt;br /&gt;
     inkscape:pagecheckerboard=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:deskcolor=&amp;quot;#d1d1d1&amp;quot;&lt;br /&gt;
     inkscape:document-units=&amp;quot;px&amp;quot;&lt;br /&gt;
     inkscape:zoom=&amp;quot;2.0661644&amp;quot;&lt;br /&gt;
     inkscape:cx=&amp;quot;423.974&amp;quot;&lt;br /&gt;
     inkscape:cy=&amp;quot;308.30073&amp;quot;&lt;br /&gt;
     inkscape:window-width=&amp;quot;1920&amp;quot;&lt;br /&gt;
     inkscape:window-height=&amp;quot;1009&amp;quot;&lt;br /&gt;
     inkscape:window-x=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:window-y=&amp;quot;32&amp;quot;&lt;br /&gt;
     inkscape:window-maximized=&amp;quot;1&amp;quot;&lt;br /&gt;
     inkscape:current-layer=&amp;quot;layer1&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;defs&lt;br /&gt;
     id=&amp;quot;defs1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;marker&lt;br /&gt;
       style=&amp;quot;overflow:visible&amp;quot;&lt;br /&gt;
       id=&amp;quot;marker2&amp;quot;&lt;br /&gt;
       refX=&amp;quot;0&amp;quot;&lt;br /&gt;
       refY=&amp;quot;0&amp;quot;&lt;br /&gt;
       orient=&amp;quot;auto-start-reverse&amp;quot;&lt;br /&gt;
       inkscape:stockid=&amp;quot;Triangle arrow&amp;quot;&lt;br /&gt;
       markerWidth=&amp;quot;1&amp;quot;&lt;br /&gt;
       markerHeight=&amp;quot;1&amp;quot;&lt;br /&gt;
       viewBox=&amp;quot;0 0 1 1&amp;quot;&lt;br /&gt;
       inkscape:isstock=&amp;quot;true&amp;quot;&lt;br /&gt;
       inkscape:collect=&amp;quot;always&amp;quot;&lt;br /&gt;
       preserveAspectRatio=&amp;quot;xMidYMid&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         transform=&amp;quot;scale(0.5)&amp;quot;&lt;br /&gt;
         style=&amp;quot;fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 5.77,0 -2.88,5 V -5 Z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path2&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/marker&amp;gt;&lt;br /&gt;
  &amp;lt;/defs&amp;gt;&lt;br /&gt;
  &amp;lt;g&lt;br /&gt;
     inkscape:label=&amp;quot;Gas system&amp;quot;&lt;br /&gt;
     inkscape:groupmode=&amp;quot;layer&amp;quot;&lt;br /&gt;
     id=&amp;quot;layer1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(4.056096,0,0,4.056096,-726.2293,-806.75454)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;rect&lt;br /&gt;
         style=&amp;quot;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.264635;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_outline&amp;quot;&lt;br /&gt;
         width=&amp;quot;45.862354&amp;quot;&lt;br /&gt;
         height=&amp;quot;58.905613&amp;quot;&lt;br /&gt;
         x=&amp;quot;267.93396&amp;quot;&lt;br /&gt;
         y=&amp;quot;308.49619&amp;quot;&lt;br /&gt;
         rx=&amp;quot;0&amp;quot;&lt;br /&gt;
         ry=&amp;quot;0&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_outline&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;g&lt;br /&gt;
         id=&amp;quot;Gas_bottle&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(3.6447445,0,0,3.4512234,112.28368,-106.71016)&amp;quot;&lt;br /&gt;
         style=&amp;quot;display:inline;mix-blend-mode:normal;fill:#ffffff;stroke-width:0.118799&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_bottle&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 45.466186,125.93812 v -1.31329 c 0,0 0.07542,-0.46399 0.475282,-0.46399 0.43403,0 0.541916,0.46399 0.541916,0.46399 v 1.31329&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11882&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;ccscc&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 44.681364,126.73032 c 0,0 0.09055,-0.94582 1.313641,-0.94582 1.120378,0 1.273201,0.94582 1.273201,0.94582 v 9.66595 h -2.586842 z&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11880&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;cscccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/g&amp;gt;&lt;br /&gt;
      &amp;lt;text&lt;br /&gt;
         xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-weight:bold;font-size:13.3333px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:3.77953&amp;quot;&lt;br /&gt;
         x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
         y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_title&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(0.49308498,0,0,0.49308498,179.04638,229.4703)&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
           sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
           style=&amp;quot;font-size:13.3333px;fill:#000000;fill-opacity:1;stroke-width:3.77953&amp;quot;&lt;br /&gt;
           x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
           y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
           id=&amp;quot;tspan96-8-4-0&amp;quot;&amp;gt;Gas cabinet&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2&amp;quot;&lt;br /&gt;
       d=&amp;quot;M 408.62454,498.40226 V 468.28588 H 288.15902 v 64.98024 H 93.771462 V 255.7319&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;cccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 220.44846,552.04894 v -37.8841 l -58.46319,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_valve&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;text&lt;br /&gt;
       xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
       style=&amp;quot;font-weight:bold;font-size:26.6666px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:7.55906&amp;quot;&lt;br /&gt;
       x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
       y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
       id=&amp;quot;Expansion_volume_title&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
         sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-size:26.6666px;fill:#000000;fill-opacity:1;stroke-width:7.55906&amp;quot;&lt;br /&gt;
         x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
         y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
         id=&amp;quot;tspan96-8-8&amp;quot;&amp;gt;Expansion volume&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;marker-end:url(#marker2)&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 251.67406,254.578 v 104.14268 h 97.7657 181.89378 V 251.47464&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(7.5560886,0,0,7.5560886,-161.95634,-143.19437)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 25.181199,26.590622 h 37.055527 l -2.746143,6.664982 2.885185,6.396771 -2.746137,6.65182 2.919945,6.490565 H 24.903107 l 2.798286,-6.490565 -2.937328,-6.813076 2.885185,-6.235515 z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path4823&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;ccccccccccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 27.691557,33.255604 H 59.644908&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3275&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 24.974876,39.556896 H 62.361591&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3277&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 27.950288,46.304195 h 31.69462&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3279&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 449.20856,377.30494 v -37.8841 l -58.4632,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_valve&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;/g&amp;gt;&lt;br /&gt;
&amp;lt;/svg&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3528</id>
		<title>SVGs on Custom Pages</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3528"/>
		<updated>2025-05-12T13:24:37Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Custom Page HTML code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:SVGcustom_page.svg|thumbnail|none|Figure 9: SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
== Custom Page HTML code ==&lt;br /&gt;
&lt;br /&gt;
The custom page displays the SVG inside an &amp;lt;code&amp;gt;mtable&amp;lt;/code&amp;gt;.&lt;br /&gt;
The two valves turn green when open and red when closed.&lt;br /&gt;
This is done by retrieving the SVG element using &amp;lt;code&amp;gt;objVal = svgDoc.getElementById(valveID)&amp;lt;/code&amp;gt; and setting its fill color via &amp;lt;code&amp;gt;obj_Valve.style.fill = ...&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Sets of &amp;lt;code&amp;gt;modbbutton&amp;lt;/code&amp;gt; are used to change the associated ODB boolean variable. A confirmation dialog appears to prevent accidental valve operation.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The expansion volume shows its fill state both via a &amp;lt;code&amp;gt;modbvbar&amp;lt;/code&amp;gt; and by adjusting the height of the corresponding SVG object.&lt;br /&gt;
Depending on how the object is constructed in Inkscape, scaling and translating it can be non-trivial.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The example code below keeps the object&#039;s bottom edge fixed while scaling its height based on the fill level.&lt;br /&gt;
This means that only the y-dimension is scaled.&lt;br /&gt;
Since scaling also shifts the y-position, the original position and height must be retrieved first.&lt;br /&gt;
There may also be an additional scaling factor in the SVG, which is why the &amp;lt;code&amp;gt;updateVolume&amp;lt;/code&amp;gt; function first compares the &amp;lt;code&amp;gt;viewBox&amp;lt;/code&amp;gt; parameters.&lt;br /&gt;
Then, the position and dimensions are extracted from the transformation matrix defined in the XML.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== SVG XML code ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;!-- Created with Inkscape (http://www.inkscape.org/) --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;svg&lt;br /&gt;
   width=&amp;quot;600&amp;quot;&lt;br /&gt;
   height=&amp;quot;750&amp;quot;&lt;br /&gt;
   viewBox=&amp;quot;0 0 600 750.00002&amp;quot;&lt;br /&gt;
   version=&amp;quot;1.1&amp;quot;&lt;br /&gt;
   id=&amp;quot;svg1&amp;quot;&lt;br /&gt;
   inkscape:version=&amp;quot;1.4.1 (unknown)&amp;quot;&lt;br /&gt;
   sodipodi:docname=&amp;quot;Midas_helium_example.svg&amp;quot;&lt;br /&gt;
   xmlns:inkscape=&amp;quot;http://www.inkscape.org/namespaces/inkscape&amp;quot;&lt;br /&gt;
   xmlns:sodipodi=&amp;quot;http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&amp;quot;&lt;br /&gt;
   xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;br /&gt;
   xmlns:svg=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;sodipodi:namedview&lt;br /&gt;
     id=&amp;quot;namedview1&amp;quot;&lt;br /&gt;
     pagecolor=&amp;quot;#ffffff&amp;quot;&lt;br /&gt;
     bordercolor=&amp;quot;#000000&amp;quot;&lt;br /&gt;
     borderopacity=&amp;quot;0.25&amp;quot;&lt;br /&gt;
     inkscape:showpageshadow=&amp;quot;2&amp;quot;&lt;br /&gt;
     inkscape:pageopacity=&amp;quot;0.0&amp;quot;&lt;br /&gt;
     inkscape:pagecheckerboard=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:deskcolor=&amp;quot;#d1d1d1&amp;quot;&lt;br /&gt;
     inkscape:document-units=&amp;quot;px&amp;quot;&lt;br /&gt;
     inkscape:zoom=&amp;quot;2.0661644&amp;quot;&lt;br /&gt;
     inkscape:cx=&amp;quot;423.974&amp;quot;&lt;br /&gt;
     inkscape:cy=&amp;quot;308.30073&amp;quot;&lt;br /&gt;
     inkscape:window-width=&amp;quot;1920&amp;quot;&lt;br /&gt;
     inkscape:window-height=&amp;quot;1009&amp;quot;&lt;br /&gt;
     inkscape:window-x=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:window-y=&amp;quot;32&amp;quot;&lt;br /&gt;
     inkscape:window-maximized=&amp;quot;1&amp;quot;&lt;br /&gt;
     inkscape:current-layer=&amp;quot;layer1&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;defs&lt;br /&gt;
     id=&amp;quot;defs1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;marker&lt;br /&gt;
       style=&amp;quot;overflow:visible&amp;quot;&lt;br /&gt;
       id=&amp;quot;marker2&amp;quot;&lt;br /&gt;
       refX=&amp;quot;0&amp;quot;&lt;br /&gt;
       refY=&amp;quot;0&amp;quot;&lt;br /&gt;
       orient=&amp;quot;auto-start-reverse&amp;quot;&lt;br /&gt;
       inkscape:stockid=&amp;quot;Triangle arrow&amp;quot;&lt;br /&gt;
       markerWidth=&amp;quot;1&amp;quot;&lt;br /&gt;
       markerHeight=&amp;quot;1&amp;quot;&lt;br /&gt;
       viewBox=&amp;quot;0 0 1 1&amp;quot;&lt;br /&gt;
       inkscape:isstock=&amp;quot;true&amp;quot;&lt;br /&gt;
       inkscape:collect=&amp;quot;always&amp;quot;&lt;br /&gt;
       preserveAspectRatio=&amp;quot;xMidYMid&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         transform=&amp;quot;scale(0.5)&amp;quot;&lt;br /&gt;
         style=&amp;quot;fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 5.77,0 -2.88,5 V -5 Z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path2&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/marker&amp;gt;&lt;br /&gt;
  &amp;lt;/defs&amp;gt;&lt;br /&gt;
  &amp;lt;g&lt;br /&gt;
     inkscape:label=&amp;quot;Gas system&amp;quot;&lt;br /&gt;
     inkscape:groupmode=&amp;quot;layer&amp;quot;&lt;br /&gt;
     id=&amp;quot;layer1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(4.056096,0,0,4.056096,-726.2293,-806.75454)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;rect&lt;br /&gt;
         style=&amp;quot;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.264635;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_outline&amp;quot;&lt;br /&gt;
         width=&amp;quot;45.862354&amp;quot;&lt;br /&gt;
         height=&amp;quot;58.905613&amp;quot;&lt;br /&gt;
         x=&amp;quot;267.93396&amp;quot;&lt;br /&gt;
         y=&amp;quot;308.49619&amp;quot;&lt;br /&gt;
         rx=&amp;quot;0&amp;quot;&lt;br /&gt;
         ry=&amp;quot;0&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_outline&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;g&lt;br /&gt;
         id=&amp;quot;Gas_bottle&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(3.6447445,0,0,3.4512234,112.28368,-106.71016)&amp;quot;&lt;br /&gt;
         style=&amp;quot;display:inline;mix-blend-mode:normal;fill:#ffffff;stroke-width:0.118799&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_bottle&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 45.466186,125.93812 v -1.31329 c 0,0 0.07542,-0.46399 0.475282,-0.46399 0.43403,0 0.541916,0.46399 0.541916,0.46399 v 1.31329&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11882&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;ccscc&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 44.681364,126.73032 c 0,0 0.09055,-0.94582 1.313641,-0.94582 1.120378,0 1.273201,0.94582 1.273201,0.94582 v 9.66595 h -2.586842 z&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11880&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;cscccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/g&amp;gt;&lt;br /&gt;
      &amp;lt;text&lt;br /&gt;
         xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-weight:bold;font-size:13.3333px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:3.77953&amp;quot;&lt;br /&gt;
         x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
         y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_title&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(0.49308498,0,0,0.49308498,179.04638,229.4703)&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
           sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
           style=&amp;quot;font-size:13.3333px;fill:#000000;fill-opacity:1;stroke-width:3.77953&amp;quot;&lt;br /&gt;
           x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
           y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
           id=&amp;quot;tspan96-8-4-0&amp;quot;&amp;gt;Gas cabinet&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2&amp;quot;&lt;br /&gt;
       d=&amp;quot;M 408.62454,498.40226 V 468.28588 H 288.15902 v 64.98024 H 93.771462 V 255.7319&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;cccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 220.44846,552.04894 v -37.8841 l -58.46319,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_valve&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;text&lt;br /&gt;
       xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
       style=&amp;quot;font-weight:bold;font-size:26.6666px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:7.55906&amp;quot;&lt;br /&gt;
       x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
       y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
       id=&amp;quot;Expansion_volume_title&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
         sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-size:26.6666px;fill:#000000;fill-opacity:1;stroke-width:7.55906&amp;quot;&lt;br /&gt;
         x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
         y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
         id=&amp;quot;tspan96-8-8&amp;quot;&amp;gt;Expansion volume&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;marker-end:url(#marker2)&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 251.67406,254.578 v 104.14268 h 97.7657 181.89378 V 251.47464&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(7.5560886,0,0,7.5560886,-161.95634,-143.19437)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 25.181199,26.590622 h 37.055527 l -2.746143,6.664982 2.885185,6.396771 -2.746137,6.65182 2.919945,6.490565 H 24.903107 l 2.798286,-6.490565 -2.937328,-6.813076 2.885185,-6.235515 z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path4823&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;ccccccccccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 27.691557,33.255604 H 59.644908&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3275&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 24.974876,39.556896 H 62.361591&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3277&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 27.950288,46.304195 h 31.69462&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3279&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 449.20856,377.30494 v -37.8841 l -58.4632,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_valve&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;/g&amp;gt;&lt;br /&gt;
&amp;lt;/svg&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3527</id>
		<title>SVGs on Custom Pages</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=SVGs_on_Custom_Pages&amp;diff=3527"/>
		<updated>2025-05-12T13:11:05Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: Created page with &amp;quot;Figure 9: SVG based custom page  In this example, an SVG image forms the basis of a custom page. It displays two valves and an expansion volume.  The valves change color depending on their status — open or closed. The expansion volume dynamically changes its height.  All objects within the SVG file are directly modified using JavaScript functions.  == Custom Page HTML code ==  &amp;lt;pre&amp;gt; &amp;lt;!DOCTYPE html&amp;gt; &amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt; &amp;lt;head&amp;gt;...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:SVGcustom_page.svg|thumbnail|none|Figure 9: SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
== Custom Page HTML code ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== SVG XML code ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;!-- Created with Inkscape (http://www.inkscape.org/) --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;svg&lt;br /&gt;
   width=&amp;quot;600&amp;quot;&lt;br /&gt;
   height=&amp;quot;750&amp;quot;&lt;br /&gt;
   viewBox=&amp;quot;0 0 600 750.00002&amp;quot;&lt;br /&gt;
   version=&amp;quot;1.1&amp;quot;&lt;br /&gt;
   id=&amp;quot;svg1&amp;quot;&lt;br /&gt;
   inkscape:version=&amp;quot;1.4.1 (unknown)&amp;quot;&lt;br /&gt;
   sodipodi:docname=&amp;quot;Midas_helium_example.svg&amp;quot;&lt;br /&gt;
   xmlns:inkscape=&amp;quot;http://www.inkscape.org/namespaces/inkscape&amp;quot;&lt;br /&gt;
   xmlns:sodipodi=&amp;quot;http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd&amp;quot;&lt;br /&gt;
   xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;br /&gt;
   xmlns:svg=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;sodipodi:namedview&lt;br /&gt;
     id=&amp;quot;namedview1&amp;quot;&lt;br /&gt;
     pagecolor=&amp;quot;#ffffff&amp;quot;&lt;br /&gt;
     bordercolor=&amp;quot;#000000&amp;quot;&lt;br /&gt;
     borderopacity=&amp;quot;0.25&amp;quot;&lt;br /&gt;
     inkscape:showpageshadow=&amp;quot;2&amp;quot;&lt;br /&gt;
     inkscape:pageopacity=&amp;quot;0.0&amp;quot;&lt;br /&gt;
     inkscape:pagecheckerboard=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:deskcolor=&amp;quot;#d1d1d1&amp;quot;&lt;br /&gt;
     inkscape:document-units=&amp;quot;px&amp;quot;&lt;br /&gt;
     inkscape:zoom=&amp;quot;2.0661644&amp;quot;&lt;br /&gt;
     inkscape:cx=&amp;quot;423.974&amp;quot;&lt;br /&gt;
     inkscape:cy=&amp;quot;308.30073&amp;quot;&lt;br /&gt;
     inkscape:window-width=&amp;quot;1920&amp;quot;&lt;br /&gt;
     inkscape:window-height=&amp;quot;1009&amp;quot;&lt;br /&gt;
     inkscape:window-x=&amp;quot;0&amp;quot;&lt;br /&gt;
     inkscape:window-y=&amp;quot;32&amp;quot;&lt;br /&gt;
     inkscape:window-maximized=&amp;quot;1&amp;quot;&lt;br /&gt;
     inkscape:current-layer=&amp;quot;layer1&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;defs&lt;br /&gt;
     id=&amp;quot;defs1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;marker&lt;br /&gt;
       style=&amp;quot;overflow:visible&amp;quot;&lt;br /&gt;
       id=&amp;quot;marker2&amp;quot;&lt;br /&gt;
       refX=&amp;quot;0&amp;quot;&lt;br /&gt;
       refY=&amp;quot;0&amp;quot;&lt;br /&gt;
       orient=&amp;quot;auto-start-reverse&amp;quot;&lt;br /&gt;
       inkscape:stockid=&amp;quot;Triangle arrow&amp;quot;&lt;br /&gt;
       markerWidth=&amp;quot;1&amp;quot;&lt;br /&gt;
       markerHeight=&amp;quot;1&amp;quot;&lt;br /&gt;
       viewBox=&amp;quot;0 0 1 1&amp;quot;&lt;br /&gt;
       inkscape:isstock=&amp;quot;true&amp;quot;&lt;br /&gt;
       inkscape:collect=&amp;quot;always&amp;quot;&lt;br /&gt;
       preserveAspectRatio=&amp;quot;xMidYMid&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         transform=&amp;quot;scale(0.5)&amp;quot;&lt;br /&gt;
         style=&amp;quot;fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 5.77,0 -2.88,5 V -5 Z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path2&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/marker&amp;gt;&lt;br /&gt;
  &amp;lt;/defs&amp;gt;&lt;br /&gt;
  &amp;lt;g&lt;br /&gt;
     inkscape:label=&amp;quot;Gas system&amp;quot;&lt;br /&gt;
     inkscape:groupmode=&amp;quot;layer&amp;quot;&lt;br /&gt;
     id=&amp;quot;layer1&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Gas_cabinet&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(4.056096,0,0,4.056096,-726.2293,-806.75454)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;rect&lt;br /&gt;
         style=&amp;quot;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.264635;stroke-miterlimit:3.2;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_outline&amp;quot;&lt;br /&gt;
         width=&amp;quot;45.862354&amp;quot;&lt;br /&gt;
         height=&amp;quot;58.905613&amp;quot;&lt;br /&gt;
         x=&amp;quot;267.93396&amp;quot;&lt;br /&gt;
         y=&amp;quot;308.49619&amp;quot;&lt;br /&gt;
         rx=&amp;quot;0&amp;quot;&lt;br /&gt;
         ry=&amp;quot;0&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_outline&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;g&lt;br /&gt;
         id=&amp;quot;Gas_bottle&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(3.6447445,0,0,3.4512234,112.28368,-106.71016)&amp;quot;&lt;br /&gt;
         style=&amp;quot;display:inline;mix-blend-mode:normal;fill:#ffffff;stroke-width:0.118799&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_bottle&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 45.466186,125.93812 v -1.31329 c 0,0 0.07542,-0.46399 0.475282,-0.46399 0.43403,0 0.541916,0.46399 0.541916,0.46399 v 1.31329&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11882&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;ccscc&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;path&lt;br /&gt;
           style=&amp;quot;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.0746005px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1&amp;quot;&lt;br /&gt;
           d=&amp;quot;m 44.681364,126.73032 c 0,0 0.09055,-0.94582 1.313641,-0.94582 1.120378,0 1.273201,0.94582 1.273201,0.94582 v 9.66595 h -2.586842 z&amp;quot;&lt;br /&gt;
           id=&amp;quot;path11880&amp;quot;&lt;br /&gt;
           sodipodi:nodetypes=&amp;quot;cscccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/g&amp;gt;&lt;br /&gt;
      &amp;lt;text&lt;br /&gt;
         xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-weight:bold;font-size:13.3333px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:3.77953&amp;quot;&lt;br /&gt;
         x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
         y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
         id=&amp;quot;Gas_cabinet_title&amp;quot;&lt;br /&gt;
         transform=&amp;quot;matrix(0.49308498,0,0,0.49308498,179.04638,229.4703)&amp;quot;&lt;br /&gt;
         inkscape:label=&amp;quot;Gas_cabinet_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
           sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
           style=&amp;quot;font-size:13.3333px;fill:#000000;fill-opacity:1;stroke-width:3.77953&amp;quot;&lt;br /&gt;
           x=&amp;quot;179.48&amp;quot;&lt;br /&gt;
           y=&amp;quot;151.93512&amp;quot;&lt;br /&gt;
           id=&amp;quot;tspan96-8-4-0&amp;quot;&amp;gt;Gas cabinet&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2&amp;quot;&lt;br /&gt;
       d=&amp;quot;M 408.62454,498.40226 V 468.28588 H 288.15902 v 64.98024 H 93.771462 V 255.7319&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;cccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 220.44846,552.04894 v -37.8841 l -58.46319,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Filling_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Filling_valve&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;text&lt;br /&gt;
       xml:space=&amp;quot;preserve&amp;quot;&lt;br /&gt;
       style=&amp;quot;font-weight:bold;font-size:26.6666px;font-family:Laksaman;-inkscape-font-specification:&#039;Laksaman Bold&#039;;text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00ffff;fill-rule:evenodd;stroke-width:7.55906&amp;quot;&lt;br /&gt;
       x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
       y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
       id=&amp;quot;Expansion_volume_title&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume_title&amp;quot;&amp;gt;&amp;lt;tspan&lt;br /&gt;
         sodipodi:role=&amp;quot;line&amp;quot;&lt;br /&gt;
         style=&amp;quot;font-size:26.6666px;fill:#000000;fill-opacity:1;stroke-width:7.55906&amp;quot;&lt;br /&gt;
         x=&amp;quot;26.068356&amp;quot;&lt;br /&gt;
         y=&amp;quot;43.542694&amp;quot;&lt;br /&gt;
         id=&amp;quot;tspan96-8-8&amp;quot;&amp;gt;Expansion volume&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-dasharray:none;marker-end:url(#marker2)&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 251.67406,254.578 v 104.14268 h 97.7657 181.89378 V 251.47464&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_line&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;g&lt;br /&gt;
       id=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Expansion_volume&amp;quot;&lt;br /&gt;
       style=&amp;quot;display:inline&amp;quot;&lt;br /&gt;
       transform=&amp;quot;matrix(7.5560886,0,0,7.5560886,-161.95634,-143.19437)&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 25.181199,26.590622 h 37.055527 l -2.746143,6.664982 2.885185,6.396771 -2.746137,6.65182 2.919945,6.490565 H 24.903107 l 2.798286,-6.490565 -2.937328,-6.813076 2.885185,-6.235515 z&amp;quot;&lt;br /&gt;
         id=&amp;quot;path4823&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;ccccccccccc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 27.691557,33.255604 H 59.644908&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3275&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;M 24.974876,39.556896 H 62.361591&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3277&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;path&lt;br /&gt;
         style=&amp;quot;mix-blend-mode:normal;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.264581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.264581, 0.264581;stroke-dashoffset:0;stroke-opacity:1&amp;quot;&lt;br /&gt;
         d=&amp;quot;m 27.950288,46.304195 h 31.69462&amp;quot;&lt;br /&gt;
         id=&amp;quot;path3279&amp;quot;&lt;br /&gt;
         sodipodi:nodetypes=&amp;quot;cc&amp;quot; /&amp;gt;&lt;br /&gt;
    &amp;lt;/g&amp;gt;&lt;br /&gt;
    &amp;lt;path&lt;br /&gt;
       style=&amp;quot;display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.53449;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1&amp;quot;&lt;br /&gt;
       d=&amp;quot;m 449.20856,377.30494 v -37.8841 l -58.4632,37.86918 v -37.8843 z&amp;quot;&lt;br /&gt;
       id=&amp;quot;Exhaust_valve&amp;quot;&lt;br /&gt;
       sodipodi:nodetypes=&amp;quot;ccccc&amp;quot;&lt;br /&gt;
       inkscape:label=&amp;quot;Exhaust_valve&amp;quot; /&amp;gt;&lt;br /&gt;
  &amp;lt;/g&amp;gt;&lt;br /&gt;
&amp;lt;/svg&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3526</id>
		<title>Custom Page</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3526"/>
		<updated>2025-05-12T13:09:31Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Implementing SVGs */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
{{mhttpdpages3|[[Custom Page Features]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}&lt;br /&gt;
&lt;br /&gt;
= Purpose =&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
= Examples of Custom Pages =&lt;br /&gt;
&lt;br /&gt;
Click on the thumbnails to enlarge.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]] || &#039;&#039;&#039;Example 1&#039;&#039;&#039;&lt;br /&gt;
This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of &amp;quot;fills&amp;quot; and &amp;quot;labels&amp;quot;. 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).&lt;br /&gt;
|-&lt;br /&gt;
| [[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]] || &#039;&#039;&#039;Example 2&#039;&#039;&#039;&lt;br /&gt;
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 &amp;quot;virtual&amp;quot; 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.&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For details using Xvfb server, please contact Ryu Sawada &amp;lt;sawada@icepp.s.u-tokyo.ac.jp&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| [[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]] || &#039;&#039;&#039;Example 3&#039;&#039;&#039;&lt;br /&gt;
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.   &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Access a Custom Page from the Regular MIDAS pages =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
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.&lt;br /&gt;
See [[Custom Page Features#Resource files]] for more information.&lt;br /&gt;
&lt;br /&gt;
If the key  {{Odbpath|path=/Custom/myPage&amp;amp;}}  (see Note) is created, e.g.&lt;br /&gt;
 odbedit&amp;gt; ls /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
&lt;br /&gt;
the custom link on the left navigation bar will be &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; and the URL for the resulting custom page will be of the form &amp;lt;code&amp;gt;http://myhost.mydomain:myport/cmd=?Custom&amp;amp;page=myPage&amp;lt;/code&amp;gt; (see also [[mhttpd#usage]]).  &lt;br /&gt;
Clicking on &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; will display the custom page in the same window.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
: With the &amp;quot;&amp;amp;&amp;quot; symbol in the key name, the page would appear in a new tab/window. See [[/Custom ODB tree#Key names|Key names]] for more information.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
 odbedit&amp;gt; ls -r /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
    Calorimeter&lt;br /&gt;
        HV                            hv.html&lt;br /&gt;
        Rates                         rates.html&lt;br /&gt;
    Baeam&lt;br /&gt;
        Beamline                      beamline.html&lt;br /&gt;
        Accelerator                   accel.html&lt;br /&gt;
&lt;br /&gt;
The pages then include the subdirectory in the URL, like &lt;br /&gt;
&lt;br /&gt;
 http://localhost:8080?cmd=custom&amp;amp;page=beam/Beamline&lt;br /&gt;
&lt;br /&gt;
Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Calorimeter/HV&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
in order to keep the submenu open after you select the custom page.&lt;br /&gt;
&lt;br /&gt;
= How to write a custom page =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
* modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.&lt;br /&gt;
* 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.&lt;br /&gt;
* modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== How to use the standard MIDAS navigation bars on your custom page == &lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;myPage&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;myPage&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_start --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
 ADD YOUR HTML/JS  CODE here...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call &amp;lt;code&amp;gt;mhttpd_init(&#039;myPage&#039;)&amp;lt;/code&amp;gt; is executed when the page is loaded, and  &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage.&lt;br /&gt;
This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying [[Mjsonrpc | javascript libraries]].&lt;br /&gt;
&lt;br /&gt;
= modb* Javascript scheme = &lt;br /&gt;
&lt;br /&gt;
The general scheme of the custom page scheme is to write &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;amp;lt;span class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt; tags with special class names, most of them starting with &amp;quot;modb...&amp;quot; (&amp;quot;MIDAS-ODB&amp;quot;). Use a &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want the element to appear in a separate line, and use the &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want to display the element in-line. The following description uses only &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tags, but all of them can be changed to &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
All HTML tags with &amp;quot;modb...&amp;quot; names are scanned by the &amp;lt;code&amp;gt;mhttp_init(&#039;name&#039;)&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;mhttpd_init(&#039;name&#039;, interval)&amp;lt;/code&amp;gt; where &amp;quot;interval&amp;quot; is in milliseconds. You can add or remove &amp;quot;modb...&amp;quot; elements at any time using javascript, and the new elements will be &amp;quot;discovered&amp;quot; automatically during the next update.&lt;br /&gt;
&lt;br /&gt;
== modbset(path, value) ==&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset(&amp;quot;odb path&amp;quot;, value)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset([&amp;quot;odb path1&amp;quot;, &amp;quot;odb path2&amp;quot;, ...], [value1, value2, ...])&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modb ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for Midas ODB) &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot; onchange=&amp;quot;func()&amp;quot;&amp;amp;gt;&amp;lt;/code&amp;gt; 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 &amp;amp;lt;script&amp;amp;gt;...&amp;amp;lt;/script&amp;amp;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.&lt;br /&gt;
&lt;br /&gt;
The current value of the ODB entry is available inside the &amp;quot;onchange&amp;quot; function as &#039;&#039;&#039;this.value&#039;&#039;&#039;. Following tag will call a function which logs the current run number in the JavaScript console window:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to &#039;&#039;&#039;this.value&#039;&#039;&#039; as a JavaSctipt object such as&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value[&amp;quot;run number&amp;quot;]);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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 &#039;&#039;&#039;value[&amp;quot;run number&amp;quot;]&#039;&#039;&#039; as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as &#039;&#039;&#039;value.state&#039;&#039;&#039; for /Runinfo/State for example.&lt;br /&gt;
&lt;br /&gt;
== modbvalue ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for &amp;quot;Midas ODB VALUE&amp;quot;) &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 1: List of valid options for modbvalue tag&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| data-name || class=&amp;quot;modbvalue&amp;quot; || Tells the framework to replace this tag with an ODB value&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || data-odb-path = &amp;quot;/Runinfo/Run number&amp;quot; || Path to the value in the ODB. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]].&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-editable || data-odb-editable=&amp;quot;1&amp;quot; || 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.&lt;br /&gt;
|-&lt;br /&gt;
| data-format || data-format=&amp;quot;f3&amp;quot; || Specify format of data shown. See Table 2 below for options.&lt;br /&gt;
|-&lt;br /&gt;
| data-size || data-size=&amp;quot;8&amp;quot; || Specify size (in chars) of edit box if one modifies the value. Default is 10.&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || data-formula=&amp;quot;2*x+3&amp;quot; || Specify an optional formula to process with the current ODB value stored in x&lt;br /&gt;
|-&lt;br /&gt;
| data-validate || data-validate=&amp;quot;func&amp;quot; || Specify an optional validation function which gets called before submitting data (see below)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Validation ===&lt;br /&gt;
&lt;br /&gt;
Before a modified value is submitted to the ODB, an optional validation function can be called via the &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; option. The function&lt;br /&gt;
will be called with the current value and a reference to the current modbvalue element. If the function returns &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, then the value&lt;br /&gt;
is not sent to the ODB.&lt;br /&gt;
&lt;br /&gt;
Following example shows a function which just rejects the submission of values above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt; &amp;amp;lt;!-- needed of dlgAlert() --&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        dlgAlert(&amp;quot;Value cannot be above 1000&amp;quot;);&lt;br /&gt;
        return false;&lt;br /&gt;
     }&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following function corrects the return value to 1000 if it&#039;s above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate2(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        element.childNodes[0].value = 1000;&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Confirmation ===&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
This is done with the option &amp;lt;code&amp;gt;data-confirm=&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt;. A dialog box is then shown with the &amp;lt;code&amp;gt;&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt; as the main text and two buttons with &amp;quot;OK&amp;quot; and &amp;quot;Cancel&amp;quot;.&lt;br /&gt;
Hitting &amp;quot;OK&amp;quot; finally writes the value to the ODB, hitting &amp;quot;Cancel&amp;quot; keeps the old value.&lt;br /&gt;
&lt;br /&gt;
Following example shows an example:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-confirm=&amp;quot;Are you sure to change the value?&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following dialog box is then showed:&lt;br /&gt;
&lt;br /&gt;
[[File:Confirm.png|frame|left|Optional confirm dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Formatting ===&lt;br /&gt;
&lt;br /&gt;
Table 2 below lists the format specifiers supported with a modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 2: Format specifiers for modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Valid for* !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| %d || int || 1234 || Shows a number in decimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %t || int, float || 1,234,567 || Shows a number in decimal encoding with commas as thousands separator&lt;br /&gt;
|-&lt;br /&gt;
| %x || int || 0x4D2 || Shows a number in hexadecimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %b || int || 10011010010b|| Shows a number in binary encoding. Options t, d, x, b can be combined, like &amp;lt;code&amp;gt;data-format=&amp;quot;%d / %x&amp;quot;&amp;lt;/code&amp;gt; to produce &amp;lt;code&amp;gt;1234 / 0x4D2&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| %f&amp;amp;lt;x&amp;amp;gt; || float || 1.234 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal. A value of f0 shows only the integer part.&lt;br /&gt;
|-&lt;br /&gt;
| %p&amp;amp;lt;x&amp;amp;gt; || float || 1.23 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012&lt;br /&gt;
|-&lt;br /&gt;
| %e&amp;amp;lt;x&amp;amp;gt; || float || 1.23e-05 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal in exponential format. Useful for small number such as pressures.&lt;br /&gt;
|-&lt;br /&gt;
| T= %f1 C || float || T= 25.4 C || Combination of text and float value is possible.&lt;br /&gt;
|-&lt;br /&gt;
| %d / %x / %b || int || 17 / 0x11 / 10001b|| Same integer value in different formats&lt;br /&gt;
|-&lt;br /&gt;
| [col1,col2] || bool || [red,var(--mgreen)] || Generates a color box filled with col1 (false, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: red&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;) or col2 (true, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: #a0ffb8&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* Note that valid for &amp;quot;int&amp;quot; means all integral ODB types (regardless of size or signed/unsigned) and valid for &amp;quot;float&amp;quot; means both float and double.&lt;br /&gt;
&lt;br /&gt;
== modbbutton ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a push-button which can set a certain ODB entry to a specific value. To set the &amp;quot;Run number&amp;quot; to 100, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;button class=&amp;quot;modbbutton mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;100&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;[Button Text]&amp;amp;lt;/button&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== modbbox ==&lt;br /&gt;
&lt;br /&gt;
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 &#039;&#039;&#039;data-color&#039;&#039;&#039; is used, otherwise the &#039;&#039;&#039;data-background-color&#039;&#039;&#039; is used&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write Data&amp;quot; data-formula=&amp;quot;x &amp;gt; 0&amp;quot; style=&amp;quot;width: 30px; height: 30px; border: 1px solid black&amp;quot; data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally, a &amp;lt;code&amp;gt;data-formula&amp;lt;/code&amp;gt; can be specified. The formula sees the ODB value in the variable &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, and can do any boolean operation. If the result of this is true, then the box gets the &amp;lt;code&amp;gt;data-color&amp;lt;/code&amp;gt;, otherwise the &amp;lt;code&amp;gt;data-background-color&amp;lt;/code&amp;gt;.&lt;br /&gt;
Examples for these formulas are &amp;lt;code&amp;gt;x &amp;gt; 10&amp;lt;/code&amp;gt; for a comparison or &amp;lt;code&amp;gt;x &amp;amp; 1&amp;lt;/code&amp;gt; which will do a bitwise AND operation and is true only for odd numbers.&lt;br /&gt;
&lt;br /&gt;
== modbcheckbox ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a check box which can set a certain ODB entry to true or false. To set the &amp;quot;Write data&amp;quot; flag for the logger true or false, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; data-validate=&amp;quot;my_validate&amp;quot; /&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;modbvalue&amp;lt;/code&amp;gt; tag.&lt;br /&gt;
&lt;br /&gt;
== modbselect ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;/Runinfo/Run number&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;1&amp;quot;&amp;amp;gt;1&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;5&amp;quot;&amp;amp;gt;5&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;10&amp;quot;&amp;amp;gt;10&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&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 &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; to enable this behaviour. For ODB key &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, you will need to create an ODB entry called &amp;lt;code&amp;gt;Options x&amp;lt;/code&amp;gt; in the same directory; this &amp;quot;options&amp;quot; key should be a list, with each element in the list being an allowable option.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;!-- Will read options from &amp;quot;/Equipment/Example/Settings/Options Something&amp;quot; in this case --&amp;gt;&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Settings/Something&amp;quot; data-auto-options=&amp;quot;1&amp;quot;&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
The benefit of the &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; approach is that the same options will be shown on the regular ODB browser webpage. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modbhbar ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px; color: lightgreen;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;color: red&amp;quot; || Color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background-color: red&amp;quot; || Background color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of bar range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of bar range || 1 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown as text overlay || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specify format of data shown. See Table 2 above for options || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== mhaxis ==&lt;br /&gt;
&lt;br /&gt;
A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width: 500px; height: 22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a horizontal axis next to the bar. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the axis, must match the width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the axis, must be enough to display labels  || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;vertical-align: top&amp;quot; || Must be &amp;quot;top&amp;quot; if the axis is below the bar || &amp;quot;bottom&amp;quot; if not given&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbvbar ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;modbhbar&amp;lt;/code&amp;gt;, except the bar grows vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== mvaxis ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;mhaxis&amp;lt;/code&amp;gt;, except the axis is shown vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== modbthermo ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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&#039;s good for testing. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 30px&amp;quot; || Width of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 100px&amp;quot; || Total height of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of thermometer || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of thermometer background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown next to the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbgauge ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width: 100px; height: 50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-color=&amp;quot;darkgreen&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a circular gauge ranging from 0 to 10. Depending on the ODB value &amp;quot;Run number&amp;quot;. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 100px&amp;quot; || Width of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 50px&amp;quot; || Total height of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of gauge || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of gauge background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Changing properties of controls dynamically ==&lt;br /&gt;
&lt;br /&gt;
All custom controls can be configured to call a user&#039;s function when the control is first set up, or when the value changes. This is done by specifying the &#039;&#039;&#039;onload&#039;&#039;&#039; and/or &#039;&#039;&#039;onchange&#039;&#039;&#039; functions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;onload&#039;&#039;&#039; is called only once, when the control&#039;s value is first read from the ODB.&lt;br /&gt;
* &#039;&#039;&#039;onchange&#039;&#039;&#039; is called each time the value in the ODB changes.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 30?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
function check_therm(elem) {&lt;br /&gt;
  if (elem.value &amp;gt; 30) {&lt;br /&gt;
    elem.dataset.color = &amp;quot;red&amp;quot;;&lt;br /&gt;
  } else {&lt;br /&gt;
    elem.dataset.color = &amp;quot;blue&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
....&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; onchange=&amp;quot;check_therm(this)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
If you want the same function to be called for both onload and onchange, you could set &amp;lt;code&amp;gt;onload=&amp;quot;onchange()&amp;quot;&amp;lt;/code&amp;gt; and have the &amp;quot;real&amp;quot; function in onchange.&lt;br /&gt;
&lt;br /&gt;
== Dynamic ODB paths ==&lt;br /&gt;
&lt;br /&gt;
The path of a custom control is static, meaning it has to be written directly inside the control via &amp;lt;code&amp;gt;data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;lt;/code&amp;gt;. There can be however cases where the path is only known at runtime. For these cases, the path can be a user function which gets called during each update of the control and has to return the current ODB path. This can be useful for equipment values with names. The standard scheme of MIDAS is to have slow control variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Variables&amp;lt;/code&amp;gt; and the names of these variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names&amp;lt;/code&amp;gt;. If there are several different arrays under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;, you might also have something like &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names XXX&amp;lt;/code&amp;gt; where &amp;lt;code&amp;gt;XXX&amp;lt;/code&amp;gt; is the name of the key under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;. If one wants to use a name of a variable rather than its index, the dynamic path function can be uses like in the following example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
   let names;&lt;br /&gt;
&lt;br /&gt;
   function getNames() {&lt;br /&gt;
      mjsonrpc_db_get_value(&amp;quot;/Equipment/.../Settings/Names&amp;quot;).then(function (rpc) {&lt;br /&gt;
         names = rpc.result.data[0];&lt;br /&gt;
         mhttpd_init(&#039;Custom Page Example&#039;);&lt;br /&gt;
      });&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   function getpath(p) {&lt;br /&gt;
      if (mscbNames)&lt;br /&gt;
         return `/Equipment/.../Variables/MSCB[${names.indexOf(p)}]`;&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;getNames();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the page is initialized, the function &amp;lt;code&amp;gt;getNames()&amp;lt;/code&amp;gt; is called, which obtains the &amp;lt;code&amp;gt;Names&amp;lt;/code&amp;gt; array from the ODB and stores it into an array. After the names have been received, the function calls the usual &amp;lt;code&amp;gt;mhttpd_init()&amp;lt;/code&amp;gt; function. The &amp;quot;modbvalue&amp;quot; path is now set to &amp;lt;code&amp;gt;data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;lt;/code&amp;gt;. This function does a lookup in the &amp;lt;code&amp;gt;names&amp;lt;/code&amp;gt; array and returns the proper index, which is then used to access the corresponding value in the variables array. This method has the advantage that if the indices of an array change (e.g. by adding new variables at the front), the code does not have to update any index since the indices are calculated dynamically.&lt;br /&gt;
&lt;br /&gt;
== Changing values of indicators programmatically ==&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For such cases, the &amp;lt;code&amp;gt;data-odb-path&amp;lt;/code&amp;gt; attribute can be removed and the the display value can be changed via JavaScript code like following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mythermo&amp;quot; class=&amp;quot;modbthermo&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
  let t = document.getElementById(&amp;quot;mythermo&amp;quot;);&lt;br /&gt;
  t.setValue(15);&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mjshistory =&lt;br /&gt;
&lt;br /&gt;
Custom pages can contain one or more specific history panels usually shown on the &amp;quot;History&amp;quot; page. This makes it easy to combine current readings of values together with the history of these values.&lt;br /&gt;
&lt;br /&gt;
To enable interactive history panels, following lines have to be added to your custom page:&lt;br /&gt;
&lt;br /&gt;
Inisde the &amp;lt;head&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside the &amp;lt;body&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;body ... onload=&amp;quot;mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;&amp;lt;group&amp;gt;&amp;quot; data-panel=&amp;quot;&amp;lt;panel&amp;gt;&amp;quot; style=&amp;quot;width: 320px; height: 200px;&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
shows a history panel defined in the ODB under /History/Display/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; (replace &amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; with groups and panels from your experiment).&lt;br /&gt;
&lt;br /&gt;
Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| data-group || ODB group of history. Has to match a group under /History/Display || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-panel || ODB panel name of history. Has to match a panel name under /History/Display/&amp;amp;lt;group&amp;amp;gt;/ || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt;/Timescale is used. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-title || Flag to show or hide the title. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-values || Flag to show or hide the variable labels with their current value. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-axis || Flag to show or hide the X/Y axis. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the menu buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the zoom buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 320px&amp;quot; || Width of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 200px&amp;quot; || Height of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;border: 1px solid black&amp;quot; || Border around the history panel || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.&lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
the possibility to show the history of that variable. Just put a button next to the value and call &#039;&#039;&#039;mhistory_dialog(&amp;amp;lt;group&amp;amp;gt;, &amp;amp;lt;panel&amp;amp;gt;)&#039;&#039;&#039; from that button like:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;span class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&lt;br /&gt;
   &amp;lt;button onclick=&amp;quot;mhistory_dialog(&#039;group&#039;,&#039;panel&#039;)&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;icons/activity.svg&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The history panel will then be opened when the user clicks the button:&lt;br /&gt;
&lt;br /&gt;
[[File:History dialog.png|frame|left|Floating history dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one wants to avoid the definition of a history panel in the ODB, a &amp;quot;direct variable plot&amp;quot; can be done with the function &#039;&#039;&#039;mhistory_dialog_var(&amp;amp;lt;variable&amp;amp;gt;)&#039;&#039;&#039; where &amp;amp;lt;variable&amp;amp;gt; has the format like the variable definition in the ODB (e.g. &amp;quot;System:Trigger per sec.&amp;quot;). 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 &#039;&#039;&#039;mhistory_dialog_var(&amp;quot;System:Trigger per sec.&amp;quot;, {&amp;quot;Timescale&amp;quot;: &amp;quot;24h&amp;quot;});&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A full example of a custom page with a history panel is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
  &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot; type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body onload=&amp;quot;mhttpd_init(&#039;history_example&#039;); mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &lt;br /&gt;
  &amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;EPICS&amp;quot; data-panel=&amp;quot;Logging&amp;quot; style=&amp;quot;width: 500px; height: 300px;&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mplot =&lt;br /&gt;
&lt;br /&gt;
Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated [[Custom plots with mplot]] page.&lt;br /&gt;
&lt;br /&gt;
= Dialog, popup and modal boxes =&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Showing a message ==&lt;br /&gt;
&lt;br /&gt;
The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an &amp;quot;Ok&amp;quot; button that the user may click to dismiss the message.&lt;br /&gt;
&lt;br /&gt;
=== dlgMessage ===&lt;br /&gt;
&lt;br /&gt;
dlgMessage is the most customisable of the 3 functions. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(title, message, modal, error, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| title || string || Title of the dialog box || Yes&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || Main message content || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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&lt;br /&gt;
|-&lt;br /&gt;
| error || boolean || If true, will use a red background for the title || No. Defaults to false&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called with the user clicks the &amp;quot;Ok&amp;quot; button. The callback function will be called with just a single paramter. || No. Defaults to not calling a callback function.&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
Some example invocations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(&amp;quot;Message title&amp;quot;, &amp;quot;Message text&amp;quot;);&lt;br /&gt;
dlgMessage(&amp;quot;It&#039;s an error!&amp;quot;, &amp;quot;&amp;lt;b&amp;gt;Something is wrong!!!&amp;lt;/b&amp;gt;&amp;quot;, true, true);&lt;br /&gt;
dlgMessage(&amp;quot;Callback example&amp;quot;, &amp;quot;Testing&amp;quot;, true, false, my_message_cb, &amp;quot;some_param&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function my_message_cb(param) {&lt;br /&gt;
   alert(&amp;quot;Message dismissed! The param was &amp;quot; + param);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgAlert ===&lt;br /&gt;
&lt;br /&gt;
dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgAlert(message, callback)&lt;br /&gt;
&lt;br /&gt;
// The above is equivalent to:&lt;br /&gt;
dlgMessage(&amp;quot;Message&amp;quot;, message, true, true, callback)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgWait ===&lt;br /&gt;
&lt;br /&gt;
dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgWait(time_in_seconds, message)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Asking for input ==&lt;br /&gt;
&lt;br /&gt;
=== dlgQuery ===&lt;br /&gt;
&lt;br /&gt;
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&#039;s response is sent to a callback function that you must implement yourself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(message, value, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| value || string || Default value to pre-populate the answer field with || Yes (but can be empty string)&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or their answer if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(&amp;quot;Enter a value:&amp;quot;, &amp;quot;my default value&amp;quot;, my_query_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_query_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      // User clicked the Cancel button&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&#039;Value is &#039; + value + &#039;, param is &#039; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgConfirm ===&lt;br /&gt;
&lt;br /&gt;
dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(message, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or true if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(&amp;quot;Are you sure you wish to do that?&amp;quot;, my_confirm_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_confirm_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      alert(&amp;quot;User clicked Cancel! Param was &amp;quot; + param);&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&amp;quot;User clicked Ok! Param was &amp;quot; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Implementing SVGs =&lt;br /&gt;
&lt;br /&gt;
An SVG file loaded on a custom page can be interactively modified using JavaScript.&lt;br /&gt;
This is useful for graphically displaying ODB values in complex service or detector systems.&lt;br /&gt;
&lt;br /&gt;
An SVG file can be embedded like this:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Objects within the SVG can be modified—for example, by changing their fill color as shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument; &lt;br /&gt;
const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
obj_Valve.style.fill = &amp;quot;#00FF00&amp;quot;; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SVG images are stored in XML format. In Inkscape, object properties can be interactively edited using the XML Editor.&lt;br /&gt;
A full example is given below in [[SVGs on Custom Pages]]&lt;br /&gt;
&lt;br /&gt;
= Complete Example =&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/Custom&lt;br /&gt;
  Path     /midas/resources&lt;br /&gt;
  Test     a_example.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Code ==&lt;br /&gt;
The file &#039;&#039;&#039;a_example.html&#039;&#039;&#039; contains the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;Example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;style&amp;gt;&lt;br /&gt;
      .mtable td { padding: 10px; }&lt;br /&gt;
   &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;th colspan=&amp;quot;2&amp;quot; class=&amp;quot;mtableheader&amp;quot;&amp;gt;Status&amp;lt;/th&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td style=&amp;quot;width: 200px;&amp;quot;&amp;gt;&lt;br /&gt;
            Run number:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run start:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Start time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run stop:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Stop time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Check box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) --&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; onchange=&amp;quot;dlgAlert(&#039;Flag has changed&#039;);&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Color box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- box changes color according to /Logger/Write data --&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbbox&amp;quot; style=&amp;quot;width: 30px; height: 30px;&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Horizontal bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width:300px;height:20px;color:orange;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-print-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:500px;height:22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px;color:lightblue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 200px; height: 10px;color:lightgreen;background-color:white&amp;quot;&lt;br /&gt;
                 data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:200px;height:22px;vertical-align:top;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-line=&amp;quot;0&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Vertical bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:right;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;modbvbar&amp;quot;&lt;br /&gt;
                  style=&amp;quot;width:20px;height:200px;color:yellow;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                  data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;width:10px;height:200px;vertical-align:top;color:red&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                  data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:left;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                                                                data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Thermometer:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:60px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-scale=&amp;quot;1&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 9?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-background-color=&amp;quot;white&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Gauges:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot; data-background-color=&amp;quot;lightgrey&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:65px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;red&amp;quot; data-value=&amp;quot;1&amp;quot; data-scale=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- div around image with &amp;quot;relative&amp;quot; position as anchor for labels and bars --&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:300px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;img src=&amp;quot;tank.gif&amp;quot;&amp;gt; &amp;lt;!-- background image of tank --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- label next to valve --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute;top:157px;left:288px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- vertical bar inside tank, render red if value &amp;gt; 9 --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;position:absolute;top:80px;left:10px;width:104px;height:170px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;11&amp;quot; data-color=&amp;quot;green&amp;quot;&lt;br /&gt;
                    onchange=&amp;quot;this.firstChild.style.backgroundColor=(this.value &amp;gt; 9)?&#039;red&#039;:&#039;green&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- thermometer inside tank --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;position:absolute;top:140px;left:20px;width:20px;height:100px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                    data-color=&amp;quot;blue&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- three buttons to change an ODB entry (run number in this example) --&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 1&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;5&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 5&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;10&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 10&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
which results in the page shown in Figure 1 below:&lt;br /&gt;
&lt;br /&gt;
[[File:Custom17.png|frame|left|Figure 1  Example custom page using most features]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Old custom page feature =&lt;br /&gt;
&lt;br /&gt;
There are a number of deprecated custom page features, which can be seen here: [[Old Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:mhttpd Pages]] [[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3525</id>
		<title>Custom Page</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3525"/>
		<updated>2025-05-12T12:23:30Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Implementing SVGs */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
{{mhttpdpages3|[[Custom Page Features]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}&lt;br /&gt;
&lt;br /&gt;
= Purpose =&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
= Examples of Custom Pages =&lt;br /&gt;
&lt;br /&gt;
Click on the thumbnails to enlarge.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]] || &#039;&#039;&#039;Example 1&#039;&#039;&#039;&lt;br /&gt;
This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of &amp;quot;fills&amp;quot; and &amp;quot;labels&amp;quot;. 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).&lt;br /&gt;
|-&lt;br /&gt;
| [[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]] || &#039;&#039;&#039;Example 2&#039;&#039;&#039;&lt;br /&gt;
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 &amp;quot;virtual&amp;quot; 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.&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For details using Xvfb server, please contact Ryu Sawada &amp;lt;sawada@icepp.s.u-tokyo.ac.jp&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| [[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]] || &#039;&#039;&#039;Example 3&#039;&#039;&#039;&lt;br /&gt;
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.   &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Access a Custom Page from the Regular MIDAS pages =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
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.&lt;br /&gt;
See [[Custom Page Features#Resource files]] for more information.&lt;br /&gt;
&lt;br /&gt;
If the key  {{Odbpath|path=/Custom/myPage&amp;amp;}}  (see Note) is created, e.g.&lt;br /&gt;
 odbedit&amp;gt; ls /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
&lt;br /&gt;
the custom link on the left navigation bar will be &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; and the URL for the resulting custom page will be of the form &amp;lt;code&amp;gt;http://myhost.mydomain:myport/cmd=?Custom&amp;amp;page=myPage&amp;lt;/code&amp;gt; (see also [[mhttpd#usage]]).  &lt;br /&gt;
Clicking on &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; will display the custom page in the same window.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
: With the &amp;quot;&amp;amp;&amp;quot; symbol in the key name, the page would appear in a new tab/window. See [[/Custom ODB tree#Key names|Key names]] for more information.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
 odbedit&amp;gt; ls -r /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
    Calorimeter&lt;br /&gt;
        HV                            hv.html&lt;br /&gt;
        Rates                         rates.html&lt;br /&gt;
    Baeam&lt;br /&gt;
        Beamline                      beamline.html&lt;br /&gt;
        Accelerator                   accel.html&lt;br /&gt;
&lt;br /&gt;
The pages then include the subdirectory in the URL, like &lt;br /&gt;
&lt;br /&gt;
 http://localhost:8080?cmd=custom&amp;amp;page=beam/Beamline&lt;br /&gt;
&lt;br /&gt;
Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Calorimeter/HV&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
in order to keep the submenu open after you select the custom page.&lt;br /&gt;
&lt;br /&gt;
= How to write a custom page =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
* modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.&lt;br /&gt;
* 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.&lt;br /&gt;
* modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== How to use the standard MIDAS navigation bars on your custom page == &lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;myPage&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;myPage&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_start --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
 ADD YOUR HTML/JS  CODE here...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call &amp;lt;code&amp;gt;mhttpd_init(&#039;myPage&#039;)&amp;lt;/code&amp;gt; is executed when the page is loaded, and  &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage.&lt;br /&gt;
This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying [[Mjsonrpc | javascript libraries]].&lt;br /&gt;
&lt;br /&gt;
= modb* Javascript scheme = &lt;br /&gt;
&lt;br /&gt;
The general scheme of the custom page scheme is to write &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;amp;lt;span class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt; tags with special class names, most of them starting with &amp;quot;modb...&amp;quot; (&amp;quot;MIDAS-ODB&amp;quot;). Use a &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want the element to appear in a separate line, and use the &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want to display the element in-line. The following description uses only &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tags, but all of them can be changed to &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
All HTML tags with &amp;quot;modb...&amp;quot; names are scanned by the &amp;lt;code&amp;gt;mhttp_init(&#039;name&#039;)&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;mhttpd_init(&#039;name&#039;, interval)&amp;lt;/code&amp;gt; where &amp;quot;interval&amp;quot; is in milliseconds. You can add or remove &amp;quot;modb...&amp;quot; elements at any time using javascript, and the new elements will be &amp;quot;discovered&amp;quot; automatically during the next update.&lt;br /&gt;
&lt;br /&gt;
== modbset(path, value) ==&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset(&amp;quot;odb path&amp;quot;, value)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset([&amp;quot;odb path1&amp;quot;, &amp;quot;odb path2&amp;quot;, ...], [value1, value2, ...])&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modb ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for Midas ODB) &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot; onchange=&amp;quot;func()&amp;quot;&amp;amp;gt;&amp;lt;/code&amp;gt; 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 &amp;amp;lt;script&amp;amp;gt;...&amp;amp;lt;/script&amp;amp;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.&lt;br /&gt;
&lt;br /&gt;
The current value of the ODB entry is available inside the &amp;quot;onchange&amp;quot; function as &#039;&#039;&#039;this.value&#039;&#039;&#039;. Following tag will call a function which logs the current run number in the JavaScript console window:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to &#039;&#039;&#039;this.value&#039;&#039;&#039; as a JavaSctipt object such as&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value[&amp;quot;run number&amp;quot;]);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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 &#039;&#039;&#039;value[&amp;quot;run number&amp;quot;]&#039;&#039;&#039; as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as &#039;&#039;&#039;value.state&#039;&#039;&#039; for /Runinfo/State for example.&lt;br /&gt;
&lt;br /&gt;
== modbvalue ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for &amp;quot;Midas ODB VALUE&amp;quot;) &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 1: List of valid options for modbvalue tag&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| data-name || class=&amp;quot;modbvalue&amp;quot; || Tells the framework to replace this tag with an ODB value&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || data-odb-path = &amp;quot;/Runinfo/Run number&amp;quot; || Path to the value in the ODB. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]].&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-editable || data-odb-editable=&amp;quot;1&amp;quot; || 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.&lt;br /&gt;
|-&lt;br /&gt;
| data-format || data-format=&amp;quot;f3&amp;quot; || Specify format of data shown. See Table 2 below for options.&lt;br /&gt;
|-&lt;br /&gt;
| data-size || data-size=&amp;quot;8&amp;quot; || Specify size (in chars) of edit box if one modifies the value. Default is 10.&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || data-formula=&amp;quot;2*x+3&amp;quot; || Specify an optional formula to process with the current ODB value stored in x&lt;br /&gt;
|-&lt;br /&gt;
| data-validate || data-validate=&amp;quot;func&amp;quot; || Specify an optional validation function which gets called before submitting data (see below)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Validation ===&lt;br /&gt;
&lt;br /&gt;
Before a modified value is submitted to the ODB, an optional validation function can be called via the &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; option. The function&lt;br /&gt;
will be called with the current value and a reference to the current modbvalue element. If the function returns &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, then the value&lt;br /&gt;
is not sent to the ODB.&lt;br /&gt;
&lt;br /&gt;
Following example shows a function which just rejects the submission of values above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt; &amp;amp;lt;!-- needed of dlgAlert() --&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        dlgAlert(&amp;quot;Value cannot be above 1000&amp;quot;);&lt;br /&gt;
        return false;&lt;br /&gt;
     }&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following function corrects the return value to 1000 if it&#039;s above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate2(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        element.childNodes[0].value = 1000;&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Confirmation ===&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
This is done with the option &amp;lt;code&amp;gt;data-confirm=&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt;. A dialog box is then shown with the &amp;lt;code&amp;gt;&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt; as the main text and two buttons with &amp;quot;OK&amp;quot; and &amp;quot;Cancel&amp;quot;.&lt;br /&gt;
Hitting &amp;quot;OK&amp;quot; finally writes the value to the ODB, hitting &amp;quot;Cancel&amp;quot; keeps the old value.&lt;br /&gt;
&lt;br /&gt;
Following example shows an example:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-confirm=&amp;quot;Are you sure to change the value?&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following dialog box is then showed:&lt;br /&gt;
&lt;br /&gt;
[[File:Confirm.png|frame|left|Optional confirm dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Formatting ===&lt;br /&gt;
&lt;br /&gt;
Table 2 below lists the format specifiers supported with a modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 2: Format specifiers for modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Valid for* !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| %d || int || 1234 || Shows a number in decimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %t || int, float || 1,234,567 || Shows a number in decimal encoding with commas as thousands separator&lt;br /&gt;
|-&lt;br /&gt;
| %x || int || 0x4D2 || Shows a number in hexadecimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %b || int || 10011010010b|| Shows a number in binary encoding. Options t, d, x, b can be combined, like &amp;lt;code&amp;gt;data-format=&amp;quot;%d / %x&amp;quot;&amp;lt;/code&amp;gt; to produce &amp;lt;code&amp;gt;1234 / 0x4D2&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| %f&amp;amp;lt;x&amp;amp;gt; || float || 1.234 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal. A value of f0 shows only the integer part.&lt;br /&gt;
|-&lt;br /&gt;
| %p&amp;amp;lt;x&amp;amp;gt; || float || 1.23 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012&lt;br /&gt;
|-&lt;br /&gt;
| %e&amp;amp;lt;x&amp;amp;gt; || float || 1.23e-05 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal in exponential format. Useful for small number such as pressures.&lt;br /&gt;
|-&lt;br /&gt;
| T= %f1 C || float || T= 25.4 C || Combination of text and float value is possible.&lt;br /&gt;
|-&lt;br /&gt;
| %d / %x / %b || int || 17 / 0x11 / 10001b|| Same integer value in different formats&lt;br /&gt;
|-&lt;br /&gt;
| [col1,col2] || bool || [red,var(--mgreen)] || Generates a color box filled with col1 (false, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: red&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;) or col2 (true, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: #a0ffb8&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* Note that valid for &amp;quot;int&amp;quot; means all integral ODB types (regardless of size or signed/unsigned) and valid for &amp;quot;float&amp;quot; means both float and double.&lt;br /&gt;
&lt;br /&gt;
== modbbutton ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a push-button which can set a certain ODB entry to a specific value. To set the &amp;quot;Run number&amp;quot; to 100, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;button class=&amp;quot;modbbutton mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;100&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;[Button Text]&amp;amp;lt;/button&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== modbbox ==&lt;br /&gt;
&lt;br /&gt;
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 &#039;&#039;&#039;data-color&#039;&#039;&#039; is used, otherwise the &#039;&#039;&#039;data-background-color&#039;&#039;&#039; is used&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write Data&amp;quot; data-formula=&amp;quot;x &amp;gt; 0&amp;quot; style=&amp;quot;width: 30px; height: 30px; border: 1px solid black&amp;quot; data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally, a &amp;lt;code&amp;gt;data-formula&amp;lt;/code&amp;gt; can be specified. The formula sees the ODB value in the variable &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, and can do any boolean operation. If the result of this is true, then the box gets the &amp;lt;code&amp;gt;data-color&amp;lt;/code&amp;gt;, otherwise the &amp;lt;code&amp;gt;data-background-color&amp;lt;/code&amp;gt;.&lt;br /&gt;
Examples for these formulas are &amp;lt;code&amp;gt;x &amp;gt; 10&amp;lt;/code&amp;gt; for a comparison or &amp;lt;code&amp;gt;x &amp;amp; 1&amp;lt;/code&amp;gt; which will do a bitwise AND operation and is true only for odd numbers.&lt;br /&gt;
&lt;br /&gt;
== modbcheckbox ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a check box which can set a certain ODB entry to true or false. To set the &amp;quot;Write data&amp;quot; flag for the logger true or false, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; data-validate=&amp;quot;my_validate&amp;quot; /&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;modbvalue&amp;lt;/code&amp;gt; tag.&lt;br /&gt;
&lt;br /&gt;
== modbselect ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;/Runinfo/Run number&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;1&amp;quot;&amp;amp;gt;1&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;5&amp;quot;&amp;amp;gt;5&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;10&amp;quot;&amp;amp;gt;10&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&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 &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; to enable this behaviour. For ODB key &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, you will need to create an ODB entry called &amp;lt;code&amp;gt;Options x&amp;lt;/code&amp;gt; in the same directory; this &amp;quot;options&amp;quot; key should be a list, with each element in the list being an allowable option.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;!-- Will read options from &amp;quot;/Equipment/Example/Settings/Options Something&amp;quot; in this case --&amp;gt;&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Settings/Something&amp;quot; data-auto-options=&amp;quot;1&amp;quot;&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
The benefit of the &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; approach is that the same options will be shown on the regular ODB browser webpage. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modbhbar ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px; color: lightgreen;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;color: red&amp;quot; || Color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background-color: red&amp;quot; || Background color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of bar range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of bar range || 1 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown as text overlay || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specify format of data shown. See Table 2 above for options || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== mhaxis ==&lt;br /&gt;
&lt;br /&gt;
A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width: 500px; height: 22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a horizontal axis next to the bar. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the axis, must match the width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the axis, must be enough to display labels  || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;vertical-align: top&amp;quot; || Must be &amp;quot;top&amp;quot; if the axis is below the bar || &amp;quot;bottom&amp;quot; if not given&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbvbar ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;modbhbar&amp;lt;/code&amp;gt;, except the bar grows vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== mvaxis ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;mhaxis&amp;lt;/code&amp;gt;, except the axis is shown vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== modbthermo ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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&#039;s good for testing. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 30px&amp;quot; || Width of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 100px&amp;quot; || Total height of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of thermometer || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of thermometer background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown next to the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbgauge ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width: 100px; height: 50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-color=&amp;quot;darkgreen&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a circular gauge ranging from 0 to 10. Depending on the ODB value &amp;quot;Run number&amp;quot;. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 100px&amp;quot; || Width of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 50px&amp;quot; || Total height of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of gauge || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of gauge background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Changing properties of controls dynamically ==&lt;br /&gt;
&lt;br /&gt;
All custom controls can be configured to call a user&#039;s function when the control is first set up, or when the value changes. This is done by specifying the &#039;&#039;&#039;onload&#039;&#039;&#039; and/or &#039;&#039;&#039;onchange&#039;&#039;&#039; functions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;onload&#039;&#039;&#039; is called only once, when the control&#039;s value is first read from the ODB.&lt;br /&gt;
* &#039;&#039;&#039;onchange&#039;&#039;&#039; is called each time the value in the ODB changes.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 30?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
function check_therm(elem) {&lt;br /&gt;
  if (elem.value &amp;gt; 30) {&lt;br /&gt;
    elem.dataset.color = &amp;quot;red&amp;quot;;&lt;br /&gt;
  } else {&lt;br /&gt;
    elem.dataset.color = &amp;quot;blue&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
....&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; onchange=&amp;quot;check_therm(this)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
If you want the same function to be called for both onload and onchange, you could set &amp;lt;code&amp;gt;onload=&amp;quot;onchange()&amp;quot;&amp;lt;/code&amp;gt; and have the &amp;quot;real&amp;quot; function in onchange.&lt;br /&gt;
&lt;br /&gt;
== Dynamic ODB paths ==&lt;br /&gt;
&lt;br /&gt;
The path of a custom control is static, meaning it has to be written directly inside the control via &amp;lt;code&amp;gt;data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;lt;/code&amp;gt;. There can be however cases where the path is only known at runtime. For these cases, the path can be a user function which gets called during each update of the control and has to return the current ODB path. This can be useful for equipment values with names. The standard scheme of MIDAS is to have slow control variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Variables&amp;lt;/code&amp;gt; and the names of these variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names&amp;lt;/code&amp;gt;. If there are several different arrays under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;, you might also have something like &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names XXX&amp;lt;/code&amp;gt; where &amp;lt;code&amp;gt;XXX&amp;lt;/code&amp;gt; is the name of the key under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;. If one wants to use a name of a variable rather than its index, the dynamic path function can be uses like in the following example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
   let names;&lt;br /&gt;
&lt;br /&gt;
   function getNames() {&lt;br /&gt;
      mjsonrpc_db_get_value(&amp;quot;/Equipment/.../Settings/Names&amp;quot;).then(function (rpc) {&lt;br /&gt;
         names = rpc.result.data[0];&lt;br /&gt;
         mhttpd_init(&#039;Custom Page Example&#039;);&lt;br /&gt;
      });&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   function getpath(p) {&lt;br /&gt;
      if (mscbNames)&lt;br /&gt;
         return `/Equipment/.../Variables/MSCB[${names.indexOf(p)}]`;&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;getNames();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the page is initialized, the function &amp;lt;code&amp;gt;getNames()&amp;lt;/code&amp;gt; is called, which obtains the &amp;lt;code&amp;gt;Names&amp;lt;/code&amp;gt; array from the ODB and stores it into an array. After the names have been received, the function calls the usual &amp;lt;code&amp;gt;mhttpd_init()&amp;lt;/code&amp;gt; function. The &amp;quot;modbvalue&amp;quot; path is now set to &amp;lt;code&amp;gt;data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;lt;/code&amp;gt;. This function does a lookup in the &amp;lt;code&amp;gt;names&amp;lt;/code&amp;gt; array and returns the proper index, which is then used to access the corresponding value in the variables array. This method has the advantage that if the indices of an array change (e.g. by adding new variables at the front), the code does not have to update any index since the indices are calculated dynamically.&lt;br /&gt;
&lt;br /&gt;
== Changing values of indicators programmatically ==&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For such cases, the &amp;lt;code&amp;gt;data-odb-path&amp;lt;/code&amp;gt; attribute can be removed and the the display value can be changed via JavaScript code like following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mythermo&amp;quot; class=&amp;quot;modbthermo&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
  let t = document.getElementById(&amp;quot;mythermo&amp;quot;);&lt;br /&gt;
  t.setValue(15);&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mjshistory =&lt;br /&gt;
&lt;br /&gt;
Custom pages can contain one or more specific history panels usually shown on the &amp;quot;History&amp;quot; page. This makes it easy to combine current readings of values together with the history of these values.&lt;br /&gt;
&lt;br /&gt;
To enable interactive history panels, following lines have to be added to your custom page:&lt;br /&gt;
&lt;br /&gt;
Inisde the &amp;lt;head&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside the &amp;lt;body&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;body ... onload=&amp;quot;mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;&amp;lt;group&amp;gt;&amp;quot; data-panel=&amp;quot;&amp;lt;panel&amp;gt;&amp;quot; style=&amp;quot;width: 320px; height: 200px;&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
shows a history panel defined in the ODB under /History/Display/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; (replace &amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; with groups and panels from your experiment).&lt;br /&gt;
&lt;br /&gt;
Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| data-group || ODB group of history. Has to match a group under /History/Display || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-panel || ODB panel name of history. Has to match a panel name under /History/Display/&amp;amp;lt;group&amp;amp;gt;/ || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt;/Timescale is used. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-title || Flag to show or hide the title. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-values || Flag to show or hide the variable labels with their current value. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-axis || Flag to show or hide the X/Y axis. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the menu buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the zoom buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 320px&amp;quot; || Width of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 200px&amp;quot; || Height of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;border: 1px solid black&amp;quot; || Border around the history panel || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.&lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
the possibility to show the history of that variable. Just put a button next to the value and call &#039;&#039;&#039;mhistory_dialog(&amp;amp;lt;group&amp;amp;gt;, &amp;amp;lt;panel&amp;amp;gt;)&#039;&#039;&#039; from that button like:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;span class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&lt;br /&gt;
   &amp;lt;button onclick=&amp;quot;mhistory_dialog(&#039;group&#039;,&#039;panel&#039;)&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;icons/activity.svg&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The history panel will then be opened when the user clicks the button:&lt;br /&gt;
&lt;br /&gt;
[[File:History dialog.png|frame|left|Floating history dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one wants to avoid the definition of a history panel in the ODB, a &amp;quot;direct variable plot&amp;quot; can be done with the function &#039;&#039;&#039;mhistory_dialog_var(&amp;amp;lt;variable&amp;amp;gt;)&#039;&#039;&#039; where &amp;amp;lt;variable&amp;amp;gt; has the format like the variable definition in the ODB (e.g. &amp;quot;System:Trigger per sec.&amp;quot;). 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 &#039;&#039;&#039;mhistory_dialog_var(&amp;quot;System:Trigger per sec.&amp;quot;, {&amp;quot;Timescale&amp;quot;: &amp;quot;24h&amp;quot;});&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A full example of a custom page with a history panel is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
  &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot; type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body onload=&amp;quot;mhttpd_init(&#039;history_example&#039;); mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &lt;br /&gt;
  &amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;EPICS&amp;quot; data-panel=&amp;quot;Logging&amp;quot; style=&amp;quot;width: 500px; height: 300px;&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mplot =&lt;br /&gt;
&lt;br /&gt;
Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated [[Custom plots with mplot]] page.&lt;br /&gt;
&lt;br /&gt;
= Dialog, popup and modal boxes =&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Showing a message ==&lt;br /&gt;
&lt;br /&gt;
The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an &amp;quot;Ok&amp;quot; button that the user may click to dismiss the message.&lt;br /&gt;
&lt;br /&gt;
=== dlgMessage ===&lt;br /&gt;
&lt;br /&gt;
dlgMessage is the most customisable of the 3 functions. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(title, message, modal, error, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| title || string || Title of the dialog box || Yes&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || Main message content || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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&lt;br /&gt;
|-&lt;br /&gt;
| error || boolean || If true, will use a red background for the title || No. Defaults to false&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called with the user clicks the &amp;quot;Ok&amp;quot; button. The callback function will be called with just a single paramter. || No. Defaults to not calling a callback function.&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
Some example invocations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(&amp;quot;Message title&amp;quot;, &amp;quot;Message text&amp;quot;);&lt;br /&gt;
dlgMessage(&amp;quot;It&#039;s an error!&amp;quot;, &amp;quot;&amp;lt;b&amp;gt;Something is wrong!!!&amp;lt;/b&amp;gt;&amp;quot;, true, true);&lt;br /&gt;
dlgMessage(&amp;quot;Callback example&amp;quot;, &amp;quot;Testing&amp;quot;, true, false, my_message_cb, &amp;quot;some_param&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function my_message_cb(param) {&lt;br /&gt;
   alert(&amp;quot;Message dismissed! The param was &amp;quot; + param);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgAlert ===&lt;br /&gt;
&lt;br /&gt;
dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgAlert(message, callback)&lt;br /&gt;
&lt;br /&gt;
// The above is equivalent to:&lt;br /&gt;
dlgMessage(&amp;quot;Message&amp;quot;, message, true, true, callback)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgWait ===&lt;br /&gt;
&lt;br /&gt;
dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgWait(time_in_seconds, message)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Asking for input ==&lt;br /&gt;
&lt;br /&gt;
=== dlgQuery ===&lt;br /&gt;
&lt;br /&gt;
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&#039;s response is sent to a callback function that you must implement yourself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(message, value, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| value || string || Default value to pre-populate the answer field with || Yes (but can be empty string)&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or their answer if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(&amp;quot;Enter a value:&amp;quot;, &amp;quot;my default value&amp;quot;, my_query_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_query_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      // User clicked the Cancel button&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&#039;Value is &#039; + value + &#039;, param is &#039; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgConfirm ===&lt;br /&gt;
&lt;br /&gt;
dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(message, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or true if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(&amp;quot;Are you sure you wish to do that?&amp;quot;, my_confirm_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_confirm_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      alert(&amp;quot;User clicked Cancel! Param was &amp;quot; + param);&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&amp;quot;User clicked Ok! Param was &amp;quot; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Implementing SVGs =&lt;br /&gt;
&lt;br /&gt;
Complex service or detector systems can be visualized using vector images, such as SVGs created with Inkscape.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These SVGs can be used, for example, as background images with modb... class objects positioned at specific locations.&lt;br /&gt;
&lt;br /&gt;
Interactive modifications — such as changing a valve’s color or resizing objects based on ODB values — are explained in more detail in [[Custom Page Features#Interactive SVG Implementation]].&lt;br /&gt;
&lt;br /&gt;
= Complete Example =&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/Custom&lt;br /&gt;
  Path     /midas/resources&lt;br /&gt;
  Test     a_example.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Code ==&lt;br /&gt;
The file &#039;&#039;&#039;a_example.html&#039;&#039;&#039; contains the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;Example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;style&amp;gt;&lt;br /&gt;
      .mtable td { padding: 10px; }&lt;br /&gt;
   &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;th colspan=&amp;quot;2&amp;quot; class=&amp;quot;mtableheader&amp;quot;&amp;gt;Status&amp;lt;/th&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td style=&amp;quot;width: 200px;&amp;quot;&amp;gt;&lt;br /&gt;
            Run number:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run start:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Start time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run stop:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Stop time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Check box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) --&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; onchange=&amp;quot;dlgAlert(&#039;Flag has changed&#039;);&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Color box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- box changes color according to /Logger/Write data --&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbbox&amp;quot; style=&amp;quot;width: 30px; height: 30px;&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Horizontal bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width:300px;height:20px;color:orange;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-print-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:500px;height:22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px;color:lightblue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 200px; height: 10px;color:lightgreen;background-color:white&amp;quot;&lt;br /&gt;
                 data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:200px;height:22px;vertical-align:top;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-line=&amp;quot;0&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Vertical bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:right;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;modbvbar&amp;quot;&lt;br /&gt;
                  style=&amp;quot;width:20px;height:200px;color:yellow;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                  data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;width:10px;height:200px;vertical-align:top;color:red&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                  data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:left;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                                                                data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Thermometer:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:60px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-scale=&amp;quot;1&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 9?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-background-color=&amp;quot;white&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Gauges:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot; data-background-color=&amp;quot;lightgrey&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:65px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;red&amp;quot; data-value=&amp;quot;1&amp;quot; data-scale=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- div around image with &amp;quot;relative&amp;quot; position as anchor for labels and bars --&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:300px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;img src=&amp;quot;tank.gif&amp;quot;&amp;gt; &amp;lt;!-- background image of tank --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- label next to valve --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute;top:157px;left:288px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- vertical bar inside tank, render red if value &amp;gt; 9 --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;position:absolute;top:80px;left:10px;width:104px;height:170px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;11&amp;quot; data-color=&amp;quot;green&amp;quot;&lt;br /&gt;
                    onchange=&amp;quot;this.firstChild.style.backgroundColor=(this.value &amp;gt; 9)?&#039;red&#039;:&#039;green&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- thermometer inside tank --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;position:absolute;top:140px;left:20px;width:20px;height:100px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                    data-color=&amp;quot;blue&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- three buttons to change an ODB entry (run number in this example) --&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 1&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;5&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 5&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;10&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 10&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
which results in the page shown in Figure 1 below:&lt;br /&gt;
&lt;br /&gt;
[[File:Custom17.png|frame|left|Figure 1  Example custom page using most features]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Old custom page feature =&lt;br /&gt;
&lt;br /&gt;
There are a number of deprecated custom page features, which can be seen here: [[Old Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:mhttpd Pages]] [[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3524</id>
		<title>Custom Page</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3524"/>
		<updated>2025-05-12T12:22:20Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Implementing SVGs */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
{{mhttpdpages3|[[Custom Page Features]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}&lt;br /&gt;
&lt;br /&gt;
= Purpose =&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
= Examples of Custom Pages =&lt;br /&gt;
&lt;br /&gt;
Click on the thumbnails to enlarge.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]] || &#039;&#039;&#039;Example 1&#039;&#039;&#039;&lt;br /&gt;
This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of &amp;quot;fills&amp;quot; and &amp;quot;labels&amp;quot;. 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).&lt;br /&gt;
|-&lt;br /&gt;
| [[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]] || &#039;&#039;&#039;Example 2&#039;&#039;&#039;&lt;br /&gt;
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 &amp;quot;virtual&amp;quot; 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.&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For details using Xvfb server, please contact Ryu Sawada &amp;lt;sawada@icepp.s.u-tokyo.ac.jp&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| [[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]] || &#039;&#039;&#039;Example 3&#039;&#039;&#039;&lt;br /&gt;
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.   &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Access a Custom Page from the Regular MIDAS pages =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
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.&lt;br /&gt;
See [[Custom Page Features#Resource files]] for more information.&lt;br /&gt;
&lt;br /&gt;
If the key  {{Odbpath|path=/Custom/myPage&amp;amp;}}  (see Note) is created, e.g.&lt;br /&gt;
 odbedit&amp;gt; ls /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
&lt;br /&gt;
the custom link on the left navigation bar will be &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; and the URL for the resulting custom page will be of the form &amp;lt;code&amp;gt;http://myhost.mydomain:myport/cmd=?Custom&amp;amp;page=myPage&amp;lt;/code&amp;gt; (see also [[mhttpd#usage]]).  &lt;br /&gt;
Clicking on &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; will display the custom page in the same window.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
: With the &amp;quot;&amp;amp;&amp;quot; symbol in the key name, the page would appear in a new tab/window. See [[/Custom ODB tree#Key names|Key names]] for more information.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
 odbedit&amp;gt; ls -r /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
    Calorimeter&lt;br /&gt;
        HV                            hv.html&lt;br /&gt;
        Rates                         rates.html&lt;br /&gt;
    Baeam&lt;br /&gt;
        Beamline                      beamline.html&lt;br /&gt;
        Accelerator                   accel.html&lt;br /&gt;
&lt;br /&gt;
The pages then include the subdirectory in the URL, like &lt;br /&gt;
&lt;br /&gt;
 http://localhost:8080?cmd=custom&amp;amp;page=beam/Beamline&lt;br /&gt;
&lt;br /&gt;
Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Calorimeter/HV&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
in order to keep the submenu open after you select the custom page.&lt;br /&gt;
&lt;br /&gt;
= How to write a custom page =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
* modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.&lt;br /&gt;
* 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.&lt;br /&gt;
* modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== How to use the standard MIDAS navigation bars on your custom page == &lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;myPage&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;myPage&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_start --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
 ADD YOUR HTML/JS  CODE here...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call &amp;lt;code&amp;gt;mhttpd_init(&#039;myPage&#039;)&amp;lt;/code&amp;gt; is executed when the page is loaded, and  &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage.&lt;br /&gt;
This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying [[Mjsonrpc | javascript libraries]].&lt;br /&gt;
&lt;br /&gt;
= modb* Javascript scheme = &lt;br /&gt;
&lt;br /&gt;
The general scheme of the custom page scheme is to write &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;amp;lt;span class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt; tags with special class names, most of them starting with &amp;quot;modb...&amp;quot; (&amp;quot;MIDAS-ODB&amp;quot;). Use a &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want the element to appear in a separate line, and use the &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want to display the element in-line. The following description uses only &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tags, but all of them can be changed to &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
All HTML tags with &amp;quot;modb...&amp;quot; names are scanned by the &amp;lt;code&amp;gt;mhttp_init(&#039;name&#039;)&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;mhttpd_init(&#039;name&#039;, interval)&amp;lt;/code&amp;gt; where &amp;quot;interval&amp;quot; is in milliseconds. You can add or remove &amp;quot;modb...&amp;quot; elements at any time using javascript, and the new elements will be &amp;quot;discovered&amp;quot; automatically during the next update.&lt;br /&gt;
&lt;br /&gt;
== modbset(path, value) ==&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset(&amp;quot;odb path&amp;quot;, value)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset([&amp;quot;odb path1&amp;quot;, &amp;quot;odb path2&amp;quot;, ...], [value1, value2, ...])&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modb ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for Midas ODB) &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot; onchange=&amp;quot;func()&amp;quot;&amp;amp;gt;&amp;lt;/code&amp;gt; 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 &amp;amp;lt;script&amp;amp;gt;...&amp;amp;lt;/script&amp;amp;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.&lt;br /&gt;
&lt;br /&gt;
The current value of the ODB entry is available inside the &amp;quot;onchange&amp;quot; function as &#039;&#039;&#039;this.value&#039;&#039;&#039;. Following tag will call a function which logs the current run number in the JavaScript console window:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to &#039;&#039;&#039;this.value&#039;&#039;&#039; as a JavaSctipt object such as&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value[&amp;quot;run number&amp;quot;]);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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 &#039;&#039;&#039;value[&amp;quot;run number&amp;quot;]&#039;&#039;&#039; as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as &#039;&#039;&#039;value.state&#039;&#039;&#039; for /Runinfo/State for example.&lt;br /&gt;
&lt;br /&gt;
== modbvalue ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for &amp;quot;Midas ODB VALUE&amp;quot;) &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 1: List of valid options for modbvalue tag&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| data-name || class=&amp;quot;modbvalue&amp;quot; || Tells the framework to replace this tag with an ODB value&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || data-odb-path = &amp;quot;/Runinfo/Run number&amp;quot; || Path to the value in the ODB. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]].&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-editable || data-odb-editable=&amp;quot;1&amp;quot; || 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.&lt;br /&gt;
|-&lt;br /&gt;
| data-format || data-format=&amp;quot;f3&amp;quot; || Specify format of data shown. See Table 2 below for options.&lt;br /&gt;
|-&lt;br /&gt;
| data-size || data-size=&amp;quot;8&amp;quot; || Specify size (in chars) of edit box if one modifies the value. Default is 10.&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || data-formula=&amp;quot;2*x+3&amp;quot; || Specify an optional formula to process with the current ODB value stored in x&lt;br /&gt;
|-&lt;br /&gt;
| data-validate || data-validate=&amp;quot;func&amp;quot; || Specify an optional validation function which gets called before submitting data (see below)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Validation ===&lt;br /&gt;
&lt;br /&gt;
Before a modified value is submitted to the ODB, an optional validation function can be called via the &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; option. The function&lt;br /&gt;
will be called with the current value and a reference to the current modbvalue element. If the function returns &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, then the value&lt;br /&gt;
is not sent to the ODB.&lt;br /&gt;
&lt;br /&gt;
Following example shows a function which just rejects the submission of values above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt; &amp;amp;lt;!-- needed of dlgAlert() --&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        dlgAlert(&amp;quot;Value cannot be above 1000&amp;quot;);&lt;br /&gt;
        return false;&lt;br /&gt;
     }&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following function corrects the return value to 1000 if it&#039;s above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate2(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        element.childNodes[0].value = 1000;&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Confirmation ===&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
This is done with the option &amp;lt;code&amp;gt;data-confirm=&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt;. A dialog box is then shown with the &amp;lt;code&amp;gt;&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt; as the main text and two buttons with &amp;quot;OK&amp;quot; and &amp;quot;Cancel&amp;quot;.&lt;br /&gt;
Hitting &amp;quot;OK&amp;quot; finally writes the value to the ODB, hitting &amp;quot;Cancel&amp;quot; keeps the old value.&lt;br /&gt;
&lt;br /&gt;
Following example shows an example:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-confirm=&amp;quot;Are you sure to change the value?&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following dialog box is then showed:&lt;br /&gt;
&lt;br /&gt;
[[File:Confirm.png|frame|left|Optional confirm dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Formatting ===&lt;br /&gt;
&lt;br /&gt;
Table 2 below lists the format specifiers supported with a modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 2: Format specifiers for modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Valid for* !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| %d || int || 1234 || Shows a number in decimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %t || int, float || 1,234,567 || Shows a number in decimal encoding with commas as thousands separator&lt;br /&gt;
|-&lt;br /&gt;
| %x || int || 0x4D2 || Shows a number in hexadecimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %b || int || 10011010010b|| Shows a number in binary encoding. Options t, d, x, b can be combined, like &amp;lt;code&amp;gt;data-format=&amp;quot;%d / %x&amp;quot;&amp;lt;/code&amp;gt; to produce &amp;lt;code&amp;gt;1234 / 0x4D2&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| %f&amp;amp;lt;x&amp;amp;gt; || float || 1.234 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal. A value of f0 shows only the integer part.&lt;br /&gt;
|-&lt;br /&gt;
| %p&amp;amp;lt;x&amp;amp;gt; || float || 1.23 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012&lt;br /&gt;
|-&lt;br /&gt;
| %e&amp;amp;lt;x&amp;amp;gt; || float || 1.23e-05 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal in exponential format. Useful for small number such as pressures.&lt;br /&gt;
|-&lt;br /&gt;
| T= %f1 C || float || T= 25.4 C || Combination of text and float value is possible.&lt;br /&gt;
|-&lt;br /&gt;
| %d / %x / %b || int || 17 / 0x11 / 10001b|| Same integer value in different formats&lt;br /&gt;
|-&lt;br /&gt;
| [col1,col2] || bool || [red,var(--mgreen)] || Generates a color box filled with col1 (false, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: red&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;) or col2 (true, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: #a0ffb8&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* Note that valid for &amp;quot;int&amp;quot; means all integral ODB types (regardless of size or signed/unsigned) and valid for &amp;quot;float&amp;quot; means both float and double.&lt;br /&gt;
&lt;br /&gt;
== modbbutton ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a push-button which can set a certain ODB entry to a specific value. To set the &amp;quot;Run number&amp;quot; to 100, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;button class=&amp;quot;modbbutton mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;100&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;[Button Text]&amp;amp;lt;/button&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== modbbox ==&lt;br /&gt;
&lt;br /&gt;
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 &#039;&#039;&#039;data-color&#039;&#039;&#039; is used, otherwise the &#039;&#039;&#039;data-background-color&#039;&#039;&#039; is used&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write Data&amp;quot; data-formula=&amp;quot;x &amp;gt; 0&amp;quot; style=&amp;quot;width: 30px; height: 30px; border: 1px solid black&amp;quot; data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally, a &amp;lt;code&amp;gt;data-formula&amp;lt;/code&amp;gt; can be specified. The formula sees the ODB value in the variable &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, and can do any boolean operation. If the result of this is true, then the box gets the &amp;lt;code&amp;gt;data-color&amp;lt;/code&amp;gt;, otherwise the &amp;lt;code&amp;gt;data-background-color&amp;lt;/code&amp;gt;.&lt;br /&gt;
Examples for these formulas are &amp;lt;code&amp;gt;x &amp;gt; 10&amp;lt;/code&amp;gt; for a comparison or &amp;lt;code&amp;gt;x &amp;amp; 1&amp;lt;/code&amp;gt; which will do a bitwise AND operation and is true only for odd numbers.&lt;br /&gt;
&lt;br /&gt;
== modbcheckbox ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a check box which can set a certain ODB entry to true or false. To set the &amp;quot;Write data&amp;quot; flag for the logger true or false, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; data-validate=&amp;quot;my_validate&amp;quot; /&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;modbvalue&amp;lt;/code&amp;gt; tag.&lt;br /&gt;
&lt;br /&gt;
== modbselect ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;/Runinfo/Run number&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;1&amp;quot;&amp;amp;gt;1&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;5&amp;quot;&amp;amp;gt;5&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;10&amp;quot;&amp;amp;gt;10&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&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 &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; to enable this behaviour. For ODB key &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, you will need to create an ODB entry called &amp;lt;code&amp;gt;Options x&amp;lt;/code&amp;gt; in the same directory; this &amp;quot;options&amp;quot; key should be a list, with each element in the list being an allowable option.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;!-- Will read options from &amp;quot;/Equipment/Example/Settings/Options Something&amp;quot; in this case --&amp;gt;&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Settings/Something&amp;quot; data-auto-options=&amp;quot;1&amp;quot;&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
The benefit of the &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; approach is that the same options will be shown on the regular ODB browser webpage. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modbhbar ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px; color: lightgreen;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;color: red&amp;quot; || Color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background-color: red&amp;quot; || Background color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of bar range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of bar range || 1 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown as text overlay || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specify format of data shown. See Table 2 above for options || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== mhaxis ==&lt;br /&gt;
&lt;br /&gt;
A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width: 500px; height: 22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a horizontal axis next to the bar. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the axis, must match the width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the axis, must be enough to display labels  || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;vertical-align: top&amp;quot; || Must be &amp;quot;top&amp;quot; if the axis is below the bar || &amp;quot;bottom&amp;quot; if not given&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbvbar ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;modbhbar&amp;lt;/code&amp;gt;, except the bar grows vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== mvaxis ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;mhaxis&amp;lt;/code&amp;gt;, except the axis is shown vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== modbthermo ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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&#039;s good for testing. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 30px&amp;quot; || Width of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 100px&amp;quot; || Total height of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of thermometer || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of thermometer background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown next to the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbgauge ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width: 100px; height: 50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-color=&amp;quot;darkgreen&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a circular gauge ranging from 0 to 10. Depending on the ODB value &amp;quot;Run number&amp;quot;. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 100px&amp;quot; || Width of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 50px&amp;quot; || Total height of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of gauge || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of gauge background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Changing properties of controls dynamically ==&lt;br /&gt;
&lt;br /&gt;
All custom controls can be configured to call a user&#039;s function when the control is first set up, or when the value changes. This is done by specifying the &#039;&#039;&#039;onload&#039;&#039;&#039; and/or &#039;&#039;&#039;onchange&#039;&#039;&#039; functions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;onload&#039;&#039;&#039; is called only once, when the control&#039;s value is first read from the ODB.&lt;br /&gt;
* &#039;&#039;&#039;onchange&#039;&#039;&#039; is called each time the value in the ODB changes.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 30?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
function check_therm(elem) {&lt;br /&gt;
  if (elem.value &amp;gt; 30) {&lt;br /&gt;
    elem.dataset.color = &amp;quot;red&amp;quot;;&lt;br /&gt;
  } else {&lt;br /&gt;
    elem.dataset.color = &amp;quot;blue&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
....&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; onchange=&amp;quot;check_therm(this)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
If you want the same function to be called for both onload and onchange, you could set &amp;lt;code&amp;gt;onload=&amp;quot;onchange()&amp;quot;&amp;lt;/code&amp;gt; and have the &amp;quot;real&amp;quot; function in onchange.&lt;br /&gt;
&lt;br /&gt;
== Dynamic ODB paths ==&lt;br /&gt;
&lt;br /&gt;
The path of a custom control is static, meaning it has to be written directly inside the control via &amp;lt;code&amp;gt;data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;lt;/code&amp;gt;. There can be however cases where the path is only known at runtime. For these cases, the path can be a user function which gets called during each update of the control and has to return the current ODB path. This can be useful for equipment values with names. The standard scheme of MIDAS is to have slow control variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Variables&amp;lt;/code&amp;gt; and the names of these variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names&amp;lt;/code&amp;gt;. If there are several different arrays under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;, you might also have something like &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names XXX&amp;lt;/code&amp;gt; where &amp;lt;code&amp;gt;XXX&amp;lt;/code&amp;gt; is the name of the key under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;. If one wants to use a name of a variable rather than its index, the dynamic path function can be uses like in the following example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
   let names;&lt;br /&gt;
&lt;br /&gt;
   function getNames() {&lt;br /&gt;
      mjsonrpc_db_get_value(&amp;quot;/Equipment/.../Settings/Names&amp;quot;).then(function (rpc) {&lt;br /&gt;
         names = rpc.result.data[0];&lt;br /&gt;
         mhttpd_init(&#039;Custom Page Example&#039;);&lt;br /&gt;
      });&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   function getpath(p) {&lt;br /&gt;
      if (mscbNames)&lt;br /&gt;
         return `/Equipment/.../Variables/MSCB[${names.indexOf(p)}]`;&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;getNames();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the page is initialized, the function &amp;lt;code&amp;gt;getNames()&amp;lt;/code&amp;gt; is called, which obtains the &amp;lt;code&amp;gt;Names&amp;lt;/code&amp;gt; array from the ODB and stores it into an array. After the names have been received, the function calls the usual &amp;lt;code&amp;gt;mhttpd_init()&amp;lt;/code&amp;gt; function. The &amp;quot;modbvalue&amp;quot; path is now set to &amp;lt;code&amp;gt;data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;lt;/code&amp;gt;. This function does a lookup in the &amp;lt;code&amp;gt;names&amp;lt;/code&amp;gt; array and returns the proper index, which is then used to access the corresponding value in the variables array. This method has the advantage that if the indices of an array change (e.g. by adding new variables at the front), the code does not have to update any index since the indices are calculated dynamically.&lt;br /&gt;
&lt;br /&gt;
== Changing values of indicators programmatically ==&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For such cases, the &amp;lt;code&amp;gt;data-odb-path&amp;lt;/code&amp;gt; attribute can be removed and the the display value can be changed via JavaScript code like following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mythermo&amp;quot; class=&amp;quot;modbthermo&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
  let t = document.getElementById(&amp;quot;mythermo&amp;quot;);&lt;br /&gt;
  t.setValue(15);&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mjshistory =&lt;br /&gt;
&lt;br /&gt;
Custom pages can contain one or more specific history panels usually shown on the &amp;quot;History&amp;quot; page. This makes it easy to combine current readings of values together with the history of these values.&lt;br /&gt;
&lt;br /&gt;
To enable interactive history panels, following lines have to be added to your custom page:&lt;br /&gt;
&lt;br /&gt;
Inisde the &amp;lt;head&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside the &amp;lt;body&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;body ... onload=&amp;quot;mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;&amp;lt;group&amp;gt;&amp;quot; data-panel=&amp;quot;&amp;lt;panel&amp;gt;&amp;quot; style=&amp;quot;width: 320px; height: 200px;&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
shows a history panel defined in the ODB under /History/Display/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; (replace &amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; with groups and panels from your experiment).&lt;br /&gt;
&lt;br /&gt;
Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| data-group || ODB group of history. Has to match a group under /History/Display || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-panel || ODB panel name of history. Has to match a panel name under /History/Display/&amp;amp;lt;group&amp;amp;gt;/ || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt;/Timescale is used. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-title || Flag to show or hide the title. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-values || Flag to show or hide the variable labels with their current value. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-axis || Flag to show or hide the X/Y axis. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the menu buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the zoom buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 320px&amp;quot; || Width of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 200px&amp;quot; || Height of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;border: 1px solid black&amp;quot; || Border around the history panel || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.&lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
the possibility to show the history of that variable. Just put a button next to the value and call &#039;&#039;&#039;mhistory_dialog(&amp;amp;lt;group&amp;amp;gt;, &amp;amp;lt;panel&amp;amp;gt;)&#039;&#039;&#039; from that button like:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;span class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&lt;br /&gt;
   &amp;lt;button onclick=&amp;quot;mhistory_dialog(&#039;group&#039;,&#039;panel&#039;)&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;icons/activity.svg&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The history panel will then be opened when the user clicks the button:&lt;br /&gt;
&lt;br /&gt;
[[File:History dialog.png|frame|left|Floating history dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one wants to avoid the definition of a history panel in the ODB, a &amp;quot;direct variable plot&amp;quot; can be done with the function &#039;&#039;&#039;mhistory_dialog_var(&amp;amp;lt;variable&amp;amp;gt;)&#039;&#039;&#039; where &amp;amp;lt;variable&amp;amp;gt; has the format like the variable definition in the ODB (e.g. &amp;quot;System:Trigger per sec.&amp;quot;). 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 &#039;&#039;&#039;mhistory_dialog_var(&amp;quot;System:Trigger per sec.&amp;quot;, {&amp;quot;Timescale&amp;quot;: &amp;quot;24h&amp;quot;});&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A full example of a custom page with a history panel is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
  &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot; type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body onload=&amp;quot;mhttpd_init(&#039;history_example&#039;); mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &lt;br /&gt;
  &amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;EPICS&amp;quot; data-panel=&amp;quot;Logging&amp;quot; style=&amp;quot;width: 500px; height: 300px;&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mplot =&lt;br /&gt;
&lt;br /&gt;
Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated [[Custom plots with mplot]] page.&lt;br /&gt;
&lt;br /&gt;
= Dialog, popup and modal boxes =&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Showing a message ==&lt;br /&gt;
&lt;br /&gt;
The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an &amp;quot;Ok&amp;quot; button that the user may click to dismiss the message.&lt;br /&gt;
&lt;br /&gt;
=== dlgMessage ===&lt;br /&gt;
&lt;br /&gt;
dlgMessage is the most customisable of the 3 functions. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(title, message, modal, error, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| title || string || Title of the dialog box || Yes&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || Main message content || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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&lt;br /&gt;
|-&lt;br /&gt;
| error || boolean || If true, will use a red background for the title || No. Defaults to false&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called with the user clicks the &amp;quot;Ok&amp;quot; button. The callback function will be called with just a single paramter. || No. Defaults to not calling a callback function.&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
Some example invocations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(&amp;quot;Message title&amp;quot;, &amp;quot;Message text&amp;quot;);&lt;br /&gt;
dlgMessage(&amp;quot;It&#039;s an error!&amp;quot;, &amp;quot;&amp;lt;b&amp;gt;Something is wrong!!!&amp;lt;/b&amp;gt;&amp;quot;, true, true);&lt;br /&gt;
dlgMessage(&amp;quot;Callback example&amp;quot;, &amp;quot;Testing&amp;quot;, true, false, my_message_cb, &amp;quot;some_param&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function my_message_cb(param) {&lt;br /&gt;
   alert(&amp;quot;Message dismissed! The param was &amp;quot; + param);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgAlert ===&lt;br /&gt;
&lt;br /&gt;
dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgAlert(message, callback)&lt;br /&gt;
&lt;br /&gt;
// The above is equivalent to:&lt;br /&gt;
dlgMessage(&amp;quot;Message&amp;quot;, message, true, true, callback)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgWait ===&lt;br /&gt;
&lt;br /&gt;
dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgWait(time_in_seconds, message)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Asking for input ==&lt;br /&gt;
&lt;br /&gt;
=== dlgQuery ===&lt;br /&gt;
&lt;br /&gt;
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&#039;s response is sent to a callback function that you must implement yourself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(message, value, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| value || string || Default value to pre-populate the answer field with || Yes (but can be empty string)&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or their answer if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(&amp;quot;Enter a value:&amp;quot;, &amp;quot;my default value&amp;quot;, my_query_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_query_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      // User clicked the Cancel button&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&#039;Value is &#039; + value + &#039;, param is &#039; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgConfirm ===&lt;br /&gt;
&lt;br /&gt;
dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(message, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or true if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(&amp;quot;Are you sure you wish to do that?&amp;quot;, my_confirm_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_confirm_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      alert(&amp;quot;User clicked Cancel! Param was &amp;quot; + param);&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&amp;quot;User clicked Ok! Param was &amp;quot; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Implementing SVGs =&lt;br /&gt;
&lt;br /&gt;
Complex services or detector systems can be displayed using vector images e.g. SVGs created by Inkscape.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
They can be loaded for example as a background image with modb ... class objects at dedicated positions.&lt;br /&gt;
&lt;br /&gt;
Interactive changes of the SVG like updating the color of a valve, or changing the size of objects depending on ODB values is explained in more detail in [[Custom Page Features#Interactive SVG Implementation]].&lt;br /&gt;
&lt;br /&gt;
= Complete Example =&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/Custom&lt;br /&gt;
  Path     /midas/resources&lt;br /&gt;
  Test     a_example.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Code ==&lt;br /&gt;
The file &#039;&#039;&#039;a_example.html&#039;&#039;&#039; contains the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;Example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;style&amp;gt;&lt;br /&gt;
      .mtable td { padding: 10px; }&lt;br /&gt;
   &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;th colspan=&amp;quot;2&amp;quot; class=&amp;quot;mtableheader&amp;quot;&amp;gt;Status&amp;lt;/th&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td style=&amp;quot;width: 200px;&amp;quot;&amp;gt;&lt;br /&gt;
            Run number:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run start:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Start time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run stop:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Stop time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Check box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) --&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; onchange=&amp;quot;dlgAlert(&#039;Flag has changed&#039;);&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Color box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- box changes color according to /Logger/Write data --&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbbox&amp;quot; style=&amp;quot;width: 30px; height: 30px;&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Horizontal bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width:300px;height:20px;color:orange;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-print-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:500px;height:22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px;color:lightblue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 200px; height: 10px;color:lightgreen;background-color:white&amp;quot;&lt;br /&gt;
                 data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:200px;height:22px;vertical-align:top;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-line=&amp;quot;0&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Vertical bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:right;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;modbvbar&amp;quot;&lt;br /&gt;
                  style=&amp;quot;width:20px;height:200px;color:yellow;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                  data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;width:10px;height:200px;vertical-align:top;color:red&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                  data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:left;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                                                                data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Thermometer:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:60px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-scale=&amp;quot;1&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 9?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-background-color=&amp;quot;white&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Gauges:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot; data-background-color=&amp;quot;lightgrey&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:65px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;red&amp;quot; data-value=&amp;quot;1&amp;quot; data-scale=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- div around image with &amp;quot;relative&amp;quot; position as anchor for labels and bars --&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:300px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;img src=&amp;quot;tank.gif&amp;quot;&amp;gt; &amp;lt;!-- background image of tank --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- label next to valve --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute;top:157px;left:288px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- vertical bar inside tank, render red if value &amp;gt; 9 --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;position:absolute;top:80px;left:10px;width:104px;height:170px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;11&amp;quot; data-color=&amp;quot;green&amp;quot;&lt;br /&gt;
                    onchange=&amp;quot;this.firstChild.style.backgroundColor=(this.value &amp;gt; 9)?&#039;red&#039;:&#039;green&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- thermometer inside tank --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;position:absolute;top:140px;left:20px;width:20px;height:100px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                    data-color=&amp;quot;blue&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- three buttons to change an ODB entry (run number in this example) --&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 1&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;5&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 5&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;10&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 10&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
which results in the page shown in Figure 1 below:&lt;br /&gt;
&lt;br /&gt;
[[File:Custom17.png|frame|left|Figure 1  Example custom page using most features]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Old custom page feature =&lt;br /&gt;
&lt;br /&gt;
There are a number of deprecated custom page features, which can be seen here: [[Old Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:mhttpd Pages]] [[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3523</id>
		<title>Custom Page</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3523"/>
		<updated>2025-05-12T12:21:52Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Implementing SVGs */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
{{mhttpdpages3|[[Custom Page Features]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}&lt;br /&gt;
&lt;br /&gt;
= Purpose =&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
= Examples of Custom Pages =&lt;br /&gt;
&lt;br /&gt;
Click on the thumbnails to enlarge.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]] || &#039;&#039;&#039;Example 1&#039;&#039;&#039;&lt;br /&gt;
This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of &amp;quot;fills&amp;quot; and &amp;quot;labels&amp;quot;. 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).&lt;br /&gt;
|-&lt;br /&gt;
| [[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]] || &#039;&#039;&#039;Example 2&#039;&#039;&#039;&lt;br /&gt;
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 &amp;quot;virtual&amp;quot; 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.&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For details using Xvfb server, please contact Ryu Sawada &amp;lt;sawada@icepp.s.u-tokyo.ac.jp&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| [[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]] || &#039;&#039;&#039;Example 3&#039;&#039;&#039;&lt;br /&gt;
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.   &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Access a Custom Page from the Regular MIDAS pages =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
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.&lt;br /&gt;
See [[Custom Page Features#Resource files]] for more information.&lt;br /&gt;
&lt;br /&gt;
If the key  {{Odbpath|path=/Custom/myPage&amp;amp;}}  (see Note) is created, e.g.&lt;br /&gt;
 odbedit&amp;gt; ls /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
&lt;br /&gt;
the custom link on the left navigation bar will be &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; and the URL for the resulting custom page will be of the form &amp;lt;code&amp;gt;http://myhost.mydomain:myport/cmd=?Custom&amp;amp;page=myPage&amp;lt;/code&amp;gt; (see also [[mhttpd#usage]]).  &lt;br /&gt;
Clicking on &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; will display the custom page in the same window.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
: With the &amp;quot;&amp;amp;&amp;quot; symbol in the key name, the page would appear in a new tab/window. See [[/Custom ODB tree#Key names|Key names]] for more information.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
 odbedit&amp;gt; ls -r /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
    Calorimeter&lt;br /&gt;
        HV                            hv.html&lt;br /&gt;
        Rates                         rates.html&lt;br /&gt;
    Baeam&lt;br /&gt;
        Beamline                      beamline.html&lt;br /&gt;
        Accelerator                   accel.html&lt;br /&gt;
&lt;br /&gt;
The pages then include the subdirectory in the URL, like &lt;br /&gt;
&lt;br /&gt;
 http://localhost:8080?cmd=custom&amp;amp;page=beam/Beamline&lt;br /&gt;
&lt;br /&gt;
Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Calorimeter/HV&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
in order to keep the submenu open after you select the custom page.&lt;br /&gt;
&lt;br /&gt;
= How to write a custom page =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
* modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.&lt;br /&gt;
* 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.&lt;br /&gt;
* modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== How to use the standard MIDAS navigation bars on your custom page == &lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;myPage&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;myPage&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_start --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
 ADD YOUR HTML/JS  CODE here...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call &amp;lt;code&amp;gt;mhttpd_init(&#039;myPage&#039;)&amp;lt;/code&amp;gt; is executed when the page is loaded, and  &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage.&lt;br /&gt;
This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying [[Mjsonrpc | javascript libraries]].&lt;br /&gt;
&lt;br /&gt;
= modb* Javascript scheme = &lt;br /&gt;
&lt;br /&gt;
The general scheme of the custom page scheme is to write &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;amp;lt;span class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt; tags with special class names, most of them starting with &amp;quot;modb...&amp;quot; (&amp;quot;MIDAS-ODB&amp;quot;). Use a &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want the element to appear in a separate line, and use the &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want to display the element in-line. The following description uses only &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tags, but all of them can be changed to &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
All HTML tags with &amp;quot;modb...&amp;quot; names are scanned by the &amp;lt;code&amp;gt;mhttp_init(&#039;name&#039;)&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;mhttpd_init(&#039;name&#039;, interval)&amp;lt;/code&amp;gt; where &amp;quot;interval&amp;quot; is in milliseconds. You can add or remove &amp;quot;modb...&amp;quot; elements at any time using javascript, and the new elements will be &amp;quot;discovered&amp;quot; automatically during the next update.&lt;br /&gt;
&lt;br /&gt;
== modbset(path, value) ==&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset(&amp;quot;odb path&amp;quot;, value)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset([&amp;quot;odb path1&amp;quot;, &amp;quot;odb path2&amp;quot;, ...], [value1, value2, ...])&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modb ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for Midas ODB) &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot; onchange=&amp;quot;func()&amp;quot;&amp;amp;gt;&amp;lt;/code&amp;gt; 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 &amp;amp;lt;script&amp;amp;gt;...&amp;amp;lt;/script&amp;amp;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.&lt;br /&gt;
&lt;br /&gt;
The current value of the ODB entry is available inside the &amp;quot;onchange&amp;quot; function as &#039;&#039;&#039;this.value&#039;&#039;&#039;. Following tag will call a function which logs the current run number in the JavaScript console window:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to &#039;&#039;&#039;this.value&#039;&#039;&#039; as a JavaSctipt object such as&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value[&amp;quot;run number&amp;quot;]);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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 &#039;&#039;&#039;value[&amp;quot;run number&amp;quot;]&#039;&#039;&#039; as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as &#039;&#039;&#039;value.state&#039;&#039;&#039; for /Runinfo/State for example.&lt;br /&gt;
&lt;br /&gt;
== modbvalue ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for &amp;quot;Midas ODB VALUE&amp;quot;) &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 1: List of valid options for modbvalue tag&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| data-name || class=&amp;quot;modbvalue&amp;quot; || Tells the framework to replace this tag with an ODB value&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || data-odb-path = &amp;quot;/Runinfo/Run number&amp;quot; || Path to the value in the ODB. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]].&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-editable || data-odb-editable=&amp;quot;1&amp;quot; || 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.&lt;br /&gt;
|-&lt;br /&gt;
| data-format || data-format=&amp;quot;f3&amp;quot; || Specify format of data shown. See Table 2 below for options.&lt;br /&gt;
|-&lt;br /&gt;
| data-size || data-size=&amp;quot;8&amp;quot; || Specify size (in chars) of edit box if one modifies the value. Default is 10.&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || data-formula=&amp;quot;2*x+3&amp;quot; || Specify an optional formula to process with the current ODB value stored in x&lt;br /&gt;
|-&lt;br /&gt;
| data-validate || data-validate=&amp;quot;func&amp;quot; || Specify an optional validation function which gets called before submitting data (see below)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Validation ===&lt;br /&gt;
&lt;br /&gt;
Before a modified value is submitted to the ODB, an optional validation function can be called via the &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; option. The function&lt;br /&gt;
will be called with the current value and a reference to the current modbvalue element. If the function returns &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, then the value&lt;br /&gt;
is not sent to the ODB.&lt;br /&gt;
&lt;br /&gt;
Following example shows a function which just rejects the submission of values above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt; &amp;amp;lt;!-- needed of dlgAlert() --&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        dlgAlert(&amp;quot;Value cannot be above 1000&amp;quot;);&lt;br /&gt;
        return false;&lt;br /&gt;
     }&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following function corrects the return value to 1000 if it&#039;s above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate2(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        element.childNodes[0].value = 1000;&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Confirmation ===&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
This is done with the option &amp;lt;code&amp;gt;data-confirm=&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt;. A dialog box is then shown with the &amp;lt;code&amp;gt;&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt; as the main text and two buttons with &amp;quot;OK&amp;quot; and &amp;quot;Cancel&amp;quot;.&lt;br /&gt;
Hitting &amp;quot;OK&amp;quot; finally writes the value to the ODB, hitting &amp;quot;Cancel&amp;quot; keeps the old value.&lt;br /&gt;
&lt;br /&gt;
Following example shows an example:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-confirm=&amp;quot;Are you sure to change the value?&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following dialog box is then showed:&lt;br /&gt;
&lt;br /&gt;
[[File:Confirm.png|frame|left|Optional confirm dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Formatting ===&lt;br /&gt;
&lt;br /&gt;
Table 2 below lists the format specifiers supported with a modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 2: Format specifiers for modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Valid for* !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| %d || int || 1234 || Shows a number in decimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %t || int, float || 1,234,567 || Shows a number in decimal encoding with commas as thousands separator&lt;br /&gt;
|-&lt;br /&gt;
| %x || int || 0x4D2 || Shows a number in hexadecimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %b || int || 10011010010b|| Shows a number in binary encoding. Options t, d, x, b can be combined, like &amp;lt;code&amp;gt;data-format=&amp;quot;%d / %x&amp;quot;&amp;lt;/code&amp;gt; to produce &amp;lt;code&amp;gt;1234 / 0x4D2&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| %f&amp;amp;lt;x&amp;amp;gt; || float || 1.234 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal. A value of f0 shows only the integer part.&lt;br /&gt;
|-&lt;br /&gt;
| %p&amp;amp;lt;x&amp;amp;gt; || float || 1.23 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012&lt;br /&gt;
|-&lt;br /&gt;
| %e&amp;amp;lt;x&amp;amp;gt; || float || 1.23e-05 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal in exponential format. Useful for small number such as pressures.&lt;br /&gt;
|-&lt;br /&gt;
| T= %f1 C || float || T= 25.4 C || Combination of text and float value is possible.&lt;br /&gt;
|-&lt;br /&gt;
| %d / %x / %b || int || 17 / 0x11 / 10001b|| Same integer value in different formats&lt;br /&gt;
|-&lt;br /&gt;
| [col1,col2] || bool || [red,var(--mgreen)] || Generates a color box filled with col1 (false, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: red&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;) or col2 (true, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: #a0ffb8&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* Note that valid for &amp;quot;int&amp;quot; means all integral ODB types (regardless of size or signed/unsigned) and valid for &amp;quot;float&amp;quot; means both float and double.&lt;br /&gt;
&lt;br /&gt;
== modbbutton ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a push-button which can set a certain ODB entry to a specific value. To set the &amp;quot;Run number&amp;quot; to 100, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;button class=&amp;quot;modbbutton mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;100&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;[Button Text]&amp;amp;lt;/button&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== modbbox ==&lt;br /&gt;
&lt;br /&gt;
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 &#039;&#039;&#039;data-color&#039;&#039;&#039; is used, otherwise the &#039;&#039;&#039;data-background-color&#039;&#039;&#039; is used&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write Data&amp;quot; data-formula=&amp;quot;x &amp;gt; 0&amp;quot; style=&amp;quot;width: 30px; height: 30px; border: 1px solid black&amp;quot; data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally, a &amp;lt;code&amp;gt;data-formula&amp;lt;/code&amp;gt; can be specified. The formula sees the ODB value in the variable &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, and can do any boolean operation. If the result of this is true, then the box gets the &amp;lt;code&amp;gt;data-color&amp;lt;/code&amp;gt;, otherwise the &amp;lt;code&amp;gt;data-background-color&amp;lt;/code&amp;gt;.&lt;br /&gt;
Examples for these formulas are &amp;lt;code&amp;gt;x &amp;gt; 10&amp;lt;/code&amp;gt; for a comparison or &amp;lt;code&amp;gt;x &amp;amp; 1&amp;lt;/code&amp;gt; which will do a bitwise AND operation and is true only for odd numbers.&lt;br /&gt;
&lt;br /&gt;
== modbcheckbox ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a check box which can set a certain ODB entry to true or false. To set the &amp;quot;Write data&amp;quot; flag for the logger true or false, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; data-validate=&amp;quot;my_validate&amp;quot; /&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;modbvalue&amp;lt;/code&amp;gt; tag.&lt;br /&gt;
&lt;br /&gt;
== modbselect ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;/Runinfo/Run number&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;1&amp;quot;&amp;amp;gt;1&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;5&amp;quot;&amp;amp;gt;5&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;10&amp;quot;&amp;amp;gt;10&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&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 &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; to enable this behaviour. For ODB key &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, you will need to create an ODB entry called &amp;lt;code&amp;gt;Options x&amp;lt;/code&amp;gt; in the same directory; this &amp;quot;options&amp;quot; key should be a list, with each element in the list being an allowable option.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;!-- Will read options from &amp;quot;/Equipment/Example/Settings/Options Something&amp;quot; in this case --&amp;gt;&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Settings/Something&amp;quot; data-auto-options=&amp;quot;1&amp;quot;&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
The benefit of the &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; approach is that the same options will be shown on the regular ODB browser webpage. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modbhbar ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px; color: lightgreen;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;color: red&amp;quot; || Color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background-color: red&amp;quot; || Background color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of bar range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of bar range || 1 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown as text overlay || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specify format of data shown. See Table 2 above for options || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== mhaxis ==&lt;br /&gt;
&lt;br /&gt;
A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width: 500px; height: 22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a horizontal axis next to the bar. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the axis, must match the width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the axis, must be enough to display labels  || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;vertical-align: top&amp;quot; || Must be &amp;quot;top&amp;quot; if the axis is below the bar || &amp;quot;bottom&amp;quot; if not given&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbvbar ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;modbhbar&amp;lt;/code&amp;gt;, except the bar grows vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== mvaxis ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;mhaxis&amp;lt;/code&amp;gt;, except the axis is shown vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== modbthermo ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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&#039;s good for testing. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 30px&amp;quot; || Width of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 100px&amp;quot; || Total height of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of thermometer || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of thermometer background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown next to the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbgauge ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width: 100px; height: 50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-color=&amp;quot;darkgreen&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a circular gauge ranging from 0 to 10. Depending on the ODB value &amp;quot;Run number&amp;quot;. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 100px&amp;quot; || Width of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 50px&amp;quot; || Total height of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of gauge || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of gauge background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Changing properties of controls dynamically ==&lt;br /&gt;
&lt;br /&gt;
All custom controls can be configured to call a user&#039;s function when the control is first set up, or when the value changes. This is done by specifying the &#039;&#039;&#039;onload&#039;&#039;&#039; and/or &#039;&#039;&#039;onchange&#039;&#039;&#039; functions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;onload&#039;&#039;&#039; is called only once, when the control&#039;s value is first read from the ODB.&lt;br /&gt;
* &#039;&#039;&#039;onchange&#039;&#039;&#039; is called each time the value in the ODB changes.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 30?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
function check_therm(elem) {&lt;br /&gt;
  if (elem.value &amp;gt; 30) {&lt;br /&gt;
    elem.dataset.color = &amp;quot;red&amp;quot;;&lt;br /&gt;
  } else {&lt;br /&gt;
    elem.dataset.color = &amp;quot;blue&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
....&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; onchange=&amp;quot;check_therm(this)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
If you want the same function to be called for both onload and onchange, you could set &amp;lt;code&amp;gt;onload=&amp;quot;onchange()&amp;quot;&amp;lt;/code&amp;gt; and have the &amp;quot;real&amp;quot; function in onchange.&lt;br /&gt;
&lt;br /&gt;
== Dynamic ODB paths ==&lt;br /&gt;
&lt;br /&gt;
The path of a custom control is static, meaning it has to be written directly inside the control via &amp;lt;code&amp;gt;data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;lt;/code&amp;gt;. There can be however cases where the path is only known at runtime. For these cases, the path can be a user function which gets called during each update of the control and has to return the current ODB path. This can be useful for equipment values with names. The standard scheme of MIDAS is to have slow control variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Variables&amp;lt;/code&amp;gt; and the names of these variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names&amp;lt;/code&amp;gt;. If there are several different arrays under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;, you might also have something like &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names XXX&amp;lt;/code&amp;gt; where &amp;lt;code&amp;gt;XXX&amp;lt;/code&amp;gt; is the name of the key under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;. If one wants to use a name of a variable rather than its index, the dynamic path function can be uses like in the following example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
   let names;&lt;br /&gt;
&lt;br /&gt;
   function getNames() {&lt;br /&gt;
      mjsonrpc_db_get_value(&amp;quot;/Equipment/.../Settings/Names&amp;quot;).then(function (rpc) {&lt;br /&gt;
         names = rpc.result.data[0];&lt;br /&gt;
         mhttpd_init(&#039;Custom Page Example&#039;);&lt;br /&gt;
      });&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   function getpath(p) {&lt;br /&gt;
      if (mscbNames)&lt;br /&gt;
         return `/Equipment/.../Variables/MSCB[${names.indexOf(p)}]`;&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;getNames();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the page is initialized, the function &amp;lt;code&amp;gt;getNames()&amp;lt;/code&amp;gt; is called, which obtains the &amp;lt;code&amp;gt;Names&amp;lt;/code&amp;gt; array from the ODB and stores it into an array. After the names have been received, the function calls the usual &amp;lt;code&amp;gt;mhttpd_init()&amp;lt;/code&amp;gt; function. The &amp;quot;modbvalue&amp;quot; path is now set to &amp;lt;code&amp;gt;data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;lt;/code&amp;gt;. This function does a lookup in the &amp;lt;code&amp;gt;names&amp;lt;/code&amp;gt; array and returns the proper index, which is then used to access the corresponding value in the variables array. This method has the advantage that if the indices of an array change (e.g. by adding new variables at the front), the code does not have to update any index since the indices are calculated dynamically.&lt;br /&gt;
&lt;br /&gt;
== Changing values of indicators programmatically ==&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For such cases, the &amp;lt;code&amp;gt;data-odb-path&amp;lt;/code&amp;gt; attribute can be removed and the the display value can be changed via JavaScript code like following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mythermo&amp;quot; class=&amp;quot;modbthermo&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
  let t = document.getElementById(&amp;quot;mythermo&amp;quot;);&lt;br /&gt;
  t.setValue(15);&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mjshistory =&lt;br /&gt;
&lt;br /&gt;
Custom pages can contain one or more specific history panels usually shown on the &amp;quot;History&amp;quot; page. This makes it easy to combine current readings of values together with the history of these values.&lt;br /&gt;
&lt;br /&gt;
To enable interactive history panels, following lines have to be added to your custom page:&lt;br /&gt;
&lt;br /&gt;
Inisde the &amp;lt;head&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside the &amp;lt;body&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;body ... onload=&amp;quot;mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;&amp;lt;group&amp;gt;&amp;quot; data-panel=&amp;quot;&amp;lt;panel&amp;gt;&amp;quot; style=&amp;quot;width: 320px; height: 200px;&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
shows a history panel defined in the ODB under /History/Display/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; (replace &amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; with groups and panels from your experiment).&lt;br /&gt;
&lt;br /&gt;
Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| data-group || ODB group of history. Has to match a group under /History/Display || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-panel || ODB panel name of history. Has to match a panel name under /History/Display/&amp;amp;lt;group&amp;amp;gt;/ || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt;/Timescale is used. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-title || Flag to show or hide the title. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-values || Flag to show or hide the variable labels with their current value. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-axis || Flag to show or hide the X/Y axis. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the menu buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the zoom buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 320px&amp;quot; || Width of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 200px&amp;quot; || Height of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;border: 1px solid black&amp;quot; || Border around the history panel || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.&lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
the possibility to show the history of that variable. Just put a button next to the value and call &#039;&#039;&#039;mhistory_dialog(&amp;amp;lt;group&amp;amp;gt;, &amp;amp;lt;panel&amp;amp;gt;)&#039;&#039;&#039; from that button like:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;span class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&lt;br /&gt;
   &amp;lt;button onclick=&amp;quot;mhistory_dialog(&#039;group&#039;,&#039;panel&#039;)&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;icons/activity.svg&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The history panel will then be opened when the user clicks the button:&lt;br /&gt;
&lt;br /&gt;
[[File:History dialog.png|frame|left|Floating history dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one wants to avoid the definition of a history panel in the ODB, a &amp;quot;direct variable plot&amp;quot; can be done with the function &#039;&#039;&#039;mhistory_dialog_var(&amp;amp;lt;variable&amp;amp;gt;)&#039;&#039;&#039; where &amp;amp;lt;variable&amp;amp;gt; has the format like the variable definition in the ODB (e.g. &amp;quot;System:Trigger per sec.&amp;quot;). 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 &#039;&#039;&#039;mhistory_dialog_var(&amp;quot;System:Trigger per sec.&amp;quot;, {&amp;quot;Timescale&amp;quot;: &amp;quot;24h&amp;quot;});&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A full example of a custom page with a history panel is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
  &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot; type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body onload=&amp;quot;mhttpd_init(&#039;history_example&#039;); mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &lt;br /&gt;
  &amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;EPICS&amp;quot; data-panel=&amp;quot;Logging&amp;quot; style=&amp;quot;width: 500px; height: 300px;&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mplot =&lt;br /&gt;
&lt;br /&gt;
Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated [[Custom plots with mplot]] page.&lt;br /&gt;
&lt;br /&gt;
= Dialog, popup and modal boxes =&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Showing a message ==&lt;br /&gt;
&lt;br /&gt;
The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an &amp;quot;Ok&amp;quot; button that the user may click to dismiss the message.&lt;br /&gt;
&lt;br /&gt;
=== dlgMessage ===&lt;br /&gt;
&lt;br /&gt;
dlgMessage is the most customisable of the 3 functions. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(title, message, modal, error, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| title || string || Title of the dialog box || Yes&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || Main message content || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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&lt;br /&gt;
|-&lt;br /&gt;
| error || boolean || If true, will use a red background for the title || No. Defaults to false&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called with the user clicks the &amp;quot;Ok&amp;quot; button. The callback function will be called with just a single paramter. || No. Defaults to not calling a callback function.&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
Some example invocations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(&amp;quot;Message title&amp;quot;, &amp;quot;Message text&amp;quot;);&lt;br /&gt;
dlgMessage(&amp;quot;It&#039;s an error!&amp;quot;, &amp;quot;&amp;lt;b&amp;gt;Something is wrong!!!&amp;lt;/b&amp;gt;&amp;quot;, true, true);&lt;br /&gt;
dlgMessage(&amp;quot;Callback example&amp;quot;, &amp;quot;Testing&amp;quot;, true, false, my_message_cb, &amp;quot;some_param&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function my_message_cb(param) {&lt;br /&gt;
   alert(&amp;quot;Message dismissed! The param was &amp;quot; + param);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgAlert ===&lt;br /&gt;
&lt;br /&gt;
dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgAlert(message, callback)&lt;br /&gt;
&lt;br /&gt;
// The above is equivalent to:&lt;br /&gt;
dlgMessage(&amp;quot;Message&amp;quot;, message, true, true, callback)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgWait ===&lt;br /&gt;
&lt;br /&gt;
dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgWait(time_in_seconds, message)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Asking for input ==&lt;br /&gt;
&lt;br /&gt;
=== dlgQuery ===&lt;br /&gt;
&lt;br /&gt;
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&#039;s response is sent to a callback function that you must implement yourself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(message, value, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| value || string || Default value to pre-populate the answer field with || Yes (but can be empty string)&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or their answer if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(&amp;quot;Enter a value:&amp;quot;, &amp;quot;my default value&amp;quot;, my_query_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_query_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      // User clicked the Cancel button&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&#039;Value is &#039; + value + &#039;, param is &#039; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgConfirm ===&lt;br /&gt;
&lt;br /&gt;
dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(message, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or true if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(&amp;quot;Are you sure you wish to do that?&amp;quot;, my_confirm_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_confirm_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      alert(&amp;quot;User clicked Cancel! Param was &amp;quot; + param);&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&amp;quot;User clicked Ok! Param was &amp;quot; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Implementing SVGs =&lt;br /&gt;
&lt;br /&gt;
Complex services or detector systems can be displayed using vector images e.g. SVGs created by Inkscape.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
They can be loaded for example as a background image with modb ... class objects at dedicated positions.&lt;br /&gt;
&lt;br /&gt;
Interactive changes of the SVG like updating the color of a valve, or changing the size of objects depending on ODB values is explained in more detail in [[Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
= Complete Example =&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/Custom&lt;br /&gt;
  Path     /midas/resources&lt;br /&gt;
  Test     a_example.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Code ==&lt;br /&gt;
The file &#039;&#039;&#039;a_example.html&#039;&#039;&#039; contains the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;Example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;style&amp;gt;&lt;br /&gt;
      .mtable td { padding: 10px; }&lt;br /&gt;
   &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;th colspan=&amp;quot;2&amp;quot; class=&amp;quot;mtableheader&amp;quot;&amp;gt;Status&amp;lt;/th&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td style=&amp;quot;width: 200px;&amp;quot;&amp;gt;&lt;br /&gt;
            Run number:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run start:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Start time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run stop:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Stop time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Check box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) --&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; onchange=&amp;quot;dlgAlert(&#039;Flag has changed&#039;);&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Color box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- box changes color according to /Logger/Write data --&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbbox&amp;quot; style=&amp;quot;width: 30px; height: 30px;&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Horizontal bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width:300px;height:20px;color:orange;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-print-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:500px;height:22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px;color:lightblue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 200px; height: 10px;color:lightgreen;background-color:white&amp;quot;&lt;br /&gt;
                 data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:200px;height:22px;vertical-align:top;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-line=&amp;quot;0&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Vertical bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:right;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;modbvbar&amp;quot;&lt;br /&gt;
                  style=&amp;quot;width:20px;height:200px;color:yellow;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                  data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;width:10px;height:200px;vertical-align:top;color:red&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                  data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:left;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                                                                data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Thermometer:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:60px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-scale=&amp;quot;1&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 9?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-background-color=&amp;quot;white&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Gauges:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot; data-background-color=&amp;quot;lightgrey&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:65px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;red&amp;quot; data-value=&amp;quot;1&amp;quot; data-scale=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- div around image with &amp;quot;relative&amp;quot; position as anchor for labels and bars --&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:300px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;img src=&amp;quot;tank.gif&amp;quot;&amp;gt; &amp;lt;!-- background image of tank --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- label next to valve --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute;top:157px;left:288px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- vertical bar inside tank, render red if value &amp;gt; 9 --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;position:absolute;top:80px;left:10px;width:104px;height:170px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;11&amp;quot; data-color=&amp;quot;green&amp;quot;&lt;br /&gt;
                    onchange=&amp;quot;this.firstChild.style.backgroundColor=(this.value &amp;gt; 9)?&#039;red&#039;:&#039;green&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- thermometer inside tank --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;position:absolute;top:140px;left:20px;width:20px;height:100px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                    data-color=&amp;quot;blue&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- three buttons to change an ODB entry (run number in this example) --&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 1&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;5&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 5&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;10&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 10&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
which results in the page shown in Figure 1 below:&lt;br /&gt;
&lt;br /&gt;
[[File:Custom17.png|frame|left|Figure 1  Example custom page using most features]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Old custom page feature =&lt;br /&gt;
&lt;br /&gt;
There are a number of deprecated custom page features, which can be seen here: [[Old Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:mhttpd Pages]] [[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3522</id>
		<title>Custom Page Features</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3522"/>
		<updated>2025-05-12T12:17:48Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Example 4 : Interactive SVG custom page */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
* [[ODB Page]]&lt;br /&gt;
* [[Custom Page]]&lt;br /&gt;
* [[/Custom ODB tree]]&lt;br /&gt;
* [[Mhttpd.js|MIDAS Javascript library (mhttpd.js)]]&lt;br /&gt;
* [[mjsonrpc|MIDAS JSON RPC library functions (mjsonrpc)]]&lt;br /&gt;
* [[odbedit]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
;NOTE&lt;br /&gt;
: Since 2018, many of the features described here can now be implemented more simply using the  mod* JS functions (see [[Custom Page|Custom Web Page]]).&lt;br /&gt;
: &#039;&#039;&#039;If writing a new web page, it is strongly recommended you make use of the mod*JS features. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page describes some of the special features provided for use on a user-created [[Custom Page|Custom Web Page]] using the [[#The MIDAS Javascript Library]], which was the recommended way to write Custom Pages before the [[Custom Page#modb* Javascript scheme]]  was available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= The MIDAS Javascript Library =&lt;br /&gt;
The MIDAS Javascript Library [[mhttpd.js]] includes routines written in Javascript and AJAX to provide features useful for writers of [[Custom Page]]s, such as access to the ODB. Recently asynchronous [[mjsonrpc|JSON-RPC functions]] using [[mjsonrpc#Javascript client library|Promises]]  has been added (January 2016).&lt;br /&gt;
;NOTE&lt;br /&gt;
:To use functions in this library, it &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Many of the older routines (ODBGet, ODBSet) use &#039;&#039;&#039;synchronous&#039;&#039;&#039; RPC requests. With synchronous request, a second RPC request must wait until the first request is complete. This can significantly slow down web pages where many calls to ODBGet are made. For this reason, functions were developed that can be synchronous or &#039;&#039;&#039;asynchronous&#039;&#039;&#039; (e.g. ODBMCopy) that include a callback mechanism. The data from ODBMCopy can be formatted in [[mjsonrpc#JSON general information|JSON]]. &lt;br /&gt;
&lt;br /&gt;
Web developers are moving away from synchronous RPC requests, which are now deprecated [https://midas.triumf.ca/elog/Midas/1128]. Some modern browsers return a warning on a synchronous RPC request. &lt;br /&gt;
&lt;br /&gt;
A new group of JSON-RPC functions using  [[mjsonrpc#Javascript client library|Promises]] has been added to the MIDAS Javascript Library (January 2016). These are asynchronous only, and they also provide more functionality and have better error handling than the older AJAX calls, which are also inconsistent in how they handle Booleans. They also have much improved handling of reading/writing arrays to the ODB. Before 2018 it was recommended that all new Custom Pages are written using these JSON-RPC functions.  {{Important|text=Since 2018 the [[Custom Page#modb* Javascript scheme]] is recommended for new Custom Pages}}.&lt;br /&gt;
&lt;br /&gt;
The MIDAS Javascript Library routines requires an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). If using an older browser version, the asynchronous AJAX calls (e.g. ODBMCopy) should be used in order to avoid having to rewrite the web page at a later date to remove all synchronous calls.  &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using mjson-rpc asynchronous functions ==&lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], which (since January 2016) includes the [[mjsonrpc]] functions.  It is [[#The MIDAS Javascript Library|recommended]] that these functions are used for &#039;&#039;&#039;new&#039;&#039;&#039; pages rather than the older AJAX calls (see below). &lt;br /&gt;
&lt;br /&gt;
To run these functions you need&lt;br /&gt;
# to  [[mhttpd.js#include js lib|include the Javascript library in the HTML code]]&#039;&#039;&#039;&lt;br /&gt;
# to use an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). &lt;br /&gt;
&lt;br /&gt;
Examples showing how to read and write to the ODB can be found at [[mjsonrpc#examples]]. Also included are examples using arrays. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using asynchronous AJAX calls ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: It is recommended that  asynchronous MIDAS JSON-RPC functions are used for new pages rather than the older AJAX calls (see [[#Access the ODB using mjson-rpc asynchronous functions|above]]).&lt;br /&gt;
&lt;br /&gt;
[[#Example 2 : Asynchronous calls using Javascript and AJAX|Example 2]] uses the asynchronous call ODBMCopy() with callback to read the data. The data are converted into JSON format. In this case, when accessing the JSON data, &#039;&#039;the ODB Keynames must be in the same case as they are in the ODB&#039;&#039;, even though the case is ignored by the ODB. If you find this feature annoying, use [[#Access the ODB using mjson-rpc asynchronous functions|mjson-rpc function db_get_values()]] as all keynames are converted to lower case.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using synchronous AJAX requests ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: Synchronous requests are now deprecated (see [[#The MIDAS Javascript Library|above]]). New pages should be written [[#Using mjson-rpc asynchronous functions]].&lt;br /&gt;
&lt;br /&gt;
See [[#Example 3 : Synchronous Calls using Javascript and AJAX]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Access the ODB with HTML-style &amp;lt;span style=&amp;quot;color:seagreen font-style:italic&amp;quot;&amp;gt;&amp;lt;odb&amp;gt;&amp;lt;/span&amp;gt; tags =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: This method pre-dates the [[Mhttpd.js|MIDAS Javascript Library]]. It is recommended that the [[Mhttpd.js|MIDAS Javascript Library]] be used for ODB access.&lt;br /&gt;
&lt;br /&gt;
If Javascript (JS) is not available, the older HTML-style  {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are still available and provide limited functionality.&lt;br /&gt;
&lt;br /&gt;
The  HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag has been defined for read/write access to the ODB under HTML. The {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags.&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: rgb(255, 255, 255);&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Access to ODB from HTML&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | HTML ODB tag&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | Meaning&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot;&amp;gt;&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: black&amp;quot;  |Display ODB field (read only)&lt;br /&gt;
	&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=1 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (inline style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=2 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (popup style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: The Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; (documented in the [http://ladd00.triumf.ca/~daqweb/doc/midas-old/html/RC_customize_ODB.html#RC_Access_Control| OldMidas Document]) may not be working.&lt;br /&gt;
: Use the  [[/Experiment ODB tree#Security subtree|Web Password security]] instead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are included in the HTML code e.g.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
 Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt;&lt;br /&gt;
 Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt; &amp;lt;br&amp;gt;  Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[Custom Page#How to write a Custom Page|HTML Custom Page example]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= ODB RPC access =&lt;br /&gt;
The [[Mhttpd.js|MIDAS Javascript Library]] function ODBRpc() defined for RPC access.&lt;br /&gt;
&lt;br /&gt;
This permits buttons on MIDAS &amp;quot;custom&amp;quot; web pages to invoke RPC calls directly into user frontend programs, for example to turn hardware modules on or off.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= JSON support =&lt;br /&gt;
[[mjsonrpc#JSON general information|JSON]] support is provided with the [[Mhttpd.js|MIDAS Javascript Library]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Access to the MIDAS Menu buttons =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should follow the instructions there to include the standard MIDAS Menu buttons.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Access to the standard MIDAS Menu buttons can be provided with HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags of the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt; --&amp;gt;&lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt;}}&lt;br /&gt;
Valid values are the standard MIDAS Menu buttons, i.e. (Start, Pause, Resume, Stop, ODB, Elog, Alarms, History, Programs etc). The {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags must be declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags (see above).&lt;br /&gt;
&lt;br /&gt;
The following HTML fragment shows the inclusion of three of the standard buttons, giving access to the Main Status, ODB and Messages pages :&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
 &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; ...  &amp;lt;br&amp;gt; &amp;lt;/form&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
See also [[#Redirect]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Redirect =&lt;br /&gt;
&lt;br /&gt;
When buttons are included on a Custom Page, after pressing a button (e.g. the Start or Stop button) it may be desirable to return to the same custom page, rather than returning to the [[Status Page]].&lt;br /&gt;
&lt;br /&gt;
This can be done by including an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag with the attributes &#039;&#039;type&#039;&#039; set to &amp;quot;hidden&amp;quot; and &#039;&#039;name&#039;&#039; set to &amp;quot;redir&amp;quot;. This name (&amp;quot;redir&amp;quot;) is detected by [[Mhttpd]], causing a redirect to the specified custom link in the &#039;&#039;value&#039;&#039; attribute.&lt;br /&gt;
&lt;br /&gt;
For example, the following redirects the screen back to the custom page link   {{Odbpath|path=/Custom/my_custom_page&amp;amp;}} when buttons are pressed:&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;my_custom_page&amp;amp;&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[#Replace Status Page by a Custom page|Redirect when a Custom Page replaces the Status Page]].&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= CustomScript Buttons =&lt;br /&gt;
&lt;br /&gt;
[[/Customscript ODB tree#Customscript-button|CustomScript buttons]] can be provided on [[Custom Page|Custom Pages]]. These buttons are equivalent to optional &#039;&#039;&#039;script buttons&#039;&#039;&#039; on the [[Status Page]], which call a script to perform a particular action when the button is pressed. See [[/Script ODB tree#Script-button|script buttons]] for details. Customscript buttons can be set up through the [[/Customscript ODB tree]].&lt;br /&gt;
&lt;br /&gt;
Any key &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/CustomScript/test&amp;lt;/span&amp;gt; will appear as a customscript-button &lt;br /&gt;
&amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; on a custom page whose code includes an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag of the form:&lt;br /&gt;
{{Html|text=&amp;lt;input type=submit name=customscript value=&amp;quot;test&amp;quot;&amp;gt;}}&lt;br /&gt;
where the action of the button &amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; will be found in the &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/customscript/test&amp;lt;/span&amp;gt; subdirectory.&lt;br /&gt;
&lt;br /&gt;
After pressing a customscript-button, the &#039;&#039;type=submit&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} will cause a page reload. This will result in a switch to the [[Status Page]], unless a [[#redirect]] input tag is included to redirect back to the original custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customscript button without a page reload ==&lt;br /&gt;
To define a customscript button that does &#039;&#039;&#039;not&#039;&#039;&#039; cause a page reload, set the &#039;&#039;type&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} to &amp;quot;button&amp;quot;, i.e. &lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;customscript&amp;quot; value=&amp;quot;test&amp;quot; type=&amp;quot;button&amp;quot; onClick=cs_button(this.value)&amp;gt;  &amp;lt;!-- Customscript button does not reload page; no callback --&amp;gt;}}&lt;br /&gt;
and use &#039;&#039;&amp;quot;onClick&amp;quot;&#039;&#039; to call a function to [[#Send an Ajax Request]] instead. The following function sends an asynchronous request, with an optional callback.&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;script&amp;gt;}}&lt;br /&gt;
 &amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function cs_button(cmd, callback)&amp;lt;br&amp;gt;&lt;br /&gt;
{  // send a request to execute a custom script&lt;br /&gt;
:   var url;&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
:&lt;br /&gt;
:   url = ODBUrlBase&lt;br /&gt;
:   if(document.getElementById(&amp;quot;redir&amp;quot;) != null)&lt;br /&gt;
::     url +=  &#039;?redir=&#039;+document.getElementById(&#039;redir&#039;).value&lt;br /&gt;
:   url+=&#039;&amp;amp;customscript=&#039;+ encodeURIComponent(cmd);&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true); // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
:  if(callback!=undefined) {&lt;br /&gt;
::      request.onreadystatechange = function() {&lt;br /&gt;
:::         if (request.readyState == 4) { &lt;br /&gt;
::::            if (request.status == 200)&lt;br /&gt;
:::::                callback();&lt;br /&gt;
::::            else&lt;br /&gt;
:::::                alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:::            }&lt;br /&gt;
::         }&lt;br /&gt;
:  }&lt;br /&gt;
:  else { &lt;br /&gt;
::   if (request.status != 200)&lt;br /&gt;
:::       alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:	   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Resource files =&lt;br /&gt;
== MIDAS resource files ==&lt;br /&gt;
A number of resource files have been provided in the MIDAS packages under {{Filepath|path=../packages/midas/resources}}.  When including a MIDAS resource file, no key in  {{Odbpath|path=/Custom}} needs to be defined as the MIDAS resources directory is searched automatically. &lt;br /&gt;
&lt;br /&gt;
One such resource file is a condensed stylesheet {{File|name=midas.css}} for users who would like their custom pages to have a similar &amp;quot;look and feel&amp;quot; to that of the standard pages. To include the MIDAS stylesheet, in the HTML header, add &lt;br /&gt;
{{HtmlTag|tag=&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;}}&lt;br /&gt;
Other resource files can provide [[Custom Page#How to use the standard MIDAS navigation bars on your custom page|the standard MIDAS navigation bars]].&lt;br /&gt;
&lt;br /&gt;
== Custom resource files ==&lt;br /&gt;
It is often desirable to serve resource files (i.e. &#039;&#039;&#039;local files&#039;&#039;&#039; such as an external stylesheet, javascript files or images&#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039;) to a custom page. The following sections describe two alternative ways of serving resource files to a custom page:&lt;br /&gt;
* [[#Resource files served WITH /custom/path key defined|with  /custom/path key defined]]&lt;br /&gt;
* [[#Resource files served WITHOUT /custom/path key defined|without  /custom/path key defined]]&lt;br /&gt;
See also serving resources to a [[#Replace Status Page by a Custom Status page|Custom Status page]].&lt;br /&gt;
;NOTE&lt;br /&gt;
:  &#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039; To superimpose labels, bars or fills onto an image, the image cannot be served as a resource file. See [[#Image insertion]].&lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITH &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
When a number of resource files are required, it is convenient to place them in the same directory on the disk, and create a key  {{Odbpath|path=/Custom/Path}} to contain this directory. &lt;br /&gt;
&lt;br /&gt;
 $ ls  /home/midas/online/custom/&lt;br /&gt;
   custom_functions.js       custom_globals.js     custom_page.html    custom_stylesheet.css      test_image.png&lt;br /&gt;
With the  {{Odbpath|path=/Custom/Path}} key defined, [[/Custom ODB tree#Custom-Link|custom-links]] for individual resource files need not be created in the  {{Odbpath|path=/Custom/Path}} tree. The custom-link  {{Odbpath|path=custom_page&amp;amp;}} &#039;&#039;&#039;is&#039;&#039;&#039; required so that the custom page can be accessed from the [[/Custom ODB tree#Custom-Button|custom-button]]  {{Button|name=custom_page}} on the Status Page. The key name ends in the special character &amp;quot;&amp;amp;&amp;quot; so that it will open in the same window as the Status page (see [[/Custom ODB tree#Key names|key names]]).&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   path                           /home/midas/online/custom/&lt;br /&gt;
   custom_page&amp;amp;                   custom_page.html&lt;br /&gt;
 &lt;br /&gt;
A {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the file name) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_globals.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;custom_stylesheet.css&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;test_image.png&amp;quot;&amp;gt; }}&lt;br /&gt;
[[mhttpd]] then loads the resource file from the directory indicated by the Path key with the correct MIME type (see  [[/Custom ODB tree#Keys in the /Custom tree|Custom tree keys]] for supported MIME types). &lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITHOUT &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
Alternatively, resource files can be served to a [[Custom Page]] by creating a key for every resource file in the [[/Custom ODB tree]]. These keys must contain the full path of the file on the disk, and the key {{Odbpath|path=/Custom/Path}} must NOT be defined. The resource files can be in different directories on the disk. By defining the key names with an appropriate file extension, the resource files are served with the appropriate MIME types. The key names end in the special character &amp;quot;!&amp;quot; so that they will not appear on the Status page as custom-links (see [[/Custom ODB tree#Key names|key names]].&lt;br /&gt;
&lt;br /&gt;
Without the {{Odbpath|path=/Custom/Path}} key,  the links for the above example in the {{Odbpath|path=/Custom}} tree might look like&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   custom_page&amp;amp;                   /home/midas/online/custom/custom_page.html&lt;br /&gt;
   custom_functions.js!           /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   globals.js!                    /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   stylesheet.css!                /home/midas/online/stylesheets/custom_stylesheet.css&lt;br /&gt;
   image.png!                     /home/midas/images/test_image.png  &lt;br /&gt;
&lt;br /&gt;
assuming the resource files are now in the subdirectories indicated.&lt;br /&gt;
The {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the custom-link) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;globals.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;stylesheet.css!&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;image.png!&amp;quot;&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The resulting Demo custom page is shown in Figure 1, which can be compared with Figure 2 (no stylesheet). &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Figure 1  !! Figure 2&lt;br /&gt;
|-&lt;br /&gt;
| Demo Custom Page using MIDAS stylesheet || Demo Custom Page&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| [[File:Mhxcustom03.jpg|thumb|300px]] || [[File:Mhxcustom02.jpg|thumb|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=  Alias-Buttons and Hyperlinks =&lt;br /&gt;
Any hyperlink can easily be included on a [[Custom Page]] by using the standard HTML anchor {{HtmlTag|tag=&amp;lt;a...&amp;gt;}} tag, e.g.&lt;br /&gt;
{{Html|text=&amp;lt;a href=&amp;quot;http://ladd00.triumf.ca/~daqweb/doc/midas/html/&amp;quot;&amp;gt;Midas Help&amp;lt;/a&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Links on a custom page equivalent to [[/Alias ODB tree#Alias-Buttons|alias-buttons]] can also be made e.g.&lt;br /&gt;
{{Html|text=&amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.location.href=&#039;/Alias/alias&amp;amp;&#039;;&amp;quot;&amp;gt;alias&amp;lt;/button&amp;gt;}}&lt;br /&gt;
See the [[/Alias ODB tree]] for details.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Page refresh =&lt;br /&gt;
The following {{HtmlTag|tag=&amp;lt;meta...&amp;gt;}} tag included in the HTML header code will cause the whole custom page to refresh in 60 seconds :&lt;br /&gt;
{{Html|text=&amp;lt;meta http-equiv=&amp;quot;Refresh&amp;quot; content=&amp;quot;60&amp;quot;&amp;gt;}}&lt;br /&gt;
It is also possible to [[#Periodic update of parts of a custom page|periodically update parts]] of a custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Periodic update of parts of a custom page =&lt;br /&gt;
&lt;br /&gt;
The functionality of [[Mhttpd.js|ODBGet]] together with the window.setInterval() function&lt;br /&gt;
can be used to update parts of the web page periodically.&lt;br /&gt;
For example the Javascript fragment below contains a function which updates the current run number every 10 seconds in the background:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt; &amp;lt;!-- JS --&amp;gt;&lt;br /&gt;
window.setInterval(&amp;quot;Refresh()&amp;quot;, 10000);&amp;lt;br&amp;gt;&lt;br /&gt;
function Refresh() {&amp;lt;br&amp;gt;&lt;br /&gt;
:document.getElementById(&amp;quot;run_number&amp;quot;).innerHTML = ODBGet(&#039;/Runinfo/Run number&#039;);&amp;lt;br&amp;gt;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
The custom page has to &lt;br /&gt;
* [[Mhttpd.js#include js lib|include the MIDAS JS library]] to access ODBGet&lt;br /&gt;
* contain an element with id=&amp;quot;run_number&amp;quot;, such as&lt;br /&gt;
{{Html|text=&amp;lt;td id=&amp;quot;run_number&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Display last MIDAS message(s) =&lt;br /&gt;
&lt;br /&gt;
The message log (see [[Message System]]) can be accessed from a custom page using a call to the JavaScript library function [[Mhttpd.js|ODBGetMsg]] (provided the [[Mhttpd.js#include js lib|JS library is included]]).&lt;br /&gt;
&lt;br /&gt;
This allows the inclusion of the &amp;quot;Last Midas message&amp;quot; on a custom page, e.g.&lt;br /&gt;
{{JS|text=document.write(&#039;Last message:&#039;+ODBGetMsg(&amp;quot;midas&amp;quot;,0,1))}}&lt;br /&gt;
More messages may be displayed by increasing the third parameter to ODBGetMsg.&lt;br /&gt;
;Note&lt;br /&gt;
: Parameters were changed August 2015. See [[Mhttpd.js|ODBGetMsg]] for older versions.&lt;br /&gt;
: Coming soon - a [[mjsonrpc]] function&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Generate a message log entry =&lt;br /&gt;
&lt;br /&gt;
A custom page can generate a message to be sent to the MIDAS message log (see [[Message System]]).  A call to mjsonrpc_cm_msg() will generate a message if using the [[mjsonrpc]] functions. Otherwise, use the AJAX function [[Mhttpd.js|ODBGenerateMsg]]. To use these functions, the  [[Mhttpd.js#include js lib|JS library must be included]] in the html code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Checkboxes =&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New  (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should use [[Custom Page#modbcheckbox|modbcheckbox]] to create a checkbox.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;Br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function [[Mhttpd.js|ODBSet]] (provided the [[Mhttpd.js#include js lib|JS library is included]]) can be used when one clicks on a checkbox for example:&lt;br /&gt;
{{Html|text=&amp;lt;input  name=&amp;quot;box0&amp;quot;  type=&amp;quot;checkbox&amp;quot;  onClick=&amp;quot;ODBSet(my_path, this.checked?&#039;1&#039;:&#039;0&#039;)&amp;quot;&amp;gt;}}&lt;br /&gt;
If used as above, the state of the checkbox must be initialized when the page is loaded. This can be done with some JavaScript code called on initialization, e.g.&lt;br /&gt;
{{JS|text=document.form1.box0.checked= ODBGet(my_path));  // initialize to the correct value}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Replace Status Page by a Custom page =&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_status.jpg|thumbnail|left|Figure 3: ODB /Custom/Status custom-link to a custom status page]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a custom-link with the [[/Custom ODB tree#Key names|reserved key name]] &#039;&#039;&#039;Status&#039;&#039;&#039;  (not &amp;quot;Status&amp;amp;&amp;quot; or &amp;quot;Status!&amp;quot;) is created in the [[/Custom ODB tree]] (as shown in  Figure 3), then that custom page will &#039;&#039;&#039;replace the default Status Page&#039;&#039;&#039;. &lt;br /&gt;
  &lt;br /&gt;
Clicking on the {{Button|name=Status}} button on any of the sub-pages (e.g. [[ODB Page]], [[Programs Page]] etc.) will now return to the Custom Status Page. If there are buttons on the Custom Status page, you &#039;&#039;&#039;must&#039;&#039;&#039; include a  [[#Redirect]] statement of the form&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;../&amp;quot;&amp;gt;}}&lt;br /&gt;
or you will see the message &lt;br /&gt;
 Invalid custom page:NULL path&lt;br /&gt;
&lt;br /&gt;
If the Custom Status page includes [[#Resource files]] served on a regular custom page with a statement such as&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
to serve them in a Custom Status page, the statement would be&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&amp;lt;span style=&amp;quot;font-weight:bold&amp;quot;&amp;gt;/CS/&amp;lt;/span&amp;gt;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
In fact, this statement can be used in a regular custom page, as the &amp;quot;/CS/&amp;quot; is ignored in that case.&lt;br /&gt;
&lt;br /&gt;
To return to the default Status Page, delete the &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Custom/Status&amp;lt;/span&amp;gt; key. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Start, Stop and Check if a program is running =&lt;br /&gt;
There are [[mjsonrpc]] functions that implemented all three program management functions - start program, &lt;br /&gt;
stop program and &amp;quot;is running?&amp;quot; as JSON-RPC methods.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Send an Ajax request =&lt;br /&gt;
By sending an Ajax request from a custom page, you can make a button perform a specific function. &lt;br /&gt;
  &lt;br /&gt;
All functions in midas are controlled through special URLs. So the URL&lt;br /&gt;
 http://&amp;lt;host:port&amp;gt;/?cmd=Start&amp;amp;value=10&lt;br /&gt;
will start run #10.&lt;br /&gt;
&lt;br /&gt;
Although it is easier to use an HTML input statement to [[#Access to the MIDAS Menu buttons]], &lt;br /&gt;
to send an Ajax request, you can use the function &#039;&#039;XMLHttpRequestGeneric&#039;&#039; which is in the MIDAS Javascript library [[mhttpd.js]].&lt;br /&gt;
Then the HTML code would be&lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;start()&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
and in your JavaScript code add a function &#039;&#039;start()&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function start()&lt;br /&gt;
{&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
&lt;br /&gt;
:   url = &#039;?cmd=Start&amp;amp;value=10&#039;;&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true);  // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
See also [[#Access to the MIDAS Menu buttons]]. Another example with optional callback can be found in [[#Customscript button without a page reload]].&lt;br /&gt;
&lt;br /&gt;
This mechanism can be used for starting a particular program, see for example (see [https://midas.triumf.ca/elog/Midas/1046]). However, this functionality is now provided by [[mjsonrpc]] functions - see [[#Start, Stop and Check if a program is running]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Image insertion  =&lt;br /&gt;
An image can be loaded from the web using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}}, e.g.  &lt;br /&gt;
   {{Html|text=&amp;quot;TRIUMF logo&amp;quot; &amp;lt;img src=&amp;quot;https://lixenon.triumf.ca/InternalDocuments/Alice/figures/TRIUMF-logo.jpg/image_preview&amp;quot;&amp;gt;}}&lt;br /&gt;
or a MIDAS History image can be inserted into a custom page using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag of the following form:&lt;br /&gt;
 {{Html|text=blah&amp;lt;img src=&amp;quot;http://hostname.domain:port/HS/Meterdis.gif&amp;amp;scale=12h&amp;amp;width=300&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If the image file is on the local disk, it can be loaded as a &#039;&#039;&#039;resource file&#039;&#039;&#039;. The image can be of format .pdf, .jpg, .gif, .png. See loading [[#Resource files]].&lt;br /&gt;
&lt;br /&gt;
If you wish to superimpose features such as &#039;&#039;&#039;labels and fills&#039;&#039;&#039;, the image file cannot be served as a [[#Resource files|resource file]]. Instead, the image file must be in &#039;&#039;&#039;gif&#039;&#039;&#039; format, and must be included in the {{Odbpath|path=/Custom/images}} subtree (Figure 4). Image insertion into a Custom page will be illustrated using the Demo custom page shown in [[#MIDAS stylesheet|Figure 2]]. All the files required for this demo can be found in the MIDAS package at $MIDASSYS/examples/custom. If you do not wish to create&lt;br /&gt;
the keys yourself, proceed to [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
Make a [[/Custom ODB tree#Custom-Link|custom-link]] to the Demo custom page file &#039;&#039;myexpt.html&#039;&#039; , i.e.&lt;br /&gt;
 [local:js:S]/&amp;gt;ls /custom&lt;br /&gt;
    myexpt&amp;amp;                  /home/test/packages/midas/examples/custom/myexpt.html&lt;br /&gt;
 &lt;br /&gt;
To make the image &#039;&#039;myexpt.gif&#039;&#039; visible on the custom page, the path and filename of the image file must be defined in the   {{Odbpath|path=/Custom/images}} subtree. To do this, &lt;br /&gt;
create the  subtrees {{Odbpath|path=/Custom/images/myexpt.gif}} where the subtree name &amp;quot;myexpt.gif&amp;quot; is named for the image file you are going to use. Multiple images can be used, by creating multiple imagefile subtrees.&lt;br /&gt;
&lt;br /&gt;
In the imagefile subtree {{Odbpath|path=myexpt.gif}}, create the STRING key  {{Odbpath|path=Background}}, and set it to contain the path and name of the image file.  The tree structure should then look similar to Figure 4, minus the labels/bars/fill subtrees which will be added to the ODB later.&lt;br /&gt;
&lt;br /&gt;
The image must also be referenced in the custom HTML file &#039;&#039;myexpt.html&#039;&#039; in the &amp;quot;src&amp;quot; field of an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag, e.g. &lt;br /&gt;
{{Html|text=&amp;lt;img &#039;&#039;&#039;src=&amp;quot;myexpt.gif&amp;quot;&#039;&#039;&#039;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_images.jpg|thumbnail|left|Figure 4: /Custom/Images ODB Tree]]&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
Once the image is visible, enable [[#HTML mapping]], optionally [[#Display mouse position]] and proceed to  [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== HTML mapping ==&lt;br /&gt;
Note that if additional features such as active clickable areas and labels, bars and fills superimposed on the image are also required, HTML mapping must also be activated with the HTML {{HtmlTag|tag=&amp;lt;map...&amp;gt;}} tag and the &amp;quot;usemap&amp;quot; attribute of the HTML {{HtmlTag|tag=&amp;lt;img&amp;gt;}} tag&lt;br /&gt;
{{Html|text=   &amp;lt;map &#039;&#039;&#039;name=&amp;quot;myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;img src=&amp;quot;myexpt.gif&amp;quot; &#039;&#039;&#039;usemap=&amp;quot;#myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt;...&amp;lt;br&amp;gt; &amp;lt;/map&amp;gt;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Display mouse position ==&lt;br /&gt;
[[File:Cursor.png|thumbnail|left|Figure 5: MEG Gas System Custom Page showing cursor position]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When writing custom pages with large background images and labels and fills placed on that image, it is hard to figure out X and Y coordinates of the labels. This can now be simplified by using the function &#039;&#039;getMouseXY()&#039;&#039; in the development JavaScript built-in library [[develop.js]]. This function supplies the X,Y position of the cursor if an element of ID &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; is present. This JS library &#039;&#039;&#039;must be [[develop.js|included in the custom page]]&#039;&#039;&#039; in order to use it:&lt;br /&gt;
&lt;br /&gt;
Then, set the &amp;quot;id&amp;quot; attribute of the background HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag to &amp;quot;refimg&amp;quot;, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: &amp;lt;img  &#039;&#039;&#039;id=&amp;quot;refimg&amp;quot;&#039;&#039;&#039; src=&amp;quot;ebit_pc.gif&amp;quot; usemap=&amp;quot;#Custom1&amp;quot;&amp;gt;   &amp;lt;!-- name=&amp;quot;refimg&amp;quot; makes crosshairs appear --&amp;gt;&lt;br /&gt;
: &amp;lt;map name=&amp;quot;Custom1&amp;quot;&amp;gt;&lt;br /&gt;
: .....&lt;br /&gt;
: &amp;lt;/map&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; tag is present, the cursor changes into a crosshair, and its absolute and relative locations in respect to the reference image are shown in the status bar (Figure 5).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
==  Superimposing Labels, Bars and Fills onto a gif image ==&lt;br /&gt;
You can enhance your custom page by superimposing multiple features based on ODB variables onto an image (e.g. [[#Display mouse position|Figure 5]]), such as&lt;br /&gt;
&lt;br /&gt;
*    labels: &amp;quot;live&amp;quot; ODB values positioned in a particular location of the page&lt;br /&gt;
*    bars : &amp;quot;bar level&amp;quot; showing graphically ODB values such as levels or rate etc.&lt;br /&gt;
*    fills : &amp;quot;color level&amp;quot; where colour is used as the level indicator.&lt;br /&gt;
&lt;br /&gt;
Each entry (label/bar/fill) will have an ODB tree associated to it defining the ODB variable path, X/Y position, colour, etc. Each time the page is updated, the latest ODB value/level/rate will be shown based on the ODB parameter to which the label, bar or fill is linked - hence the term &amp;quot;live&amp;quot;. The overlay of the requested features is done onto the selected image file.&lt;br /&gt;
&lt;br /&gt;
This powerful new extension brings the [[mhttpd]] capability closer to other experimental web controllers similar to EPICS.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
:    Be sure to enable the feature to [[#Display mouse position]] in order to facilitate finding the X,Y positions of the various features.&lt;br /&gt;
: [[#HTML mapping]] must be activated for labels/bars/fills to work&lt;br /&gt;
&lt;br /&gt;
A Demo custom page showing labels, bars and fills superimposed on an image is shown in [[#MIDAS stylesheet|Figure 2]]. &lt;br /&gt;
All the files for this demo can be found in $MIDASSYS/examples/custom/. The file xcustom.odb contains the ODB keys required, including those to insert the image and superimpose the various labels, fills etc. This file can be loaded into the ODB with the  {{Odbedit cmd|cmd=load}}.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Labels ===&lt;br /&gt;
&lt;br /&gt;
 [[File:Mhcustom_label.jpg|thumbnail|left|Figure 6: /Custom/Images/Labels ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In order to include a readout of ODB values (i.e. labels), on the image, a further ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Labels}} must be created. Creating a subdirectory for a particular label i.e. {{Odbpath|path=&amp;lt;label name&amp;gt;}} in the   {{Odbpath|path=Labels}} subtree will, at the next custom web page refresh, cause the complete structure for that label to be created and filled with default values. Once the {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is created, the user fills the various keys as desired. See [[/Custom ODB tree#Labels subtree]] for details of the various fields. This procedure is repeated for all the labels required, using a unique {{Odbpath|path=&amp;lt;label name&amp;gt;}} subdirectory for each label. An example of a {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is shown in Figure 6. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Bars ===&lt;br /&gt;
 &lt;br /&gt;
[[File:Mhcustom_fill.jpg|thumbnail|left|Figure 7: /Custom/Images/Fills ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Bars can be superimposed on the image. Create  a new ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Bars}}. Create a subdirectory for a particular Bar ({{Odbpath|path=&amp;lt;bar name&amp;gt;}}) in the {{Odbpath|path=Bars}}  subdirectory. Refresh the web page and fill the various keys as desired.  See [[/Custom ODB tree#Bars subtree]] for details of the various fields.  Examples of a &amp;lt;bar-name&amp;gt; subtree is shown in Figure 7.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Fills ===&lt;br /&gt;
[[File:Mhcustom_bar.jpg|thumbnail|left|Figure 8: /Custom/Images/Bars ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Fills can be superimposed on the image. Create new ODB subdirectory   {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Fills}}. Create a subdirectory for a particular  Fill ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}) in the {{Odbpath|path=Fills}} subdirectory. Refresh the web page and fill the various keys as desired.  See  [[/Custom ODB tree#Fills subtree]] for details of the various fields.  Examples of a ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}  subtree is shown in Figure 8. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Mapping active areas onto the image ==&lt;br /&gt;
Provided [[#HTML mapping]] is activated, &amp;quot;clickable&amp;quot; areas can be created on the image.&lt;br /&gt;
&lt;br /&gt;
This can be done now with a new function like this:&lt;br /&gt;
{{Html|text= &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;40,200,100,300&amp;quot; alt=&amp;quot;Main Valve&amp;quot; href=&amp;quot;Custom1?cmd=Toggle&amp;amp;odb=/Equipment/Environment/Variables/Output[2]&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
This defines a clickable map on top of the custom image. The area(s) should match with some area(s) on the image, e.g. the box of a valve. Determining the co-ordinates of this area is simplified by using the Display mouse position feature.&lt;br /&gt;
&lt;br /&gt;
By clicking on this area, the supplied path to the ODB is used (in this case  {{Odbpath|path=/Equipment/Environment/Variables/Output[2]}}) and its value is toggled. If the valve value is then used in the image via a [[#Adding Fills|Fill]] statement to change the color of the valve, it can turn green or red depending on its state. This is illustrated in [[#Display mouse position|Figure 5]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Clicking an active area can also be made to open a new custom page, for example:&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;687,530, 890,648&amp;quot; alt=&amp;quot;Pump detail&amp;quot; href = &amp;quot;Pump!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;560,574,775,662&amp;quot; alt=&amp;quot;Buffer Tank detail&amp;quot; href = &amp;quot;BufferTank!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
where &#039;&#039;Pump!&#039;&#039; and &#039;&#039;BufferTank!&#039;&#039; are defined as links to custom pages in the [[/Custom ODB tree]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Edit boxes floating on top of a graphic ==&lt;br /&gt;
&lt;br /&gt;
An edit box can be placed on top of a graphic in a particular position by means of an HTML  &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/span&amp;gt; tag. Using the ODBEdit function from the Midas JS library [[Mhttpd.js]], the custom page code would look like this:&lt;br /&gt;
&amp;lt;!-- Complicated... have to use &amp;lt;pre&amp;gt; because of &amp;lt;div&amp;gt;, then a table to keep the background colour between &amp;lt;pre&amp;gt;s --&amp;gt;&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: floralwhite;&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;    &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
document.write(&#039;Run number: &#039;)&lt;br /&gt;
path=&#039;/runinfo/run number&#039;&lt;br /&gt;
rn = ODBGet(path)&lt;br /&gt;
document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(path)&amp;quot; &amp;gt;&#039;)  &lt;br /&gt;
document.write(rn)&lt;br /&gt;
document.write(&#039;&amp;lt;/a&amp;gt;&#039;);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same thing could be done with the HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag :&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;&lt;br /&gt;
Run number:  &amp;lt;odb src=&amp;quot;/Runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Interactive SVG Implementation ==&lt;br /&gt;
&lt;br /&gt;
An SVG file loaded on a custom page can be interactively modified using JavaScript.&lt;br /&gt;
This is useful for graphically displaying ODB values in complex service or detector systems.&lt;br /&gt;
&lt;br /&gt;
An SVG file can be embedded like this:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Objects within the SVG can be modified—for example, by changing their fill color as shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument; &lt;br /&gt;
const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
obj_Valve.style.fill = &amp;quot;#00FF00&amp;quot;; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SVG images are stored in XML format. In Inkscape, object properties can be interactively edited using the XML Editor.&lt;br /&gt;
An example is given below in [[Custom Page Features#Example 4 : Interactive SVG custom page]]&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
For more examples of accessing the ODB with the Javascript library [[mhttpd.js]] look at the example experiment in the MIDAS package $MIDASSYS/examples/javascript1/example.html.&lt;br /&gt;
&lt;br /&gt;
== Example 1 : Asynchronous calls using mjsonrpc functions ==&lt;br /&gt;
See [[mjsonrpc#examples]]&lt;br /&gt;
&lt;br /&gt;
== Example 2 : Asynchronous calls using Javascript and AJAX ==&lt;br /&gt;
&lt;br /&gt;
Example 2 shows html code illustrating the use of ODBMCopy(), where innerHTML is used to display the data. On a page with images or a lot of data, innerHTML allows you to update variables without having to reload the whole page. In the example, a timer causes the page to update every 10s (reread the data). Often the ODB data for the whole page can be read by one asynchronous ODBMCopy() call, rather than scattering synchronous ODBGet() calls throughout the page.  &lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], included with the line &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;script src=&#039;mhttpd.js&#039;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;MyTitle&amp;lt;/title&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;script src=&#039;mhttpd.js&#039;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
var updatePeriod = 10000; // in msec &amp;lt;br&amp;gt;&lt;br /&gt;
var updateTimerId = 0; &amp;lt;br&amp;gt;&lt;br /&gt;
var counter=0; &amp;lt;br&amp;gt;&lt;br /&gt;
function update()  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:clearTimeout(updateTimerId); &amp;lt;br&amp;gt;&lt;br /&gt;
:      load(); &lt;br /&gt;
:      if (updatePeriod &amp;gt; 0) &lt;br /&gt;
:      updateTimerId = setTimeout(&#039;update()&#039;, updatePeriod); &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function load()  { &amp;lt;br&amp;gt;&lt;br /&gt;
: document.getElementById(&#039;LastUpdated&#039;).innerHTML = &amp;quot;Updating...&amp;quot; + new Date; &lt;br /&gt;
: var paths = [ &amp;quot;/Runinfo&amp;quot;, &amp;quot;/Experiment&amp;quot;]; &lt;br /&gt;
: var data_odb=ODBMCopy(paths, mcopy_callback, &amp;quot;json&amp;quot;) &lt;br /&gt;
: counter++; &lt;br /&gt;
: document.getElementById(&#039;counter&#039;).innerHTML = &#039;Counter: &#039;+ counter &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function mcopy_callback(data)  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:    var obj= JSON.parse(data);&lt;br /&gt;
: var runinfo=obj[0];&lt;br /&gt;
: document.getElementById(&#039;rn&#039;).innerHTML = &#039;Run Number =&#039;+ parseInt(runinfo[&amp;quot;Run number&amp;quot;]);&lt;br /&gt;
: document.getElementById(&#039;state&#039;).innerHTML  =&#039;Run State= &#039;+ runinfo.State;&lt;br /&gt;
: var experiment=obj[1];&lt;br /&gt;
: document.getElementById(&#039;name&#039;).innerHTML=&#039;Experiment name = &#039;+ experiment.Name &amp;lt;br&amp;gt; &lt;br /&gt;
}&amp;lt;br&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/head&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;body&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;b&amp;amp;gt;Javascript code using ODBMCopy with callback&amp;amp;lt;/b&amp;amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
The data on the page is updated every 10 sec using a timer&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;LastUpdated&amp;quot; &amp;amp;gt; Last updated: never&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;rn&amp;quot;&amp;amp;gt; Run Number : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;state&amp;quot;&amp;amp;gt; State : unknown&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;name&amp;quot;&amp;amp;gt; Experiment name : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;counter&amp;quot;&amp;amp;gt;Counter: zero  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/div&amp;gt;  &lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: if (updatePeriod &amp;gt; 0)&lt;br /&gt;
:  update(); &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Example 3 : Synchronous Calls using Javascript and AJAX ==&lt;br /&gt;
In the following example, JS functions ODBGet and  ODBEdit from the [[Mhttpd.js|MIDAS Javascript Library]] are used to access the ODB.  &lt;br /&gt;
;NOTE&lt;br /&gt;
# Synchronous calls are now deprecated (see [[#The MIDAS Javascript Library|above]]).&lt;br /&gt;
# The Javascript library &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;script&amp;gt;}}&lt;br /&gt;
{{JS|text=document.write (&#039;Experiment Name: &#039;+ ODBGet(&amp;quot;/Experiment/Name&amp;quot;)) &amp;lt;br&amp;gt; var alarm_path=&amp;quot;/alarms/Alarm system active&amp;quot;; &amp;lt;br&amp;gt; var alarm_active=ODBGet(alarm_path); &amp;lt;br&amp;gt; document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(alarm_path)&amp;quot; &amp;gt;&#039;+alarm_active+&#039;&amp;lt;/a&amp;gt;&#039;)}}&lt;br /&gt;
{{Html|text=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
== Example 4 : Interactive SVG custom page  ==&lt;br /&gt;
&lt;br /&gt;
[[File:SVGcustom_page.svg|thumbnail|none|Figure 9: SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=File:SVGcustom_page.svg&amp;diff=3521</id>
		<title>File:SVGcustom page.svg</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=File:SVGcustom_page.svg&amp;diff=3521"/>
		<updated>2025-05-12T12:16:35Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3520</id>
		<title>Custom Page Features</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3520"/>
		<updated>2025-05-12T12:15:04Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Example 4 : Interactive SVG custom page */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
* [[ODB Page]]&lt;br /&gt;
* [[Custom Page]]&lt;br /&gt;
* [[/Custom ODB tree]]&lt;br /&gt;
* [[Mhttpd.js|MIDAS Javascript library (mhttpd.js)]]&lt;br /&gt;
* [[mjsonrpc|MIDAS JSON RPC library functions (mjsonrpc)]]&lt;br /&gt;
* [[odbedit]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
;NOTE&lt;br /&gt;
: Since 2018, many of the features described here can now be implemented more simply using the  mod* JS functions (see [[Custom Page|Custom Web Page]]).&lt;br /&gt;
: &#039;&#039;&#039;If writing a new web page, it is strongly recommended you make use of the mod*JS features. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page describes some of the special features provided for use on a user-created [[Custom Page|Custom Web Page]] using the [[#The MIDAS Javascript Library]], which was the recommended way to write Custom Pages before the [[Custom Page#modb* Javascript scheme]]  was available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= The MIDAS Javascript Library =&lt;br /&gt;
The MIDAS Javascript Library [[mhttpd.js]] includes routines written in Javascript and AJAX to provide features useful for writers of [[Custom Page]]s, such as access to the ODB. Recently asynchronous [[mjsonrpc|JSON-RPC functions]] using [[mjsonrpc#Javascript client library|Promises]]  has been added (January 2016).&lt;br /&gt;
;NOTE&lt;br /&gt;
:To use functions in this library, it &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Many of the older routines (ODBGet, ODBSet) use &#039;&#039;&#039;synchronous&#039;&#039;&#039; RPC requests. With synchronous request, a second RPC request must wait until the first request is complete. This can significantly slow down web pages where many calls to ODBGet are made. For this reason, functions were developed that can be synchronous or &#039;&#039;&#039;asynchronous&#039;&#039;&#039; (e.g. ODBMCopy) that include a callback mechanism. The data from ODBMCopy can be formatted in [[mjsonrpc#JSON general information|JSON]]. &lt;br /&gt;
&lt;br /&gt;
Web developers are moving away from synchronous RPC requests, which are now deprecated [https://midas.triumf.ca/elog/Midas/1128]. Some modern browsers return a warning on a synchronous RPC request. &lt;br /&gt;
&lt;br /&gt;
A new group of JSON-RPC functions using  [[mjsonrpc#Javascript client library|Promises]] has been added to the MIDAS Javascript Library (January 2016). These are asynchronous only, and they also provide more functionality and have better error handling than the older AJAX calls, which are also inconsistent in how they handle Booleans. They also have much improved handling of reading/writing arrays to the ODB. Before 2018 it was recommended that all new Custom Pages are written using these JSON-RPC functions.  {{Important|text=Since 2018 the [[Custom Page#modb* Javascript scheme]] is recommended for new Custom Pages}}.&lt;br /&gt;
&lt;br /&gt;
The MIDAS Javascript Library routines requires an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). If using an older browser version, the asynchronous AJAX calls (e.g. ODBMCopy) should be used in order to avoid having to rewrite the web page at a later date to remove all synchronous calls.  &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using mjson-rpc asynchronous functions ==&lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], which (since January 2016) includes the [[mjsonrpc]] functions.  It is [[#The MIDAS Javascript Library|recommended]] that these functions are used for &#039;&#039;&#039;new&#039;&#039;&#039; pages rather than the older AJAX calls (see below). &lt;br /&gt;
&lt;br /&gt;
To run these functions you need&lt;br /&gt;
# to  [[mhttpd.js#include js lib|include the Javascript library in the HTML code]]&#039;&#039;&#039;&lt;br /&gt;
# to use an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). &lt;br /&gt;
&lt;br /&gt;
Examples showing how to read and write to the ODB can be found at [[mjsonrpc#examples]]. Also included are examples using arrays. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using asynchronous AJAX calls ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: It is recommended that  asynchronous MIDAS JSON-RPC functions are used for new pages rather than the older AJAX calls (see [[#Access the ODB using mjson-rpc asynchronous functions|above]]).&lt;br /&gt;
&lt;br /&gt;
[[#Example 2 : Asynchronous calls using Javascript and AJAX|Example 2]] uses the asynchronous call ODBMCopy() with callback to read the data. The data are converted into JSON format. In this case, when accessing the JSON data, &#039;&#039;the ODB Keynames must be in the same case as they are in the ODB&#039;&#039;, even though the case is ignored by the ODB. If you find this feature annoying, use [[#Access the ODB using mjson-rpc asynchronous functions|mjson-rpc function db_get_values()]] as all keynames are converted to lower case.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using synchronous AJAX requests ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: Synchronous requests are now deprecated (see [[#The MIDAS Javascript Library|above]]). New pages should be written [[#Using mjson-rpc asynchronous functions]].&lt;br /&gt;
&lt;br /&gt;
See [[#Example 3 : Synchronous Calls using Javascript and AJAX]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Access the ODB with HTML-style &amp;lt;span style=&amp;quot;color:seagreen font-style:italic&amp;quot;&amp;gt;&amp;lt;odb&amp;gt;&amp;lt;/span&amp;gt; tags =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: This method pre-dates the [[Mhttpd.js|MIDAS Javascript Library]]. It is recommended that the [[Mhttpd.js|MIDAS Javascript Library]] be used for ODB access.&lt;br /&gt;
&lt;br /&gt;
If Javascript (JS) is not available, the older HTML-style  {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are still available and provide limited functionality.&lt;br /&gt;
&lt;br /&gt;
The  HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag has been defined for read/write access to the ODB under HTML. The {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags.&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: rgb(255, 255, 255);&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Access to ODB from HTML&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | HTML ODB tag&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | Meaning&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot;&amp;gt;&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: black&amp;quot;  |Display ODB field (read only)&lt;br /&gt;
	&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=1 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (inline style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=2 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (popup style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: The Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; (documented in the [http://ladd00.triumf.ca/~daqweb/doc/midas-old/html/RC_customize_ODB.html#RC_Access_Control| OldMidas Document]) may not be working.&lt;br /&gt;
: Use the  [[/Experiment ODB tree#Security subtree|Web Password security]] instead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are included in the HTML code e.g.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
 Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt;&lt;br /&gt;
 Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt; &amp;lt;br&amp;gt;  Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[Custom Page#How to write a Custom Page|HTML Custom Page example]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= ODB RPC access =&lt;br /&gt;
The [[Mhttpd.js|MIDAS Javascript Library]] function ODBRpc() defined for RPC access.&lt;br /&gt;
&lt;br /&gt;
This permits buttons on MIDAS &amp;quot;custom&amp;quot; web pages to invoke RPC calls directly into user frontend programs, for example to turn hardware modules on or off.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= JSON support =&lt;br /&gt;
[[mjsonrpc#JSON general information|JSON]] support is provided with the [[Mhttpd.js|MIDAS Javascript Library]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Access to the MIDAS Menu buttons =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should follow the instructions there to include the standard MIDAS Menu buttons.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Access to the standard MIDAS Menu buttons can be provided with HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags of the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt; --&amp;gt;&lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt;}}&lt;br /&gt;
Valid values are the standard MIDAS Menu buttons, i.e. (Start, Pause, Resume, Stop, ODB, Elog, Alarms, History, Programs etc). The {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags must be declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags (see above).&lt;br /&gt;
&lt;br /&gt;
The following HTML fragment shows the inclusion of three of the standard buttons, giving access to the Main Status, ODB and Messages pages :&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
 &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; ...  &amp;lt;br&amp;gt; &amp;lt;/form&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
See also [[#Redirect]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Redirect =&lt;br /&gt;
&lt;br /&gt;
When buttons are included on a Custom Page, after pressing a button (e.g. the Start or Stop button) it may be desirable to return to the same custom page, rather than returning to the [[Status Page]].&lt;br /&gt;
&lt;br /&gt;
This can be done by including an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag with the attributes &#039;&#039;type&#039;&#039; set to &amp;quot;hidden&amp;quot; and &#039;&#039;name&#039;&#039; set to &amp;quot;redir&amp;quot;. This name (&amp;quot;redir&amp;quot;) is detected by [[Mhttpd]], causing a redirect to the specified custom link in the &#039;&#039;value&#039;&#039; attribute.&lt;br /&gt;
&lt;br /&gt;
For example, the following redirects the screen back to the custom page link   {{Odbpath|path=/Custom/my_custom_page&amp;amp;}} when buttons are pressed:&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;my_custom_page&amp;amp;&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[#Replace Status Page by a Custom page|Redirect when a Custom Page replaces the Status Page]].&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= CustomScript Buttons =&lt;br /&gt;
&lt;br /&gt;
[[/Customscript ODB tree#Customscript-button|CustomScript buttons]] can be provided on [[Custom Page|Custom Pages]]. These buttons are equivalent to optional &#039;&#039;&#039;script buttons&#039;&#039;&#039; on the [[Status Page]], which call a script to perform a particular action when the button is pressed. See [[/Script ODB tree#Script-button|script buttons]] for details. Customscript buttons can be set up through the [[/Customscript ODB tree]].&lt;br /&gt;
&lt;br /&gt;
Any key &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/CustomScript/test&amp;lt;/span&amp;gt; will appear as a customscript-button &lt;br /&gt;
&amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; on a custom page whose code includes an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag of the form:&lt;br /&gt;
{{Html|text=&amp;lt;input type=submit name=customscript value=&amp;quot;test&amp;quot;&amp;gt;}}&lt;br /&gt;
where the action of the button &amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; will be found in the &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/customscript/test&amp;lt;/span&amp;gt; subdirectory.&lt;br /&gt;
&lt;br /&gt;
After pressing a customscript-button, the &#039;&#039;type=submit&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} will cause a page reload. This will result in a switch to the [[Status Page]], unless a [[#redirect]] input tag is included to redirect back to the original custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customscript button without a page reload ==&lt;br /&gt;
To define a customscript button that does &#039;&#039;&#039;not&#039;&#039;&#039; cause a page reload, set the &#039;&#039;type&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} to &amp;quot;button&amp;quot;, i.e. &lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;customscript&amp;quot; value=&amp;quot;test&amp;quot; type=&amp;quot;button&amp;quot; onClick=cs_button(this.value)&amp;gt;  &amp;lt;!-- Customscript button does not reload page; no callback --&amp;gt;}}&lt;br /&gt;
and use &#039;&#039;&amp;quot;onClick&amp;quot;&#039;&#039; to call a function to [[#Send an Ajax Request]] instead. The following function sends an asynchronous request, with an optional callback.&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;script&amp;gt;}}&lt;br /&gt;
 &amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function cs_button(cmd, callback)&amp;lt;br&amp;gt;&lt;br /&gt;
{  // send a request to execute a custom script&lt;br /&gt;
:   var url;&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
:&lt;br /&gt;
:   url = ODBUrlBase&lt;br /&gt;
:   if(document.getElementById(&amp;quot;redir&amp;quot;) != null)&lt;br /&gt;
::     url +=  &#039;?redir=&#039;+document.getElementById(&#039;redir&#039;).value&lt;br /&gt;
:   url+=&#039;&amp;amp;customscript=&#039;+ encodeURIComponent(cmd);&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true); // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
:  if(callback!=undefined) {&lt;br /&gt;
::      request.onreadystatechange = function() {&lt;br /&gt;
:::         if (request.readyState == 4) { &lt;br /&gt;
::::            if (request.status == 200)&lt;br /&gt;
:::::                callback();&lt;br /&gt;
::::            else&lt;br /&gt;
:::::                alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:::            }&lt;br /&gt;
::         }&lt;br /&gt;
:  }&lt;br /&gt;
:  else { &lt;br /&gt;
::   if (request.status != 200)&lt;br /&gt;
:::       alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:	   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Resource files =&lt;br /&gt;
== MIDAS resource files ==&lt;br /&gt;
A number of resource files have been provided in the MIDAS packages under {{Filepath|path=../packages/midas/resources}}.  When including a MIDAS resource file, no key in  {{Odbpath|path=/Custom}} needs to be defined as the MIDAS resources directory is searched automatically. &lt;br /&gt;
&lt;br /&gt;
One such resource file is a condensed stylesheet {{File|name=midas.css}} for users who would like their custom pages to have a similar &amp;quot;look and feel&amp;quot; to that of the standard pages. To include the MIDAS stylesheet, in the HTML header, add &lt;br /&gt;
{{HtmlTag|tag=&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;}}&lt;br /&gt;
Other resource files can provide [[Custom Page#How to use the standard MIDAS navigation bars on your custom page|the standard MIDAS navigation bars]].&lt;br /&gt;
&lt;br /&gt;
== Custom resource files ==&lt;br /&gt;
It is often desirable to serve resource files (i.e. &#039;&#039;&#039;local files&#039;&#039;&#039; such as an external stylesheet, javascript files or images&#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039;) to a custom page. The following sections describe two alternative ways of serving resource files to a custom page:&lt;br /&gt;
* [[#Resource files served WITH /custom/path key defined|with  /custom/path key defined]]&lt;br /&gt;
* [[#Resource files served WITHOUT /custom/path key defined|without  /custom/path key defined]]&lt;br /&gt;
See also serving resources to a [[#Replace Status Page by a Custom Status page|Custom Status page]].&lt;br /&gt;
;NOTE&lt;br /&gt;
:  &#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039; To superimpose labels, bars or fills onto an image, the image cannot be served as a resource file. See [[#Image insertion]].&lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITH &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
When a number of resource files are required, it is convenient to place them in the same directory on the disk, and create a key  {{Odbpath|path=/Custom/Path}} to contain this directory. &lt;br /&gt;
&lt;br /&gt;
 $ ls  /home/midas/online/custom/&lt;br /&gt;
   custom_functions.js       custom_globals.js     custom_page.html    custom_stylesheet.css      test_image.png&lt;br /&gt;
With the  {{Odbpath|path=/Custom/Path}} key defined, [[/Custom ODB tree#Custom-Link|custom-links]] for individual resource files need not be created in the  {{Odbpath|path=/Custom/Path}} tree. The custom-link  {{Odbpath|path=custom_page&amp;amp;}} &#039;&#039;&#039;is&#039;&#039;&#039; required so that the custom page can be accessed from the [[/Custom ODB tree#Custom-Button|custom-button]]  {{Button|name=custom_page}} on the Status Page. The key name ends in the special character &amp;quot;&amp;amp;&amp;quot; so that it will open in the same window as the Status page (see [[/Custom ODB tree#Key names|key names]]).&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   path                           /home/midas/online/custom/&lt;br /&gt;
   custom_page&amp;amp;                   custom_page.html&lt;br /&gt;
 &lt;br /&gt;
A {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the file name) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_globals.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;custom_stylesheet.css&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;test_image.png&amp;quot;&amp;gt; }}&lt;br /&gt;
[[mhttpd]] then loads the resource file from the directory indicated by the Path key with the correct MIME type (see  [[/Custom ODB tree#Keys in the /Custom tree|Custom tree keys]] for supported MIME types). &lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITHOUT &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
Alternatively, resource files can be served to a [[Custom Page]] by creating a key for every resource file in the [[/Custom ODB tree]]. These keys must contain the full path of the file on the disk, and the key {{Odbpath|path=/Custom/Path}} must NOT be defined. The resource files can be in different directories on the disk. By defining the key names with an appropriate file extension, the resource files are served with the appropriate MIME types. The key names end in the special character &amp;quot;!&amp;quot; so that they will not appear on the Status page as custom-links (see [[/Custom ODB tree#Key names|key names]].&lt;br /&gt;
&lt;br /&gt;
Without the {{Odbpath|path=/Custom/Path}} key,  the links for the above example in the {{Odbpath|path=/Custom}} tree might look like&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   custom_page&amp;amp;                   /home/midas/online/custom/custom_page.html&lt;br /&gt;
   custom_functions.js!           /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   globals.js!                    /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   stylesheet.css!                /home/midas/online/stylesheets/custom_stylesheet.css&lt;br /&gt;
   image.png!                     /home/midas/images/test_image.png  &lt;br /&gt;
&lt;br /&gt;
assuming the resource files are now in the subdirectories indicated.&lt;br /&gt;
The {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the custom-link) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;globals.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;stylesheet.css!&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;image.png!&amp;quot;&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The resulting Demo custom page is shown in Figure 1, which can be compared with Figure 2 (no stylesheet). &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Figure 1  !! Figure 2&lt;br /&gt;
|-&lt;br /&gt;
| Demo Custom Page using MIDAS stylesheet || Demo Custom Page&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| [[File:Mhxcustom03.jpg|thumb|300px]] || [[File:Mhxcustom02.jpg|thumb|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=  Alias-Buttons and Hyperlinks =&lt;br /&gt;
Any hyperlink can easily be included on a [[Custom Page]] by using the standard HTML anchor {{HtmlTag|tag=&amp;lt;a...&amp;gt;}} tag, e.g.&lt;br /&gt;
{{Html|text=&amp;lt;a href=&amp;quot;http://ladd00.triumf.ca/~daqweb/doc/midas/html/&amp;quot;&amp;gt;Midas Help&amp;lt;/a&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Links on a custom page equivalent to [[/Alias ODB tree#Alias-Buttons|alias-buttons]] can also be made e.g.&lt;br /&gt;
{{Html|text=&amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.location.href=&#039;/Alias/alias&amp;amp;&#039;;&amp;quot;&amp;gt;alias&amp;lt;/button&amp;gt;}}&lt;br /&gt;
See the [[/Alias ODB tree]] for details.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Page refresh =&lt;br /&gt;
The following {{HtmlTag|tag=&amp;lt;meta...&amp;gt;}} tag included in the HTML header code will cause the whole custom page to refresh in 60 seconds :&lt;br /&gt;
{{Html|text=&amp;lt;meta http-equiv=&amp;quot;Refresh&amp;quot; content=&amp;quot;60&amp;quot;&amp;gt;}}&lt;br /&gt;
It is also possible to [[#Periodic update of parts of a custom page|periodically update parts]] of a custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Periodic update of parts of a custom page =&lt;br /&gt;
&lt;br /&gt;
The functionality of [[Mhttpd.js|ODBGet]] together with the window.setInterval() function&lt;br /&gt;
can be used to update parts of the web page periodically.&lt;br /&gt;
For example the Javascript fragment below contains a function which updates the current run number every 10 seconds in the background:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt; &amp;lt;!-- JS --&amp;gt;&lt;br /&gt;
window.setInterval(&amp;quot;Refresh()&amp;quot;, 10000);&amp;lt;br&amp;gt;&lt;br /&gt;
function Refresh() {&amp;lt;br&amp;gt;&lt;br /&gt;
:document.getElementById(&amp;quot;run_number&amp;quot;).innerHTML = ODBGet(&#039;/Runinfo/Run number&#039;);&amp;lt;br&amp;gt;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
The custom page has to &lt;br /&gt;
* [[Mhttpd.js#include js lib|include the MIDAS JS library]] to access ODBGet&lt;br /&gt;
* contain an element with id=&amp;quot;run_number&amp;quot;, such as&lt;br /&gt;
{{Html|text=&amp;lt;td id=&amp;quot;run_number&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Display last MIDAS message(s) =&lt;br /&gt;
&lt;br /&gt;
The message log (see [[Message System]]) can be accessed from a custom page using a call to the JavaScript library function [[Mhttpd.js|ODBGetMsg]] (provided the [[Mhttpd.js#include js lib|JS library is included]]).&lt;br /&gt;
&lt;br /&gt;
This allows the inclusion of the &amp;quot;Last Midas message&amp;quot; on a custom page, e.g.&lt;br /&gt;
{{JS|text=document.write(&#039;Last message:&#039;+ODBGetMsg(&amp;quot;midas&amp;quot;,0,1))}}&lt;br /&gt;
More messages may be displayed by increasing the third parameter to ODBGetMsg.&lt;br /&gt;
;Note&lt;br /&gt;
: Parameters were changed August 2015. See [[Mhttpd.js|ODBGetMsg]] for older versions.&lt;br /&gt;
: Coming soon - a [[mjsonrpc]] function&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Generate a message log entry =&lt;br /&gt;
&lt;br /&gt;
A custom page can generate a message to be sent to the MIDAS message log (see [[Message System]]).  A call to mjsonrpc_cm_msg() will generate a message if using the [[mjsonrpc]] functions. Otherwise, use the AJAX function [[Mhttpd.js|ODBGenerateMsg]]. To use these functions, the  [[Mhttpd.js#include js lib|JS library must be included]] in the html code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Checkboxes =&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New  (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should use [[Custom Page#modbcheckbox|modbcheckbox]] to create a checkbox.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;Br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function [[Mhttpd.js|ODBSet]] (provided the [[Mhttpd.js#include js lib|JS library is included]]) can be used when one clicks on a checkbox for example:&lt;br /&gt;
{{Html|text=&amp;lt;input  name=&amp;quot;box0&amp;quot;  type=&amp;quot;checkbox&amp;quot;  onClick=&amp;quot;ODBSet(my_path, this.checked?&#039;1&#039;:&#039;0&#039;)&amp;quot;&amp;gt;}}&lt;br /&gt;
If used as above, the state of the checkbox must be initialized when the page is loaded. This can be done with some JavaScript code called on initialization, e.g.&lt;br /&gt;
{{JS|text=document.form1.box0.checked= ODBGet(my_path));  // initialize to the correct value}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Replace Status Page by a Custom page =&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_status.jpg|thumbnail|left|Figure 3: ODB /Custom/Status custom-link to a custom status page]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a custom-link with the [[/Custom ODB tree#Key names|reserved key name]] &#039;&#039;&#039;Status&#039;&#039;&#039;  (not &amp;quot;Status&amp;amp;&amp;quot; or &amp;quot;Status!&amp;quot;) is created in the [[/Custom ODB tree]] (as shown in  Figure 3), then that custom page will &#039;&#039;&#039;replace the default Status Page&#039;&#039;&#039;. &lt;br /&gt;
  &lt;br /&gt;
Clicking on the {{Button|name=Status}} button on any of the sub-pages (e.g. [[ODB Page]], [[Programs Page]] etc.) will now return to the Custom Status Page. If there are buttons on the Custom Status page, you &#039;&#039;&#039;must&#039;&#039;&#039; include a  [[#Redirect]] statement of the form&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;../&amp;quot;&amp;gt;}}&lt;br /&gt;
or you will see the message &lt;br /&gt;
 Invalid custom page:NULL path&lt;br /&gt;
&lt;br /&gt;
If the Custom Status page includes [[#Resource files]] served on a regular custom page with a statement such as&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
to serve them in a Custom Status page, the statement would be&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&amp;lt;span style=&amp;quot;font-weight:bold&amp;quot;&amp;gt;/CS/&amp;lt;/span&amp;gt;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
In fact, this statement can be used in a regular custom page, as the &amp;quot;/CS/&amp;quot; is ignored in that case.&lt;br /&gt;
&lt;br /&gt;
To return to the default Status Page, delete the &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Custom/Status&amp;lt;/span&amp;gt; key. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Start, Stop and Check if a program is running =&lt;br /&gt;
There are [[mjsonrpc]] functions that implemented all three program management functions - start program, &lt;br /&gt;
stop program and &amp;quot;is running?&amp;quot; as JSON-RPC methods.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Send an Ajax request =&lt;br /&gt;
By sending an Ajax request from a custom page, you can make a button perform a specific function. &lt;br /&gt;
  &lt;br /&gt;
All functions in midas are controlled through special URLs. So the URL&lt;br /&gt;
 http://&amp;lt;host:port&amp;gt;/?cmd=Start&amp;amp;value=10&lt;br /&gt;
will start run #10.&lt;br /&gt;
&lt;br /&gt;
Although it is easier to use an HTML input statement to [[#Access to the MIDAS Menu buttons]], &lt;br /&gt;
to send an Ajax request, you can use the function &#039;&#039;XMLHttpRequestGeneric&#039;&#039; which is in the MIDAS Javascript library [[mhttpd.js]].&lt;br /&gt;
Then the HTML code would be&lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;start()&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
and in your JavaScript code add a function &#039;&#039;start()&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function start()&lt;br /&gt;
{&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
&lt;br /&gt;
:   url = &#039;?cmd=Start&amp;amp;value=10&#039;;&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true);  // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
See also [[#Access to the MIDAS Menu buttons]]. Another example with optional callback can be found in [[#Customscript button without a page reload]].&lt;br /&gt;
&lt;br /&gt;
This mechanism can be used for starting a particular program, see for example (see [https://midas.triumf.ca/elog/Midas/1046]). However, this functionality is now provided by [[mjsonrpc]] functions - see [[#Start, Stop and Check if a program is running]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Image insertion  =&lt;br /&gt;
An image can be loaded from the web using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}}, e.g.  &lt;br /&gt;
   {{Html|text=&amp;quot;TRIUMF logo&amp;quot; &amp;lt;img src=&amp;quot;https://lixenon.triumf.ca/InternalDocuments/Alice/figures/TRIUMF-logo.jpg/image_preview&amp;quot;&amp;gt;}}&lt;br /&gt;
or a MIDAS History image can be inserted into a custom page using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag of the following form:&lt;br /&gt;
 {{Html|text=blah&amp;lt;img src=&amp;quot;http://hostname.domain:port/HS/Meterdis.gif&amp;amp;scale=12h&amp;amp;width=300&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If the image file is on the local disk, it can be loaded as a &#039;&#039;&#039;resource file&#039;&#039;&#039;. The image can be of format .pdf, .jpg, .gif, .png. See loading [[#Resource files]].&lt;br /&gt;
&lt;br /&gt;
If you wish to superimpose features such as &#039;&#039;&#039;labels and fills&#039;&#039;&#039;, the image file cannot be served as a [[#Resource files|resource file]]. Instead, the image file must be in &#039;&#039;&#039;gif&#039;&#039;&#039; format, and must be included in the {{Odbpath|path=/Custom/images}} subtree (Figure 4). Image insertion into a Custom page will be illustrated using the Demo custom page shown in [[#MIDAS stylesheet|Figure 2]]. All the files required for this demo can be found in the MIDAS package at $MIDASSYS/examples/custom. If you do not wish to create&lt;br /&gt;
the keys yourself, proceed to [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
Make a [[/Custom ODB tree#Custom-Link|custom-link]] to the Demo custom page file &#039;&#039;myexpt.html&#039;&#039; , i.e.&lt;br /&gt;
 [local:js:S]/&amp;gt;ls /custom&lt;br /&gt;
    myexpt&amp;amp;                  /home/test/packages/midas/examples/custom/myexpt.html&lt;br /&gt;
 &lt;br /&gt;
To make the image &#039;&#039;myexpt.gif&#039;&#039; visible on the custom page, the path and filename of the image file must be defined in the   {{Odbpath|path=/Custom/images}} subtree. To do this, &lt;br /&gt;
create the  subtrees {{Odbpath|path=/Custom/images/myexpt.gif}} where the subtree name &amp;quot;myexpt.gif&amp;quot; is named for the image file you are going to use. Multiple images can be used, by creating multiple imagefile subtrees.&lt;br /&gt;
&lt;br /&gt;
In the imagefile subtree {{Odbpath|path=myexpt.gif}}, create the STRING key  {{Odbpath|path=Background}}, and set it to contain the path and name of the image file.  The tree structure should then look similar to Figure 4, minus the labels/bars/fill subtrees which will be added to the ODB later.&lt;br /&gt;
&lt;br /&gt;
The image must also be referenced in the custom HTML file &#039;&#039;myexpt.html&#039;&#039; in the &amp;quot;src&amp;quot; field of an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag, e.g. &lt;br /&gt;
{{Html|text=&amp;lt;img &#039;&#039;&#039;src=&amp;quot;myexpt.gif&amp;quot;&#039;&#039;&#039;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_images.jpg|thumbnail|left|Figure 4: /Custom/Images ODB Tree]]&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
Once the image is visible, enable [[#HTML mapping]], optionally [[#Display mouse position]] and proceed to  [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== HTML mapping ==&lt;br /&gt;
Note that if additional features such as active clickable areas and labels, bars and fills superimposed on the image are also required, HTML mapping must also be activated with the HTML {{HtmlTag|tag=&amp;lt;map...&amp;gt;}} tag and the &amp;quot;usemap&amp;quot; attribute of the HTML {{HtmlTag|tag=&amp;lt;img&amp;gt;}} tag&lt;br /&gt;
{{Html|text=   &amp;lt;map &#039;&#039;&#039;name=&amp;quot;myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;img src=&amp;quot;myexpt.gif&amp;quot; &#039;&#039;&#039;usemap=&amp;quot;#myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt;...&amp;lt;br&amp;gt; &amp;lt;/map&amp;gt;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Display mouse position ==&lt;br /&gt;
[[File:Cursor.png|thumbnail|left|Figure 5: MEG Gas System Custom Page showing cursor position]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When writing custom pages with large background images and labels and fills placed on that image, it is hard to figure out X and Y coordinates of the labels. This can now be simplified by using the function &#039;&#039;getMouseXY()&#039;&#039; in the development JavaScript built-in library [[develop.js]]. This function supplies the X,Y position of the cursor if an element of ID &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; is present. This JS library &#039;&#039;&#039;must be [[develop.js|included in the custom page]]&#039;&#039;&#039; in order to use it:&lt;br /&gt;
&lt;br /&gt;
Then, set the &amp;quot;id&amp;quot; attribute of the background HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag to &amp;quot;refimg&amp;quot;, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: &amp;lt;img  &#039;&#039;&#039;id=&amp;quot;refimg&amp;quot;&#039;&#039;&#039; src=&amp;quot;ebit_pc.gif&amp;quot; usemap=&amp;quot;#Custom1&amp;quot;&amp;gt;   &amp;lt;!-- name=&amp;quot;refimg&amp;quot; makes crosshairs appear --&amp;gt;&lt;br /&gt;
: &amp;lt;map name=&amp;quot;Custom1&amp;quot;&amp;gt;&lt;br /&gt;
: .....&lt;br /&gt;
: &amp;lt;/map&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; tag is present, the cursor changes into a crosshair, and its absolute and relative locations in respect to the reference image are shown in the status bar (Figure 5).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
==  Superimposing Labels, Bars and Fills onto a gif image ==&lt;br /&gt;
You can enhance your custom page by superimposing multiple features based on ODB variables onto an image (e.g. [[#Display mouse position|Figure 5]]), such as&lt;br /&gt;
&lt;br /&gt;
*    labels: &amp;quot;live&amp;quot; ODB values positioned in a particular location of the page&lt;br /&gt;
*    bars : &amp;quot;bar level&amp;quot; showing graphically ODB values such as levels or rate etc.&lt;br /&gt;
*    fills : &amp;quot;color level&amp;quot; where colour is used as the level indicator.&lt;br /&gt;
&lt;br /&gt;
Each entry (label/bar/fill) will have an ODB tree associated to it defining the ODB variable path, X/Y position, colour, etc. Each time the page is updated, the latest ODB value/level/rate will be shown based on the ODB parameter to which the label, bar or fill is linked - hence the term &amp;quot;live&amp;quot;. The overlay of the requested features is done onto the selected image file.&lt;br /&gt;
&lt;br /&gt;
This powerful new extension brings the [[mhttpd]] capability closer to other experimental web controllers similar to EPICS.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
:    Be sure to enable the feature to [[#Display mouse position]] in order to facilitate finding the X,Y positions of the various features.&lt;br /&gt;
: [[#HTML mapping]] must be activated for labels/bars/fills to work&lt;br /&gt;
&lt;br /&gt;
A Demo custom page showing labels, bars and fills superimposed on an image is shown in [[#MIDAS stylesheet|Figure 2]]. &lt;br /&gt;
All the files for this demo can be found in $MIDASSYS/examples/custom/. The file xcustom.odb contains the ODB keys required, including those to insert the image and superimpose the various labels, fills etc. This file can be loaded into the ODB with the  {{Odbedit cmd|cmd=load}}.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Labels ===&lt;br /&gt;
&lt;br /&gt;
 [[File:Mhcustom_label.jpg|thumbnail|left|Figure 6: /Custom/Images/Labels ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In order to include a readout of ODB values (i.e. labels), on the image, a further ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Labels}} must be created. Creating a subdirectory for a particular label i.e. {{Odbpath|path=&amp;lt;label name&amp;gt;}} in the   {{Odbpath|path=Labels}} subtree will, at the next custom web page refresh, cause the complete structure for that label to be created and filled with default values. Once the {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is created, the user fills the various keys as desired. See [[/Custom ODB tree#Labels subtree]] for details of the various fields. This procedure is repeated for all the labels required, using a unique {{Odbpath|path=&amp;lt;label name&amp;gt;}} subdirectory for each label. An example of a {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is shown in Figure 6. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Bars ===&lt;br /&gt;
 &lt;br /&gt;
[[File:Mhcustom_fill.jpg|thumbnail|left|Figure 7: /Custom/Images/Fills ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Bars can be superimposed on the image. Create  a new ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Bars}}. Create a subdirectory for a particular Bar ({{Odbpath|path=&amp;lt;bar name&amp;gt;}}) in the {{Odbpath|path=Bars}}  subdirectory. Refresh the web page and fill the various keys as desired.  See [[/Custom ODB tree#Bars subtree]] for details of the various fields.  Examples of a &amp;lt;bar-name&amp;gt; subtree is shown in Figure 7.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Fills ===&lt;br /&gt;
[[File:Mhcustom_bar.jpg|thumbnail|left|Figure 8: /Custom/Images/Bars ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Fills can be superimposed on the image. Create new ODB subdirectory   {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Fills}}. Create a subdirectory for a particular  Fill ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}) in the {{Odbpath|path=Fills}} subdirectory. Refresh the web page and fill the various keys as desired.  See  [[/Custom ODB tree#Fills subtree]] for details of the various fields.  Examples of a ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}  subtree is shown in Figure 8. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Mapping active areas onto the image ==&lt;br /&gt;
Provided [[#HTML mapping]] is activated, &amp;quot;clickable&amp;quot; areas can be created on the image.&lt;br /&gt;
&lt;br /&gt;
This can be done now with a new function like this:&lt;br /&gt;
{{Html|text= &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;40,200,100,300&amp;quot; alt=&amp;quot;Main Valve&amp;quot; href=&amp;quot;Custom1?cmd=Toggle&amp;amp;odb=/Equipment/Environment/Variables/Output[2]&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
This defines a clickable map on top of the custom image. The area(s) should match with some area(s) on the image, e.g. the box of a valve. Determining the co-ordinates of this area is simplified by using the Display mouse position feature.&lt;br /&gt;
&lt;br /&gt;
By clicking on this area, the supplied path to the ODB is used (in this case  {{Odbpath|path=/Equipment/Environment/Variables/Output[2]}}) and its value is toggled. If the valve value is then used in the image via a [[#Adding Fills|Fill]] statement to change the color of the valve, it can turn green or red depending on its state. This is illustrated in [[#Display mouse position|Figure 5]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Clicking an active area can also be made to open a new custom page, for example:&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;687,530, 890,648&amp;quot; alt=&amp;quot;Pump detail&amp;quot; href = &amp;quot;Pump!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;560,574,775,662&amp;quot; alt=&amp;quot;Buffer Tank detail&amp;quot; href = &amp;quot;BufferTank!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
where &#039;&#039;Pump!&#039;&#039; and &#039;&#039;BufferTank!&#039;&#039; are defined as links to custom pages in the [[/Custom ODB tree]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Edit boxes floating on top of a graphic ==&lt;br /&gt;
&lt;br /&gt;
An edit box can be placed on top of a graphic in a particular position by means of an HTML  &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/span&amp;gt; tag. Using the ODBEdit function from the Midas JS library [[Mhttpd.js]], the custom page code would look like this:&lt;br /&gt;
&amp;lt;!-- Complicated... have to use &amp;lt;pre&amp;gt; because of &amp;lt;div&amp;gt;, then a table to keep the background colour between &amp;lt;pre&amp;gt;s --&amp;gt;&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: floralwhite;&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;    &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
document.write(&#039;Run number: &#039;)&lt;br /&gt;
path=&#039;/runinfo/run number&#039;&lt;br /&gt;
rn = ODBGet(path)&lt;br /&gt;
document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(path)&amp;quot; &amp;gt;&#039;)  &lt;br /&gt;
document.write(rn)&lt;br /&gt;
document.write(&#039;&amp;lt;/a&amp;gt;&#039;);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same thing could be done with the HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag :&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;&lt;br /&gt;
Run number:  &amp;lt;odb src=&amp;quot;/Runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Interactive SVG Implementation ==&lt;br /&gt;
&lt;br /&gt;
An SVG file loaded on a custom page can be interactively modified using JavaScript.&lt;br /&gt;
This is useful for graphically displaying ODB values in complex service or detector systems.&lt;br /&gt;
&lt;br /&gt;
An SVG file can be embedded like this:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Objects within the SVG can be modified—for example, by changing their fill color as shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument; &lt;br /&gt;
const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
obj_Valve.style.fill = &amp;quot;#00FF00&amp;quot;; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SVG images are stored in XML format. In Inkscape, object properties can be interactively edited using the XML Editor.&lt;br /&gt;
An example is given below in [[Custom Page Features#Example 4 : Interactive SVG custom page]]&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
For more examples of accessing the ODB with the Javascript library [[mhttpd.js]] look at the example experiment in the MIDAS package $MIDASSYS/examples/javascript1/example.html.&lt;br /&gt;
&lt;br /&gt;
== Example 1 : Asynchronous calls using mjsonrpc functions ==&lt;br /&gt;
See [[mjsonrpc#examples]]&lt;br /&gt;
&lt;br /&gt;
== Example 2 : Asynchronous calls using Javascript and AJAX ==&lt;br /&gt;
&lt;br /&gt;
Example 2 shows html code illustrating the use of ODBMCopy(), where innerHTML is used to display the data. On a page with images or a lot of data, innerHTML allows you to update variables without having to reload the whole page. In the example, a timer causes the page to update every 10s (reread the data). Often the ODB data for the whole page can be read by one asynchronous ODBMCopy() call, rather than scattering synchronous ODBGet() calls throughout the page.  &lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], included with the line &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;script src=&#039;mhttpd.js&#039;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;MyTitle&amp;lt;/title&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;script src=&#039;mhttpd.js&#039;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
var updatePeriod = 10000; // in msec &amp;lt;br&amp;gt;&lt;br /&gt;
var updateTimerId = 0; &amp;lt;br&amp;gt;&lt;br /&gt;
var counter=0; &amp;lt;br&amp;gt;&lt;br /&gt;
function update()  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:clearTimeout(updateTimerId); &amp;lt;br&amp;gt;&lt;br /&gt;
:      load(); &lt;br /&gt;
:      if (updatePeriod &amp;gt; 0) &lt;br /&gt;
:      updateTimerId = setTimeout(&#039;update()&#039;, updatePeriod); &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function load()  { &amp;lt;br&amp;gt;&lt;br /&gt;
: document.getElementById(&#039;LastUpdated&#039;).innerHTML = &amp;quot;Updating...&amp;quot; + new Date; &lt;br /&gt;
: var paths = [ &amp;quot;/Runinfo&amp;quot;, &amp;quot;/Experiment&amp;quot;]; &lt;br /&gt;
: var data_odb=ODBMCopy(paths, mcopy_callback, &amp;quot;json&amp;quot;) &lt;br /&gt;
: counter++; &lt;br /&gt;
: document.getElementById(&#039;counter&#039;).innerHTML = &#039;Counter: &#039;+ counter &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function mcopy_callback(data)  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:    var obj= JSON.parse(data);&lt;br /&gt;
: var runinfo=obj[0];&lt;br /&gt;
: document.getElementById(&#039;rn&#039;).innerHTML = &#039;Run Number =&#039;+ parseInt(runinfo[&amp;quot;Run number&amp;quot;]);&lt;br /&gt;
: document.getElementById(&#039;state&#039;).innerHTML  =&#039;Run State= &#039;+ runinfo.State;&lt;br /&gt;
: var experiment=obj[1];&lt;br /&gt;
: document.getElementById(&#039;name&#039;).innerHTML=&#039;Experiment name = &#039;+ experiment.Name &amp;lt;br&amp;gt; &lt;br /&gt;
}&amp;lt;br&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/head&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;body&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;b&amp;amp;gt;Javascript code using ODBMCopy with callback&amp;amp;lt;/b&amp;amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
The data on the page is updated every 10 sec using a timer&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;LastUpdated&amp;quot; &amp;amp;gt; Last updated: never&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;rn&amp;quot;&amp;amp;gt; Run Number : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;state&amp;quot;&amp;amp;gt; State : unknown&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;name&amp;quot;&amp;amp;gt; Experiment name : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;counter&amp;quot;&amp;amp;gt;Counter: zero  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/div&amp;gt;  &lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: if (updatePeriod &amp;gt; 0)&lt;br /&gt;
:  update(); &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Example 3 : Synchronous Calls using Javascript and AJAX ==&lt;br /&gt;
In the following example, JS functions ODBGet and  ODBEdit from the [[Mhttpd.js|MIDAS Javascript Library]] are used to access the ODB.  &lt;br /&gt;
;NOTE&lt;br /&gt;
# Synchronous calls are now deprecated (see [[#The MIDAS Javascript Library|above]]).&lt;br /&gt;
# The Javascript library &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;script&amp;gt;}}&lt;br /&gt;
{{JS|text=document.write (&#039;Experiment Name: &#039;+ ODBGet(&amp;quot;/Experiment/Name&amp;quot;)) &amp;lt;br&amp;gt; var alarm_path=&amp;quot;/alarms/Alarm system active&amp;quot;; &amp;lt;br&amp;gt; var alarm_active=ODBGet(alarm_path); &amp;lt;br&amp;gt; document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(alarm_path)&amp;quot; &amp;gt;&#039;+alarm_active+&#039;&amp;lt;/a&amp;gt;&#039;)}}&lt;br /&gt;
{{Html|text=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
== Example 4 : Interactive SVG custom page  ==&lt;br /&gt;
&lt;br /&gt;
[[File:SVGcustom_page.svg|thumbnail|left|Figure X: SVG based custom page]]&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3519</id>
		<title>Custom Page Features</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3519"/>
		<updated>2025-05-12T12:14:00Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Interactive SVG Implementation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
* [[ODB Page]]&lt;br /&gt;
* [[Custom Page]]&lt;br /&gt;
* [[/Custom ODB tree]]&lt;br /&gt;
* [[Mhttpd.js|MIDAS Javascript library (mhttpd.js)]]&lt;br /&gt;
* [[mjsonrpc|MIDAS JSON RPC library functions (mjsonrpc)]]&lt;br /&gt;
* [[odbedit]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
;NOTE&lt;br /&gt;
: Since 2018, many of the features described here can now be implemented more simply using the  mod* JS functions (see [[Custom Page|Custom Web Page]]).&lt;br /&gt;
: &#039;&#039;&#039;If writing a new web page, it is strongly recommended you make use of the mod*JS features. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page describes some of the special features provided for use on a user-created [[Custom Page|Custom Web Page]] using the [[#The MIDAS Javascript Library]], which was the recommended way to write Custom Pages before the [[Custom Page#modb* Javascript scheme]]  was available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= The MIDAS Javascript Library =&lt;br /&gt;
The MIDAS Javascript Library [[mhttpd.js]] includes routines written in Javascript and AJAX to provide features useful for writers of [[Custom Page]]s, such as access to the ODB. Recently asynchronous [[mjsonrpc|JSON-RPC functions]] using [[mjsonrpc#Javascript client library|Promises]]  has been added (January 2016).&lt;br /&gt;
;NOTE&lt;br /&gt;
:To use functions in this library, it &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Many of the older routines (ODBGet, ODBSet) use &#039;&#039;&#039;synchronous&#039;&#039;&#039; RPC requests. With synchronous request, a second RPC request must wait until the first request is complete. This can significantly slow down web pages where many calls to ODBGet are made. For this reason, functions were developed that can be synchronous or &#039;&#039;&#039;asynchronous&#039;&#039;&#039; (e.g. ODBMCopy) that include a callback mechanism. The data from ODBMCopy can be formatted in [[mjsonrpc#JSON general information|JSON]]. &lt;br /&gt;
&lt;br /&gt;
Web developers are moving away from synchronous RPC requests, which are now deprecated [https://midas.triumf.ca/elog/Midas/1128]. Some modern browsers return a warning on a synchronous RPC request. &lt;br /&gt;
&lt;br /&gt;
A new group of JSON-RPC functions using  [[mjsonrpc#Javascript client library|Promises]] has been added to the MIDAS Javascript Library (January 2016). These are asynchronous only, and they also provide more functionality and have better error handling than the older AJAX calls, which are also inconsistent in how they handle Booleans. They also have much improved handling of reading/writing arrays to the ODB. Before 2018 it was recommended that all new Custom Pages are written using these JSON-RPC functions.  {{Important|text=Since 2018 the [[Custom Page#modb* Javascript scheme]] is recommended for new Custom Pages}}.&lt;br /&gt;
&lt;br /&gt;
The MIDAS Javascript Library routines requires an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). If using an older browser version, the asynchronous AJAX calls (e.g. ODBMCopy) should be used in order to avoid having to rewrite the web page at a later date to remove all synchronous calls.  &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using mjson-rpc asynchronous functions ==&lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], which (since January 2016) includes the [[mjsonrpc]] functions.  It is [[#The MIDAS Javascript Library|recommended]] that these functions are used for &#039;&#039;&#039;new&#039;&#039;&#039; pages rather than the older AJAX calls (see below). &lt;br /&gt;
&lt;br /&gt;
To run these functions you need&lt;br /&gt;
# to  [[mhttpd.js#include js lib|include the Javascript library in the HTML code]]&#039;&#039;&#039;&lt;br /&gt;
# to use an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). &lt;br /&gt;
&lt;br /&gt;
Examples showing how to read and write to the ODB can be found at [[mjsonrpc#examples]]. Also included are examples using arrays. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using asynchronous AJAX calls ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: It is recommended that  asynchronous MIDAS JSON-RPC functions are used for new pages rather than the older AJAX calls (see [[#Access the ODB using mjson-rpc asynchronous functions|above]]).&lt;br /&gt;
&lt;br /&gt;
[[#Example 2 : Asynchronous calls using Javascript and AJAX|Example 2]] uses the asynchronous call ODBMCopy() with callback to read the data. The data are converted into JSON format. In this case, when accessing the JSON data, &#039;&#039;the ODB Keynames must be in the same case as they are in the ODB&#039;&#039;, even though the case is ignored by the ODB. If you find this feature annoying, use [[#Access the ODB using mjson-rpc asynchronous functions|mjson-rpc function db_get_values()]] as all keynames are converted to lower case.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using synchronous AJAX requests ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: Synchronous requests are now deprecated (see [[#The MIDAS Javascript Library|above]]). New pages should be written [[#Using mjson-rpc asynchronous functions]].&lt;br /&gt;
&lt;br /&gt;
See [[#Example 3 : Synchronous Calls using Javascript and AJAX]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Access the ODB with HTML-style &amp;lt;span style=&amp;quot;color:seagreen font-style:italic&amp;quot;&amp;gt;&amp;lt;odb&amp;gt;&amp;lt;/span&amp;gt; tags =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: This method pre-dates the [[Mhttpd.js|MIDAS Javascript Library]]. It is recommended that the [[Mhttpd.js|MIDAS Javascript Library]] be used for ODB access.&lt;br /&gt;
&lt;br /&gt;
If Javascript (JS) is not available, the older HTML-style  {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are still available and provide limited functionality.&lt;br /&gt;
&lt;br /&gt;
The  HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag has been defined for read/write access to the ODB under HTML. The {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags.&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: rgb(255, 255, 255);&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Access to ODB from HTML&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | HTML ODB tag&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | Meaning&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot;&amp;gt;&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: black&amp;quot;  |Display ODB field (read only)&lt;br /&gt;
	&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=1 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (inline style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=2 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (popup style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: The Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; (documented in the [http://ladd00.triumf.ca/~daqweb/doc/midas-old/html/RC_customize_ODB.html#RC_Access_Control| OldMidas Document]) may not be working.&lt;br /&gt;
: Use the  [[/Experiment ODB tree#Security subtree|Web Password security]] instead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are included in the HTML code e.g.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
 Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt;&lt;br /&gt;
 Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt; &amp;lt;br&amp;gt;  Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[Custom Page#How to write a Custom Page|HTML Custom Page example]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= ODB RPC access =&lt;br /&gt;
The [[Mhttpd.js|MIDAS Javascript Library]] function ODBRpc() defined for RPC access.&lt;br /&gt;
&lt;br /&gt;
This permits buttons on MIDAS &amp;quot;custom&amp;quot; web pages to invoke RPC calls directly into user frontend programs, for example to turn hardware modules on or off.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= JSON support =&lt;br /&gt;
[[mjsonrpc#JSON general information|JSON]] support is provided with the [[Mhttpd.js|MIDAS Javascript Library]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Access to the MIDAS Menu buttons =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should follow the instructions there to include the standard MIDAS Menu buttons.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Access to the standard MIDAS Menu buttons can be provided with HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags of the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt; --&amp;gt;&lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt;}}&lt;br /&gt;
Valid values are the standard MIDAS Menu buttons, i.e. (Start, Pause, Resume, Stop, ODB, Elog, Alarms, History, Programs etc). The {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags must be declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags (see above).&lt;br /&gt;
&lt;br /&gt;
The following HTML fragment shows the inclusion of three of the standard buttons, giving access to the Main Status, ODB and Messages pages :&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
 &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; ...  &amp;lt;br&amp;gt; &amp;lt;/form&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
See also [[#Redirect]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Redirect =&lt;br /&gt;
&lt;br /&gt;
When buttons are included on a Custom Page, after pressing a button (e.g. the Start or Stop button) it may be desirable to return to the same custom page, rather than returning to the [[Status Page]].&lt;br /&gt;
&lt;br /&gt;
This can be done by including an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag with the attributes &#039;&#039;type&#039;&#039; set to &amp;quot;hidden&amp;quot; and &#039;&#039;name&#039;&#039; set to &amp;quot;redir&amp;quot;. This name (&amp;quot;redir&amp;quot;) is detected by [[Mhttpd]], causing a redirect to the specified custom link in the &#039;&#039;value&#039;&#039; attribute.&lt;br /&gt;
&lt;br /&gt;
For example, the following redirects the screen back to the custom page link   {{Odbpath|path=/Custom/my_custom_page&amp;amp;}} when buttons are pressed:&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;my_custom_page&amp;amp;&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[#Replace Status Page by a Custom page|Redirect when a Custom Page replaces the Status Page]].&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= CustomScript Buttons =&lt;br /&gt;
&lt;br /&gt;
[[/Customscript ODB tree#Customscript-button|CustomScript buttons]] can be provided on [[Custom Page|Custom Pages]]. These buttons are equivalent to optional &#039;&#039;&#039;script buttons&#039;&#039;&#039; on the [[Status Page]], which call a script to perform a particular action when the button is pressed. See [[/Script ODB tree#Script-button|script buttons]] for details. Customscript buttons can be set up through the [[/Customscript ODB tree]].&lt;br /&gt;
&lt;br /&gt;
Any key &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/CustomScript/test&amp;lt;/span&amp;gt; will appear as a customscript-button &lt;br /&gt;
&amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; on a custom page whose code includes an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag of the form:&lt;br /&gt;
{{Html|text=&amp;lt;input type=submit name=customscript value=&amp;quot;test&amp;quot;&amp;gt;}}&lt;br /&gt;
where the action of the button &amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; will be found in the &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/customscript/test&amp;lt;/span&amp;gt; subdirectory.&lt;br /&gt;
&lt;br /&gt;
After pressing a customscript-button, the &#039;&#039;type=submit&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} will cause a page reload. This will result in a switch to the [[Status Page]], unless a [[#redirect]] input tag is included to redirect back to the original custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customscript button without a page reload ==&lt;br /&gt;
To define a customscript button that does &#039;&#039;&#039;not&#039;&#039;&#039; cause a page reload, set the &#039;&#039;type&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} to &amp;quot;button&amp;quot;, i.e. &lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;customscript&amp;quot; value=&amp;quot;test&amp;quot; type=&amp;quot;button&amp;quot; onClick=cs_button(this.value)&amp;gt;  &amp;lt;!-- Customscript button does not reload page; no callback --&amp;gt;}}&lt;br /&gt;
and use &#039;&#039;&amp;quot;onClick&amp;quot;&#039;&#039; to call a function to [[#Send an Ajax Request]] instead. The following function sends an asynchronous request, with an optional callback.&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;script&amp;gt;}}&lt;br /&gt;
 &amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function cs_button(cmd, callback)&amp;lt;br&amp;gt;&lt;br /&gt;
{  // send a request to execute a custom script&lt;br /&gt;
:   var url;&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
:&lt;br /&gt;
:   url = ODBUrlBase&lt;br /&gt;
:   if(document.getElementById(&amp;quot;redir&amp;quot;) != null)&lt;br /&gt;
::     url +=  &#039;?redir=&#039;+document.getElementById(&#039;redir&#039;).value&lt;br /&gt;
:   url+=&#039;&amp;amp;customscript=&#039;+ encodeURIComponent(cmd);&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true); // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
:  if(callback!=undefined) {&lt;br /&gt;
::      request.onreadystatechange = function() {&lt;br /&gt;
:::         if (request.readyState == 4) { &lt;br /&gt;
::::            if (request.status == 200)&lt;br /&gt;
:::::                callback();&lt;br /&gt;
::::            else&lt;br /&gt;
:::::                alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:::            }&lt;br /&gt;
::         }&lt;br /&gt;
:  }&lt;br /&gt;
:  else { &lt;br /&gt;
::   if (request.status != 200)&lt;br /&gt;
:::       alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:	   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Resource files =&lt;br /&gt;
== MIDAS resource files ==&lt;br /&gt;
A number of resource files have been provided in the MIDAS packages under {{Filepath|path=../packages/midas/resources}}.  When including a MIDAS resource file, no key in  {{Odbpath|path=/Custom}} needs to be defined as the MIDAS resources directory is searched automatically. &lt;br /&gt;
&lt;br /&gt;
One such resource file is a condensed stylesheet {{File|name=midas.css}} for users who would like their custom pages to have a similar &amp;quot;look and feel&amp;quot; to that of the standard pages. To include the MIDAS stylesheet, in the HTML header, add &lt;br /&gt;
{{HtmlTag|tag=&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;}}&lt;br /&gt;
Other resource files can provide [[Custom Page#How to use the standard MIDAS navigation bars on your custom page|the standard MIDAS navigation bars]].&lt;br /&gt;
&lt;br /&gt;
== Custom resource files ==&lt;br /&gt;
It is often desirable to serve resource files (i.e. &#039;&#039;&#039;local files&#039;&#039;&#039; such as an external stylesheet, javascript files or images&#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039;) to a custom page. The following sections describe two alternative ways of serving resource files to a custom page:&lt;br /&gt;
* [[#Resource files served WITH /custom/path key defined|with  /custom/path key defined]]&lt;br /&gt;
* [[#Resource files served WITHOUT /custom/path key defined|without  /custom/path key defined]]&lt;br /&gt;
See also serving resources to a [[#Replace Status Page by a Custom Status page|Custom Status page]].&lt;br /&gt;
;NOTE&lt;br /&gt;
:  &#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039; To superimpose labels, bars or fills onto an image, the image cannot be served as a resource file. See [[#Image insertion]].&lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITH &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
When a number of resource files are required, it is convenient to place them in the same directory on the disk, and create a key  {{Odbpath|path=/Custom/Path}} to contain this directory. &lt;br /&gt;
&lt;br /&gt;
 $ ls  /home/midas/online/custom/&lt;br /&gt;
   custom_functions.js       custom_globals.js     custom_page.html    custom_stylesheet.css      test_image.png&lt;br /&gt;
With the  {{Odbpath|path=/Custom/Path}} key defined, [[/Custom ODB tree#Custom-Link|custom-links]] for individual resource files need not be created in the  {{Odbpath|path=/Custom/Path}} tree. The custom-link  {{Odbpath|path=custom_page&amp;amp;}} &#039;&#039;&#039;is&#039;&#039;&#039; required so that the custom page can be accessed from the [[/Custom ODB tree#Custom-Button|custom-button]]  {{Button|name=custom_page}} on the Status Page. The key name ends in the special character &amp;quot;&amp;amp;&amp;quot; so that it will open in the same window as the Status page (see [[/Custom ODB tree#Key names|key names]]).&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   path                           /home/midas/online/custom/&lt;br /&gt;
   custom_page&amp;amp;                   custom_page.html&lt;br /&gt;
 &lt;br /&gt;
A {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the file name) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_globals.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;custom_stylesheet.css&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;test_image.png&amp;quot;&amp;gt; }}&lt;br /&gt;
[[mhttpd]] then loads the resource file from the directory indicated by the Path key with the correct MIME type (see  [[/Custom ODB tree#Keys in the /Custom tree|Custom tree keys]] for supported MIME types). &lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITHOUT &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
Alternatively, resource files can be served to a [[Custom Page]] by creating a key for every resource file in the [[/Custom ODB tree]]. These keys must contain the full path of the file on the disk, and the key {{Odbpath|path=/Custom/Path}} must NOT be defined. The resource files can be in different directories on the disk. By defining the key names with an appropriate file extension, the resource files are served with the appropriate MIME types. The key names end in the special character &amp;quot;!&amp;quot; so that they will not appear on the Status page as custom-links (see [[/Custom ODB tree#Key names|key names]].&lt;br /&gt;
&lt;br /&gt;
Without the {{Odbpath|path=/Custom/Path}} key,  the links for the above example in the {{Odbpath|path=/Custom}} tree might look like&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   custom_page&amp;amp;                   /home/midas/online/custom/custom_page.html&lt;br /&gt;
   custom_functions.js!           /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   globals.js!                    /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   stylesheet.css!                /home/midas/online/stylesheets/custom_stylesheet.css&lt;br /&gt;
   image.png!                     /home/midas/images/test_image.png  &lt;br /&gt;
&lt;br /&gt;
assuming the resource files are now in the subdirectories indicated.&lt;br /&gt;
The {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the custom-link) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;globals.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;stylesheet.css!&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;image.png!&amp;quot;&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The resulting Demo custom page is shown in Figure 1, which can be compared with Figure 2 (no stylesheet). &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Figure 1  !! Figure 2&lt;br /&gt;
|-&lt;br /&gt;
| Demo Custom Page using MIDAS stylesheet || Demo Custom Page&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| [[File:Mhxcustom03.jpg|thumb|300px]] || [[File:Mhxcustom02.jpg|thumb|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=  Alias-Buttons and Hyperlinks =&lt;br /&gt;
Any hyperlink can easily be included on a [[Custom Page]] by using the standard HTML anchor {{HtmlTag|tag=&amp;lt;a...&amp;gt;}} tag, e.g.&lt;br /&gt;
{{Html|text=&amp;lt;a href=&amp;quot;http://ladd00.triumf.ca/~daqweb/doc/midas/html/&amp;quot;&amp;gt;Midas Help&amp;lt;/a&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Links on a custom page equivalent to [[/Alias ODB tree#Alias-Buttons|alias-buttons]] can also be made e.g.&lt;br /&gt;
{{Html|text=&amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.location.href=&#039;/Alias/alias&amp;amp;&#039;;&amp;quot;&amp;gt;alias&amp;lt;/button&amp;gt;}}&lt;br /&gt;
See the [[/Alias ODB tree]] for details.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Page refresh =&lt;br /&gt;
The following {{HtmlTag|tag=&amp;lt;meta...&amp;gt;}} tag included in the HTML header code will cause the whole custom page to refresh in 60 seconds :&lt;br /&gt;
{{Html|text=&amp;lt;meta http-equiv=&amp;quot;Refresh&amp;quot; content=&amp;quot;60&amp;quot;&amp;gt;}}&lt;br /&gt;
It is also possible to [[#Periodic update of parts of a custom page|periodically update parts]] of a custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Periodic update of parts of a custom page =&lt;br /&gt;
&lt;br /&gt;
The functionality of [[Mhttpd.js|ODBGet]] together with the window.setInterval() function&lt;br /&gt;
can be used to update parts of the web page periodically.&lt;br /&gt;
For example the Javascript fragment below contains a function which updates the current run number every 10 seconds in the background:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt; &amp;lt;!-- JS --&amp;gt;&lt;br /&gt;
window.setInterval(&amp;quot;Refresh()&amp;quot;, 10000);&amp;lt;br&amp;gt;&lt;br /&gt;
function Refresh() {&amp;lt;br&amp;gt;&lt;br /&gt;
:document.getElementById(&amp;quot;run_number&amp;quot;).innerHTML = ODBGet(&#039;/Runinfo/Run number&#039;);&amp;lt;br&amp;gt;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
The custom page has to &lt;br /&gt;
* [[Mhttpd.js#include js lib|include the MIDAS JS library]] to access ODBGet&lt;br /&gt;
* contain an element with id=&amp;quot;run_number&amp;quot;, such as&lt;br /&gt;
{{Html|text=&amp;lt;td id=&amp;quot;run_number&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Display last MIDAS message(s) =&lt;br /&gt;
&lt;br /&gt;
The message log (see [[Message System]]) can be accessed from a custom page using a call to the JavaScript library function [[Mhttpd.js|ODBGetMsg]] (provided the [[Mhttpd.js#include js lib|JS library is included]]).&lt;br /&gt;
&lt;br /&gt;
This allows the inclusion of the &amp;quot;Last Midas message&amp;quot; on a custom page, e.g.&lt;br /&gt;
{{JS|text=document.write(&#039;Last message:&#039;+ODBGetMsg(&amp;quot;midas&amp;quot;,0,1))}}&lt;br /&gt;
More messages may be displayed by increasing the third parameter to ODBGetMsg.&lt;br /&gt;
;Note&lt;br /&gt;
: Parameters were changed August 2015. See [[Mhttpd.js|ODBGetMsg]] for older versions.&lt;br /&gt;
: Coming soon - a [[mjsonrpc]] function&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Generate a message log entry =&lt;br /&gt;
&lt;br /&gt;
A custom page can generate a message to be sent to the MIDAS message log (see [[Message System]]).  A call to mjsonrpc_cm_msg() will generate a message if using the [[mjsonrpc]] functions. Otherwise, use the AJAX function [[Mhttpd.js|ODBGenerateMsg]]. To use these functions, the  [[Mhttpd.js#include js lib|JS library must be included]] in the html code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Checkboxes =&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New  (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should use [[Custom Page#modbcheckbox|modbcheckbox]] to create a checkbox.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;Br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function [[Mhttpd.js|ODBSet]] (provided the [[Mhttpd.js#include js lib|JS library is included]]) can be used when one clicks on a checkbox for example:&lt;br /&gt;
{{Html|text=&amp;lt;input  name=&amp;quot;box0&amp;quot;  type=&amp;quot;checkbox&amp;quot;  onClick=&amp;quot;ODBSet(my_path, this.checked?&#039;1&#039;:&#039;0&#039;)&amp;quot;&amp;gt;}}&lt;br /&gt;
If used as above, the state of the checkbox must be initialized when the page is loaded. This can be done with some JavaScript code called on initialization, e.g.&lt;br /&gt;
{{JS|text=document.form1.box0.checked= ODBGet(my_path));  // initialize to the correct value}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Replace Status Page by a Custom page =&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_status.jpg|thumbnail|left|Figure 3: ODB /Custom/Status custom-link to a custom status page]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a custom-link with the [[/Custom ODB tree#Key names|reserved key name]] &#039;&#039;&#039;Status&#039;&#039;&#039;  (not &amp;quot;Status&amp;amp;&amp;quot; or &amp;quot;Status!&amp;quot;) is created in the [[/Custom ODB tree]] (as shown in  Figure 3), then that custom page will &#039;&#039;&#039;replace the default Status Page&#039;&#039;&#039;. &lt;br /&gt;
  &lt;br /&gt;
Clicking on the {{Button|name=Status}} button on any of the sub-pages (e.g. [[ODB Page]], [[Programs Page]] etc.) will now return to the Custom Status Page. If there are buttons on the Custom Status page, you &#039;&#039;&#039;must&#039;&#039;&#039; include a  [[#Redirect]] statement of the form&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;../&amp;quot;&amp;gt;}}&lt;br /&gt;
or you will see the message &lt;br /&gt;
 Invalid custom page:NULL path&lt;br /&gt;
&lt;br /&gt;
If the Custom Status page includes [[#Resource files]] served on a regular custom page with a statement such as&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
to serve them in a Custom Status page, the statement would be&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&amp;lt;span style=&amp;quot;font-weight:bold&amp;quot;&amp;gt;/CS/&amp;lt;/span&amp;gt;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
In fact, this statement can be used in a regular custom page, as the &amp;quot;/CS/&amp;quot; is ignored in that case.&lt;br /&gt;
&lt;br /&gt;
To return to the default Status Page, delete the &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Custom/Status&amp;lt;/span&amp;gt; key. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Start, Stop and Check if a program is running =&lt;br /&gt;
There are [[mjsonrpc]] functions that implemented all three program management functions - start program, &lt;br /&gt;
stop program and &amp;quot;is running?&amp;quot; as JSON-RPC methods.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Send an Ajax request =&lt;br /&gt;
By sending an Ajax request from a custom page, you can make a button perform a specific function. &lt;br /&gt;
  &lt;br /&gt;
All functions in midas are controlled through special URLs. So the URL&lt;br /&gt;
 http://&amp;lt;host:port&amp;gt;/?cmd=Start&amp;amp;value=10&lt;br /&gt;
will start run #10.&lt;br /&gt;
&lt;br /&gt;
Although it is easier to use an HTML input statement to [[#Access to the MIDAS Menu buttons]], &lt;br /&gt;
to send an Ajax request, you can use the function &#039;&#039;XMLHttpRequestGeneric&#039;&#039; which is in the MIDAS Javascript library [[mhttpd.js]].&lt;br /&gt;
Then the HTML code would be&lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;start()&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
and in your JavaScript code add a function &#039;&#039;start()&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function start()&lt;br /&gt;
{&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
&lt;br /&gt;
:   url = &#039;?cmd=Start&amp;amp;value=10&#039;;&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true);  // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
See also [[#Access to the MIDAS Menu buttons]]. Another example with optional callback can be found in [[#Customscript button without a page reload]].&lt;br /&gt;
&lt;br /&gt;
This mechanism can be used for starting a particular program, see for example (see [https://midas.triumf.ca/elog/Midas/1046]). However, this functionality is now provided by [[mjsonrpc]] functions - see [[#Start, Stop and Check if a program is running]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Image insertion  =&lt;br /&gt;
An image can be loaded from the web using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}}, e.g.  &lt;br /&gt;
   {{Html|text=&amp;quot;TRIUMF logo&amp;quot; &amp;lt;img src=&amp;quot;https://lixenon.triumf.ca/InternalDocuments/Alice/figures/TRIUMF-logo.jpg/image_preview&amp;quot;&amp;gt;}}&lt;br /&gt;
or a MIDAS History image can be inserted into a custom page using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag of the following form:&lt;br /&gt;
 {{Html|text=blah&amp;lt;img src=&amp;quot;http://hostname.domain:port/HS/Meterdis.gif&amp;amp;scale=12h&amp;amp;width=300&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If the image file is on the local disk, it can be loaded as a &#039;&#039;&#039;resource file&#039;&#039;&#039;. The image can be of format .pdf, .jpg, .gif, .png. See loading [[#Resource files]].&lt;br /&gt;
&lt;br /&gt;
If you wish to superimpose features such as &#039;&#039;&#039;labels and fills&#039;&#039;&#039;, the image file cannot be served as a [[#Resource files|resource file]]. Instead, the image file must be in &#039;&#039;&#039;gif&#039;&#039;&#039; format, and must be included in the {{Odbpath|path=/Custom/images}} subtree (Figure 4). Image insertion into a Custom page will be illustrated using the Demo custom page shown in [[#MIDAS stylesheet|Figure 2]]. All the files required for this demo can be found in the MIDAS package at $MIDASSYS/examples/custom. If you do not wish to create&lt;br /&gt;
the keys yourself, proceed to [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
Make a [[/Custom ODB tree#Custom-Link|custom-link]] to the Demo custom page file &#039;&#039;myexpt.html&#039;&#039; , i.e.&lt;br /&gt;
 [local:js:S]/&amp;gt;ls /custom&lt;br /&gt;
    myexpt&amp;amp;                  /home/test/packages/midas/examples/custom/myexpt.html&lt;br /&gt;
 &lt;br /&gt;
To make the image &#039;&#039;myexpt.gif&#039;&#039; visible on the custom page, the path and filename of the image file must be defined in the   {{Odbpath|path=/Custom/images}} subtree. To do this, &lt;br /&gt;
create the  subtrees {{Odbpath|path=/Custom/images/myexpt.gif}} where the subtree name &amp;quot;myexpt.gif&amp;quot; is named for the image file you are going to use. Multiple images can be used, by creating multiple imagefile subtrees.&lt;br /&gt;
&lt;br /&gt;
In the imagefile subtree {{Odbpath|path=myexpt.gif}}, create the STRING key  {{Odbpath|path=Background}}, and set it to contain the path and name of the image file.  The tree structure should then look similar to Figure 4, minus the labels/bars/fill subtrees which will be added to the ODB later.&lt;br /&gt;
&lt;br /&gt;
The image must also be referenced in the custom HTML file &#039;&#039;myexpt.html&#039;&#039; in the &amp;quot;src&amp;quot; field of an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag, e.g. &lt;br /&gt;
{{Html|text=&amp;lt;img &#039;&#039;&#039;src=&amp;quot;myexpt.gif&amp;quot;&#039;&#039;&#039;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_images.jpg|thumbnail|left|Figure 4: /Custom/Images ODB Tree]]&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
Once the image is visible, enable [[#HTML mapping]], optionally [[#Display mouse position]] and proceed to  [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== HTML mapping ==&lt;br /&gt;
Note that if additional features such as active clickable areas and labels, bars and fills superimposed on the image are also required, HTML mapping must also be activated with the HTML {{HtmlTag|tag=&amp;lt;map...&amp;gt;}} tag and the &amp;quot;usemap&amp;quot; attribute of the HTML {{HtmlTag|tag=&amp;lt;img&amp;gt;}} tag&lt;br /&gt;
{{Html|text=   &amp;lt;map &#039;&#039;&#039;name=&amp;quot;myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;img src=&amp;quot;myexpt.gif&amp;quot; &#039;&#039;&#039;usemap=&amp;quot;#myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt;...&amp;lt;br&amp;gt; &amp;lt;/map&amp;gt;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Display mouse position ==&lt;br /&gt;
[[File:Cursor.png|thumbnail|left|Figure 5: MEG Gas System Custom Page showing cursor position]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When writing custom pages with large background images and labels and fills placed on that image, it is hard to figure out X and Y coordinates of the labels. This can now be simplified by using the function &#039;&#039;getMouseXY()&#039;&#039; in the development JavaScript built-in library [[develop.js]]. This function supplies the X,Y position of the cursor if an element of ID &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; is present. This JS library &#039;&#039;&#039;must be [[develop.js|included in the custom page]]&#039;&#039;&#039; in order to use it:&lt;br /&gt;
&lt;br /&gt;
Then, set the &amp;quot;id&amp;quot; attribute of the background HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag to &amp;quot;refimg&amp;quot;, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: &amp;lt;img  &#039;&#039;&#039;id=&amp;quot;refimg&amp;quot;&#039;&#039;&#039; src=&amp;quot;ebit_pc.gif&amp;quot; usemap=&amp;quot;#Custom1&amp;quot;&amp;gt;   &amp;lt;!-- name=&amp;quot;refimg&amp;quot; makes crosshairs appear --&amp;gt;&lt;br /&gt;
: &amp;lt;map name=&amp;quot;Custom1&amp;quot;&amp;gt;&lt;br /&gt;
: .....&lt;br /&gt;
: &amp;lt;/map&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; tag is present, the cursor changes into a crosshair, and its absolute and relative locations in respect to the reference image are shown in the status bar (Figure 5).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
==  Superimposing Labels, Bars and Fills onto a gif image ==&lt;br /&gt;
You can enhance your custom page by superimposing multiple features based on ODB variables onto an image (e.g. [[#Display mouse position|Figure 5]]), such as&lt;br /&gt;
&lt;br /&gt;
*    labels: &amp;quot;live&amp;quot; ODB values positioned in a particular location of the page&lt;br /&gt;
*    bars : &amp;quot;bar level&amp;quot; showing graphically ODB values such as levels or rate etc.&lt;br /&gt;
*    fills : &amp;quot;color level&amp;quot; where colour is used as the level indicator.&lt;br /&gt;
&lt;br /&gt;
Each entry (label/bar/fill) will have an ODB tree associated to it defining the ODB variable path, X/Y position, colour, etc. Each time the page is updated, the latest ODB value/level/rate will be shown based on the ODB parameter to which the label, bar or fill is linked - hence the term &amp;quot;live&amp;quot;. The overlay of the requested features is done onto the selected image file.&lt;br /&gt;
&lt;br /&gt;
This powerful new extension brings the [[mhttpd]] capability closer to other experimental web controllers similar to EPICS.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
:    Be sure to enable the feature to [[#Display mouse position]] in order to facilitate finding the X,Y positions of the various features.&lt;br /&gt;
: [[#HTML mapping]] must be activated for labels/bars/fills to work&lt;br /&gt;
&lt;br /&gt;
A Demo custom page showing labels, bars and fills superimposed on an image is shown in [[#MIDAS stylesheet|Figure 2]]. &lt;br /&gt;
All the files for this demo can be found in $MIDASSYS/examples/custom/. The file xcustom.odb contains the ODB keys required, including those to insert the image and superimpose the various labels, fills etc. This file can be loaded into the ODB with the  {{Odbedit cmd|cmd=load}}.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Labels ===&lt;br /&gt;
&lt;br /&gt;
 [[File:Mhcustom_label.jpg|thumbnail|left|Figure 6: /Custom/Images/Labels ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In order to include a readout of ODB values (i.e. labels), on the image, a further ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Labels}} must be created. Creating a subdirectory for a particular label i.e. {{Odbpath|path=&amp;lt;label name&amp;gt;}} in the   {{Odbpath|path=Labels}} subtree will, at the next custom web page refresh, cause the complete structure for that label to be created and filled with default values. Once the {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is created, the user fills the various keys as desired. See [[/Custom ODB tree#Labels subtree]] for details of the various fields. This procedure is repeated for all the labels required, using a unique {{Odbpath|path=&amp;lt;label name&amp;gt;}} subdirectory for each label. An example of a {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is shown in Figure 6. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Bars ===&lt;br /&gt;
 &lt;br /&gt;
[[File:Mhcustom_fill.jpg|thumbnail|left|Figure 7: /Custom/Images/Fills ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Bars can be superimposed on the image. Create  a new ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Bars}}. Create a subdirectory for a particular Bar ({{Odbpath|path=&amp;lt;bar name&amp;gt;}}) in the {{Odbpath|path=Bars}}  subdirectory. Refresh the web page and fill the various keys as desired.  See [[/Custom ODB tree#Bars subtree]] for details of the various fields.  Examples of a &amp;lt;bar-name&amp;gt; subtree is shown in Figure 7.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Fills ===&lt;br /&gt;
[[File:Mhcustom_bar.jpg|thumbnail|left|Figure 8: /Custom/Images/Bars ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Fills can be superimposed on the image. Create new ODB subdirectory   {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Fills}}. Create a subdirectory for a particular  Fill ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}) in the {{Odbpath|path=Fills}} subdirectory. Refresh the web page and fill the various keys as desired.  See  [[/Custom ODB tree#Fills subtree]] for details of the various fields.  Examples of a ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}  subtree is shown in Figure 8. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Mapping active areas onto the image ==&lt;br /&gt;
Provided [[#HTML mapping]] is activated, &amp;quot;clickable&amp;quot; areas can be created on the image.&lt;br /&gt;
&lt;br /&gt;
This can be done now with a new function like this:&lt;br /&gt;
{{Html|text= &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;40,200,100,300&amp;quot; alt=&amp;quot;Main Valve&amp;quot; href=&amp;quot;Custom1?cmd=Toggle&amp;amp;odb=/Equipment/Environment/Variables/Output[2]&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
This defines a clickable map on top of the custom image. The area(s) should match with some area(s) on the image, e.g. the box of a valve. Determining the co-ordinates of this area is simplified by using the Display mouse position feature.&lt;br /&gt;
&lt;br /&gt;
By clicking on this area, the supplied path to the ODB is used (in this case  {{Odbpath|path=/Equipment/Environment/Variables/Output[2]}}) and its value is toggled. If the valve value is then used in the image via a [[#Adding Fills|Fill]] statement to change the color of the valve, it can turn green or red depending on its state. This is illustrated in [[#Display mouse position|Figure 5]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Clicking an active area can also be made to open a new custom page, for example:&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;687,530, 890,648&amp;quot; alt=&amp;quot;Pump detail&amp;quot; href = &amp;quot;Pump!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;560,574,775,662&amp;quot; alt=&amp;quot;Buffer Tank detail&amp;quot; href = &amp;quot;BufferTank!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
where &#039;&#039;Pump!&#039;&#039; and &#039;&#039;BufferTank!&#039;&#039; are defined as links to custom pages in the [[/Custom ODB tree]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Edit boxes floating on top of a graphic ==&lt;br /&gt;
&lt;br /&gt;
An edit box can be placed on top of a graphic in a particular position by means of an HTML  &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/span&amp;gt; tag. Using the ODBEdit function from the Midas JS library [[Mhttpd.js]], the custom page code would look like this:&lt;br /&gt;
&amp;lt;!-- Complicated... have to use &amp;lt;pre&amp;gt; because of &amp;lt;div&amp;gt;, then a table to keep the background colour between &amp;lt;pre&amp;gt;s --&amp;gt;&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: floralwhite;&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;    &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
document.write(&#039;Run number: &#039;)&lt;br /&gt;
path=&#039;/runinfo/run number&#039;&lt;br /&gt;
rn = ODBGet(path)&lt;br /&gt;
document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(path)&amp;quot; &amp;gt;&#039;)  &lt;br /&gt;
document.write(rn)&lt;br /&gt;
document.write(&#039;&amp;lt;/a&amp;gt;&#039;);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same thing could be done with the HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag :&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;&lt;br /&gt;
Run number:  &amp;lt;odb src=&amp;quot;/Runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Interactive SVG Implementation ==&lt;br /&gt;
&lt;br /&gt;
An SVG file loaded on a custom page can be interactively modified using JavaScript.&lt;br /&gt;
This is useful for graphically displaying ODB values in complex service or detector systems.&lt;br /&gt;
&lt;br /&gt;
An SVG file can be embedded like this:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Objects within the SVG can be modified—for example, by changing their fill color as shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument; &lt;br /&gt;
const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
obj_Valve.style.fill = &amp;quot;#00FF00&amp;quot;; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SVG images are stored in XML format. In Inkscape, object properties can be interactively edited using the XML Editor.&lt;br /&gt;
An example is given below in [[Custom Page Features#Example 4 : Interactive SVG custom page]]&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
For more examples of accessing the ODB with the Javascript library [[mhttpd.js]] look at the example experiment in the MIDAS package $MIDASSYS/examples/javascript1/example.html.&lt;br /&gt;
&lt;br /&gt;
== Example 1 : Asynchronous calls using mjsonrpc functions ==&lt;br /&gt;
See [[mjsonrpc#examples]]&lt;br /&gt;
&lt;br /&gt;
== Example 2 : Asynchronous calls using Javascript and AJAX ==&lt;br /&gt;
&lt;br /&gt;
Example 2 shows html code illustrating the use of ODBMCopy(), where innerHTML is used to display the data. On a page with images or a lot of data, innerHTML allows you to update variables without having to reload the whole page. In the example, a timer causes the page to update every 10s (reread the data). Often the ODB data for the whole page can be read by one asynchronous ODBMCopy() call, rather than scattering synchronous ODBGet() calls throughout the page.  &lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], included with the line &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;script src=&#039;mhttpd.js&#039;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;MyTitle&amp;lt;/title&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;script src=&#039;mhttpd.js&#039;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
var updatePeriod = 10000; // in msec &amp;lt;br&amp;gt;&lt;br /&gt;
var updateTimerId = 0; &amp;lt;br&amp;gt;&lt;br /&gt;
var counter=0; &amp;lt;br&amp;gt;&lt;br /&gt;
function update()  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:clearTimeout(updateTimerId); &amp;lt;br&amp;gt;&lt;br /&gt;
:      load(); &lt;br /&gt;
:      if (updatePeriod &amp;gt; 0) &lt;br /&gt;
:      updateTimerId = setTimeout(&#039;update()&#039;, updatePeriod); &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function load()  { &amp;lt;br&amp;gt;&lt;br /&gt;
: document.getElementById(&#039;LastUpdated&#039;).innerHTML = &amp;quot;Updating...&amp;quot; + new Date; &lt;br /&gt;
: var paths = [ &amp;quot;/Runinfo&amp;quot;, &amp;quot;/Experiment&amp;quot;]; &lt;br /&gt;
: var data_odb=ODBMCopy(paths, mcopy_callback, &amp;quot;json&amp;quot;) &lt;br /&gt;
: counter++; &lt;br /&gt;
: document.getElementById(&#039;counter&#039;).innerHTML = &#039;Counter: &#039;+ counter &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function mcopy_callback(data)  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:    var obj= JSON.parse(data);&lt;br /&gt;
: var runinfo=obj[0];&lt;br /&gt;
: document.getElementById(&#039;rn&#039;).innerHTML = &#039;Run Number =&#039;+ parseInt(runinfo[&amp;quot;Run number&amp;quot;]);&lt;br /&gt;
: document.getElementById(&#039;state&#039;).innerHTML  =&#039;Run State= &#039;+ runinfo.State;&lt;br /&gt;
: var experiment=obj[1];&lt;br /&gt;
: document.getElementById(&#039;name&#039;).innerHTML=&#039;Experiment name = &#039;+ experiment.Name &amp;lt;br&amp;gt; &lt;br /&gt;
}&amp;lt;br&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/head&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;body&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;b&amp;amp;gt;Javascript code using ODBMCopy with callback&amp;amp;lt;/b&amp;amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
The data on the page is updated every 10 sec using a timer&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;LastUpdated&amp;quot; &amp;amp;gt; Last updated: never&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;rn&amp;quot;&amp;amp;gt; Run Number : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;state&amp;quot;&amp;amp;gt; State : unknown&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;name&amp;quot;&amp;amp;gt; Experiment name : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;counter&amp;quot;&amp;amp;gt;Counter: zero  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/div&amp;gt;  &lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: if (updatePeriod &amp;gt; 0)&lt;br /&gt;
:  update(); &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Example 3 : Synchronous Calls using Javascript and AJAX ==&lt;br /&gt;
In the following example, JS functions ODBGet and  ODBEdit from the [[Mhttpd.js|MIDAS Javascript Library]] are used to access the ODB.  &lt;br /&gt;
;NOTE&lt;br /&gt;
# Synchronous calls are now deprecated (see [[#The MIDAS Javascript Library|above]]).&lt;br /&gt;
# The Javascript library &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;script&amp;gt;}}&lt;br /&gt;
{{JS|text=document.write (&#039;Experiment Name: &#039;+ ODBGet(&amp;quot;/Experiment/Name&amp;quot;)) &amp;lt;br&amp;gt; var alarm_path=&amp;quot;/alarms/Alarm system active&amp;quot;; &amp;lt;br&amp;gt; var alarm_active=ODBGet(alarm_path); &amp;lt;br&amp;gt; document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(alarm_path)&amp;quot; &amp;gt;&#039;+alarm_active+&#039;&amp;lt;/a&amp;gt;&#039;)}}&lt;br /&gt;
{{Html|text=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
== Example 4 : Interactive SVG custom page  ==&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3518</id>
		<title>Custom Page Features</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3518"/>
		<updated>2025-05-12T12:12:58Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
* [[ODB Page]]&lt;br /&gt;
* [[Custom Page]]&lt;br /&gt;
* [[/Custom ODB tree]]&lt;br /&gt;
* [[Mhttpd.js|MIDAS Javascript library (mhttpd.js)]]&lt;br /&gt;
* [[mjsonrpc|MIDAS JSON RPC library functions (mjsonrpc)]]&lt;br /&gt;
* [[odbedit]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
;NOTE&lt;br /&gt;
: Since 2018, many of the features described here can now be implemented more simply using the  mod* JS functions (see [[Custom Page|Custom Web Page]]).&lt;br /&gt;
: &#039;&#039;&#039;If writing a new web page, it is strongly recommended you make use of the mod*JS features. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page describes some of the special features provided for use on a user-created [[Custom Page|Custom Web Page]] using the [[#The MIDAS Javascript Library]], which was the recommended way to write Custom Pages before the [[Custom Page#modb* Javascript scheme]]  was available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= The MIDAS Javascript Library =&lt;br /&gt;
The MIDAS Javascript Library [[mhttpd.js]] includes routines written in Javascript and AJAX to provide features useful for writers of [[Custom Page]]s, such as access to the ODB. Recently asynchronous [[mjsonrpc|JSON-RPC functions]] using [[mjsonrpc#Javascript client library|Promises]]  has been added (January 2016).&lt;br /&gt;
;NOTE&lt;br /&gt;
:To use functions in this library, it &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Many of the older routines (ODBGet, ODBSet) use &#039;&#039;&#039;synchronous&#039;&#039;&#039; RPC requests. With synchronous request, a second RPC request must wait until the first request is complete. This can significantly slow down web pages where many calls to ODBGet are made. For this reason, functions were developed that can be synchronous or &#039;&#039;&#039;asynchronous&#039;&#039;&#039; (e.g. ODBMCopy) that include a callback mechanism. The data from ODBMCopy can be formatted in [[mjsonrpc#JSON general information|JSON]]. &lt;br /&gt;
&lt;br /&gt;
Web developers are moving away from synchronous RPC requests, which are now deprecated [https://midas.triumf.ca/elog/Midas/1128]. Some modern browsers return a warning on a synchronous RPC request. &lt;br /&gt;
&lt;br /&gt;
A new group of JSON-RPC functions using  [[mjsonrpc#Javascript client library|Promises]] has been added to the MIDAS Javascript Library (January 2016). These are asynchronous only, and they also provide more functionality and have better error handling than the older AJAX calls, which are also inconsistent in how they handle Booleans. They also have much improved handling of reading/writing arrays to the ODB. Before 2018 it was recommended that all new Custom Pages are written using these JSON-RPC functions.  {{Important|text=Since 2018 the [[Custom Page#modb* Javascript scheme]] is recommended for new Custom Pages}}.&lt;br /&gt;
&lt;br /&gt;
The MIDAS Javascript Library routines requires an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). If using an older browser version, the asynchronous AJAX calls (e.g. ODBMCopy) should be used in order to avoid having to rewrite the web page at a later date to remove all synchronous calls.  &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using mjson-rpc asynchronous functions ==&lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], which (since January 2016) includes the [[mjsonrpc]] functions.  It is [[#The MIDAS Javascript Library|recommended]] that these functions are used for &#039;&#039;&#039;new&#039;&#039;&#039; pages rather than the older AJAX calls (see below). &lt;br /&gt;
&lt;br /&gt;
To run these functions you need&lt;br /&gt;
# to  [[mhttpd.js#include js lib|include the Javascript library in the HTML code]]&#039;&#039;&#039;&lt;br /&gt;
# to use an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). &lt;br /&gt;
&lt;br /&gt;
Examples showing how to read and write to the ODB can be found at [[mjsonrpc#examples]]. Also included are examples using arrays. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using asynchronous AJAX calls ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: It is recommended that  asynchronous MIDAS JSON-RPC functions are used for new pages rather than the older AJAX calls (see [[#Access the ODB using mjson-rpc asynchronous functions|above]]).&lt;br /&gt;
&lt;br /&gt;
[[#Example 2 : Asynchronous calls using Javascript and AJAX|Example 2]] uses the asynchronous call ODBMCopy() with callback to read the data. The data are converted into JSON format. In this case, when accessing the JSON data, &#039;&#039;the ODB Keynames must be in the same case as they are in the ODB&#039;&#039;, even though the case is ignored by the ODB. If you find this feature annoying, use [[#Access the ODB using mjson-rpc asynchronous functions|mjson-rpc function db_get_values()]] as all keynames are converted to lower case.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using synchronous AJAX requests ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: Synchronous requests are now deprecated (see [[#The MIDAS Javascript Library|above]]). New pages should be written [[#Using mjson-rpc asynchronous functions]].&lt;br /&gt;
&lt;br /&gt;
See [[#Example 3 : Synchronous Calls using Javascript and AJAX]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Access the ODB with HTML-style &amp;lt;span style=&amp;quot;color:seagreen font-style:italic&amp;quot;&amp;gt;&amp;lt;odb&amp;gt;&amp;lt;/span&amp;gt; tags =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: This method pre-dates the [[Mhttpd.js|MIDAS Javascript Library]]. It is recommended that the [[Mhttpd.js|MIDAS Javascript Library]] be used for ODB access.&lt;br /&gt;
&lt;br /&gt;
If Javascript (JS) is not available, the older HTML-style  {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are still available and provide limited functionality.&lt;br /&gt;
&lt;br /&gt;
The  HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag has been defined for read/write access to the ODB under HTML. The {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags.&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: rgb(255, 255, 255);&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Access to ODB from HTML&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | HTML ODB tag&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | Meaning&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot;&amp;gt;&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: black&amp;quot;  |Display ODB field (read only)&lt;br /&gt;
	&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=1 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (inline style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=2 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (popup style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: The Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; (documented in the [http://ladd00.triumf.ca/~daqweb/doc/midas-old/html/RC_customize_ODB.html#RC_Access_Control| OldMidas Document]) may not be working.&lt;br /&gt;
: Use the  [[/Experiment ODB tree#Security subtree|Web Password security]] instead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are included in the HTML code e.g.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
 Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt;&lt;br /&gt;
 Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt; &amp;lt;br&amp;gt;  Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[Custom Page#How to write a Custom Page|HTML Custom Page example]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= ODB RPC access =&lt;br /&gt;
The [[Mhttpd.js|MIDAS Javascript Library]] function ODBRpc() defined for RPC access.&lt;br /&gt;
&lt;br /&gt;
This permits buttons on MIDAS &amp;quot;custom&amp;quot; web pages to invoke RPC calls directly into user frontend programs, for example to turn hardware modules on or off.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= JSON support =&lt;br /&gt;
[[mjsonrpc#JSON general information|JSON]] support is provided with the [[Mhttpd.js|MIDAS Javascript Library]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Access to the MIDAS Menu buttons =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should follow the instructions there to include the standard MIDAS Menu buttons.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Access to the standard MIDAS Menu buttons can be provided with HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags of the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt; --&amp;gt;&lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt;}}&lt;br /&gt;
Valid values are the standard MIDAS Menu buttons, i.e. (Start, Pause, Resume, Stop, ODB, Elog, Alarms, History, Programs etc). The {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags must be declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags (see above).&lt;br /&gt;
&lt;br /&gt;
The following HTML fragment shows the inclusion of three of the standard buttons, giving access to the Main Status, ODB and Messages pages :&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
 &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; ...  &amp;lt;br&amp;gt; &amp;lt;/form&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
See also [[#Redirect]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Redirect =&lt;br /&gt;
&lt;br /&gt;
When buttons are included on a Custom Page, after pressing a button (e.g. the Start or Stop button) it may be desirable to return to the same custom page, rather than returning to the [[Status Page]].&lt;br /&gt;
&lt;br /&gt;
This can be done by including an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag with the attributes &#039;&#039;type&#039;&#039; set to &amp;quot;hidden&amp;quot; and &#039;&#039;name&#039;&#039; set to &amp;quot;redir&amp;quot;. This name (&amp;quot;redir&amp;quot;) is detected by [[Mhttpd]], causing a redirect to the specified custom link in the &#039;&#039;value&#039;&#039; attribute.&lt;br /&gt;
&lt;br /&gt;
For example, the following redirects the screen back to the custom page link   {{Odbpath|path=/Custom/my_custom_page&amp;amp;}} when buttons are pressed:&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;my_custom_page&amp;amp;&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[#Replace Status Page by a Custom page|Redirect when a Custom Page replaces the Status Page]].&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= CustomScript Buttons =&lt;br /&gt;
&lt;br /&gt;
[[/Customscript ODB tree#Customscript-button|CustomScript buttons]] can be provided on [[Custom Page|Custom Pages]]. These buttons are equivalent to optional &#039;&#039;&#039;script buttons&#039;&#039;&#039; on the [[Status Page]], which call a script to perform a particular action when the button is pressed. See [[/Script ODB tree#Script-button|script buttons]] for details. Customscript buttons can be set up through the [[/Customscript ODB tree]].&lt;br /&gt;
&lt;br /&gt;
Any key &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/CustomScript/test&amp;lt;/span&amp;gt; will appear as a customscript-button &lt;br /&gt;
&amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; on a custom page whose code includes an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag of the form:&lt;br /&gt;
{{Html|text=&amp;lt;input type=submit name=customscript value=&amp;quot;test&amp;quot;&amp;gt;}}&lt;br /&gt;
where the action of the button &amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; will be found in the &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/customscript/test&amp;lt;/span&amp;gt; subdirectory.&lt;br /&gt;
&lt;br /&gt;
After pressing a customscript-button, the &#039;&#039;type=submit&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} will cause a page reload. This will result in a switch to the [[Status Page]], unless a [[#redirect]] input tag is included to redirect back to the original custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customscript button without a page reload ==&lt;br /&gt;
To define a customscript button that does &#039;&#039;&#039;not&#039;&#039;&#039; cause a page reload, set the &#039;&#039;type&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} to &amp;quot;button&amp;quot;, i.e. &lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;customscript&amp;quot; value=&amp;quot;test&amp;quot; type=&amp;quot;button&amp;quot; onClick=cs_button(this.value)&amp;gt;  &amp;lt;!-- Customscript button does not reload page; no callback --&amp;gt;}}&lt;br /&gt;
and use &#039;&#039;&amp;quot;onClick&amp;quot;&#039;&#039; to call a function to [[#Send an Ajax Request]] instead. The following function sends an asynchronous request, with an optional callback.&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;script&amp;gt;}}&lt;br /&gt;
 &amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function cs_button(cmd, callback)&amp;lt;br&amp;gt;&lt;br /&gt;
{  // send a request to execute a custom script&lt;br /&gt;
:   var url;&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
:&lt;br /&gt;
:   url = ODBUrlBase&lt;br /&gt;
:   if(document.getElementById(&amp;quot;redir&amp;quot;) != null)&lt;br /&gt;
::     url +=  &#039;?redir=&#039;+document.getElementById(&#039;redir&#039;).value&lt;br /&gt;
:   url+=&#039;&amp;amp;customscript=&#039;+ encodeURIComponent(cmd);&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true); // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
:  if(callback!=undefined) {&lt;br /&gt;
::      request.onreadystatechange = function() {&lt;br /&gt;
:::         if (request.readyState == 4) { &lt;br /&gt;
::::            if (request.status == 200)&lt;br /&gt;
:::::                callback();&lt;br /&gt;
::::            else&lt;br /&gt;
:::::                alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:::            }&lt;br /&gt;
::         }&lt;br /&gt;
:  }&lt;br /&gt;
:  else { &lt;br /&gt;
::   if (request.status != 200)&lt;br /&gt;
:::       alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:	   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Resource files =&lt;br /&gt;
== MIDAS resource files ==&lt;br /&gt;
A number of resource files have been provided in the MIDAS packages under {{Filepath|path=../packages/midas/resources}}.  When including a MIDAS resource file, no key in  {{Odbpath|path=/Custom}} needs to be defined as the MIDAS resources directory is searched automatically. &lt;br /&gt;
&lt;br /&gt;
One such resource file is a condensed stylesheet {{File|name=midas.css}} for users who would like their custom pages to have a similar &amp;quot;look and feel&amp;quot; to that of the standard pages. To include the MIDAS stylesheet, in the HTML header, add &lt;br /&gt;
{{HtmlTag|tag=&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;}}&lt;br /&gt;
Other resource files can provide [[Custom Page#How to use the standard MIDAS navigation bars on your custom page|the standard MIDAS navigation bars]].&lt;br /&gt;
&lt;br /&gt;
== Custom resource files ==&lt;br /&gt;
It is often desirable to serve resource files (i.e. &#039;&#039;&#039;local files&#039;&#039;&#039; such as an external stylesheet, javascript files or images&#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039;) to a custom page. The following sections describe two alternative ways of serving resource files to a custom page:&lt;br /&gt;
* [[#Resource files served WITH /custom/path key defined|with  /custom/path key defined]]&lt;br /&gt;
* [[#Resource files served WITHOUT /custom/path key defined|without  /custom/path key defined]]&lt;br /&gt;
See also serving resources to a [[#Replace Status Page by a Custom Status page|Custom Status page]].&lt;br /&gt;
;NOTE&lt;br /&gt;
:  &#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039; To superimpose labels, bars or fills onto an image, the image cannot be served as a resource file. See [[#Image insertion]].&lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITH &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
When a number of resource files are required, it is convenient to place them in the same directory on the disk, and create a key  {{Odbpath|path=/Custom/Path}} to contain this directory. &lt;br /&gt;
&lt;br /&gt;
 $ ls  /home/midas/online/custom/&lt;br /&gt;
   custom_functions.js       custom_globals.js     custom_page.html    custom_stylesheet.css      test_image.png&lt;br /&gt;
With the  {{Odbpath|path=/Custom/Path}} key defined, [[/Custom ODB tree#Custom-Link|custom-links]] for individual resource files need not be created in the  {{Odbpath|path=/Custom/Path}} tree. The custom-link  {{Odbpath|path=custom_page&amp;amp;}} &#039;&#039;&#039;is&#039;&#039;&#039; required so that the custom page can be accessed from the [[/Custom ODB tree#Custom-Button|custom-button]]  {{Button|name=custom_page}} on the Status Page. The key name ends in the special character &amp;quot;&amp;amp;&amp;quot; so that it will open in the same window as the Status page (see [[/Custom ODB tree#Key names|key names]]).&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   path                           /home/midas/online/custom/&lt;br /&gt;
   custom_page&amp;amp;                   custom_page.html&lt;br /&gt;
 &lt;br /&gt;
A {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the file name) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_globals.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;custom_stylesheet.css&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;test_image.png&amp;quot;&amp;gt; }}&lt;br /&gt;
[[mhttpd]] then loads the resource file from the directory indicated by the Path key with the correct MIME type (see  [[/Custom ODB tree#Keys in the /Custom tree|Custom tree keys]] for supported MIME types). &lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITHOUT &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
Alternatively, resource files can be served to a [[Custom Page]] by creating a key for every resource file in the [[/Custom ODB tree]]. These keys must contain the full path of the file on the disk, and the key {{Odbpath|path=/Custom/Path}} must NOT be defined. The resource files can be in different directories on the disk. By defining the key names with an appropriate file extension, the resource files are served with the appropriate MIME types. The key names end in the special character &amp;quot;!&amp;quot; so that they will not appear on the Status page as custom-links (see [[/Custom ODB tree#Key names|key names]].&lt;br /&gt;
&lt;br /&gt;
Without the {{Odbpath|path=/Custom/Path}} key,  the links for the above example in the {{Odbpath|path=/Custom}} tree might look like&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   custom_page&amp;amp;                   /home/midas/online/custom/custom_page.html&lt;br /&gt;
   custom_functions.js!           /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   globals.js!                    /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   stylesheet.css!                /home/midas/online/stylesheets/custom_stylesheet.css&lt;br /&gt;
   image.png!                     /home/midas/images/test_image.png  &lt;br /&gt;
&lt;br /&gt;
assuming the resource files are now in the subdirectories indicated.&lt;br /&gt;
The {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the custom-link) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;globals.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;stylesheet.css!&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;image.png!&amp;quot;&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The resulting Demo custom page is shown in Figure 1, which can be compared with Figure 2 (no stylesheet). &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Figure 1  !! Figure 2&lt;br /&gt;
|-&lt;br /&gt;
| Demo Custom Page using MIDAS stylesheet || Demo Custom Page&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| [[File:Mhxcustom03.jpg|thumb|300px]] || [[File:Mhxcustom02.jpg|thumb|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=  Alias-Buttons and Hyperlinks =&lt;br /&gt;
Any hyperlink can easily be included on a [[Custom Page]] by using the standard HTML anchor {{HtmlTag|tag=&amp;lt;a...&amp;gt;}} tag, e.g.&lt;br /&gt;
{{Html|text=&amp;lt;a href=&amp;quot;http://ladd00.triumf.ca/~daqweb/doc/midas/html/&amp;quot;&amp;gt;Midas Help&amp;lt;/a&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Links on a custom page equivalent to [[/Alias ODB tree#Alias-Buttons|alias-buttons]] can also be made e.g.&lt;br /&gt;
{{Html|text=&amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.location.href=&#039;/Alias/alias&amp;amp;&#039;;&amp;quot;&amp;gt;alias&amp;lt;/button&amp;gt;}}&lt;br /&gt;
See the [[/Alias ODB tree]] for details.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Page refresh =&lt;br /&gt;
The following {{HtmlTag|tag=&amp;lt;meta...&amp;gt;}} tag included in the HTML header code will cause the whole custom page to refresh in 60 seconds :&lt;br /&gt;
{{Html|text=&amp;lt;meta http-equiv=&amp;quot;Refresh&amp;quot; content=&amp;quot;60&amp;quot;&amp;gt;}}&lt;br /&gt;
It is also possible to [[#Periodic update of parts of a custom page|periodically update parts]] of a custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Periodic update of parts of a custom page =&lt;br /&gt;
&lt;br /&gt;
The functionality of [[Mhttpd.js|ODBGet]] together with the window.setInterval() function&lt;br /&gt;
can be used to update parts of the web page periodically.&lt;br /&gt;
For example the Javascript fragment below contains a function which updates the current run number every 10 seconds in the background:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt; &amp;lt;!-- JS --&amp;gt;&lt;br /&gt;
window.setInterval(&amp;quot;Refresh()&amp;quot;, 10000);&amp;lt;br&amp;gt;&lt;br /&gt;
function Refresh() {&amp;lt;br&amp;gt;&lt;br /&gt;
:document.getElementById(&amp;quot;run_number&amp;quot;).innerHTML = ODBGet(&#039;/Runinfo/Run number&#039;);&amp;lt;br&amp;gt;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
The custom page has to &lt;br /&gt;
* [[Mhttpd.js#include js lib|include the MIDAS JS library]] to access ODBGet&lt;br /&gt;
* contain an element with id=&amp;quot;run_number&amp;quot;, such as&lt;br /&gt;
{{Html|text=&amp;lt;td id=&amp;quot;run_number&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Display last MIDAS message(s) =&lt;br /&gt;
&lt;br /&gt;
The message log (see [[Message System]]) can be accessed from a custom page using a call to the JavaScript library function [[Mhttpd.js|ODBGetMsg]] (provided the [[Mhttpd.js#include js lib|JS library is included]]).&lt;br /&gt;
&lt;br /&gt;
This allows the inclusion of the &amp;quot;Last Midas message&amp;quot; on a custom page, e.g.&lt;br /&gt;
{{JS|text=document.write(&#039;Last message:&#039;+ODBGetMsg(&amp;quot;midas&amp;quot;,0,1))}}&lt;br /&gt;
More messages may be displayed by increasing the third parameter to ODBGetMsg.&lt;br /&gt;
;Note&lt;br /&gt;
: Parameters were changed August 2015. See [[Mhttpd.js|ODBGetMsg]] for older versions.&lt;br /&gt;
: Coming soon - a [[mjsonrpc]] function&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Generate a message log entry =&lt;br /&gt;
&lt;br /&gt;
A custom page can generate a message to be sent to the MIDAS message log (see [[Message System]]).  A call to mjsonrpc_cm_msg() will generate a message if using the [[mjsonrpc]] functions. Otherwise, use the AJAX function [[Mhttpd.js|ODBGenerateMsg]]. To use these functions, the  [[Mhttpd.js#include js lib|JS library must be included]] in the html code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Checkboxes =&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New  (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should use [[Custom Page#modbcheckbox|modbcheckbox]] to create a checkbox.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;Br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function [[Mhttpd.js|ODBSet]] (provided the [[Mhttpd.js#include js lib|JS library is included]]) can be used when one clicks on a checkbox for example:&lt;br /&gt;
{{Html|text=&amp;lt;input  name=&amp;quot;box0&amp;quot;  type=&amp;quot;checkbox&amp;quot;  onClick=&amp;quot;ODBSet(my_path, this.checked?&#039;1&#039;:&#039;0&#039;)&amp;quot;&amp;gt;}}&lt;br /&gt;
If used as above, the state of the checkbox must be initialized when the page is loaded. This can be done with some JavaScript code called on initialization, e.g.&lt;br /&gt;
{{JS|text=document.form1.box0.checked= ODBGet(my_path));  // initialize to the correct value}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Replace Status Page by a Custom page =&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_status.jpg|thumbnail|left|Figure 3: ODB /Custom/Status custom-link to a custom status page]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a custom-link with the [[/Custom ODB tree#Key names|reserved key name]] &#039;&#039;&#039;Status&#039;&#039;&#039;  (not &amp;quot;Status&amp;amp;&amp;quot; or &amp;quot;Status!&amp;quot;) is created in the [[/Custom ODB tree]] (as shown in  Figure 3), then that custom page will &#039;&#039;&#039;replace the default Status Page&#039;&#039;&#039;. &lt;br /&gt;
  &lt;br /&gt;
Clicking on the {{Button|name=Status}} button on any of the sub-pages (e.g. [[ODB Page]], [[Programs Page]] etc.) will now return to the Custom Status Page. If there are buttons on the Custom Status page, you &#039;&#039;&#039;must&#039;&#039;&#039; include a  [[#Redirect]] statement of the form&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;../&amp;quot;&amp;gt;}}&lt;br /&gt;
or you will see the message &lt;br /&gt;
 Invalid custom page:NULL path&lt;br /&gt;
&lt;br /&gt;
If the Custom Status page includes [[#Resource files]] served on a regular custom page with a statement such as&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
to serve them in a Custom Status page, the statement would be&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&amp;lt;span style=&amp;quot;font-weight:bold&amp;quot;&amp;gt;/CS/&amp;lt;/span&amp;gt;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
In fact, this statement can be used in a regular custom page, as the &amp;quot;/CS/&amp;quot; is ignored in that case.&lt;br /&gt;
&lt;br /&gt;
To return to the default Status Page, delete the &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Custom/Status&amp;lt;/span&amp;gt; key. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Start, Stop and Check if a program is running =&lt;br /&gt;
There are [[mjsonrpc]] functions that implemented all three program management functions - start program, &lt;br /&gt;
stop program and &amp;quot;is running?&amp;quot; as JSON-RPC methods.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Send an Ajax request =&lt;br /&gt;
By sending an Ajax request from a custom page, you can make a button perform a specific function. &lt;br /&gt;
  &lt;br /&gt;
All functions in midas are controlled through special URLs. So the URL&lt;br /&gt;
 http://&amp;lt;host:port&amp;gt;/?cmd=Start&amp;amp;value=10&lt;br /&gt;
will start run #10.&lt;br /&gt;
&lt;br /&gt;
Although it is easier to use an HTML input statement to [[#Access to the MIDAS Menu buttons]], &lt;br /&gt;
to send an Ajax request, you can use the function &#039;&#039;XMLHttpRequestGeneric&#039;&#039; which is in the MIDAS Javascript library [[mhttpd.js]].&lt;br /&gt;
Then the HTML code would be&lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;start()&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
and in your JavaScript code add a function &#039;&#039;start()&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function start()&lt;br /&gt;
{&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
&lt;br /&gt;
:   url = &#039;?cmd=Start&amp;amp;value=10&#039;;&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true);  // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
See also [[#Access to the MIDAS Menu buttons]]. Another example with optional callback can be found in [[#Customscript button without a page reload]].&lt;br /&gt;
&lt;br /&gt;
This mechanism can be used for starting a particular program, see for example (see [https://midas.triumf.ca/elog/Midas/1046]). However, this functionality is now provided by [[mjsonrpc]] functions - see [[#Start, Stop and Check if a program is running]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Image insertion  =&lt;br /&gt;
An image can be loaded from the web using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}}, e.g.  &lt;br /&gt;
   {{Html|text=&amp;quot;TRIUMF logo&amp;quot; &amp;lt;img src=&amp;quot;https://lixenon.triumf.ca/InternalDocuments/Alice/figures/TRIUMF-logo.jpg/image_preview&amp;quot;&amp;gt;}}&lt;br /&gt;
or a MIDAS History image can be inserted into a custom page using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag of the following form:&lt;br /&gt;
 {{Html|text=blah&amp;lt;img src=&amp;quot;http://hostname.domain:port/HS/Meterdis.gif&amp;amp;scale=12h&amp;amp;width=300&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If the image file is on the local disk, it can be loaded as a &#039;&#039;&#039;resource file&#039;&#039;&#039;. The image can be of format .pdf, .jpg, .gif, .png. See loading [[#Resource files]].&lt;br /&gt;
&lt;br /&gt;
If you wish to superimpose features such as &#039;&#039;&#039;labels and fills&#039;&#039;&#039;, the image file cannot be served as a [[#Resource files|resource file]]. Instead, the image file must be in &#039;&#039;&#039;gif&#039;&#039;&#039; format, and must be included in the {{Odbpath|path=/Custom/images}} subtree (Figure 4). Image insertion into a Custom page will be illustrated using the Demo custom page shown in [[#MIDAS stylesheet|Figure 2]]. All the files required for this demo can be found in the MIDAS package at $MIDASSYS/examples/custom. If you do not wish to create&lt;br /&gt;
the keys yourself, proceed to [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
Make a [[/Custom ODB tree#Custom-Link|custom-link]] to the Demo custom page file &#039;&#039;myexpt.html&#039;&#039; , i.e.&lt;br /&gt;
 [local:js:S]/&amp;gt;ls /custom&lt;br /&gt;
    myexpt&amp;amp;                  /home/test/packages/midas/examples/custom/myexpt.html&lt;br /&gt;
 &lt;br /&gt;
To make the image &#039;&#039;myexpt.gif&#039;&#039; visible on the custom page, the path and filename of the image file must be defined in the   {{Odbpath|path=/Custom/images}} subtree. To do this, &lt;br /&gt;
create the  subtrees {{Odbpath|path=/Custom/images/myexpt.gif}} where the subtree name &amp;quot;myexpt.gif&amp;quot; is named for the image file you are going to use. Multiple images can be used, by creating multiple imagefile subtrees.&lt;br /&gt;
&lt;br /&gt;
In the imagefile subtree {{Odbpath|path=myexpt.gif}}, create the STRING key  {{Odbpath|path=Background}}, and set it to contain the path and name of the image file.  The tree structure should then look similar to Figure 4, minus the labels/bars/fill subtrees which will be added to the ODB later.&lt;br /&gt;
&lt;br /&gt;
The image must also be referenced in the custom HTML file &#039;&#039;myexpt.html&#039;&#039; in the &amp;quot;src&amp;quot; field of an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag, e.g. &lt;br /&gt;
{{Html|text=&amp;lt;img &#039;&#039;&#039;src=&amp;quot;myexpt.gif&amp;quot;&#039;&#039;&#039;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_images.jpg|thumbnail|left|Figure 4: /Custom/Images ODB Tree]]&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
Once the image is visible, enable [[#HTML mapping]], optionally [[#Display mouse position]] and proceed to  [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== HTML mapping ==&lt;br /&gt;
Note that if additional features such as active clickable areas and labels, bars and fills superimposed on the image are also required, HTML mapping must also be activated with the HTML {{HtmlTag|tag=&amp;lt;map...&amp;gt;}} tag and the &amp;quot;usemap&amp;quot; attribute of the HTML {{HtmlTag|tag=&amp;lt;img&amp;gt;}} tag&lt;br /&gt;
{{Html|text=   &amp;lt;map &#039;&#039;&#039;name=&amp;quot;myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;img src=&amp;quot;myexpt.gif&amp;quot; &#039;&#039;&#039;usemap=&amp;quot;#myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt;...&amp;lt;br&amp;gt; &amp;lt;/map&amp;gt;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Display mouse position ==&lt;br /&gt;
[[File:Cursor.png|thumbnail|left|Figure 5: MEG Gas System Custom Page showing cursor position]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When writing custom pages with large background images and labels and fills placed on that image, it is hard to figure out X and Y coordinates of the labels. This can now be simplified by using the function &#039;&#039;getMouseXY()&#039;&#039; in the development JavaScript built-in library [[develop.js]]. This function supplies the X,Y position of the cursor if an element of ID &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; is present. This JS library &#039;&#039;&#039;must be [[develop.js|included in the custom page]]&#039;&#039;&#039; in order to use it:&lt;br /&gt;
&lt;br /&gt;
Then, set the &amp;quot;id&amp;quot; attribute of the background HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag to &amp;quot;refimg&amp;quot;, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: &amp;lt;img  &#039;&#039;&#039;id=&amp;quot;refimg&amp;quot;&#039;&#039;&#039; src=&amp;quot;ebit_pc.gif&amp;quot; usemap=&amp;quot;#Custom1&amp;quot;&amp;gt;   &amp;lt;!-- name=&amp;quot;refimg&amp;quot; makes crosshairs appear --&amp;gt;&lt;br /&gt;
: &amp;lt;map name=&amp;quot;Custom1&amp;quot;&amp;gt;&lt;br /&gt;
: .....&lt;br /&gt;
: &amp;lt;/map&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; tag is present, the cursor changes into a crosshair, and its absolute and relative locations in respect to the reference image are shown in the status bar (Figure 5).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
==  Superimposing Labels, Bars and Fills onto a gif image ==&lt;br /&gt;
You can enhance your custom page by superimposing multiple features based on ODB variables onto an image (e.g. [[#Display mouse position|Figure 5]]), such as&lt;br /&gt;
&lt;br /&gt;
*    labels: &amp;quot;live&amp;quot; ODB values positioned in a particular location of the page&lt;br /&gt;
*    bars : &amp;quot;bar level&amp;quot; showing graphically ODB values such as levels or rate etc.&lt;br /&gt;
*    fills : &amp;quot;color level&amp;quot; where colour is used as the level indicator.&lt;br /&gt;
&lt;br /&gt;
Each entry (label/bar/fill) will have an ODB tree associated to it defining the ODB variable path, X/Y position, colour, etc. Each time the page is updated, the latest ODB value/level/rate will be shown based on the ODB parameter to which the label, bar or fill is linked - hence the term &amp;quot;live&amp;quot;. The overlay of the requested features is done onto the selected image file.&lt;br /&gt;
&lt;br /&gt;
This powerful new extension brings the [[mhttpd]] capability closer to other experimental web controllers similar to EPICS.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
:    Be sure to enable the feature to [[#Display mouse position]] in order to facilitate finding the X,Y positions of the various features.&lt;br /&gt;
: [[#HTML mapping]] must be activated for labels/bars/fills to work&lt;br /&gt;
&lt;br /&gt;
A Demo custom page showing labels, bars and fills superimposed on an image is shown in [[#MIDAS stylesheet|Figure 2]]. &lt;br /&gt;
All the files for this demo can be found in $MIDASSYS/examples/custom/. The file xcustom.odb contains the ODB keys required, including those to insert the image and superimpose the various labels, fills etc. This file can be loaded into the ODB with the  {{Odbedit cmd|cmd=load}}.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Labels ===&lt;br /&gt;
&lt;br /&gt;
 [[File:Mhcustom_label.jpg|thumbnail|left|Figure 6: /Custom/Images/Labels ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In order to include a readout of ODB values (i.e. labels), on the image, a further ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Labels}} must be created. Creating a subdirectory for a particular label i.e. {{Odbpath|path=&amp;lt;label name&amp;gt;}} in the   {{Odbpath|path=Labels}} subtree will, at the next custom web page refresh, cause the complete structure for that label to be created and filled with default values. Once the {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is created, the user fills the various keys as desired. See [[/Custom ODB tree#Labels subtree]] for details of the various fields. This procedure is repeated for all the labels required, using a unique {{Odbpath|path=&amp;lt;label name&amp;gt;}} subdirectory for each label. An example of a {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is shown in Figure 6. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Bars ===&lt;br /&gt;
 &lt;br /&gt;
[[File:Mhcustom_fill.jpg|thumbnail|left|Figure 7: /Custom/Images/Fills ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Bars can be superimposed on the image. Create  a new ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Bars}}. Create a subdirectory for a particular Bar ({{Odbpath|path=&amp;lt;bar name&amp;gt;}}) in the {{Odbpath|path=Bars}}  subdirectory. Refresh the web page and fill the various keys as desired.  See [[/Custom ODB tree#Bars subtree]] for details of the various fields.  Examples of a &amp;lt;bar-name&amp;gt; subtree is shown in Figure 7.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Fills ===&lt;br /&gt;
[[File:Mhcustom_bar.jpg|thumbnail|left|Figure 8: /Custom/Images/Bars ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Fills can be superimposed on the image. Create new ODB subdirectory   {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Fills}}. Create a subdirectory for a particular  Fill ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}) in the {{Odbpath|path=Fills}} subdirectory. Refresh the web page and fill the various keys as desired.  See  [[/Custom ODB tree#Fills subtree]] for details of the various fields.  Examples of a ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}  subtree is shown in Figure 8. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Mapping active areas onto the image ==&lt;br /&gt;
Provided [[#HTML mapping]] is activated, &amp;quot;clickable&amp;quot; areas can be created on the image.&lt;br /&gt;
&lt;br /&gt;
This can be done now with a new function like this:&lt;br /&gt;
{{Html|text= &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;40,200,100,300&amp;quot; alt=&amp;quot;Main Valve&amp;quot; href=&amp;quot;Custom1?cmd=Toggle&amp;amp;odb=/Equipment/Environment/Variables/Output[2]&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
This defines a clickable map on top of the custom image. The area(s) should match with some area(s) on the image, e.g. the box of a valve. Determining the co-ordinates of this area is simplified by using the Display mouse position feature.&lt;br /&gt;
&lt;br /&gt;
By clicking on this area, the supplied path to the ODB is used (in this case  {{Odbpath|path=/Equipment/Environment/Variables/Output[2]}}) and its value is toggled. If the valve value is then used in the image via a [[#Adding Fills|Fill]] statement to change the color of the valve, it can turn green or red depending on its state. This is illustrated in [[#Display mouse position|Figure 5]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Clicking an active area can also be made to open a new custom page, for example:&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;687,530, 890,648&amp;quot; alt=&amp;quot;Pump detail&amp;quot; href = &amp;quot;Pump!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;560,574,775,662&amp;quot; alt=&amp;quot;Buffer Tank detail&amp;quot; href = &amp;quot;BufferTank!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
where &#039;&#039;Pump!&#039;&#039; and &#039;&#039;BufferTank!&#039;&#039; are defined as links to custom pages in the [[/Custom ODB tree]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Edit boxes floating on top of a graphic ==&lt;br /&gt;
&lt;br /&gt;
An edit box can be placed on top of a graphic in a particular position by means of an HTML  &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/span&amp;gt; tag. Using the ODBEdit function from the Midas JS library [[Mhttpd.js]], the custom page code would look like this:&lt;br /&gt;
&amp;lt;!-- Complicated... have to use &amp;lt;pre&amp;gt; because of &amp;lt;div&amp;gt;, then a table to keep the background colour between &amp;lt;pre&amp;gt;s --&amp;gt;&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: floralwhite;&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;    &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
document.write(&#039;Run number: &#039;)&lt;br /&gt;
path=&#039;/runinfo/run number&#039;&lt;br /&gt;
rn = ODBGet(path)&lt;br /&gt;
document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(path)&amp;quot; &amp;gt;&#039;)  &lt;br /&gt;
document.write(rn)&lt;br /&gt;
document.write(&#039;&amp;lt;/a&amp;gt;&#039;);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same thing could be done with the HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag :&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;&lt;br /&gt;
Run number:  &amp;lt;odb src=&amp;quot;/Runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Interactive SVG Implementation ==&lt;br /&gt;
&lt;br /&gt;
An SVG file loaded on a custom page can be interactively modified using JavaScript.&lt;br /&gt;
This is useful for graphically displaying ODB values in complex service or detector systems.&lt;br /&gt;
&lt;br /&gt;
An SVG file can be embedded like this:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Objects within the SVG can be modified—for example, by changing their fill color as shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument; &lt;br /&gt;
const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
obj_Valve.style.fill = &amp;quot;#00FF00&amp;quot;; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SVG images are stored in XML format. In Inkscape, object properties can be interactively edited using the XML Editor.&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
For more examples of accessing the ODB with the Javascript library [[mhttpd.js]] look at the example experiment in the MIDAS package $MIDASSYS/examples/javascript1/example.html.&lt;br /&gt;
&lt;br /&gt;
== Example 1 : Asynchronous calls using mjsonrpc functions ==&lt;br /&gt;
See [[mjsonrpc#examples]]&lt;br /&gt;
&lt;br /&gt;
== Example 2 : Asynchronous calls using Javascript and AJAX ==&lt;br /&gt;
&lt;br /&gt;
Example 2 shows html code illustrating the use of ODBMCopy(), where innerHTML is used to display the data. On a page with images or a lot of data, innerHTML allows you to update variables without having to reload the whole page. In the example, a timer causes the page to update every 10s (reread the data). Often the ODB data for the whole page can be read by one asynchronous ODBMCopy() call, rather than scattering synchronous ODBGet() calls throughout the page.  &lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], included with the line &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;script src=&#039;mhttpd.js&#039;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;MyTitle&amp;lt;/title&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;script src=&#039;mhttpd.js&#039;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
var updatePeriod = 10000; // in msec &amp;lt;br&amp;gt;&lt;br /&gt;
var updateTimerId = 0; &amp;lt;br&amp;gt;&lt;br /&gt;
var counter=0; &amp;lt;br&amp;gt;&lt;br /&gt;
function update()  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:clearTimeout(updateTimerId); &amp;lt;br&amp;gt;&lt;br /&gt;
:      load(); &lt;br /&gt;
:      if (updatePeriod &amp;gt; 0) &lt;br /&gt;
:      updateTimerId = setTimeout(&#039;update()&#039;, updatePeriod); &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function load()  { &amp;lt;br&amp;gt;&lt;br /&gt;
: document.getElementById(&#039;LastUpdated&#039;).innerHTML = &amp;quot;Updating...&amp;quot; + new Date; &lt;br /&gt;
: var paths = [ &amp;quot;/Runinfo&amp;quot;, &amp;quot;/Experiment&amp;quot;]; &lt;br /&gt;
: var data_odb=ODBMCopy(paths, mcopy_callback, &amp;quot;json&amp;quot;) &lt;br /&gt;
: counter++; &lt;br /&gt;
: document.getElementById(&#039;counter&#039;).innerHTML = &#039;Counter: &#039;+ counter &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function mcopy_callback(data)  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:    var obj= JSON.parse(data);&lt;br /&gt;
: var runinfo=obj[0];&lt;br /&gt;
: document.getElementById(&#039;rn&#039;).innerHTML = &#039;Run Number =&#039;+ parseInt(runinfo[&amp;quot;Run number&amp;quot;]);&lt;br /&gt;
: document.getElementById(&#039;state&#039;).innerHTML  =&#039;Run State= &#039;+ runinfo.State;&lt;br /&gt;
: var experiment=obj[1];&lt;br /&gt;
: document.getElementById(&#039;name&#039;).innerHTML=&#039;Experiment name = &#039;+ experiment.Name &amp;lt;br&amp;gt; &lt;br /&gt;
}&amp;lt;br&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/head&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;body&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;b&amp;amp;gt;Javascript code using ODBMCopy with callback&amp;amp;lt;/b&amp;amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
The data on the page is updated every 10 sec using a timer&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;LastUpdated&amp;quot; &amp;amp;gt; Last updated: never&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;rn&amp;quot;&amp;amp;gt; Run Number : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;state&amp;quot;&amp;amp;gt; State : unknown&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;name&amp;quot;&amp;amp;gt; Experiment name : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;counter&amp;quot;&amp;amp;gt;Counter: zero  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/div&amp;gt;  &lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: if (updatePeriod &amp;gt; 0)&lt;br /&gt;
:  update(); &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Example 3 : Synchronous Calls using Javascript and AJAX ==&lt;br /&gt;
In the following example, JS functions ODBGet and  ODBEdit from the [[Mhttpd.js|MIDAS Javascript Library]] are used to access the ODB.  &lt;br /&gt;
;NOTE&lt;br /&gt;
# Synchronous calls are now deprecated (see [[#The MIDAS Javascript Library|above]]).&lt;br /&gt;
# The Javascript library &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;script&amp;gt;}}&lt;br /&gt;
{{JS|text=document.write (&#039;Experiment Name: &#039;+ ODBGet(&amp;quot;/Experiment/Name&amp;quot;)) &amp;lt;br&amp;gt; var alarm_path=&amp;quot;/alarms/Alarm system active&amp;quot;; &amp;lt;br&amp;gt; var alarm_active=ODBGet(alarm_path); &amp;lt;br&amp;gt; document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(alarm_path)&amp;quot; &amp;gt;&#039;+alarm_active+&#039;&amp;lt;/a&amp;gt;&#039;)}}&lt;br /&gt;
{{Html|text=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
== Example 4 : Interactive SVG custom page  ==&lt;br /&gt;
&lt;br /&gt;
In this example, an SVG image forms the basis of a custom page.&lt;br /&gt;
It displays two valves and an expansion volume.&lt;br /&gt;
&lt;br /&gt;
The valves change color depending on their status — open or closed.&lt;br /&gt;
The expansion volume dynamically changes its height.&lt;br /&gt;
&lt;br /&gt;
All objects within the SVG file are directly modified using JavaScript functions.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
    &amp;lt;title&amp;gt;Helium example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;style&amp;gt;&lt;br /&gt;
        .modbbutton {font-size:20px;}&lt;br /&gt;
	.text {font-size:20px;}&lt;br /&gt;
    &amp;lt;/style&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // Valve Coloring  --------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
	function updateValveColor(flag, valveID) {&lt;br /&gt;
	    console.log(valveID, flag);&lt;br /&gt;
       	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
       	    //The valveID needs to match the ID given in the svg file&lt;br /&gt;
       	    const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
&lt;br /&gt;
  	    obj_Valve.style.fill = (flag === 1) ? &amp;quot;#00FF00&amp;quot; : &amp;quot;#FF0000&amp;quot;;&lt;br /&gt;
       	}&lt;br /&gt;
&lt;br /&gt;
        // Confirmation routines --------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
        function confirmValve(arg, valveAddress) {&lt;br /&gt;
	    const txt = arg === &amp;quot;Close&amp;quot; ? &amp;quot;Are you sure to close the valve?&amp;quot; : &amp;quot;Are you sure to open the valve?&amp;quot;;&lt;br /&gt;
            const val = arg === &amp;quot;Open&amp;quot; ? 1 : 0;&lt;br /&gt;
            dlgConfirm(txt, function(confirmed) {&lt;br /&gt;
                if (confirmed) {&lt;br /&gt;
                    modbset(valveAddress, val);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return false; // Prevent ODB value change immediately&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
	// Animation -------------------------------------------------------------------&lt;br /&gt;
        &lt;br /&gt;
	/**&lt;br /&gt;
	 * Update the volume of an SVG element by applying a transformation based on input.&lt;br /&gt;
	 * @param {number} elem - The scale factor to modify the volume.&lt;br /&gt;
	 * @param {string} svgID - The ID of the SVG element to be transformed.&lt;br /&gt;
	 * Comment: This example specifically transform only the ySize of the object,&lt;br /&gt;
	 * 	    with the special case that the bottom edge stays in place&lt;br /&gt;
	 */&lt;br /&gt;
	function updateVolume(elem, svgID) {&lt;br /&gt;
	    const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument;&lt;br /&gt;
	    const rootSvg = svgDoc.documentElement;&lt;br /&gt;
&lt;br /&gt;
	    // Get the width and height of the root SVG element.&lt;br /&gt;
	    const widthAttr = rootSvg.getAttribute(&amp;quot;width&amp;quot;);&lt;br /&gt;
	    const heightAttr = rootSvg.getAttribute(&amp;quot;height&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
	    // Get and parse the viewBox attribute.&lt;br /&gt;
	    const viewBoxAttr = rootSvg.getAttribute(&amp;quot;viewBox&amp;quot;);                &lt;br /&gt;
	    const viewBoxParts = viewBoxAttr.split(&amp;quot; &amp;quot;).map(parseFloat);&lt;br /&gt;
	    const viewBoxWidth = viewBoxParts[2];&lt;br /&gt;
	    const viewBoxHeight = viewBoxParts[3];&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the scaling factors for x and y axes based on the viewBox and actual width/height.&lt;br /&gt;
	    const xScale = widthAttr / viewBoxWidth;&lt;br /&gt;
	    const yScale = heightAttr / viewBoxHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Get the element to modify based on the provided svgID.&lt;br /&gt;
	    const obj_Vol = svgDoc.getElementById(svgID); &lt;br /&gt;
	    const bbox = obj_Vol.getBBox();&lt;br /&gt;
&lt;br /&gt;
	    // Extract the transformation matrix from the element&#039;s transform attribute.&lt;br /&gt;
	    const transformAttr = obj_Vol.getAttribute(&amp;quot;transform&amp;quot;);&lt;br /&gt;
	    const matrixMatch = transformAttr.match(/matrix\(([^)]+)\)/);&lt;br /&gt;
&lt;br /&gt;
	    let transformedY, transformedHeight;&lt;br /&gt;
&lt;br /&gt;
	    // Apply matrix transformation if it exists.&lt;br /&gt;
	    if (matrixMatch) {&lt;br /&gt;
	        const [a, b, c, d, e, f] = matrixMatch[1].split(&amp;quot;,&amp;quot;).map(parseFloat);&lt;br /&gt;
&lt;br /&gt;
	        // Apply matrix to the top-left corner of the bbox to get transformed Y position and height.&lt;br /&gt;
	        transformedY = d * bbox.y + f;&lt;br /&gt;
	        transformedHeight = d * bbox.height;&lt;br /&gt;
	    }&lt;br /&gt;
&lt;br /&gt;
	    // Scale factor is 0.01 times the provided &#039;elem&#039; value.&lt;br /&gt;
	    const scale = 0.01 * elem;&lt;br /&gt;
&lt;br /&gt;
	    // Calculate the new Y position after applying scaling.&lt;br /&gt;
	    const yPosInit = (transformedY / yScale + transformedHeight / yScale) * (1 - scale);&lt;br /&gt;
&lt;br /&gt;
	    // Update the element&#039;s transform attribute with the new translation and scaling.&lt;br /&gt;
	    // In this example only the height (y-axis) is changed&lt;br /&gt;
	    updateTransform(obj_Vol, `0,${yPosInit}`, `1,${scale}`);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/**&lt;br /&gt;
	 * Updates the transform attribute of an SVG element by applying new translation and scale values.&lt;br /&gt;
	 * @param {Element} elem - The SVG element whose transform attribute is to be updated.&lt;br /&gt;
	 * @param {string} newTranslate - The new translation value as a string (e.g., &amp;quot;0,100&amp;quot;).&lt;br /&gt;
	 * @param {string} newScale - The new scale value as a string (e.g., &amp;quot;1,0.5&amp;quot;).&lt;br /&gt;
	 */&lt;br /&gt;
	function updateTransform(elem, newTranslate, newScale) {&lt;br /&gt;
	    let transform = elem.getAttribute(&amp;quot;transform&amp;quot;) || &amp;quot;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
	    // Regular expressions to match existing translate and scale transformations.&lt;br /&gt;
	    const translateRegex = /translate\(([^)]+)\)/;&lt;br /&gt;
	    const scaleRegex = /scale\(([^)]+)\)/;&lt;br /&gt;
&lt;br /&gt;
	    // Remove any existing translate and scale transformations from the transform string.&lt;br /&gt;
	    transform = transform&lt;br /&gt;
	        .replace(translateRegex, &#039;&#039;)&lt;br /&gt;
	        .replace(scaleRegex, &#039;&#039;)&lt;br /&gt;
	        .trim();&lt;br /&gt;
&lt;br /&gt;
	    // Construct the new transform string with updated translation and scale.&lt;br /&gt;
	    const newTransform = `translate(${newTranslate}) scale(${newScale}) ${transform}`.trim();&lt;br /&gt;
&lt;br /&gt;
	    // Apply the new transform to the element.&lt;br /&gt;
	    elem.setAttribute(&amp;quot;transform&amp;quot;, newTransform);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;!-- modb values to listen for status changes --&amp;gt;	&lt;br /&gt;
    &amp;lt;div id=&amp;quot;filling_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Filling_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div id=&amp;quot;exhaust_valve&amp;quot; class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; onchange=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot; onload=&amp;quot;updateValveColor(this.value,&#039;Exhaust_valve&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;th class=&amp;quot;mtableheader&amp;quot;&amp;gt;Helium example&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:600px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Import of a single SVG file, colors and sizes are changed directly for each object --&amp;gt; &lt;br /&gt;
            &amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
	    &lt;br /&gt;
	    &amp;lt;!-- Control and monitoring instances --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Filling line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:125px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Filling Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 473px;left:195px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Filling Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 443px; left:125px; font-weight: bold;&amp;quot;&amp;gt;Filling valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
	   &lt;br /&gt;
            &amp;lt;!-- Exhaust line --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:352px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Open&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Open&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot; &lt;br /&gt;
                    style=&amp;quot;position:absolute; top: 300px;left:422px;&amp;quot;&lt;br /&gt;
                    data-validate=&#039;confirmValve(&amp;quot;Close&amp;quot;,&amp;quot;/Equipment/Example/Exhaust Valve&amp;quot;)&#039;&lt;br /&gt;
                    data-odb-value=&amp;quot;0&amp;quot;&amp;gt;Close&amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;text&amp;quot; style=&amp;quot;position:absolute; top: 270px; left:352px; font-weight: bold;&amp;quot;&amp;gt;Exhaust valve:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;!-- Expansion Volume --&amp;gt;&lt;br /&gt;
            &lt;br /&gt;
	    &amp;lt;div class=&amp;quot;modbvbar&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Expansion volume&amp;quot;&lt;br /&gt;
                 style=&amp;quot;width:20px;height:200px; color:grey; position:absolute; top:56px;left:320px&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;updateVolume(this.value, &#039;Expansion_volume&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:30px;height:200px; position:absolute; top:56px;left:340px; text-align:left&amp;quot;&lt;br /&gt;
                 data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;100&amp;quot; data-log=&amp;quot;0&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/table&amp;gt;&lt;br /&gt;
	 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3517</id>
		<title>Custom Page Features</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page_Features&amp;diff=3517"/>
		<updated>2025-05-12T12:06:00Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: /* Image insertion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
= Links =&lt;br /&gt;
&amp;lt;div style=&amp;quot;column-count:3;-moz-column-count:3;-webkit-column-count:3&amp;quot;&amp;gt;&lt;br /&gt;
* [[Mhttpd|mhttpd MIDAS web server]]&lt;br /&gt;
* [[ODB Page]]&lt;br /&gt;
* [[Custom Page]]&lt;br /&gt;
* [[/Custom ODB tree]]&lt;br /&gt;
* [[Mhttpd.js|MIDAS Javascript library (mhttpd.js)]]&lt;br /&gt;
* [[mjsonrpc|MIDAS JSON RPC library functions (mjsonrpc)]]&lt;br /&gt;
* [[odbedit]] &lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
;NOTE&lt;br /&gt;
: Since 2018, many of the features described here can now be implemented more simply using the  mod* JS functions (see [[Custom Page|Custom Web Page]]).&lt;br /&gt;
: &#039;&#039;&#039;If writing a new web page, it is strongly recommended you make use of the mod*JS features. &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This page describes some of the special features provided for use on a user-created [[Custom Page|Custom Web Page]] using the [[#The MIDAS Javascript Library]], which was the recommended way to write Custom Pages before the [[Custom Page#modb* Javascript scheme]]  was available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= The MIDAS Javascript Library =&lt;br /&gt;
The MIDAS Javascript Library [[mhttpd.js]] includes routines written in Javascript and AJAX to provide features useful for writers of [[Custom Page]]s, such as access to the ODB. Recently asynchronous [[mjsonrpc|JSON-RPC functions]] using [[mjsonrpc#Javascript client library|Promises]]  has been added (January 2016).&lt;br /&gt;
;NOTE&lt;br /&gt;
:To use functions in this library, it &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Many of the older routines (ODBGet, ODBSet) use &#039;&#039;&#039;synchronous&#039;&#039;&#039; RPC requests. With synchronous request, a second RPC request must wait until the first request is complete. This can significantly slow down web pages where many calls to ODBGet are made. For this reason, functions were developed that can be synchronous or &#039;&#039;&#039;asynchronous&#039;&#039;&#039; (e.g. ODBMCopy) that include a callback mechanism. The data from ODBMCopy can be formatted in [[mjsonrpc#JSON general information|JSON]]. &lt;br /&gt;
&lt;br /&gt;
Web developers are moving away from synchronous RPC requests, which are now deprecated [https://midas.triumf.ca/elog/Midas/1128]. Some modern browsers return a warning on a synchronous RPC request. &lt;br /&gt;
&lt;br /&gt;
A new group of JSON-RPC functions using  [[mjsonrpc#Javascript client library|Promises]] has been added to the MIDAS Javascript Library (January 2016). These are asynchronous only, and they also provide more functionality and have better error handling than the older AJAX calls, which are also inconsistent in how they handle Booleans. They also have much improved handling of reading/writing arrays to the ODB. Before 2018 it was recommended that all new Custom Pages are written using these JSON-RPC functions.  {{Important|text=Since 2018 the [[Custom Page#modb* Javascript scheme]] is recommended for new Custom Pages}}.&lt;br /&gt;
&lt;br /&gt;
The MIDAS Javascript Library routines requires an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). If using an older browser version, the asynchronous AJAX calls (e.g. ODBMCopy) should be used in order to avoid having to rewrite the web page at a later date to remove all synchronous calls.  &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using mjson-rpc asynchronous functions ==&lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], which (since January 2016) includes the [[mjsonrpc]] functions.  It is [[#The MIDAS Javascript Library|recommended]] that these functions are used for &#039;&#039;&#039;new&#039;&#039;&#039; pages rather than the older AJAX calls (see below). &lt;br /&gt;
&lt;br /&gt;
To run these functions you need&lt;br /&gt;
# to  [[mhttpd.js#include js lib|include the Javascript library in the HTML code]]&#039;&#039;&#039;&lt;br /&gt;
# to use an up-to-date browser that supports Promises (see [https://midas.triumf.ca/elog/Midas/1145]). &lt;br /&gt;
&lt;br /&gt;
Examples showing how to read and write to the ODB can be found at [[mjsonrpc#examples]]. Also included are examples using arrays. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using asynchronous AJAX calls ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: It is recommended that  asynchronous MIDAS JSON-RPC functions are used for new pages rather than the older AJAX calls (see [[#Access the ODB using mjson-rpc asynchronous functions|above]]).&lt;br /&gt;
&lt;br /&gt;
[[#Example 2 : Asynchronous calls using Javascript and AJAX|Example 2]] uses the asynchronous call ODBMCopy() with callback to read the data. The data are converted into JSON format. In this case, when accessing the JSON data, &#039;&#039;the ODB Keynames must be in the same case as they are in the ODB&#039;&#039;, even though the case is ignored by the ODB. If you find this feature annoying, use [[#Access the ODB using mjson-rpc asynchronous functions|mjson-rpc function db_get_values()]] as all keynames are converted to lower case.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Access the ODB using synchronous AJAX requests ==&lt;br /&gt;
;NOTE&lt;br /&gt;
: Synchronous requests are now deprecated (see [[#The MIDAS Javascript Library|above]]). New pages should be written [[#Using mjson-rpc asynchronous functions]].&lt;br /&gt;
&lt;br /&gt;
See [[#Example 3 : Synchronous Calls using Javascript and AJAX]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Access the ODB with HTML-style &amp;lt;span style=&amp;quot;color:seagreen font-style:italic&amp;quot;&amp;gt;&amp;lt;odb&amp;gt;&amp;lt;/span&amp;gt; tags =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: This method pre-dates the [[Mhttpd.js|MIDAS Javascript Library]]. It is recommended that the [[Mhttpd.js|MIDAS Javascript Library]] be used for ODB access.&lt;br /&gt;
&lt;br /&gt;
If Javascript (JS) is not available, the older HTML-style  {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are still available and provide limited functionality.&lt;br /&gt;
&lt;br /&gt;
The  HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag has been defined for read/write access to the ODB under HTML. The {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags.&lt;br /&gt;
&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: rgb(255, 255, 255);&amp;quot; border=&amp;quot;3&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;2&amp;quot;&lt;br /&gt;
|+ Access to ODB from HTML&lt;br /&gt;
|-&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | HTML ODB tag&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot; style=&amp;quot;text-align: center; vertical-align: top; background-color: rgb(204, 204, 255); font-weight: bold;&amp;quot; | Meaning&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot;&amp;gt;&lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: black&amp;quot;  |Display ODB field (read only)&lt;br /&gt;
	&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=1 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (inline style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
&lt;br /&gt;
|-   &lt;br /&gt;
| colspan=&amp;quot;1&amp;quot; rowspan=&amp;quot;1&amp;quot;  style=&amp;quot;text-align: left; vertical-align: top; background-color: rgb(255, 255, 255); color: seagreen; font-style:italic&amp;quot;  |&amp;lt;odb src=&amp;quot;odb path&amp;quot; edit=2 pwd=&amp;quot;CustomPwd&amp;quot;&amp;gt;&lt;br /&gt;
| Display an Editable ODB field (popup style). Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; .&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: The Optional password protection with &#039;&#039;&#039;pwd&#039;&#039;&#039; (documented in the [http://ladd00.triumf.ca/~daqweb/doc/midas-old/html/RC_customize_ODB.html#RC_Access_Control| OldMidas Document]) may not be working.&lt;br /&gt;
: Use the  [[/Experiment ODB tree#Security subtree|Web Password security]] instead. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tags are included in the HTML code e.g.&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
 Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt;&lt;br /&gt;
 Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= Experiment Name: &amp;lt;odb src=&amp;quot;/Experiment/Name&amp;quot;&amp;gt; &amp;lt;br&amp;gt;  Run Number: &amp;lt;odb src=&amp;quot;/runinfo/run number&amp;quot; edit=1&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[Custom Page#How to write a Custom Page|HTML Custom Page example]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= ODB RPC access =&lt;br /&gt;
The [[Mhttpd.js|MIDAS Javascript Library]] function ODBRpc() defined for RPC access.&lt;br /&gt;
&lt;br /&gt;
This permits buttons on MIDAS &amp;quot;custom&amp;quot; web pages to invoke RPC calls directly into user frontend programs, for example to turn hardware modules on or off.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= JSON support =&lt;br /&gt;
[[mjsonrpc#JSON general information|JSON]] support is provided with the [[Mhttpd.js|MIDAS Javascript Library]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Access to the MIDAS Menu buttons =&lt;br /&gt;
&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should follow the instructions there to include the standard MIDAS Menu buttons.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
Access to the standard MIDAS Menu buttons can be provided with HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags of the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt; --&amp;gt;&lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;cmd&amp;quot; value=&#039;&#039;&amp;lt;button-name&amp;gt;&#039;&#039; type=&amp;quot;submit&amp;quot; &amp;gt;}}&lt;br /&gt;
Valid values are the standard MIDAS Menu buttons, i.e. (Start, Pause, Resume, Stop, ODB, Elog, Alarms, History, Programs etc). The {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tags must be declared within enclosing HTML {{HtmlTag|tag=&amp;lt;form...&amp;gt;....&amp;lt;/form&amp;gt;}} tags (see above).&lt;br /&gt;
&lt;br /&gt;
The following HTML fragment shows the inclusion of three of the standard buttons, giving access to the Main Status, ODB and Messages pages :&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
 &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;/form&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
{{Html|text= &amp;lt;form name=&amp;quot;form1&amp;quot; method=&amp;quot;Get&amp;quot; action=&amp;quot;/CS/MyExpt&amp;amp;&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Status&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;ODB&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;input name=&amp;quot;cmd&amp;quot; value=&amp;quot;Messages&amp;quot; type=&amp;quot;submit&amp;quot;&amp;gt; &amp;lt;br&amp;gt; ...  &amp;lt;br&amp;gt; &amp;lt;/form&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
See also [[#Redirect]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Redirect =&lt;br /&gt;
&lt;br /&gt;
When buttons are included on a Custom Page, after pressing a button (e.g. the Start or Stop button) it may be desirable to return to the same custom page, rather than returning to the [[Status Page]].&lt;br /&gt;
&lt;br /&gt;
This can be done by including an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag with the attributes &#039;&#039;type&#039;&#039; set to &amp;quot;hidden&amp;quot; and &#039;&#039;name&#039;&#039; set to &amp;quot;redir&amp;quot;. This name (&amp;quot;redir&amp;quot;) is detected by [[Mhttpd]], causing a redirect to the specified custom link in the &#039;&#039;value&#039;&#039; attribute.&lt;br /&gt;
&lt;br /&gt;
For example, the following redirects the screen back to the custom page link   {{Odbpath|path=/Custom/my_custom_page&amp;amp;}} when buttons are pressed:&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;my_custom_page&amp;amp;&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
See also [[#Replace Status Page by a Custom page|Redirect when a Custom Page replaces the Status Page]].&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= CustomScript Buttons =&lt;br /&gt;
&lt;br /&gt;
[[/Customscript ODB tree#Customscript-button|CustomScript buttons]] can be provided on [[Custom Page|Custom Pages]]. These buttons are equivalent to optional &#039;&#039;&#039;script buttons&#039;&#039;&#039; on the [[Status Page]], which call a script to perform a particular action when the button is pressed. See [[/Script ODB tree#Script-button|script buttons]] for details. Customscript buttons can be set up through the [[/Customscript ODB tree]].&lt;br /&gt;
&lt;br /&gt;
Any key &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/CustomScript/test&amp;lt;/span&amp;gt; will appear as a customscript-button &lt;br /&gt;
&amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; on a custom page whose code includes an HTML {{HtmlTag|tag=&amp;lt;input...&amp;gt;}} tag of the form:&lt;br /&gt;
{{Html|text=&amp;lt;input type=submit name=customscript value=&amp;quot;test&amp;quot;&amp;gt;}}&lt;br /&gt;
where the action of the button &amp;lt;span style=&amp;quot;color: #444444; background-color: #CCCCCC; font-style:italic; font-size: 90; padding:0.25em;padding-left: 0.5em;padding-right: 0.5em;border:1px solid #808080;border-radius: 5px;margin-bottom:1px;&amp;quot;&amp;gt;test&amp;lt;/span&amp;gt; will be found in the &amp;lt;span style=&amp;quot;color:purple; font-style:italic&amp;quot;&amp;gt;/customscript/test&amp;lt;/span&amp;gt; subdirectory.&lt;br /&gt;
&lt;br /&gt;
After pressing a customscript-button, the &#039;&#039;type=submit&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} will cause a page reload. This will result in a switch to the [[Status Page]], unless a [[#redirect]] input tag is included to redirect back to the original custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Customscript button without a page reload ==&lt;br /&gt;
To define a customscript button that does &#039;&#039;&#039;not&#039;&#039;&#039; cause a page reload, set the &#039;&#039;type&#039;&#039; in the HTML {{HtmlTag|tag=&amp;lt;input&amp;gt;}} to &amp;quot;button&amp;quot;, i.e. &lt;br /&gt;
{{Html|text=&amp;lt;input name=&amp;quot;customscript&amp;quot; value=&amp;quot;test&amp;quot; type=&amp;quot;button&amp;quot; onClick=cs_button(this.value)&amp;gt;  &amp;lt;!-- Customscript button does not reload page; no callback --&amp;gt;}}&lt;br /&gt;
and use &#039;&#039;&amp;quot;onClick&amp;quot;&#039;&#039; to call a function to [[#Send an Ajax Request]] instead. The following function sends an asynchronous request, with an optional callback.&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;script&amp;gt;}}&lt;br /&gt;
 &amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function cs_button(cmd, callback)&amp;lt;br&amp;gt;&lt;br /&gt;
{  // send a request to execute a custom script&lt;br /&gt;
:   var url;&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
:&lt;br /&gt;
:   url = ODBUrlBase&lt;br /&gt;
:   if(document.getElementById(&amp;quot;redir&amp;quot;) != null)&lt;br /&gt;
::     url +=  &#039;?redir=&#039;+document.getElementById(&#039;redir&#039;).value&lt;br /&gt;
:   url+=&#039;&amp;amp;customscript=&#039;+ encodeURIComponent(cmd);&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true); // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
:  if(callback!=undefined) {&lt;br /&gt;
::      request.onreadystatechange = function() {&lt;br /&gt;
:::         if (request.readyState == 4) { &lt;br /&gt;
::::            if (request.status == 200)&lt;br /&gt;
:::::                callback();&lt;br /&gt;
::::            else&lt;br /&gt;
:::::                alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:::            }&lt;br /&gt;
::         }&lt;br /&gt;
:  }&lt;br /&gt;
:  else { &lt;br /&gt;
::   if (request.status != 200)&lt;br /&gt;
:::       alert(&#039;cs_button: Error after sending request to Custom Script &amp;quot;&#039;+cmd+&#039;&amp;quot; :\nHTTP Status: &#039;+request.status)&lt;br /&gt;
:	   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{HtmlTag|tag=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Resource files =&lt;br /&gt;
== MIDAS resource files ==&lt;br /&gt;
A number of resource files have been provided in the MIDAS packages under {{Filepath|path=../packages/midas/resources}}.  When including a MIDAS resource file, no key in  {{Odbpath|path=/Custom}} needs to be defined as the MIDAS resources directory is searched automatically. &lt;br /&gt;
&lt;br /&gt;
One such resource file is a condensed stylesheet {{File|name=midas.css}} for users who would like their custom pages to have a similar &amp;quot;look and feel&amp;quot; to that of the standard pages. To include the MIDAS stylesheet, in the HTML header, add &lt;br /&gt;
{{HtmlTag|tag=&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;}}&lt;br /&gt;
Other resource files can provide [[Custom Page#How to use the standard MIDAS navigation bars on your custom page|the standard MIDAS navigation bars]].&lt;br /&gt;
&lt;br /&gt;
== Custom resource files ==&lt;br /&gt;
It is often desirable to serve resource files (i.e. &#039;&#039;&#039;local files&#039;&#039;&#039; such as an external stylesheet, javascript files or images&#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039;) to a custom page. The following sections describe two alternative ways of serving resource files to a custom page:&lt;br /&gt;
* [[#Resource files served WITH /custom/path key defined|with  /custom/path key defined]]&lt;br /&gt;
* [[#Resource files served WITHOUT /custom/path key defined|without  /custom/path key defined]]&lt;br /&gt;
See also serving resources to a [[#Replace Status Page by a Custom Status page|Custom Status page]].&lt;br /&gt;
;NOTE&lt;br /&gt;
:  &#039;&#039;&#039;&amp;lt;sup&amp;gt;***&amp;lt;/sup&amp;gt;&#039;&#039;&#039; To superimpose labels, bars or fills onto an image, the image cannot be served as a resource file. See [[#Image insertion]].&lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITH &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
When a number of resource files are required, it is convenient to place them in the same directory on the disk, and create a key  {{Odbpath|path=/Custom/Path}} to contain this directory. &lt;br /&gt;
&lt;br /&gt;
 $ ls  /home/midas/online/custom/&lt;br /&gt;
   custom_functions.js       custom_globals.js     custom_page.html    custom_stylesheet.css      test_image.png&lt;br /&gt;
With the  {{Odbpath|path=/Custom/Path}} key defined, [[/Custom ODB tree#Custom-Link|custom-links]] for individual resource files need not be created in the  {{Odbpath|path=/Custom/Path}} tree. The custom-link  {{Odbpath|path=custom_page&amp;amp;}} &#039;&#039;&#039;is&#039;&#039;&#039; required so that the custom page can be accessed from the [[/Custom ODB tree#Custom-Button|custom-button]]  {{Button|name=custom_page}} on the Status Page. The key name ends in the special character &amp;quot;&amp;amp;&amp;quot; so that it will open in the same window as the Status page (see [[/Custom ODB tree#Key names|key names]]).&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   path                           /home/midas/online/custom/&lt;br /&gt;
   custom_page&amp;amp;                   custom_page.html&lt;br /&gt;
 &lt;br /&gt;
A {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the file name) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_globals.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;custom_stylesheet.css&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;test_image.png&amp;quot;&amp;gt; }}&lt;br /&gt;
[[mhttpd]] then loads the resource file from the directory indicated by the Path key with the correct MIME type (see  [[/Custom ODB tree#Keys in the /Custom tree|Custom tree keys]] for supported MIME types). &lt;br /&gt;
&lt;br /&gt;
=== Resource files served WITHOUT &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/custom/path&amp;lt;/span&amp;gt; key defined ===&lt;br /&gt;
Alternatively, resource files can be served to a [[Custom Page]] by creating a key for every resource file in the [[/Custom ODB tree]]. These keys must contain the full path of the file on the disk, and the key {{Odbpath|path=/Custom/Path}} must NOT be defined. The resource files can be in different directories on the disk. By defining the key names with an appropriate file extension, the resource files are served with the appropriate MIME types. The key names end in the special character &amp;quot;!&amp;quot; so that they will not appear on the Status page as custom-links (see [[/Custom ODB tree#Key names|key names]].&lt;br /&gt;
&lt;br /&gt;
Without the {{Odbpath|path=/Custom/Path}} key,  the links for the above example in the {{Odbpath|path=/Custom}} tree might look like&lt;br /&gt;
 $ odbedit&lt;br /&gt;
   [local:exp:S] ls /custom&lt;br /&gt;
   custom_page&amp;amp;                   /home/midas/online/custom/custom_page.html&lt;br /&gt;
   custom_functions.js!           /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   globals.js!                    /home/midas/online/resources/custom_functions.js&lt;br /&gt;
   stylesheet.css!                /home/midas/online/stylesheets/custom_stylesheet.css&lt;br /&gt;
   image.png!                     /home/midas/images/test_image.png  &lt;br /&gt;
&lt;br /&gt;
assuming the resource files are now in the subdirectories indicated.&lt;br /&gt;
The {{HtmlTag|tag=&amp;lt;script&amp;gt;}} tag for each resource file to be served (specifying the custom-link) is then placed in the header of  {{File|name=custom_page.html}} &lt;br /&gt;
i.e.&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;custom_functions.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;globals.js!&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&amp;lt;br&amp;gt; and a {{HtmlTag|tag=&amp;lt;link&amp;gt;}} tag for an external stylesheet&lt;br /&gt;
{{Html|text=&amp;lt;link type=&amp;quot;text/css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;stylesheet.css!&amp;quot; title=&amp;quot;Stylesheet&amp;quot;&amp;gt;}}&lt;br /&gt;
An image tag is placed in the body of  {{File|name=custom_page.html}} &lt;br /&gt;
{{Html|text=&amp;lt;img src=&amp;quot;image.png!&amp;quot;&amp;gt; }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The resulting Demo custom page is shown in Figure 1, which can be compared with Figure 2 (no stylesheet). &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Figure 1  !! Figure 2&lt;br /&gt;
|-&lt;br /&gt;
| Demo Custom Page using MIDAS stylesheet || Demo Custom Page&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
| [[File:Mhxcustom03.jpg|thumb|300px]] || [[File:Mhxcustom02.jpg|thumb|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=  Alias-Buttons and Hyperlinks =&lt;br /&gt;
Any hyperlink can easily be included on a [[Custom Page]] by using the standard HTML anchor {{HtmlTag|tag=&amp;lt;a...&amp;gt;}} tag, e.g.&lt;br /&gt;
{{Html|text=&amp;lt;a href=&amp;quot;http://ladd00.triumf.ca/~daqweb/doc/midas/html/&amp;quot;&amp;gt;Midas Help&amp;lt;/a&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Links on a custom page equivalent to [[/Alias ODB tree#Alias-Buttons|alias-buttons]] can also be made e.g.&lt;br /&gt;
{{Html|text=&amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.location.href=&#039;/Alias/alias&amp;amp;&#039;;&amp;quot;&amp;gt;alias&amp;lt;/button&amp;gt;}}&lt;br /&gt;
See the [[/Alias ODB tree]] for details.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Page refresh =&lt;br /&gt;
The following {{HtmlTag|tag=&amp;lt;meta...&amp;gt;}} tag included in the HTML header code will cause the whole custom page to refresh in 60 seconds :&lt;br /&gt;
{{Html|text=&amp;lt;meta http-equiv=&amp;quot;Refresh&amp;quot; content=&amp;quot;60&amp;quot;&amp;gt;}}&lt;br /&gt;
It is also possible to [[#Periodic update of parts of a custom page|periodically update parts]] of a custom page.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Periodic update of parts of a custom page =&lt;br /&gt;
&lt;br /&gt;
The functionality of [[Mhttpd.js|ODBGet]] together with the window.setInterval() function&lt;br /&gt;
can be used to update parts of the web page periodically.&lt;br /&gt;
For example the Javascript fragment below contains a function which updates the current run number every 10 seconds in the background:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt; &amp;lt;!-- JS --&amp;gt;&lt;br /&gt;
window.setInterval(&amp;quot;Refresh()&amp;quot;, 10000);&amp;lt;br&amp;gt;&lt;br /&gt;
function Refresh() {&amp;lt;br&amp;gt;&lt;br /&gt;
:document.getElementById(&amp;quot;run_number&amp;quot;).innerHTML = ODBGet(&#039;/Runinfo/Run number&#039;);&amp;lt;br&amp;gt;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
The custom page has to &lt;br /&gt;
* [[Mhttpd.js#include js lib|include the MIDAS JS library]] to access ODBGet&lt;br /&gt;
* contain an element with id=&amp;quot;run_number&amp;quot;, such as&lt;br /&gt;
{{Html|text=&amp;lt;td id=&amp;quot;run_number&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Display last MIDAS message(s) =&lt;br /&gt;
&lt;br /&gt;
The message log (see [[Message System]]) can be accessed from a custom page using a call to the JavaScript library function [[Mhttpd.js|ODBGetMsg]] (provided the [[Mhttpd.js#include js lib|JS library is included]]).&lt;br /&gt;
&lt;br /&gt;
This allows the inclusion of the &amp;quot;Last Midas message&amp;quot; on a custom page, e.g.&lt;br /&gt;
{{JS|text=document.write(&#039;Last message:&#039;+ODBGetMsg(&amp;quot;midas&amp;quot;,0,1))}}&lt;br /&gt;
More messages may be displayed by increasing the third parameter to ODBGetMsg.&lt;br /&gt;
;Note&lt;br /&gt;
: Parameters were changed August 2015. See [[Mhttpd.js|ODBGetMsg]] for older versions.&lt;br /&gt;
: Coming soon - a [[mjsonrpc]] function&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Generate a message log entry =&lt;br /&gt;
&lt;br /&gt;
A custom page can generate a message to be sent to the MIDAS message log (see [[Message System]]).  A call to mjsonrpc_cm_msg() will generate a message if using the [[mjsonrpc]] functions. Otherwise, use the AJAX function [[Mhttpd.js|ODBGenerateMsg]]. To use these functions, the  [[Mhttpd.js#include js lib|JS library must be included]] in the html code.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=   Checkboxes =&lt;br /&gt;
;NOTE&lt;br /&gt;
: &#039;&#039;&#039;New  (since 2018) custom pages using the [[Custom Page#modb* Javascript scheme]] should use [[Custom Page#modbcheckbox|modbcheckbox]] to create a checkbox.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;Br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function [[Mhttpd.js|ODBSet]] (provided the [[Mhttpd.js#include js lib|JS library is included]]) can be used when one clicks on a checkbox for example:&lt;br /&gt;
{{Html|text=&amp;lt;input  name=&amp;quot;box0&amp;quot;  type=&amp;quot;checkbox&amp;quot;  onClick=&amp;quot;ODBSet(my_path, this.checked?&#039;1&#039;:&#039;0&#039;)&amp;quot;&amp;gt;}}&lt;br /&gt;
If used as above, the state of the checkbox must be initialized when the page is loaded. This can be done with some JavaScript code called on initialization, e.g.&lt;br /&gt;
{{JS|text=document.form1.box0.checked= ODBGet(my_path));  // initialize to the correct value}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Replace Status Page by a Custom page =&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_status.jpg|thumbnail|left|Figure 3: ODB /Custom/Status custom-link to a custom status page]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a custom-link with the [[/Custom ODB tree#Key names|reserved key name]] &#039;&#039;&#039;Status&#039;&#039;&#039;  (not &amp;quot;Status&amp;amp;&amp;quot; or &amp;quot;Status!&amp;quot;) is created in the [[/Custom ODB tree]] (as shown in  Figure 3), then that custom page will &#039;&#039;&#039;replace the default Status Page&#039;&#039;&#039;. &lt;br /&gt;
  &lt;br /&gt;
Clicking on the {{Button|name=Status}} button on any of the sub-pages (e.g. [[ODB Page]], [[Programs Page]] etc.) will now return to the Custom Status Page. If there are buttons on the Custom Status page, you &#039;&#039;&#039;must&#039;&#039;&#039; include a  [[#Redirect]] statement of the form&lt;br /&gt;
{{Html|text=&amp;lt;input type=hidden name=&amp;quot;redir&amp;quot; value=&amp;quot;../&amp;quot;&amp;gt;}}&lt;br /&gt;
or you will see the message &lt;br /&gt;
 Invalid custom page:NULL path&lt;br /&gt;
&lt;br /&gt;
If the Custom Status page includes [[#Resource files]] served on a regular custom page with a statement such as&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
to serve them in a Custom Status page, the statement would be&lt;br /&gt;
{{Html|text=&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;&amp;lt;span style=&amp;quot;font-weight:bold&amp;quot;&amp;gt;/CS/&amp;lt;/span&amp;gt;cs_functions!&amp;quot;&amp;gt;}}&lt;br /&gt;
In fact, this statement can be used in a regular custom page, as the &amp;quot;/CS/&amp;quot; is ignored in that case.&lt;br /&gt;
&lt;br /&gt;
To return to the default Status Page, delete the &amp;lt;span style=&amp;quot;color: purple; font-style:italic;&amp;quot;&amp;gt;/Custom/Status&amp;lt;/span&amp;gt; key. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Start, Stop and Check if a program is running =&lt;br /&gt;
There are [[mjsonrpc]] functions that implemented all three program management functions - start program, &lt;br /&gt;
stop program and &amp;quot;is running?&amp;quot; as JSON-RPC methods.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
= Send an Ajax request =&lt;br /&gt;
By sending an Ajax request from a custom page, you can make a button perform a specific function. &lt;br /&gt;
  &lt;br /&gt;
All functions in midas are controlled through special URLs. So the URL&lt;br /&gt;
 http://&amp;lt;host:port&amp;gt;/?cmd=Start&amp;amp;value=10&lt;br /&gt;
will start run #10.&lt;br /&gt;
&lt;br /&gt;
Although it is easier to use an HTML input statement to [[#Access to the MIDAS Menu buttons]], &lt;br /&gt;
to send an Ajax request, you can use the function &#039;&#039;XMLHttpRequestGeneric&#039;&#039; which is in the MIDAS Javascript library [[mhttpd.js]].&lt;br /&gt;
Then the HTML code would be&lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;input type=&amp;quot;button&amp;quot; onclick=&amp;quot;start()&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
and in your JavaScript code add a function &#039;&#039;start()&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
function start()&lt;br /&gt;
{&lt;br /&gt;
:   var request = XMLHttpRequestGeneric();&lt;br /&gt;
&lt;br /&gt;
:   url = &#039;?cmd=Start&amp;amp;value=10&#039;;&lt;br /&gt;
:   request.open(&#039;GET&#039;, url, true);  // asynchronous request&lt;br /&gt;
:   request.send(null);&lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
See also [[#Access to the MIDAS Menu buttons]]. Another example with optional callback can be found in [[#Customscript button without a page reload]].&lt;br /&gt;
&lt;br /&gt;
This mechanism can be used for starting a particular program, see for example (see [https://midas.triumf.ca/elog/Midas/1046]). However, this functionality is now provided by [[mjsonrpc]] functions - see [[#Start, Stop and Check if a program is running]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=    Image insertion  =&lt;br /&gt;
An image can be loaded from the web using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}}, e.g.  &lt;br /&gt;
   {{Html|text=&amp;quot;TRIUMF logo&amp;quot; &amp;lt;img src=&amp;quot;https://lixenon.triumf.ca/InternalDocuments/Alice/figures/TRIUMF-logo.jpg/image_preview&amp;quot;&amp;gt;}}&lt;br /&gt;
or a MIDAS History image can be inserted into a custom page using an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag of the following form:&lt;br /&gt;
 {{Html|text=blah&amp;lt;img src=&amp;quot;http://hostname.domain:port/HS/Meterdis.gif&amp;amp;scale=12h&amp;amp;width=300&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
If the image file is on the local disk, it can be loaded as a &#039;&#039;&#039;resource file&#039;&#039;&#039;. The image can be of format .pdf, .jpg, .gif, .png. See loading [[#Resource files]].&lt;br /&gt;
&lt;br /&gt;
If you wish to superimpose features such as &#039;&#039;&#039;labels and fills&#039;&#039;&#039;, the image file cannot be served as a [[#Resource files|resource file]]. Instead, the image file must be in &#039;&#039;&#039;gif&#039;&#039;&#039; format, and must be included in the {{Odbpath|path=/Custom/images}} subtree (Figure 4). Image insertion into a Custom page will be illustrated using the Demo custom page shown in [[#MIDAS stylesheet|Figure 2]]. All the files required for this demo can be found in the MIDAS package at $MIDASSYS/examples/custom. If you do not wish to create&lt;br /&gt;
the keys yourself, proceed to [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
Make a [[/Custom ODB tree#Custom-Link|custom-link]] to the Demo custom page file &#039;&#039;myexpt.html&#039;&#039; , i.e.&lt;br /&gt;
 [local:js:S]/&amp;gt;ls /custom&lt;br /&gt;
    myexpt&amp;amp;                  /home/test/packages/midas/examples/custom/myexpt.html&lt;br /&gt;
 &lt;br /&gt;
To make the image &#039;&#039;myexpt.gif&#039;&#039; visible on the custom page, the path and filename of the image file must be defined in the   {{Odbpath|path=/Custom/images}} subtree. To do this, &lt;br /&gt;
create the  subtrees {{Odbpath|path=/Custom/images/myexpt.gif}} where the subtree name &amp;quot;myexpt.gif&amp;quot; is named for the image file you are going to use. Multiple images can be used, by creating multiple imagefile subtrees.&lt;br /&gt;
&lt;br /&gt;
In the imagefile subtree {{Odbpath|path=myexpt.gif}}, create the STRING key  {{Odbpath|path=Background}}, and set it to contain the path and name of the image file.  The tree structure should then look similar to Figure 4, minus the labels/bars/fill subtrees which will be added to the ODB later.&lt;br /&gt;
&lt;br /&gt;
The image must also be referenced in the custom HTML file &#039;&#039;myexpt.html&#039;&#039; in the &amp;quot;src&amp;quot; field of an HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag, e.g. &lt;br /&gt;
{{Html|text=&amp;lt;img &#039;&#039;&#039;src=&amp;quot;myexpt.gif&amp;quot;&#039;&#039;&#039;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
Click to enlarge thumbnail&lt;br /&gt;
[[File:Mhcustom_images.jpg|thumbnail|left|Figure 4: /Custom/Images ODB Tree]]&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
Once the image is visible, enable [[#HTML mapping]], optionally [[#Display mouse position]] and proceed to  [[#Superimposing Labels, Bars and Fills onto a gif image]]. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== HTML mapping ==&lt;br /&gt;
Note that if additional features such as active clickable areas and labels, bars and fills superimposed on the image are also required, HTML mapping must also be activated with the HTML {{HtmlTag|tag=&amp;lt;map...&amp;gt;}} tag and the &amp;quot;usemap&amp;quot; attribute of the HTML {{HtmlTag|tag=&amp;lt;img&amp;gt;}} tag&lt;br /&gt;
{{Html|text=   &amp;lt;map &#039;&#039;&#039;name=&amp;quot;myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt; &amp;lt;img src=&amp;quot;myexpt.gif&amp;quot; &#039;&#039;&#039;usemap=&amp;quot;#myexpt.map&amp;quot;&#039;&#039;&#039;&amp;gt; &amp;lt;br&amp;gt;...&amp;lt;br&amp;gt; &amp;lt;/map&amp;gt;&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Display mouse position ==&lt;br /&gt;
[[File:Cursor.png|thumbnail|left|Figure 5: MEG Gas System Custom Page showing cursor position]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When writing custom pages with large background images and labels and fills placed on that image, it is hard to figure out X and Y coordinates of the labels. This can now be simplified by using the function &#039;&#039;getMouseXY()&#039;&#039; in the development JavaScript built-in library [[develop.js]]. This function supplies the X,Y position of the cursor if an element of ID &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; is present. This JS library &#039;&#039;&#039;must be [[develop.js|included in the custom page]]&#039;&#039;&#039; in order to use it:&lt;br /&gt;
&lt;br /&gt;
Then, set the &amp;quot;id&amp;quot; attribute of the background HTML {{HtmlTag|tag=&amp;lt;img...&amp;gt;}} tag to &amp;quot;refimg&amp;quot;, e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: &amp;lt;img  &#039;&#039;&#039;id=&amp;quot;refimg&amp;quot;&#039;&#039;&#039; src=&amp;quot;ebit_pc.gif&amp;quot; usemap=&amp;quot;#Custom1&amp;quot;&amp;gt;   &amp;lt;!-- name=&amp;quot;refimg&amp;quot; makes crosshairs appear --&amp;gt;&lt;br /&gt;
: &amp;lt;map name=&amp;quot;Custom1&amp;quot;&amp;gt;&lt;br /&gt;
: .....&lt;br /&gt;
: &amp;lt;/map&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;quot;&#039;&#039;&#039;refimg&#039;&#039;&#039;&amp;quot; tag is present, the cursor changes into a crosshair, and its absolute and relative locations in respect to the reference image are shown in the status bar (Figure 5).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
==  Superimposing Labels, Bars and Fills onto a gif image ==&lt;br /&gt;
You can enhance your custom page by superimposing multiple features based on ODB variables onto an image (e.g. [[#Display mouse position|Figure 5]]), such as&lt;br /&gt;
&lt;br /&gt;
*    labels: &amp;quot;live&amp;quot; ODB values positioned in a particular location of the page&lt;br /&gt;
*    bars : &amp;quot;bar level&amp;quot; showing graphically ODB values such as levels or rate etc.&lt;br /&gt;
*    fills : &amp;quot;color level&amp;quot; where colour is used as the level indicator.&lt;br /&gt;
&lt;br /&gt;
Each entry (label/bar/fill) will have an ODB tree associated to it defining the ODB variable path, X/Y position, colour, etc. Each time the page is updated, the latest ODB value/level/rate will be shown based on the ODB parameter to which the label, bar or fill is linked - hence the term &amp;quot;live&amp;quot;. The overlay of the requested features is done onto the selected image file.&lt;br /&gt;
&lt;br /&gt;
This powerful new extension brings the [[mhttpd]] capability closer to other experimental web controllers similar to EPICS.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
:    Be sure to enable the feature to [[#Display mouse position]] in order to facilitate finding the X,Y positions of the various features.&lt;br /&gt;
: [[#HTML mapping]] must be activated for labels/bars/fills to work&lt;br /&gt;
&lt;br /&gt;
A Demo custom page showing labels, bars and fills superimposed on an image is shown in [[#MIDAS stylesheet|Figure 2]]. &lt;br /&gt;
All the files for this demo can be found in $MIDASSYS/examples/custom/. The file xcustom.odb contains the ODB keys required, including those to insert the image and superimpose the various labels, fills etc. This file can be loaded into the ODB with the  {{Odbedit cmd|cmd=load}}.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Labels ===&lt;br /&gt;
&lt;br /&gt;
 [[File:Mhcustom_label.jpg|thumbnail|left|Figure 6: /Custom/Images/Labels ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In order to include a readout of ODB values (i.e. labels), on the image, a further ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Labels}} must be created. Creating a subdirectory for a particular label i.e. {{Odbpath|path=&amp;lt;label name&amp;gt;}} in the   {{Odbpath|path=Labels}} subtree will, at the next custom web page refresh, cause the complete structure for that label to be created and filled with default values. Once the {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is created, the user fills the various keys as desired. See [[/Custom ODB tree#Labels subtree]] for details of the various fields. This procedure is repeated for all the labels required, using a unique {{Odbpath|path=&amp;lt;label name&amp;gt;}} subdirectory for each label. An example of a {{Odbpath|path=&amp;lt;label name&amp;gt;}} subtree is shown in Figure 6. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Bars ===&lt;br /&gt;
 &lt;br /&gt;
[[File:Mhcustom_fill.jpg|thumbnail|left|Figure 7: /Custom/Images/Fills ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Bars can be superimposed on the image. Create  a new ODB subdirectory  {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Bars}}. Create a subdirectory for a particular Bar ({{Odbpath|path=&amp;lt;bar name&amp;gt;}}) in the {{Odbpath|path=Bars}}  subdirectory. Refresh the web page and fill the various keys as desired.  See [[/Custom ODB tree#Bars subtree]] for details of the various fields.  Examples of a &amp;lt;bar-name&amp;gt; subtree is shown in Figure 7.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
=== Adding Fills ===&lt;br /&gt;
[[File:Mhcustom_bar.jpg|thumbnail|left|Figure 8: /Custom/Images/Bars ODB subtree]]&lt;br /&gt;
&lt;br /&gt;
In a similar way, Fills can be superimposed on the image. Create new ODB subdirectory   {{Odbpath|path=/Custom/images/&amp;lt;imagefile.gif/Fills}}. Create a subdirectory for a particular  Fill ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}) in the {{Odbpath|path=Fills}} subdirectory. Refresh the web page and fill the various keys as desired.  See  [[/Custom ODB tree#Fills subtree]] for details of the various fields.  Examples of a ({{Odbpath|path=&amp;lt;fill name&amp;gt;}}  subtree is shown in Figure 8. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Mapping active areas onto the image ==&lt;br /&gt;
Provided [[#HTML mapping]] is activated, &amp;quot;clickable&amp;quot; areas can be created on the image.&lt;br /&gt;
&lt;br /&gt;
This can be done now with a new function like this:&lt;br /&gt;
{{Html|text= &amp;lt;area shape=&amp;quot;rect&amp;quot; coords=&amp;quot;40,200,100,300&amp;quot; alt=&amp;quot;Main Valve&amp;quot; href=&amp;quot;Custom1?cmd=Toggle&amp;amp;odb=/Equipment/Environment/Variables/Output[2]&amp;quot;&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
This defines a clickable map on top of the custom image. The area(s) should match with some area(s) on the image, e.g. the box of a valve. Determining the co-ordinates of this area is simplified by using the Display mouse position feature.&lt;br /&gt;
&lt;br /&gt;
By clicking on this area, the supplied path to the ODB is used (in this case  {{Odbpath|path=/Equipment/Environment/Variables/Output[2]}}) and its value is toggled. If the valve value is then used in the image via a [[#Adding Fills|Fill]] statement to change the color of the valve, it can turn green or red depending on its state. This is illustrated in [[#Display mouse position|Figure 5]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Clicking an active area can also be made to open a new custom page, for example:&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;687,530, 890,648&amp;quot; alt=&amp;quot;Pump detail&amp;quot; href = &amp;quot;Pump!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
{{Html|text=&amp;lt;area shape=rect coords=&amp;quot;560,574,775,662&amp;quot; alt=&amp;quot;Buffer Tank detail&amp;quot; href = &amp;quot;BufferTank!&amp;quot; title=&amp;quot;Click for Details&amp;quot;&amp;gt;}}&lt;br /&gt;
where &#039;&#039;Pump!&#039;&#039; and &#039;&#039;BufferTank!&#039;&#039; are defined as links to custom pages in the [[/Custom ODB tree]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Edit boxes floating on top of a graphic ==&lt;br /&gt;
&lt;br /&gt;
An edit box can be placed on top of a graphic in a particular position by means of an HTML  &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/span&amp;gt; tag. Using the ODBEdit function from the Midas JS library [[Mhttpd.js]], the custom page code would look like this:&lt;br /&gt;
&amp;lt;!-- Complicated... have to use &amp;lt;pre&amp;gt; because of &amp;lt;div&amp;gt;, then a table to keep the background colour between &amp;lt;pre&amp;gt;s --&amp;gt;&lt;br /&gt;
{|  style=&amp;quot;text-align: left; width: 100%; background-color: floralwhite;&amp;quot; border=&amp;quot;0&amp;quot; cellpadding=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;    &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
document.write(&#039;Run number: &#039;)&lt;br /&gt;
path=&#039;/runinfo/run number&#039;&lt;br /&gt;
rn = ODBGet(path)&lt;br /&gt;
document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(path)&amp;quot; &amp;gt;&#039;)  &lt;br /&gt;
document.write(rn)&lt;br /&gt;
document.write(&#039;&amp;lt;/a&amp;gt;&#039;);&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The same thing could be done with the HTML-style {{HtmlTag|tag=&amp;lt;odb&amp;gt;}} tag :&lt;br /&gt;
&amp;lt;pre style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;position:absolute; top:100px; left:50px;&amp;quot;&amp;gt;&lt;br /&gt;
Run number:  &amp;lt;odb src=&amp;quot;/Runinfo/run number&amp;quot; edit=1&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;custom.gif&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Interactive SVG Implementation ==&lt;br /&gt;
&lt;br /&gt;
An SVG file loaded on a custom page can be interactively modified using JavaScript.&lt;br /&gt;
This is useful for graphically displaying ODB values in complex service or detector systems.&lt;br /&gt;
&lt;br /&gt;
An SVG file can be embedded like this:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Objects within the SVG can be modified—for example, by changing their fill color as shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
const svgDoc = document.getElementById(&#039;svgObject&#039;).contentDocument; &lt;br /&gt;
const obj_Valve = svgDoc.getElementById(valveID); &lt;br /&gt;
obj_Valve.style.fill = &amp;quot;#00FF00&amp;quot;; &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SVG images are stored in XML format. In Inkscape, object properties can be interactively edited using the XML Editor.&lt;br /&gt;
&lt;br /&gt;
= Examples =&lt;br /&gt;
For more examples of accessing the ODB with the Javascript library [[mhttpd.js]] look at the example experiment in the MIDAS package $MIDASSYS/examples/javascript1/example.html.&lt;br /&gt;
&lt;br /&gt;
== Example 1 : Asynchronous calls using mjsonrpc functions ==&lt;br /&gt;
See [[mjsonrpc#examples]]&lt;br /&gt;
&lt;br /&gt;
== Example 2 : Asynchronous calls using Javascript and AJAX ==&lt;br /&gt;
&lt;br /&gt;
Example 2 shows html code illustrating the use of ODBMCopy(), where innerHTML is used to display the data. On a page with images or a lot of data, innerHTML allows you to update variables without having to reload the whole page. In the example, a timer causes the page to update every 10s (reread the data). Often the ODB data for the whole page can be read by one asynchronous ODBMCopy() call, rather than scattering synchronous ODBGet() calls throughout the page.  &lt;br /&gt;
&lt;br /&gt;
Access to the ODB is provided by the  [[Mhttpd.js|MIDAS Javascript Library]], included with the line &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;&amp;amp;lt;script src=&#039;mhttpd.js&#039;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt;&amp;lt;/span&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;title&amp;gt;MyTitle&amp;lt;/title&amp;gt; &amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;script src=&#039;mhttpd.js&#039;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
var updatePeriod = 10000; // in msec &amp;lt;br&amp;gt;&lt;br /&gt;
var updateTimerId = 0; &amp;lt;br&amp;gt;&lt;br /&gt;
var counter=0; &amp;lt;br&amp;gt;&lt;br /&gt;
function update()  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:clearTimeout(updateTimerId); &amp;lt;br&amp;gt;&lt;br /&gt;
:      load(); &lt;br /&gt;
:      if (updatePeriod &amp;gt; 0) &lt;br /&gt;
:      updateTimerId = setTimeout(&#039;update()&#039;, updatePeriod); &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function load()  { &amp;lt;br&amp;gt;&lt;br /&gt;
: document.getElementById(&#039;LastUpdated&#039;).innerHTML = &amp;quot;Updating...&amp;quot; + new Date; &lt;br /&gt;
: var paths = [ &amp;quot;/Runinfo&amp;quot;, &amp;quot;/Experiment&amp;quot;]; &lt;br /&gt;
: var data_odb=ODBMCopy(paths, mcopy_callback, &amp;quot;json&amp;quot;) &lt;br /&gt;
: counter++; &lt;br /&gt;
: document.getElementById(&#039;counter&#039;).innerHTML = &#039;Counter: &#039;+ counter &lt;br /&gt;
} &amp;lt;br&amp;gt;&lt;br /&gt;
function mcopy_callback(data)  {  &amp;lt;br&amp;gt;&lt;br /&gt;
:    var obj= JSON.parse(data);&lt;br /&gt;
: var runinfo=obj[0];&lt;br /&gt;
: document.getElementById(&#039;rn&#039;).innerHTML = &#039;Run Number =&#039;+ parseInt(runinfo[&amp;quot;Run number&amp;quot;]);&lt;br /&gt;
: document.getElementById(&#039;state&#039;).innerHTML  =&#039;Run State= &#039;+ runinfo.State;&lt;br /&gt;
: var experiment=obj[1];&lt;br /&gt;
: document.getElementById(&#039;name&#039;).innerHTML=&#039;Experiment name = &#039;+ experiment.Name &amp;lt;br&amp;gt; &lt;br /&gt;
}&amp;lt;br&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/head&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;body&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;b&amp;amp;gt;Javascript code using ODBMCopy with callback&amp;amp;lt;/b&amp;amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
The data on the page is updated every 10 sec using a timer&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;LastUpdated&amp;quot; &amp;amp;gt; Last updated: never&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;rn&amp;quot;&amp;amp;gt; Run Number : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;state&amp;quot;&amp;amp;gt; State : unknown&amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;name&amp;quot;&amp;amp;gt; Experiment name : unknown  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;amp;lt;p id=&amp;quot;counter&amp;quot;&amp;amp;gt;Counter: zero  &amp;amp;lt;/p&amp;amp;gt;&amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt; &amp;lt;/div&amp;gt;  &lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:steelblue; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
: if (updatePeriod &amp;gt; 0)&lt;br /&gt;
:  update(); &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;background-color:floralwhite; color:seagreen; font-style:italic&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/script&amp;gt; &amp;lt;br&amp;gt; &lt;br /&gt;
&amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
== Example 3 : Synchronous Calls using Javascript and AJAX ==&lt;br /&gt;
In the following example, JS functions ODBGet and  ODBEdit from the [[Mhttpd.js|MIDAS Javascript Library]] are used to access the ODB.  &lt;br /&gt;
;NOTE&lt;br /&gt;
# Synchronous calls are now deprecated (see [[#The MIDAS Javascript Library|above]]).&lt;br /&gt;
# The Javascript library &#039;&#039;&#039;must be [[mhttpd.js#include js lib|included in the HTML code]]&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
{{Html|text=&amp;lt;script&amp;gt;}}&lt;br /&gt;
{{JS|text=document.write (&#039;Experiment Name: &#039;+ ODBGet(&amp;quot;/Experiment/Name&amp;quot;)) &amp;lt;br&amp;gt; var alarm_path=&amp;quot;/alarms/Alarm system active&amp;quot;; &amp;lt;br&amp;gt; var alarm_active=ODBGet(alarm_path); &amp;lt;br&amp;gt; document.write(&#039;&amp;lt;a href=&amp;quot;#&amp;quot; onclick=&amp;quot;ODBEdit(alarm_path)&amp;quot; &amp;gt;&#039;+alarm_active+&#039;&amp;lt;/a&amp;gt;&#039;)}}&lt;br /&gt;
{{Html|text=&amp;lt;/script&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
	<entry>
		<id>https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3516</id>
		<title>Custom Page</title>
		<link rel="alternate" type="text/html" href="https://daq00.triumf.ca/MidasWiki/index.php?title=Custom_Page&amp;diff=3516"/>
		<updated>2025-05-12T11:57:06Z</updated>

		<summary type="html">&lt;p&gt;Rudzki: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Pagelinks}}&lt;br /&gt;
&lt;br /&gt;
= Links =&lt;br /&gt;
{{mhttpdpages3|[[Custom Page Features]]|[[/Custom ODB tree]]|[[Mhttpd.js|MIDAS Javascript library]]}}&lt;br /&gt;
&lt;br /&gt;
= Purpose =&lt;br /&gt;
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]].&lt;br /&gt;
&lt;br /&gt;
= Introduction =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
= Examples of Custom Pages =&lt;br /&gt;
&lt;br /&gt;
Click on the thumbnails to enlarge.&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Capture_sgas.png|thumb|left|Figure 1: MEG Gas System]] || &#039;&#039;&#039;Example 1&#039;&#039;&#039;&lt;br /&gt;
This page (Figure 1) from the MEG experiment at PSI shows a complex gas system. This shows the use of &amp;quot;fills&amp;quot; and &amp;quot;labels&amp;quot;. 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).&lt;br /&gt;
|-&lt;br /&gt;
| [[File:custom_ROOT_analyzer_page.png|thumb|left|Figure 2: ROOT Analyzer (MEG Experiment)]] || &#039;&#039;&#039;Example 2&#039;&#039;&#039;&lt;br /&gt;
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 &amp;quot;virtual&amp;quot; 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.&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For details using Xvfb server, please contact Ryu Sawada &amp;lt;sawada@icepp.s.u-tokyo.ac.jp&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| [[File:deap_custom_scb.png|thumb|left|Figure 3: SCB Setup (Deap Experiment)]] || &#039;&#039;&#039;Example 3&#039;&#039;&#039;&lt;br /&gt;
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.   &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Access a Custom Page from the Regular MIDAS pages =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
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.&lt;br /&gt;
See [[Custom Page Features#Resource files]] for more information.&lt;br /&gt;
&lt;br /&gt;
If the key  {{Odbpath|path=/Custom/myPage&amp;amp;}}  (see Note) is created, e.g.&lt;br /&gt;
 odbedit&amp;gt; ls /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
&lt;br /&gt;
the custom link on the left navigation bar will be &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; and the URL for the resulting custom page will be of the form &amp;lt;code&amp;gt;http://myhost.mydomain:myport/cmd=?Custom&amp;amp;page=myPage&amp;lt;/code&amp;gt; (see also [[mhttpd#usage]]).  &lt;br /&gt;
Clicking on &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; will display the custom page in the same window.&lt;br /&gt;
&lt;br /&gt;
;Note&lt;br /&gt;
: With the &amp;quot;&amp;amp;&amp;quot; symbol in the key name, the page would appear in a new tab/window. See [[/Custom ODB tree#Key names|Key names]] for more information.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
 odbedit&amp;gt; ls -r /Custom&lt;br /&gt;
    Path                              /home/expt/online/custom&lt;br /&gt;
    myPage&amp;amp;                           mypage.html&lt;br /&gt;
    Calorimeter&lt;br /&gt;
        HV                            hv.html&lt;br /&gt;
        Rates                         rates.html&lt;br /&gt;
    Baeam&lt;br /&gt;
        Beamline                      beamline.html&lt;br /&gt;
        Accelerator                   accel.html&lt;br /&gt;
&lt;br /&gt;
The pages then include the subdirectory in the URL, like &lt;br /&gt;
&lt;br /&gt;
 http://localhost:8080?cmd=custom&amp;amp;page=beam/Beamline&lt;br /&gt;
&lt;br /&gt;
Subdirectories can contain nested subdirectories. Please make sure that you specify the full path in your mhttpd_init() call, such as&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Calorimeter/HV&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
in order to keep the submenu open after you select the custom page.&lt;br /&gt;
&lt;br /&gt;
= How to write a custom page =&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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&lt;br /&gt;
&lt;br /&gt;
* modb* functions hide details about the underlying MjsonRPC calls, which should allow a user to write pretty and sophisticated custom pages quickly and cleanly.&lt;br /&gt;
* 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.&lt;br /&gt;
* modb* functions encapsulate the underlying communication. Should the communication change in the future, the custom pages do not have to be changed.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== How to use the standard MIDAS navigation bars on your custom page == &lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;myPage&amp;lt;/title&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;myPage&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_start --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
 ADD YOUR HTML/JS  CODE here...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call &amp;lt;code&amp;gt;mhttpd_init(&#039;myPage&#039;)&amp;lt;/code&amp;gt; is executed when the page is loaded, and  &amp;lt;code&amp;gt;myPage&amp;lt;/code&amp;gt; is the name of the page shown on the left menu bar. This corresponds to an ODB entry /Custom/myPage.&lt;br /&gt;
This pattern will allow you to use the standard MIDAS navigation whether you are using the modb* functions or the underlying [[Mjsonrpc | javascript libraries]].&lt;br /&gt;
&lt;br /&gt;
= modb* Javascript scheme = &lt;br /&gt;
&lt;br /&gt;
The general scheme of the custom page scheme is to write &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;amp;lt;span class=&amp;quot;modb...&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&amp;lt;/code&amp;gt; tags with special class names, most of them starting with &amp;quot;modb...&amp;quot; (&amp;quot;MIDAS-ODB&amp;quot;). Use a &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want the element to appear in a separate line, and use the &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt; tag if you want to display the element in-line. The following description uses only &amp;lt;code&amp;gt;&amp;amp;lt;div&amp;amp;gt;&amp;lt;/code&amp;gt; tags, but all of them can be changed to &amp;lt;code&amp;gt;&amp;amp;lt;span&amp;amp;gt;&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
All HTML tags with &amp;quot;modb...&amp;quot; names are scanned by the &amp;lt;code&amp;gt;mhttp_init(&#039;name&#039;)&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;mhttpd_init(&#039;name&#039;, interval)&amp;lt;/code&amp;gt; where &amp;quot;interval&amp;quot; is in milliseconds. You can add or remove &amp;quot;modb...&amp;quot; elements at any time using javascript, and the new elements will be &amp;quot;discovered&amp;quot; automatically during the next update.&lt;br /&gt;
&lt;br /&gt;
== modbset(path, value) ==&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset(&amp;quot;odb path&amp;quot;, value)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
modbset([&amp;quot;odb path1&amp;quot;, &amp;quot;odb path2&amp;quot;, ...], [value1, value2, ...])&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modb ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for Midas ODB) &amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot; onchange=&amp;quot;func()&amp;quot;&amp;amp;gt;&amp;lt;/code&amp;gt; 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 &amp;amp;lt;script&amp;amp;gt;...&amp;amp;lt;/script&amp;amp;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.&lt;br /&gt;
&lt;br /&gt;
The current value of the ODB entry is available inside the &amp;quot;onchange&amp;quot; function as &#039;&#039;&#039;this.value&#039;&#039;&#039;. Following tag will call a function which logs the current run number in the JavaScript console window:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB path does not point to an individual value but to a subdirectory, the whole subdirectory is mapped to &#039;&#039;&#039;this.value&#039;&#039;&#039; as a JavaSctipt object such as&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Runinfo&amp;quot; onchange=&amp;quot;func(this.value)&amp;quot;&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;script&amp;amp;gt;function func(value) {&lt;br /&gt;
console.log(value[&amp;quot;run number&amp;quot;]);&lt;br /&gt;
}&amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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 &#039;&#039;&#039;value[&amp;quot;run number&amp;quot;]&#039;&#039;&#039; as shown in the above example. Otherwise, the entry can be accessed via the dot notation, such as &#039;&#039;&#039;value.state&#039;&#039;&#039; for /Runinfo/State for example.&lt;br /&gt;
&lt;br /&gt;
== modbvalue ==&lt;br /&gt;
&lt;br /&gt;
This special HTML div tag (abbreviation stands for &amp;quot;Midas ODB VALUE&amp;quot;) &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt; &lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 1: List of valid options for modbvalue tag&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| data-name || class=&amp;quot;modbvalue&amp;quot; || Tells the framework to replace this tag with an ODB value&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || data-odb-path = &amp;quot;/Runinfo/Run number&amp;quot; || Path to the value in the ODB. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]].&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-editable || data-odb-editable=&amp;quot;1&amp;quot; || 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.&lt;br /&gt;
|-&lt;br /&gt;
| data-format || data-format=&amp;quot;f3&amp;quot; || Specify format of data shown. See Table 2 below for options.&lt;br /&gt;
|-&lt;br /&gt;
| data-size || data-size=&amp;quot;8&amp;quot; || Specify size (in chars) of edit box if one modifies the value. Default is 10.&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || data-formula=&amp;quot;2*x+3&amp;quot; || Specify an optional formula to process with the current ODB value stored in x&lt;br /&gt;
|-&lt;br /&gt;
| data-validate || data-validate=&amp;quot;func&amp;quot; || Specify an optional validation function which gets called before submitting data (see below)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Validation ===&lt;br /&gt;
&lt;br /&gt;
Before a modified value is submitted to the ODB, an optional validation function can be called via the &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; option. The function&lt;br /&gt;
will be called with the current value and a reference to the current modbvalue element. If the function returns &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;, then the value&lt;br /&gt;
is not sent to the ODB.&lt;br /&gt;
&lt;br /&gt;
Following example shows a function which just rejects the submission of values above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;amp;gt;&amp;amp;lt;/script&amp;amp;gt; &amp;amp;lt;!-- needed of dlgAlert() --&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        dlgAlert(&amp;quot;Value cannot be above 1000&amp;quot;);&lt;br /&gt;
        return false;&lt;br /&gt;
     }&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following function corrects the return value to 1000 if it&#039;s above 1000:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;script&amp;amp;gt;&lt;br /&gt;
  function my_validate2(value, element) {&lt;br /&gt;
     if (value &amp;gt; 1000) {&lt;br /&gt;
        element.childNodes[0].value = 1000;&lt;br /&gt;
     return true;&lt;br /&gt;
  }&lt;br /&gt;
  &amp;amp;lt;/script&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Confirmation ===&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
This is done with the option &amp;lt;code&amp;gt;data-confirm=&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt;. A dialog box is then shown with the &amp;lt;code&amp;gt;&amp;amp;lt;string&amp;amp;gt;&amp;lt;/code&amp;gt; as the main text and two buttons with &amp;quot;OK&amp;quot; and &amp;quot;Cancel&amp;quot;.&lt;br /&gt;
Hitting &amp;quot;OK&amp;quot; finally writes the value to the ODB, hitting &amp;quot;Cancel&amp;quot; keeps the old value.&lt;br /&gt;
&lt;br /&gt;
Following example shows an example:&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;div class=&amp;quot;modbvalue&amp;quot; ... data-odb-editable=&amp;quot;1&amp;quot; data-confirm=&amp;quot;Are you sure to change the value?&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Following dialog box is then showed:&lt;br /&gt;
&lt;br /&gt;
[[File:Confirm.png|frame|left|Optional confirm dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Formatting ===&lt;br /&gt;
&lt;br /&gt;
Table 2 below lists the format specifiers supported with a modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Table 2: Format specifiers for modbvalue tag &amp;lt;code&amp;gt;data-format=&amp;quot;...&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
! Option !! Valid for* !! Example !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| %d || int || 1234 || Shows a number in decimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %t || int, float || 1,234,567 || Shows a number in decimal encoding with commas as thousands separator&lt;br /&gt;
|-&lt;br /&gt;
| %x || int || 0x4D2 || Shows a number in hexadecimal encoding&lt;br /&gt;
|-&lt;br /&gt;
| %b || int || 10011010010b|| Shows a number in binary encoding. Options t, d, x, b can be combined, like &amp;lt;code&amp;gt;data-format=&amp;quot;%d / %x&amp;quot;&amp;lt;/code&amp;gt; to produce &amp;lt;code&amp;gt;1234 / 0x4D2&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| %f&amp;amp;lt;x&amp;amp;gt; || float || 1.234 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal. A value of f0 shows only the integer part.&lt;br /&gt;
|-&lt;br /&gt;
| %p&amp;amp;lt;x&amp;amp;gt; || float || 1.23 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; significant digits of precision, independent of the decimal. For example a value of p2 can render a number to 12000 or 0.0012&lt;br /&gt;
|-&lt;br /&gt;
| %e&amp;amp;lt;x&amp;amp;gt; || float || 1.23e-05 || Shows a floating point number with &amp;amp;lt;x&amp;amp;gt; digits after the decimal in exponential format. Useful for small number such as pressures.&lt;br /&gt;
|-&lt;br /&gt;
| T= %f1 C || float || T= 25.4 C || Combination of text and float value is possible.&lt;br /&gt;
|-&lt;br /&gt;
| %d / %x / %b || int || 17 / 0x11 / 10001b|| Same integer value in different formats&lt;br /&gt;
|-&lt;br /&gt;
| [col1,col2] || bool || [red,var(--mgreen)] || Generates a color box filled with col1 (false, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: red&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;) or col2 (true, &amp;lt;span style=&amp;quot;display: inline-block; width: 1em; height: 1em; border: 1px solid black; background-color: #a0ffb8&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* Note that valid for &amp;quot;int&amp;quot; means all integral ODB types (regardless of size or signed/unsigned) and valid for &amp;quot;float&amp;quot; means both float and double.&lt;br /&gt;
&lt;br /&gt;
== modbbutton ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a push-button which can set a certain ODB entry to a specific value. To set the &amp;quot;Run number&amp;quot; to 100, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;button class=&amp;quot;modbbutton mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;100&amp;quot; data-validate=&amp;quot;my_validate&amp;quot;&amp;amp;gt;[Button Text]&amp;amp;lt;/button&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== modbbox ==&lt;br /&gt;
&lt;br /&gt;
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 &#039;&#039;&#039;data-color&#039;&#039;&#039; is used, otherwise the &#039;&#039;&#039;data-background-color&#039;&#039;&#039; is used&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write Data&amp;quot; data-formula=&amp;quot;x &amp;gt; 0&amp;quot; style=&amp;quot;width: 30px; height: 30px; border: 1px solid black&amp;quot; data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Optionally, a &amp;lt;code&amp;gt;data-formula&amp;lt;/code&amp;gt; can be specified. The formula sees the ODB value in the variable &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, and can do any boolean operation. If the result of this is true, then the box gets the &amp;lt;code&amp;gt;data-color&amp;lt;/code&amp;gt;, otherwise the &amp;lt;code&amp;gt;data-background-color&amp;lt;/code&amp;gt;.&lt;br /&gt;
Examples for these formulas are &amp;lt;code&amp;gt;x &amp;gt; 10&amp;lt;/code&amp;gt; for a comparison or &amp;lt;code&amp;gt;x &amp;amp; 1&amp;lt;/code&amp;gt; which will do a bitwise AND operation and is true only for odd numbers.&lt;br /&gt;
&lt;br /&gt;
== modbcheckbox ==&lt;br /&gt;
&lt;br /&gt;
This tag generates a check box which can set a certain ODB entry to true or false. To set the &amp;quot;Write data&amp;quot; flag for the logger true or false, one can use the following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; data-validate=&amp;quot;my_validate&amp;quot; /&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
If the ODB value changed by this control is of type integer, its value will be set to 1 or 0. The optional &amp;lt;code&amp;gt;data-validate&amp;lt;/code&amp;gt; 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 &amp;lt;code&amp;gt;modbvalue&amp;lt;/code&amp;gt; tag.&lt;br /&gt;
&lt;br /&gt;
== modbselect ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;/Runinfo/Run number&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;1&amp;quot;&amp;amp;gt;1&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;5&amp;quot;&amp;amp;gt;5&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
    &amp;amp;lt;option value=&amp;quot;10&amp;quot;&amp;amp;gt;10&amp;amp;lt;/option&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&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 &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; to enable this behaviour. For ODB key &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, you will need to create an ODB entry called &amp;lt;code&amp;gt;Options x&amp;lt;/code&amp;gt; in the same directory; this &amp;quot;options&amp;quot; key should be a list, with each element in the list being an allowable option.&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;!-- Will read options from &amp;quot;/Equipment/Example/Settings/Options Something&amp;quot; in this case --&amp;gt;&lt;br /&gt;
  &amp;amp;lt;select class=&amp;quot;modbselect&amp;quot; data-odb-path=&amp;quot;/Equipment/Example/Settings/Something&amp;quot; data-auto-options=&amp;quot;1&amp;quot;&amp;amp;gt;&lt;br /&gt;
  &amp;amp;lt;/select&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
The benefit of the &amp;lt;code&amp;gt;data-auto-options=&amp;quot;1&amp;quot;&amp;lt;/code&amp;gt; approach is that the same options will be shown on the regular ODB browser webpage. &lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== modbhbar ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px; color: lightgreen;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;color: red&amp;quot; || Color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background-color: red&amp;quot; || Background color of horizontal bar || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of bar range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of bar range || 1 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown as text overlay || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specify format of data shown. See Table 2 above for options || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== mhaxis ==&lt;br /&gt;
&lt;br /&gt;
A horizontal bar can be combined with an axis with tick marks and labels. The axis can be above or below the bar.&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width: 500px; height: 22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a horizontal axis next to the bar. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 500px&amp;quot; || Total width of the axis, must match the width of the horizontal bar || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 18px&amp;quot; || Height of the axis, must be enough to display labels  || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;vertical-align: top&amp;quot; || Must be &amp;quot;top&amp;quot; if the axis is below the bar || &amp;quot;bottom&amp;quot; if not given&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Left limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Right limit of axis range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-log || Logarithmic display || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbvbar ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;modbhbar&amp;lt;/code&amp;gt;, except the bar grows vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== mvaxis ==&lt;br /&gt;
&lt;br /&gt;
Same as &amp;lt;code&amp;gt;mhaxis&amp;lt;/code&amp;gt;, except the axis is shown vertically instead of horizontally.&lt;br /&gt;
&lt;br /&gt;
== modbthermo ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
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&#039;s good for testing. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 30px&amp;quot; || Width of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 100px&amp;quot; || Total height of the thermometer || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of thermometer || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of thermometer background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown next to the thermometer || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== modbgauge ==&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;&amp;amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width: 100px; height: 50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-color=&amp;quot;darkgreen&amp;quot;&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
shows a circular gauge ranging from 0 to 10. Depending on the ODB value &amp;quot;Run number&amp;quot;. Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 100px&amp;quot; || Width of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 50px&amp;quot; || Total height of the gauge || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-odb-path || ODB path of value being displayed. If omitted, the value must be set via [[Custom_Page#Changing_values_of_indicators_programmatically | set_value]]. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-max-value || Upper range || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-min-value || Lower range || 0 if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-color || Color of gauge || Black if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-background-color || Color of gauge background || Transparent if not present&lt;br /&gt;
|-&lt;br /&gt;
| data-print-value || If &amp;quot;1&amp;quot;, data value is shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-format || Specifies format of temperature shown below gauge. See Table 2 for options. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-scale || If &amp;quot;1&amp;quot;, the min and max values of the range are shown below the gauge || No&lt;br /&gt;
|-&lt;br /&gt;
| data-formula || Specify an optional formula to process with the current ODB value stored in x || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Changing properties of controls dynamically ==&lt;br /&gt;
&lt;br /&gt;
All custom controls can be configured to call a user&#039;s function when the control is first set up, or when the value changes. This is done by specifying the &#039;&#039;&#039;onload&#039;&#039;&#039; and/or &#039;&#039;&#039;onchange&#039;&#039;&#039; functions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;onload&#039;&#039;&#039; is called only once, when the control&#039;s value is first read from the ODB.&lt;br /&gt;
* &#039;&#039;&#039;onchange&#039;&#039;&#039; is called each time the value in the ODB changes.&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 30?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
onchange can call any arbitrary javascript function. Rather than specifying the logic in the tag itself, the above example could also be implemented like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
function check_therm(elem) {&lt;br /&gt;
  if (elem.value &amp;gt; 30) {&lt;br /&gt;
    elem.dataset.color = &amp;quot;red&amp;quot;;&lt;br /&gt;
  } else {&lt;br /&gt;
    elem.dataset.color = &amp;quot;blue&amp;quot;;&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
....&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width: 30px; height: 100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot; data-color=&amp;quot;blue&amp;quot; data-print-value=&amp;quot;1&amp;quot; onchange=&amp;quot;check_therm(this)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
If you want the same function to be called for both onload and onchange, you could set &amp;lt;code&amp;gt;onload=&amp;quot;onchange()&amp;quot;&amp;lt;/code&amp;gt; and have the &amp;quot;real&amp;quot; function in onchange.&lt;br /&gt;
&lt;br /&gt;
== Dynamic ODB paths ==&lt;br /&gt;
&lt;br /&gt;
The path of a custom control is static, meaning it has to be written directly inside the control via &amp;lt;code&amp;gt;data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;lt;/code&amp;gt;. There can be however cases where the path is only known at runtime. For these cases, the path can be a user function which gets called during each update of the control and has to return the current ODB path. This can be useful for equipment values with names. The standard scheme of MIDAS is to have slow control variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Variables&amp;lt;/code&amp;gt; and the names of these variables under &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names&amp;lt;/code&amp;gt;. If there are several different arrays under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;, you might also have something like &amp;lt;code&amp;gt;/Equipment/&amp;lt;name&amp;gt;/Settings/Names XXX&amp;lt;/code&amp;gt; where &amp;lt;code&amp;gt;XXX&amp;lt;/code&amp;gt; is the name of the key under &amp;lt;code&amp;gt;Variables&amp;lt;/code&amp;gt;. If one wants to use a name of a variable rather than its index, the dynamic path function can be uses like in the following example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;script&amp;gt;&lt;br /&gt;
   let names;&lt;br /&gt;
&lt;br /&gt;
   function getNames() {&lt;br /&gt;
      mjsonrpc_db_get_value(&amp;quot;/Equipment/.../Settings/Names&amp;quot;).then(function (rpc) {&lt;br /&gt;
         names = rpc.result.data[0];&lt;br /&gt;
         mhttpd_init(&#039;Custom Page Example&#039;);&lt;br /&gt;
      });&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   function getpath(p) {&lt;br /&gt;
      if (mscbNames)&lt;br /&gt;
         return `/Equipment/.../Variables/MSCB[${names.indexOf(p)}]`;&lt;br /&gt;
   }&lt;br /&gt;
&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;getNames();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the page is initialized, the function &amp;lt;code&amp;gt;getNames()&amp;lt;/code&amp;gt; is called, which obtains the &amp;lt;code&amp;gt;Names&amp;lt;/code&amp;gt; array from the ODB and stores it into an array. After the names have been received, the function calls the usual &amp;lt;code&amp;gt;mhttpd_init()&amp;lt;/code&amp;gt; function. The &amp;quot;modbvalue&amp;quot; path is now set to &amp;lt;code&amp;gt;data-odb-path=&amp;quot;getpath(&#039;ADC1&#039;)&amp;quot;&amp;lt;/code&amp;gt;. This function does a lookup in the &amp;lt;code&amp;gt;names&amp;lt;/code&amp;gt; array and returns the proper index, which is then used to access the corresponding value in the variables array. This method has the advantage that if the indices of an array change (e.g. by adding new variables at the front), the code does not have to update any index since the indices are calculated dynamically.&lt;br /&gt;
&lt;br /&gt;
== Changing values of indicators programmatically ==&lt;br /&gt;
&lt;br /&gt;
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. &lt;br /&gt;
&lt;br /&gt;
For such cases, the &amp;lt;code&amp;gt;data-odb-path&amp;lt;/code&amp;gt; attribute can be removed and the the display value can be changed via JavaScript code like following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mythermo&amp;quot; class=&amp;quot;modbthermo&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
  let t = document.getElementById(&amp;quot;mythermo&amp;quot;);&lt;br /&gt;
  t.setValue(15);&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mjshistory =&lt;br /&gt;
&lt;br /&gt;
Custom pages can contain one or more specific history panels usually shown on the &amp;quot;History&amp;quot; page. This makes it easy to combine current readings of values together with the history of these values.&lt;br /&gt;
&lt;br /&gt;
To enable interactive history panels, following lines have to be added to your custom page:&lt;br /&gt;
&lt;br /&gt;
Inisde the &amp;lt;head&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside the &amp;lt;body&amp;gt; tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;body ... onload=&amp;quot;mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following tag:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;&amp;lt;group&amp;gt;&amp;quot; data-panel=&amp;quot;&amp;lt;panel&amp;gt;&amp;quot; style=&amp;quot;width: 320px; height: 200px;&amp;quot; &amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
shows a history panel defined in the ODB under /History/Display/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; (replace &amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt; with groups and panels from your experiment).&lt;br /&gt;
&lt;br /&gt;
Following options are possible:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Setting !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| data-group || ODB group of history. Has to match a group under /History/Display || Yes&lt;br /&gt;
|-&lt;br /&gt;
| data-panel || ODB panel name of history. Has to match a panel name under /History/Display/&amp;amp;lt;group&amp;amp;gt;/ || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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/&amp;amp;lt;group&amp;amp;gt;/&amp;amp;lt;panel&amp;amp;gt;/Timescale is used. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-title || Flag to show or hide the title. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-values || Flag to show or hide the variable labels with their current value. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-axis || Flag to show or hide the X/Y axis. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the menu buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| data-show-menu-buttons || Flag to show or hide the zoom buttons. Default is &amp;quot;1&amp;quot;. || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;width: 320px&amp;quot; || Width of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;height: 200px&amp;quot; || Height of the history panel || No&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;border: 1px solid black&amp;quot; || Border around the history panel || No&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
If width and height are omitted, the default values of 320px and 200px are used. History panels are automatically updated every second.&lt;br /&gt;
&lt;br /&gt;
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 &lt;br /&gt;
the possibility to show the history of that variable. Just put a button next to the value and call &#039;&#039;&#039;mhistory_dialog(&amp;amp;lt;group&amp;amp;gt;, &amp;amp;lt;panel&amp;amp;gt;)&#039;&#039;&#039; from that button like:&lt;br /&gt;
&lt;br /&gt;
   &amp;amp;lt;span class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Some/Path&amp;quot;&amp;amp;gt;&amp;amp;lt;/span&amp;amp;gt;&lt;br /&gt;
   &amp;lt;button onclick=&amp;quot;mhistory_dialog(&#039;group&#039;,&#039;panel&#039;)&amp;quot;&amp;gt;&amp;lt;img src=&amp;quot;icons/activity.svg&amp;quot;&amp;gt;&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The history panel will then be opened when the user clicks the button:&lt;br /&gt;
&lt;br /&gt;
[[File:History dialog.png|frame|left|Floating history dialog]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br clear=all&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one wants to avoid the definition of a history panel in the ODB, a &amp;quot;direct variable plot&amp;quot; can be done with the function &#039;&#039;&#039;mhistory_dialog_var(&amp;amp;lt;variable&amp;amp;gt;)&#039;&#039;&#039; where &amp;amp;lt;variable&amp;amp;gt; has the format like the variable definition in the ODB (e.g. &amp;quot;System:Trigger per sec.&amp;quot;). 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 &#039;&#039;&#039;mhistory_dialog_var(&amp;quot;System:Trigger per sec.&amp;quot;, {&amp;quot;Timescale&amp;quot;: &amp;quot;24h&amp;quot;});&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A full example of a custom page with a history panel is shown below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
  &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot; type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;script src=&amp;quot;mhistory.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&amp;lt;body onload=&amp;quot;mhttpd_init(&#039;history_example&#039;); mhistory_init();&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;  &lt;br /&gt;
  &amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;mjshistory&amp;quot; data-group=&amp;quot;EPICS&amp;quot; data-panel=&amp;quot;Logging&amp;quot; style=&amp;quot;width: 500px; height: 300px;&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= mplot =&lt;br /&gt;
&lt;br /&gt;
Custom pages may contain scatter plots, histograms and other graphics. The syntax is described on the dedicated [[Custom plots with mplot]] page.&lt;br /&gt;
&lt;br /&gt;
= Dialog, popup and modal boxes =&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Showing a message ==&lt;br /&gt;
&lt;br /&gt;
The dlgMessage, dlgAlert and dlgWait functions show a message to the user. The dialog box contains an &amp;quot;Ok&amp;quot; button that the user may click to dismiss the message.&lt;br /&gt;
&lt;br /&gt;
=== dlgMessage ===&lt;br /&gt;
&lt;br /&gt;
dlgMessage is the most customisable of the 3 functions. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(title, message, modal, error, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| title || string || Title of the dialog box || Yes&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || Main message content || Yes&lt;br /&gt;
|-&lt;br /&gt;
| 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&lt;br /&gt;
|-&lt;br /&gt;
| error || boolean || If true, will use a red background for the title || No. Defaults to false&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called with the user clicks the &amp;quot;Ok&amp;quot; button. The callback function will be called with just a single paramter. || No. Defaults to not calling a callback function.&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
 &lt;br /&gt;
Some example invocations are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgMessage(&amp;quot;Message title&amp;quot;, &amp;quot;Message text&amp;quot;);&lt;br /&gt;
dlgMessage(&amp;quot;It&#039;s an error!&amp;quot;, &amp;quot;&amp;lt;b&amp;gt;Something is wrong!!!&amp;lt;/b&amp;gt;&amp;quot;, true, true);&lt;br /&gt;
dlgMessage(&amp;quot;Callback example&amp;quot;, &amp;quot;Testing&amp;quot;, true, false, my_message_cb, &amp;quot;some_param&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
function my_message_cb(param) {&lt;br /&gt;
   alert(&amp;quot;Message dismissed! The param was &amp;quot; + param);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgAlert ===&lt;br /&gt;
&lt;br /&gt;
dlgAlert is a convenience function for making it easier to show a warning message to the user. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgAlert(message, callback)&lt;br /&gt;
&lt;br /&gt;
// The above is equivalent to:&lt;br /&gt;
dlgMessage(&amp;quot;Message&amp;quot;, message, true, true, callback)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgWait ===&lt;br /&gt;
&lt;br /&gt;
dlgWait shows a message for a certain amount of time and then automatically closes itself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgWait(time_in_seconds, message)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Asking for input ==&lt;br /&gt;
&lt;br /&gt;
=== dlgQuery ===&lt;br /&gt;
&lt;br /&gt;
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&#039;s response is sent to a callback function that you must implement yourself. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(message, value, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| value || string || Default value to pre-populate the answer field with || Yes (but can be empty string)&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or their answer if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgQuery(&amp;quot;Enter a value:&amp;quot;, &amp;quot;my default value&amp;quot;, my_query_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_query_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      // User clicked the Cancel button&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&#039;Value is &#039; + value + &#039;, param is &#039; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== dlgConfirm ===&lt;br /&gt;
&lt;br /&gt;
dlgConfirm is like dlgQuery, but just shows the Ok and Cancel buttons without an extra input field. The signature is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(message, callback, param)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Parameter !! Type !! Meaning !! Required&lt;br /&gt;
|-&lt;br /&gt;
| message || string (can be arbitrary HTML) || The question to show the user || Yes&lt;br /&gt;
|-&lt;br /&gt;
| callback || function || Function to be called when the user finishes with the dialog. The first parameter will be false if the user clicked the &amp;quot;Cancel&amp;quot; button, or true if they clicked the &amp;quot;Ok&amp;quot; button. || Yes&lt;br /&gt;
|-&lt;br /&gt;
| param || anything || Parameter that will be passed to the `callback` function as the 2nd parameter || No. Defaults to undefined.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Example usage is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
dlgConfirm(&amp;quot;Are you sure you wish to do that?&amp;quot;, my_confirm_cb, &amp;quot;some_other_param&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
function my_confirm_cb(value, param) {&lt;br /&gt;
   if (value === false) {&lt;br /&gt;
      alert(&amp;quot;User clicked Cancel! Param was &amp;quot; + param);&lt;br /&gt;
   } else {&lt;br /&gt;
      alert(&amp;quot;User clicked Ok! Param was &amp;quot; + param);&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// where &#039;param&#039; can also be ommitted.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Implementing SVGs =&lt;br /&gt;
&lt;br /&gt;
Complex services or detector systems can be displayed using vector images e.g. SVGs created by Inkscape.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;object id=&amp;quot;svgObject&amp;quot; type=&amp;quot;image/svg+xml&amp;quot; data=&amp;quot;Midas_helium_example.svg&amp;quot;&amp;gt;&amp;lt;/object&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
They can be loaded for example as a background image with modb ... class object at the dedicated position.&lt;br /&gt;
&lt;br /&gt;
Interactive changes of the SVG like updating a the color of a valve, or changing the size of objects depending on ODB values is explained in more detail in [[Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
= Complete Example =&lt;br /&gt;
&lt;br /&gt;
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:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/Custom&lt;br /&gt;
  Path     /midas/resources&lt;br /&gt;
  Test     a_example.html&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Code ==&lt;br /&gt;
The file &#039;&#039;&#039;a_example.html&#039;&#039;&#039; contains the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;!DOCTYPE html&amp;gt;&lt;br /&gt;
&amp;lt;html class=&amp;quot;mcss&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;head&amp;gt;&lt;br /&gt;
   &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;midas.css&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;controls.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;midas.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;script src=&amp;quot;mhttpd.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;
   &amp;lt;title&amp;gt;Example&amp;lt;/title&amp;gt;&lt;br /&gt;
&lt;br /&gt;
   &amp;lt;style&amp;gt;&lt;br /&gt;
      .mtable td { padding: 10px; }&lt;br /&gt;
   &amp;lt;/style&amp;gt;&lt;br /&gt;
&amp;lt;/head&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;body class=&amp;quot;mcss&amp;quot; onload=&amp;quot;mhttpd_init(&#039;Example&#039;);&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- header and side navigation will be filled in mhttpd_init --&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;mheader&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;msidenav&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;mmain&amp;quot;&amp;gt;&lt;br /&gt;
   &amp;lt;table class=&amp;quot;mtable&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;th colspan=&amp;quot;2&amp;quot; class=&amp;quot;mtableheader&amp;quot;&amp;gt;Status&amp;lt;/th&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td style=&amp;quot;width: 200px;&amp;quot;&amp;gt;&lt;br /&gt;
            Run number:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run start:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Start time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Last run stop:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Stop time&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Check box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- checkbox changes /Logger/Write data, fire dialog box on change (even if changed by odbedit) --&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; class=&amp;quot;modbcheckbox&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&amp;gt;&amp;lt;/input&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modb&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot; onchange=&amp;quot;dlgAlert(&#039;Flag has changed&#039;);&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Color box:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;!-- box changes color according to /Logger/Write data --&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbbox&amp;quot; style=&amp;quot;width: 30px; height: 30px;&amp;quot; data-odb-path=&amp;quot;/Logger/Write data&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;lightgreen&amp;quot; data-background-color=&amp;quot;red&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Horizontal bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width:300px;height:20px;color:orange;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-print-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:500px;height:22px;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 500px; height: 18px;color:lightblue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbhbar&amp;quot; style=&amp;quot;width: 200px; height: 10px;color:lightgreen;background-color:white&amp;quot;&lt;br /&gt;
                 data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot; data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;mhaxis&amp;quot; style=&amp;quot;width:200px;height:22px;vertical-align:top;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                 data-max-value=&amp;quot;10&amp;quot; data-line=&amp;quot;0&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Vertical bars:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:right;&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;modbvbar&amp;quot;&lt;br /&gt;
                  style=&amp;quot;width:20px;height:200px;color:yellow;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot;&lt;br /&gt;
                  data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;20&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
            &amp;lt;span class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;width:10px;height:200px;vertical-align:top;color:red&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                  data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;mvaxis&amp;quot; style=&amp;quot;width:100px;height:200px;text-align:left;&amp;quot; data-min-value=&amp;quot;0.1&amp;quot;&lt;br /&gt;
                                                                data-max-value=&amp;quot;10&amp;quot; data-log=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Thermometer:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:60px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-scale=&amp;quot;1&amp;quot;&lt;br /&gt;
                 onchange=&amp;quot;this.dataset.color=this.value &amp;gt; 9?&#039;red&#039;:&#039;blue&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;width:30px;height:100px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;blue&amp;quot; data-background-color=&amp;quot;white&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            Gauges:&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
         &amp;lt;td&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:50px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;darkgreen&amp;quot; data-background-color=&amp;quot;lightgrey&amp;quot; &amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;modbgauge&amp;quot; style=&amp;quot;width:100px;height:65px;&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;0&amp;quot; data-max-value=&amp;quot;10&amp;quot;&lt;br /&gt;
                 data-color=&amp;quot;red&amp;quot; data-value=&amp;quot;1&amp;quot; data-scale=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- div around image with &amp;quot;relative&amp;quot; position as anchor for labels and bars --&amp;gt;&lt;br /&gt;
            &amp;lt;div style=&amp;quot;position:relative;width:300px;margin:auto&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;img src=&amp;quot;tank.gif&amp;quot;&amp;gt; &amp;lt;!-- background image of tank --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- label next to valve --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvalue&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-editable=&amp;quot;1&amp;quot;&lt;br /&gt;
                    style=&amp;quot;position:absolute;top:157px;left:288px;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- vertical bar inside tank, render red if value &amp;gt; 9 --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbvbar&amp;quot; style=&amp;quot;position:absolute;top:80px;left:10px;width:104px;height:170px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-max-value=&amp;quot;11&amp;quot; data-color=&amp;quot;green&amp;quot;&lt;br /&gt;
                    onchange=&amp;quot;this.firstChild.style.backgroundColor=(this.value &amp;gt; 9)?&#039;red&#039;:&#039;green&#039;;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
               &amp;lt;!-- thermometer inside tank --&amp;gt;&lt;br /&gt;
               &amp;lt;div class=&amp;quot;modbthermo&amp;quot; style=&amp;quot;position:absolute;top:140px;left:20px;width:20px;height:100px;&amp;quot;&lt;br /&gt;
                    data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-min-value=&amp;quot;-10&amp;quot; data-max-value=&amp;quot;30&amp;quot;&lt;br /&gt;
                    data-color=&amp;quot;blue&amp;quot; data-value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
&lt;br /&gt;
      &amp;lt;tr&amp;gt;&lt;br /&gt;
         &amp;lt;td colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- three buttons to change an ODB entry (run number in this example) --&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;1&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 1&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;5&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 5&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;button class=&amp;quot;modbbutton&amp;quot; class=&amp;quot;mbutton&amp;quot; data-odb-path=&amp;quot;/Runinfo/Run number&amp;quot; data-odb-value=&amp;quot;10&amp;quot;&amp;gt;Set run&lt;br /&gt;
               number to 10&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
         &amp;lt;/td&amp;gt;&lt;br /&gt;
      &amp;lt;/tr&amp;gt;&lt;br /&gt;
   &amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
which results in the page shown in Figure 1 below:&lt;br /&gt;
&lt;br /&gt;
[[File:Custom17.png|frame|left|Figure 1  Example custom page using most features]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both&amp;quot;&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- clear wraparound after thumbnail --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Old custom page feature =&lt;br /&gt;
&lt;br /&gt;
There are a number of deprecated custom page features, which can be seen here: [[Old Custom Page Features]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:mhttpd Pages]] [[Category:Custom]]&lt;/div&gt;</summary>
		<author><name>Rudzki</name></author>
	</entry>
</feed>