Custom web pagesWith the domotcl web server running, data located under the domotcl/web/html directory can be accessed by a http client (typically a web browser).Most files are simply served exactly as they are stored on disk. There are two exceptions: If a version of a requested file exists with a .script or .tmpl extension, then the web server generates the output from either of these files. A file with a .tmpl extension functions as a template. A file with a .script extension is executed by the web server as a Tcl script. Template filesLines in a template file that start with a percent sign (%) are executed as Tcl code, after stripping off the %. All other lines are simply returned, after substituting variables and code in square brackets.Example: The following template file will list all png files in the images directory and their sizes:
<table>
% set dir [dict get $state options root]
% foreach file [glob -directory [file join $dir images] *.png] {
<tr><td>$file</td><td align="right">[file size $file]</td></tr>
% }
</table>
Script filesA script file is executed by the web server. The return value of the script is served to the http client. The value can be returned using an actual return command. Otherwise, the return value will be whatever the last command of the script produced.The same example as above, but written as a script file, could be done like this:
set dir [dict get $state options root]
set lines "<table>\n"
foreach file [glob -directory [file join $dir images] *.png] {
append lines "<tr><td>$file</td><td align=\"right\">[file size $file]</td></tr>"
}
append lines "</table>\n"
Note that no explicit return command is used, but the script still works
because the append command returns the value of the variable after the
specified strings have been appended to it.
The $state variableIn the examples above, the $state variable was used. This variable contains a dict with information about the request. The dict has 3 sections:
HTML as Tcl codeIn script files, HTML code can easily be generated by writing HTML as Tcl commands. For example, the code on the left produces the HTML code shown on the right:
html {
head {
title {
str "Test page"
}
}
body {
str "Hello, World"
}
}
<!DOCTYPE html> <html> <head> <title>Test page</title> </head> <body>Hello, World</body> </html>
html {
upvar 1 state state
body {
foreach key [dict keys $state] {
str $key
br
}
}
}
The code within the html body runs in a stack frame that is one level below
the top level for the script. That is why the $state variable has to be
pulled into scope to be able to use it.
Attributes of HTML tags can be specified as options, for example: a -href index.html {str Home}
Domotcl data and commandsWhile the information provided above makes it possible to generate web pages, the question arises how domotcl comes into play.commands
websocketsMost of the time, a web page showing data from domotcl should be updated dynamically when anything changes. This is where websockets come into play. Because this is such a common situation, it has been made very easy for the web page developer. The websocket command takes a list of property patterns. Changes of properties matching the patterns are then sent to the web page. An HTML element with an id equal to the device:property will automatically be updated.The websocket can also be used to send commands to domotcl without reloading the web page. To use this, the javascript wscommand() function is provided when the websocket command is included in the web page body. The function takes the name of the device, the command, and any arguments. If the arguments to the command are something else than strings, you must pass a signature to the wscommand() function. Example: Assuming you have an integer variable called /variable/counter, the following code will show the value on a button. Clicking the button increments the variable:
html {
body {
websocket /variable/counter:value
button -id /variable/counter:value \
-onclick {wscommand("-signature i", "/variable/counter", "add", 1)}
}
}
If you want to show something else than the default presentation of a
device property, you can provide your own javascript function. You then
have to tell the websocket code to use this function instead of the default
function. This can be achieved by providing an onload handler to the body
element.
The following code obtains all x10 appliance modules from the database and displays them as checkboxes on the web page. The checkbox shows when the module is on. Clicking the checkbox toggles the module on and off.
html {
head {
javascript {
function wsfunc(evt) {
var msg = JSON.parse(evt.data)
for (var name in msg) {
var w = document.getElementById(name)
if (w) w.checked = msg[name].value === "on"
}
}
}
}
body -onload {connect("status.ws", wsfunc)} {
websocket /x10:state /x10/*:state
foreach name [sched eval {select name from device \
where class = 'x10' and type = 'appliance'}] {
input -type checkbox -id $name:state \
-onclick "wscommand('$name', 'toggle')"
str $name
br
}
}
}
|