|
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 } |