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