svghmi/widget_jsontable.ysl2
changeset 3302 c89fc366bebd
parent 3241 fe945f1f48b7
child 3320 9fe5b4a04acc
equal deleted inserted replaced
2744:577118ebd179 3302:c89fc366bebd
       
     1 // widget_jsontable.ysl2
       
     2 
       
     3 widget_desc("JsonTable") {
       
     4     longdesc
       
     5     || 
       
     6     Send given variables as POST to http URL argument, spread returned JSON in
       
     7     SVG sub-elements of "data" labeled element.
       
     8     
       
     9     Documentation to be written. see svbghmi exemple.
       
    10     ||
       
    11 
       
    12     shortdesc > Http POST variables, spread JSON back
       
    13 
       
    14     arg name="url" accepts="string" >  
       
    15 
       
    16     path name="edit" accepts="HMI_INT, HMI_REAL, HMI_STRING" > single variable to edit
       
    17     
       
    18 }
       
    19 
       
    20 widget_class("JsonTable")
       
    21     ||
       
    22         // arbitrary defaults to avoid missing entries in query
       
    23         cache = [0,0,0];
       
    24         init_common() {
       
    25             this.spread_json_data_bound = this.spread_json_data.bind(this);
       
    26             this.handle_http_response_bound = this.handle_http_response.bind(this);
       
    27             this.fetch_error_bound = this.fetch_error.bind(this);
       
    28             this.promised = false;
       
    29         }
       
    30 
       
    31         handle_http_response(response) {
       
    32             if (!response.ok) {
       
    33               console.log("HTTP error, status = " + response.status);
       
    34             }
       
    35             return response.json();
       
    36         }
       
    37 
       
    38         fetch_error(e){
       
    39             console.log("HTTP fetch error, message = " + e.message + "Widget:" + this.element_id);
       
    40         }
       
    41 
       
    42         do_http_request(...opt) {
       
    43             this.abort_controller = new AbortController();
       
    44             return Promise.resolve().then(() => {
       
    45 
       
    46                 const query = {
       
    47                     args: this.args,
       
    48                     range: this.cache[1],
       
    49                     position: this.cache[2],
       
    50                     visible: this.visible,
       
    51                     extra: this.cache.slice(4),
       
    52                     options: opt
       
    53                 };
       
    54 
       
    55                 const options = {
       
    56                      method: 'POST',
       
    57                      body: JSON.stringify(query),
       
    58                      headers: {'Content-Type': 'application/json'},
       
    59                      signal: this.abort_controller.signal
       
    60                 };
       
    61 
       
    62                 return fetch(this.args[0], options)
       
    63                         .then(this.handle_http_response_bound)
       
    64                         .then(this.spread_json_data_bound)
       
    65                         .catch(this.fetch_error_bound);
       
    66             });
       
    67         }
       
    68 
       
    69         unsub(){
       
    70             this.abort_controller.abort();
       
    71             super.unsub();
       
    72         }
       
    73 
       
    74         sub(...args){
       
    75             this.cache[0] = undefined;
       
    76             super.sub(...args);
       
    77         }
       
    78 
       
    79         dispatch(value, oldval, index) {
       
    80 
       
    81             if(this.cache[index] != value)
       
    82                 this.cache[index] = value;
       
    83             else
       
    84                 return;
       
    85 
       
    86             if(!this.promised){
       
    87                 this.promised = true;
       
    88                 this.do_http_request().finally(() => {
       
    89                     this.promised = false;
       
    90                 });
       
    91             }
       
    92         }
       
    93         make_on_click(...options){
       
    94             let that = this;
       
    95             return function(evt){
       
    96                 that.do_http_request(...options);
       
    97             }
       
    98         }
       
    99         // on_click(evt, ...options) {
       
   100         //     this.do_http_request(...options);
       
   101         // }
       
   102     ||
       
   103 
       
   104 gen_index_xhtml {
       
   105 
       
   106 template "svg:*", mode="json_table_elt_render" {
       
   107     error > JsonTable Widget can't contain element of type «local-name()».
       
   108 }
       
   109 
       
   110 
       
   111 const "hmi_textstylelists_descs", "$parsed_widgets/widget[@type = 'TextStyleList']";
       
   112 const "hmi_textstylelists", "$hmi_elements[@id = $hmi_textstylelists_descs/@id]";
       
   113 
       
   114 const "textstylelist_related" foreach "$hmi_textstylelists" list {
       
   115     attrib "listid" value "@id";
       
   116     foreach "func:refered_elements(.)" elt {
       
   117         attrib "eltid" value "@id";
       
   118     }
       
   119 }
       
   120 const "textstylelist_related_ns", "exsl:node-set($textstylelist_related)";
       
   121 
       
   122 def "func:json_expressions" {
       
   123     param "expressions";
       
   124     param "label";
       
   125 
       
   126     // compute javascript expressions to access JSON data
       
   127     // desscribed in given svg element's "label"
       
   128     // knowing that parent element already has given "expressions".
       
   129 
       
   130     choose {
       
   131         when "$label" {
       
   132             const "suffixes", "str:split($label)";
       
   133             const "res" foreach "$suffixes" expression {
       
   134                 const "suffix",".";
       
   135                 const "pos","position()";
       
   136                 // take last available expression (i.e can have more suffixes than expressions)
       
   137                 const "expr","$expressions[position() <= $pos][last()]/expression";
       
   138                 choose {
       
   139                     when "contains($suffix,'=')" {
       
   140                         const "name", "substring-before($suffix,'=')";
       
   141                         if "$expr/@name[. != $name]"
       
   142                             error > JsonTable : missplaced '=' or inconsistent names in Json data expressions.
       
   143                         attrib "name" value "$name";
       
   144                         attrib "content" > «$expr/@content»«substring-after($suffix,'=')»
       
   145                     }
       
   146                     otherwise {
       
   147                         copy "$expr/@name";
       
   148                         attrib "content" > «$expr/@content»«$suffix»
       
   149                     }
       
   150                 }
       
   151             }
       
   152             result "exsl:node-set($res)";
       
   153         }
       
   154         // Empty labels are ignored, expressions are then passed as-is.
       
   155         otherwise result "$expressions";
       
   156     }
       
   157 
       
   158 }
       
   159 
       
   160 const "initexpr" expression attrib "content" > jdata
       
   161 const "initexpr_ns", "exsl:node-set($initexpr)";
       
   162 
       
   163 template "svg:use", mode="json_table_elt_render" {
       
   164     param "expressions";
       
   165     // cloned element must be part of a HMI:List
       
   166     const "targetid", "substring-after(@xlink:href,'#')";
       
   167     const "from_list", "$hmi_lists[(@id | */@id) = $targetid]";
       
   168 
       
   169     choose {
       
   170         when "count($from_list) > 0" {
       
   171             |         id("«@id»").setAttribute("xlink:href",
       
   172             // obtain new target id from HMI:List widget
       
   173             |             "#"+hmi_widgets["«$from_list/@id»"].items[«$expressions/expression[1]/@content»]);
       
   174         }
       
   175         otherwise
       
   176             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.
       
   177     }
       
   178 }
       
   179 
       
   180 template "svg:text", mode="json_table_elt_render" {
       
   181     param "expressions";
       
   182     const "value_expr", "$expressions/expression[1]/@content";
       
   183     const "original", "@original";
       
   184     const "from_textstylelist", "$textstylelist_related_ns/list[elt/@eltid = $original]";
       
   185     choose {
       
   186 
       
   187         when "count($from_textstylelist) > 0" {
       
   188             const "content_expr", "$expressions/expression[2]/@content";
       
   189             if "string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'"
       
   190                 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.
       
   191             |         {
       
   192             |           let elt = id("«@id»");
       
   193             |           elt.textContent = String(«$content_expr»);
       
   194             |           elt.style = hmi_widgets["«$from_textstylelist/@listid»"].styles[«$value_expr»];
       
   195             |         }
       
   196         }
       
   197         otherwise {
       
   198             |         id("«@id»").textContent = String(«$value_expr»);
       
   199         }
       
   200     }
       
   201 }
       
   202 
       
   203 
       
   204 // only labels comming from Json widget are counted in
       
   205 def "func:filter_non_widget_label" {
       
   206     param "elt";
       
   207     param "widget_elts";
       
   208     const "eltid" choose {
       
   209         when "$elt/@original" value "$elt/@original";
       
   210         otherwise value "$elt/@id";
       
   211     }
       
   212     result "$widget_elts[@id=$eltid]/@inkscape:label";
       
   213 }
       
   214 
       
   215 template "svg:*", mode="json_table_render_except_comments"{
       
   216     param "expressions";
       
   217     param "widget_elts";
       
   218 
       
   219     const "label", "func:filter_non_widget_label(., $widget_elts)";
       
   220     // filter out "# commented" elements
       
   221     if "not(starts-with($label,'#'))" 
       
   222         apply ".", mode="json_table_render"{
       
   223             with "expressions", "$expressions";
       
   224             with "widget_elts", "$widget_elts";
       
   225             with "label", "$label";
       
   226         }
       
   227 }
       
   228 
       
   229 
       
   230 template "svg:*", mode="json_table_render" {
       
   231     param "expressions";
       
   232     param "widget_elts";
       
   233     param "label";
       
   234 
       
   235     const "new_expressions", "func:json_expressions($expressions, $label)";
       
   236 
       
   237     const "elt",".";
       
   238     foreach "$new_expressions/expression[position() > 1][starts-with(@name,'onClick')]"
       
   239     |         id("«$elt/@id»").onclick = this.make_on_click('«@name»', «@content»);
       
   240 
       
   241     apply ".", mode="json_table_elt_render"
       
   242         with "expressions", "$new_expressions";
       
   243 }
       
   244 
       
   245 template "svg:g", mode="json_table_render" {
       
   246     param "expressions";
       
   247     param "widget_elts";
       
   248     param "label";
       
   249 
       
   250     // use intermediate variables for optimization
       
   251     const "varprefix" > obj_«@id»_
       
   252     |         try {
       
   253 
       
   254     foreach "$expressions/expression"{
       
   255     |          let «$varprefix»«position()» = «@content»;
       
   256     |          if(«$varprefix»«position()» == undefined) {
       
   257     |               throw null;
       
   258     |          }
       
   259     }
       
   260 
       
   261     // because we put values in a variables, we can replace corresponding expression with variable name
       
   262     const "new_expressions" foreach "$expressions/expression" xsl:copy {
       
   263         copy "@name";
       
   264         attrib "content" > «$varprefix»«position()»
       
   265     }
       
   266 
       
   267     // revert hiding in case it did happen before
       
   268     |           id("«@id»").style = "«@style»";
       
   269 
       
   270     apply "*", mode="json_table_render_except_comments" {
       
   271         with "expressions", "func:json_expressions(exsl:node-set($new_expressions), $label)";
       
   272         with "widget_elts", "$widget_elts";
       
   273     }
       
   274     |         } catch(err) {
       
   275     |           id("«@id»").style = "display:none";
       
   276     |         }
       
   277 }
       
   278 
       
   279 }
       
   280 
       
   281 widget_defs("JsonTable") {
       
   282     labels("data");
       
   283     const "data_elt", "$result_svg_ns//*[@id = $hmi_element/@id]/*[@inkscape:label = 'data']";
       
   284     |     visible: «count($data_elt/*[@inkscape:label])»,
       
   285     |     spread_json_data: function(janswer) {
       
   286     |         let [range,position,jdata] = janswer;
       
   287     |         [[1, range], [2, position], [3, this.visible]].map(([i,v]) => {
       
   288     |              this.apply_hmi_value(i,v);
       
   289     |              this.cache[i] = v;
       
   290     |         });
       
   291     apply "$data_elt", mode="json_table_render_except_comments" {
       
   292         with "expressions","$initexpr_ns";
       
   293         with "widget_elts","$hmi_element/*[@inkscape:label = 'data']/descendant::svg:*";
       
   294     }
       
   295     |     },
       
   296     |     init() {
       
   297     |        this.init_common();
       
   298     foreach "$hmi_element/*[starts-with(@inkscape:label,'action_')]" {
       
   299     |         id("«@id»").onclick = this.make_on_click("«func:escape_quotes(@inkscape:label)»");
       
   300     }
       
   301     |     }
       
   302 
       
   303 }