--- /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)»");
+ }
+ | }
+
+}