diff -r 577118ebd179 -r c89fc366bebd svghmi/widget_jsontable.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/widget_jsontable.ysl2 Thu Sep 02 21:36:29 2021 +0200 @@ -0,0 +1,303 @@ +// widget_jsontable.ysl2 + +widget_desc("JsonTable") { + longdesc + || + 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 svbghmi exemple. + || + + 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 + cache = [0,0,0]; + init_common() { + 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); + this.promised = false; + } + + handle_http_response(response) { + if (!response.ok) { + console.log("HTTP error, status = " + response.status); + } + return response.json(); + } + + fetch_error(e){ + 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(() => { + + const query = { + args: this.args, + range: this.cache[1], + position: this.cache[2], + visible: this.visible, + extra: this.cache.slice(4), + options: opt + }; + + const options = { + method: 'POST', + 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); + }); + } + + unsub(){ + this.abort_controller.abort(); + super.unsub(); + } + + sub(...args){ + this.cache[0] = undefined; + super.sub(...args); + } + + dispatch(value, oldval, index) { + + if(this.cache[index] != value) + this.cache[index] = value; + else + return; + + if(!this.promised){ + this.promised = true; + this.do_http_request().finally(() => { + this.promised = false; + }); + } + } + make_on_click(...options){ + let that = this; + return function(evt){ + that.do_http_request(...options); + } + } + // on_click(evt, ...options) { + // this.do_http_request(...options); + // } + || + +gen_index_xhtml { + +template "svg:*", mode="json_table_elt_render" { + error > JsonTable Widget can't contain element of type «local-name()». +} + + +const "hmi_textstylelists_descs", "$parsed_widgets/widget[@type = 'TextStyleList']"; +const "hmi_textstylelists", "$hmi_elements[@id = $hmi_textstylelists_descs/@id]"; + +const "textstylelist_related" foreach "$hmi_textstylelists" list { + attrib "listid" value "@id"; + foreach "func:refered_elements(.)" elt { + attrib "eltid" value "@id"; + } +} +const "textstylelist_related_ns", "exsl:node-set($textstylelist_related)"; + +def "func:json_expressions" { + param "expressions"; + param "label"; + + // compute javascript expressions to access JSON data + // desscribed in given svg element's "label" + // knowing that parent element already has given "expressions". + + choose { + when "$label" { + const "suffixes", "str:split($label)"; + const "res" foreach "$suffixes" expression { + const "suffix","."; + const "pos","position()"; + // take last available expression (i.e can have more suffixes than expressions) + const "expr","$expressions[position() <= $pos][last()]/expression"; + choose { + when "contains($suffix,'=')" { + const "name", "substring-before($suffix,'=')"; + if "$expr/@name[. != $name]" + error > JsonTable : missplaced '=' or inconsistent names in Json data expressions. + attrib "name" value "$name"; + attrib "content" > «$expr/@content»«substring-after($suffix,'=')» + } + otherwise { + copy "$expr/@name"; + 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" { + param "expressions"; + // cloned element must be part of a HMI:List + const "targetid", "substring-after(@xlink:href,'#')"; + const "from_list", "$hmi_lists[(@id | */@id) = $targetid]"; + + choose { + when "count($from_list) > 0" { + | id("«@id»").setAttribute("xlink:href", + // obtain new target id from HMI:List widget + | "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]); + } + otherwise + 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" { + param "expressions"; + const "value_expr", "$expressions/expression[1]/@content"; + const "original", "@original"; + const "from_textstylelist", "$textstylelist_related_ns/list[elt/@eltid = $original]"; + choose { + + 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" assignement following value expression in label. + | { + | let elt = id("«@id»"); + | elt.textContent = String(«$content_expr»); + | elt.style = hmi_widgets["«$from_textstylelist/@listid»"].styles[«$value_expr»]; + | } + } + otherwise { + | id("«@id»").textContent = String(«$value_expr»); + } + } +} + + +// only labels comming from Json widget are counted in +def "func:filter_non_widget_label" { + param "elt"; + param "widget_elts"; + const "eltid" choose { + 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"{ + param "expressions"; + param "widget_elts"; + + 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"; + with "label", "$label"; + } +} + + +template "svg:*", mode="json_table_render" { + param "expressions"; + param "widget_elts"; + param "label"; + + const "new_expressions", "func:json_expressions($expressions, $label)"; + + const "elt","."; + 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" { + param "expressions"; + param "widget_elts"; + param "label"; + + // use intermediate variables for optimization + const "varprefix" > obj_«@id»_ + | try { + + foreach "$expressions/expression"{ + | let «$varprefix»«position()» = «@content»; + | if(«$varprefix»«position()» == undefined) { + | throw null; + | } + } + + // because we put values in a variables, we can replace corresponding expression with variable name + const "new_expressions" foreach "$expressions/expression" xsl:copy { + copy "@name"; + 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"; + } + | } catch(err) { + | id("«@id»").style = "display:none"; + | } +} + +} + +widget_defs("JsonTable") { + labels("data"); + 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); + | this.cache[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:*"; + } + | }, + | init() { + | this.init_common(); + foreach "$hmi_element/*[starts-with(@inkscape:label,'action_')]" { + | id("«@id»").onclick = this.make_on_click("«func:escape_quotes(@inkscape:label)»"); + } + | } + +}