widget_desc("JsonTable") {
Send given variables as POST to http URL argument, spread returned JSON in
SVG sub-elements of "data" labeled element.
Documentation to be written. see svghmi example.
shortdesc > Http POST variables, spread JSON back
arg name="url" accepts="string" >
path name="edit" accepts="HMI_INT, HMI_REAL, HMI_STRING" > single variable to edit
widget_class("JsonTable")
// arbitrary defaults to avoid missing entries in query
this.spread_json_data_bound = this.spread_json_data.bind(this);
this.handle_http_response_bound = this.handle_http_response.bind(this);
this.fetch_error_bound = this.fetch_error.bind(this);
handle_http_response(response) {
console.log("HTTP error, status = " + response.status);
console.log("HTTP fetch error, message = " + e.message + "Widget:" + this.element_id);
do_http_request(...opt) {
this.abort_controller = new AbortController();
return Promise.resolve().then(() => {
extra: this.cache.slice(4),
body: JSON.stringify(query),
headers: {'Content-Type': 'application/json'},
signal: this.abort_controller.signal
return fetch(this.args[0], options)
.then(this.handle_http_response_bound)
.then(this.spread_json_data_bound)
.catch(this.fetch_error_bound);
this.abort_controller.abort();
this.cache[0] = undefined;
dispatch(value, oldval, index) {
if(this.cache[index] != value)
this.cache[index] = value;
this.do_http_request().finally(() => {
make_on_click(...options){
that.do_http_request(...options);
// on_click(evt, ...options) {
// this.do_http_request(...options);
template "svg:*", mode="json_table_elt_render" {
error > JsonTable Widget can't contain element of type «local-name()».
def "func:json_expressions" {
// compute javascript expressions to access JSON data
// described in given svg element's "label"
// knowing that parent element already has given "expressions".
const "suffixes", "str:split($label)";
const "res" foreach "$suffixes" expression {
const "pos","position()";
// take last available expression (i.e can have more suffixes than expressions)
const "expr","$expressions[position() <= $pos][last()]/expression";
when "contains($suffix,'=')" {
const "name", "substring-before($suffix,'=')";
if "$expr/@name[. != $name]"
error > JsonTable : misplaced '=' or inconsistent names in Json data expressions.
attrib "name" value "$name";
attrib "content" > «$expr/@content»«substring-after($suffix,'=')»
attrib "content" > «$expr/@content»«$suffix»
result "exsl:node-set($res)";
// Empty labels are ignored, expressions are then passed as-is.
otherwise result "$expressions";
const "initexpr" expression attrib "content" > jdata
const "initexpr_ns", "exsl:node-set($initexpr)";
template "svg:use", mode="json_table_elt_render" {
// cloned element must be part of a HMI:List or a HMI:List
const "targetid", "substring-after(@xlink:href,'#')";
const "from_list", "$hmi_lists[(@id | */@id) = $targetid]";
when "count($from_list) > 0" {
| id("«@id»").href.baseVal =
// obtain new target id from HMI:List widget
| "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»];
warning > Clones (svg:use) in JsonTable Widget must point to a valid HMI:List widget or item. Reference "«@xlink:href»" is not valid and will not be updated.
template "svg:text", mode="json_table_elt_render" {
const "value_expr", "$expressions/expression[1]/@content";
const "original", "@original";
const "from_textstylelist", "$textstylelist_related_ns/list[elt/@eltid = $original]";
when "count($from_textstylelist) > 0" {
const "content_expr", "$expressions/expression[2]/@content";
if "string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'"
error > Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignment following value expression in label.
| elt.textContent = String(«$content_expr»);
| elt.style = hmi_widgets["«$from_textstylelist/@listid»"].styles[«$value_expr»];
| id("«@id»").textContent = String(«$value_expr»);
template "svg:image", mode="json_table_elt_render" {
const "value_expr", "$expressions/expression[1]/@content";
| id("«@id»").setAttribute('href', String(«$value_expr»));
// only labels coming from Json widget are counted in
def "func:filter_non_widget_label" {
when "$elt/@original" value "$elt/@original";
otherwise value "$elt/@id";
result "$widget_elts[@id=$eltid]/@inkscape:label";
template "svg:*", mode="json_table_render_except_comments"{
const "label", "func:filter_non_widget_label(., $widget_elts)";
// filter out "# commented" elements
if "not(starts-with($label,'#'))"
apply ".", mode="json_table_render"{
with "expressions", "$expressions";
with "widget_elts", "$widget_elts";
template "svg:*", mode="json_table_render" {
const "new_expressions", "func:json_expressions($expressions, $label)";
foreach "$new_expressions/expression[position() > 1][starts-with(@name,'onClick')]"
| id("«$elt/@id»").onclick = this.make_on_click('«@name»', «@content»);
apply ".", mode="json_table_elt_render"
with "expressions", "$new_expressions";
template "svg:g", mode="json_table_render" {
// use intermediate variables for optimization
const "varprefix" > obj_«@id»_
foreach "$expressions/expression"{
| let «$varprefix»«position()» = «@content»;
| if(«$varprefix»«position()» == undefined) {
// because we put values in a variables, we can replace corresponding expression with variable name
const "new_expressions" foreach "$expressions/expression" xsl:copy {
attrib "content" > «$varprefix»«position()»
// revert hiding in case it did happen before
| id("«@id»").style = "«@style»";
apply "*", mode="json_table_render_except_comments" {
with "expressions", "func:json_expressions(exsl:node-set($new_expressions), $label)";
with "widget_elts", "$widget_elts";
| id("«@id»").style = "display:none";
widget_defs("JsonTable") {
const "data_elt", "$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']";
| visible: «count($data_elt/*[@inkscape:label])»,
| spread_json_data: function(janswer) {
| let [range,position,jdata] = janswer;
| [[1, range], [2, position], [3, this.visible]].map(([i,v]) => {
| this.apply_hmi_value(i,v);
apply "$data_elt", mode="json_table_render_except_comments" {
with "expressions","$initexpr_ns";
with "widget_elts","$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*";
foreach "$hmi_element/*[starts-with(@inkscape:label,'action_')]" {
| id("«@id»").onclick = this.make_on_click("«func:escape_quotes(@inkscape:label)»");