svghmi/widget_jsontable.ysl2
changeset 3302 c89fc366bebd
parent 3241 fe945f1f48b7
child 3320 9fe5b4a04acc
--- /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)»");
+    }
+    |     }
+
+}