svghmi/widget_jsontable.ysl2
author Edouard Tisserant
Tue, 13 Jul 2021 16:16:58 +0200
branchsvghmi
changeset 3278 2bcfbea6a2a8
parent 3241 fe945f1f48b7
child 3320 9fe5b4a04acc
permissions -rw-r--r--
SVGHMI: Fixed typo on session manager unregister, leading to wrong count of sessions and then exceptions when creating more session than allowed in protocol options. Also added more safety check in protocol in case session would be missing.
// 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)»");
    }
    |     }

}