# HG changeset patch # User Edouard Tisserant # Date 1585054999 -3600 # Node ID 89c02b452717ff9c6d847d3aea16c33702836e6e # Parent 4cf9ad35e6d0151217a9b130b0e141f774838fdd SVGHMI: ForEach now has working (un)subscribe. Fixed PageSwitch that wasn't behaving when jumping to current page with another path. diff -r 4cf9ad35e6d0 -r 89c02b452717 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Mon Mar 23 21:44:28 2020 +0100 +++ b/svghmi/gen_index_xhtml.xslt Tue Mar 24 14:03:19 2020 +0100 @@ -747,9 +747,7 @@ <xsl:value-of select="$hmi_element/@id"/> <xsl:text>'].on_click(evt)");*/ </xsl:text> - <xsl:text> }, -</xsl:text> - <xsl:text> items: [ + <xsl:text> this.items = [ </xsl:text> <xsl:variable name="items_regex" select="concat('^',$prefix,'[0-9]+')"/> <xsl:variable name="unordered_items" select="$hmi_element//*[regexp:test(@inkscape:label, $items_regex)]"/> @@ -758,7 +756,7 @@ <xsl:variable name="elt" select="$unordered_items[@inkscape:label = $elt_label]"/> <xsl:variable name="pos" select="position()"/> <xsl:variable name="item_path" select="$items_paths[$pos]"/> - <xsl:text> [ /* item="</xsl:text> + <xsl:text> [ /* item="</xsl:text> <xsl:value-of select="$elt_label"/> <xsl:text>" path="</xsl:text> <xsl:value-of select="$item_path"/> @@ -786,7 +784,7 @@ <xsl:text>.</xsl:text> </xsl:message> </xsl:if> - <xsl:text> hmi_widgets["</xsl:text> + <xsl:text> hmi_widgets["</xsl:text> <xsl:value-of select="@id"/> <xsl:text>"]</xsl:text> <xsl:if test="position()!=last()"> @@ -795,26 +793,30 @@ <xsl:text> </xsl:text> </xsl:for-each> - <xsl:text> ]</xsl:text> + <xsl:text> ]</xsl:text> <xsl:if test="position()!=last()"> <xsl:text>,</xsl:text> </xsl:if> <xsl:text> </xsl:text> </xsl:for-each> - <xsl:text> ], + <xsl:text> ] +</xsl:text> + <xsl:text> }, +</xsl:text> + <xsl:text> item_offset: 0, </xsl:text> </xsl:template> <xsl:template mode="widget_subscribe" match="widget[@type='ForEach']"> <xsl:text> sub: function(off){ </xsl:text> - <xsl:text> /*subscribe.call(this,off);*/ + <xsl:text> subscribe_foreach.call(this,off); </xsl:text> <xsl:text> }, </xsl:text> <xsl:text> unsub: function(){ </xsl:text> - <xsl:text> /*unsubscribe.call(this);*/ + <xsl:text> unsubscribe_foreach.call(this); </xsl:text> <xsl:text> }, </xsl:text> @@ -1315,6 +1317,14 @@ </xsl:text> <xsl:text> } </xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(current_subscribed_page_index != current_visible_page_index){ +</xsl:text> + <xsl:text> apply_cache(); +</xsl:text> + <xsl:text> } +</xsl:text> <xsl:text> apply_updates(); </xsl:text> <xsl:text> requestAnimationFrameID = null; @@ -1649,6 +1659,10 @@ </xsl:text> <xsl:text>var current_subscribed_page; </xsl:text> + <xsl:text>var current_visible_page_index; +</xsl:text> + <xsl:text>var current_subscribed_page_index; +</xsl:text> <xsl:text> </xsl:text> <xsl:text>function prepare_svg() { @@ -1675,9 +1689,117 @@ </xsl:text> <xsl:text> return; </xsl:text> - <xsl:text> } else if(page_name == current_visible_page){ -</xsl:text> - <xsl:text> /* already in that page */ + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(page_name == undefined) +</xsl:text> + <xsl:text> page_name = current_subscribed_page; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> switch_subscribed_page(page_name, page_index); +</xsl:text> + <xsl:text>}; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function* chain(a,b){ +</xsl:text> + <xsl:text> yield* a; +</xsl:text> + <xsl:text> yield* b; +</xsl:text> + <xsl:text>}; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function unsubscribe(){ +</xsl:text> + <xsl:text> /* remove subsribers */ +</xsl:text> + <xsl:text> for(let index of this.indexes){ +</xsl:text> + <xsl:text> let idx = index + this.offset; +</xsl:text> + <xsl:text> subscribers[idx].delete(this); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> this.offset = 0; +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function subscribe(new_offset=0){ +</xsl:text> + <xsl:text> /* set the offset because relative */ +</xsl:text> + <xsl:text> this.offset = new_offset; +</xsl:text> + <xsl:text> /* add this's subsribers */ +</xsl:text> + <xsl:text> for(let index of this.indexes){ +</xsl:text> + <xsl:text> subscribers[index + new_offset].add(this); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function unsubscribe_foreach(){ +</xsl:text> + <xsl:text> for(let item of this.items){ +</xsl:text> + <xsl:text> for(let widget of item) { +</xsl:text> + <xsl:text> unsubscribe.call(widget); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function subscribe_foreach(new_offset=0){ +</xsl:text> + <xsl:text> for(let i = 0; i < this.items.length; i++) { +</xsl:text> + <xsl:text> let item = this.items[i]; +</xsl:text> + <xsl:text> let orig_item_index = this.index_pool[i]; +</xsl:text> + <xsl:text> let item_index = this.index_pool[i+this.item_offset]; +</xsl:text> + <xsl:text> let item_index_offset = item_index - orig_item_index; +</xsl:text> + <xsl:text> for(let widget of item) { +</xsl:text> + <xsl:text> subscribe.call(widget,new_offset + item_index_offset); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function switch_subscribed_page(page_name, page_index) { +</xsl:text> + <xsl:text> let old_desc = page_desc[current_subscribed_page]; +</xsl:text> + <xsl:text> let new_desc = page_desc[page_name]; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(new_desc == undefined){ </xsl:text> <xsl:text> /* TODO LOG ERROR */ </xsl:text> @@ -1685,229 +1807,171 @@ </xsl:text> <xsl:text> } </xsl:text> - <xsl:text> switch_subscribed_page(page_name, page_index); + <xsl:text> +</xsl:text> + <xsl:text> if(page_index == undefined){ +</xsl:text> + <xsl:text> page_index = new_desc.page_index; +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(old_desc){ +</xsl:text> + <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub()); +</xsl:text> + <xsl:text> old_desc.relative_widgets.map(w=>w.unsub()); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> new_desc.absolute_widgets.map(w=>w.sub()); +</xsl:text> + <xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; +</xsl:text> + <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset)); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> update_subscriptions(); +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> current_subscribed_page = page_name; +</xsl:text> + <xsl:text> current_subscribed_page_index = page_index; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> requestHMIAnimation(); +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>function switch_visible_page(page_name) { +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> let old_desc = page_desc[current_visible_page]; +</xsl:text> + <xsl:text> let new_desc = page_desc[page_name]; +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> if(old_desc){ +</xsl:text> + <xsl:text> for(let eltid in old_desc.required_detachables){ +</xsl:text> + <xsl:text> if(!(eltid in new_desc.required_detachables)){ +</xsl:text> + <xsl:text> let [element, parent] = old_desc.required_detachables[eltid]; +</xsl:text> + <xsl:text> parent.removeChild(element); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> for(let eltid in new_desc.required_detachables){ +</xsl:text> + <xsl:text> if(!(eltid in old_desc.required_detachables)){ +</xsl:text> + <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; +</xsl:text> + <xsl:text> parent.appendChild(element); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> }else{ +</xsl:text> + <xsl:text> for(let eltid in new_desc.required_detachables){ +</xsl:text> + <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; +</xsl:text> + <xsl:text> parent.appendChild(element); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); +</xsl:text> + <xsl:text> current_visible_page = page_name; </xsl:text> <xsl:text>}; </xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function* chain(a,b){ -</xsl:text> - <xsl:text> yield* a; -</xsl:text> - <xsl:text> yield* b; + <xsl:text> +</xsl:text> + <xsl:text>function apply_cache() { +</xsl:text> + <xsl:text> let new_desc = page_desc[current_visible_page]; +</xsl:text> + <xsl:text> for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){ +</xsl:text> + <xsl:text> for(let index of widget.indexes){ +</xsl:text> + <xsl:text> /* dispatch current cache in newly opened page widgets */ +</xsl:text> + <xsl:text> let realindex = index+widget.offset; +</xsl:text> + <xsl:text> let cached_val = cache[realindex]; +</xsl:text> + <xsl:text> if(cached_val != undefined) +</xsl:text> + <xsl:text> dispatch_value_to_widget(widget, realindex, cached_val, cached_val); +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> } +</xsl:text> + <xsl:text> current_visible_page_index = current_subscribed_page_index; +</xsl:text> + <xsl:text>} +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text> +</xsl:text> + <xsl:text>// Once connection established +</xsl:text> + <xsl:text>ws.onopen = function (evt) { +</xsl:text> + <xsl:text> init_widgets(); +</xsl:text> + <xsl:text> send_reset(); +</xsl:text> + <xsl:text> // show main page +</xsl:text> + <xsl:text> prepare_svg(); +</xsl:text> + <xsl:text> switch_page(default_page); </xsl:text> <xsl:text>}; </xsl:text> <xsl:text> </xsl:text> - <xsl:text>function unsubscribe(){ -</xsl:text> - <xsl:text> widget = this; -</xsl:text> - <xsl:text> /* remove subsribers */ -</xsl:text> - <xsl:text> for(let index of widget.indexes){ -</xsl:text> - <xsl:text> let idx = index + widget.offset; -</xsl:text> - <xsl:text> subscribers[idx].delete(widget); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> widget.offset = 0; -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function subscribe(new_offset=0){ -</xsl:text> - <xsl:text> widget = this; -</xsl:text> - <xsl:text> /* set the offset because relative */ -</xsl:text> - <xsl:text> widget.offset = new_offset; -</xsl:text> - <xsl:text> /* add widget's subsribers */ -</xsl:text> - <xsl:text> for(let index of widget.indexes){ -</xsl:text> - <xsl:text> subscribers[index + new_offset].add(widget); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function switch_subscribed_page(page_name, page_index) { -</xsl:text> - <xsl:text> let old_desc = page_desc[current_subscribed_page]; -</xsl:text> - <xsl:text> let new_desc = page_desc[page_name]; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(new_desc == undefined){ -</xsl:text> - <xsl:text> /* TODO LOG ERROR */ -</xsl:text> - <xsl:text> return; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(page_index == undefined){ -</xsl:text> - <xsl:text> page_index = new_desc.page_index; -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(old_desc){ -</xsl:text> - <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub()); -</xsl:text> - <xsl:text> old_desc.relative_widgets.map(w=>w.unsub()); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> new_desc.absolute_widgets.map(w=>w.sub()); -</xsl:text> - <xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; -</xsl:text> - <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset)); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> update_subscriptions(); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> current_subscribed_page = page_name; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> requestHMIAnimation(); -</xsl:text> - <xsl:text>} -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>function switch_visible_page(page_name) { -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> let old_desc = page_desc[current_visible_page]; -</xsl:text> - <xsl:text> let new_desc = page_desc[page_name]; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> if(old_desc){ -</xsl:text> - <xsl:text> for(let eltid in old_desc.required_detachables){ -</xsl:text> - <xsl:text> if(!(eltid in new_desc.required_detachables)){ -</xsl:text> - <xsl:text> let [element, parent] = old_desc.required_detachables[eltid]; -</xsl:text> - <xsl:text> parent.removeChild(element); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> for(let eltid in new_desc.required_detachables){ -</xsl:text> - <xsl:text> if(!(eltid in old_desc.required_detachables)){ -</xsl:text> - <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; -</xsl:text> - <xsl:text> parent.appendChild(element); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> }else{ -</xsl:text> - <xsl:text> for(let eltid in new_desc.required_detachables){ -</xsl:text> - <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; -</xsl:text> - <xsl:text> parent.appendChild(element); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){ -</xsl:text> - <xsl:text> for(let index of widget.indexes){ -</xsl:text> - <xsl:text> /* dispatch current cache in newly opened page widgets */ -</xsl:text> - <xsl:text> let cached_val = cache[index]; -</xsl:text> - <xsl:text> if(cached_val != undefined) -</xsl:text> - <xsl:text> dispatch_value_to_widget(widget, index, cached_val, cached_val); -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> } -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); -</xsl:text> - <xsl:text> current_visible_page = page_name; + <xsl:text>ws.onclose = function (evt) { +</xsl:text> + <xsl:text> // TODO : add visible notification while waiting for reload +</xsl:text> + <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); +</xsl:text> + <xsl:text> // TODO : re-enable auto reload when not in debug +</xsl:text> + <xsl:text> //window.setTimeout(() => location.reload(true), 10000); +</xsl:text> + <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); +</xsl:text> + <xsl:text> </xsl:text> <xsl:text>}; </xsl:text> - <xsl:text> -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>// Once connection established -</xsl:text> - <xsl:text>ws.onopen = function (evt) { -</xsl:text> - <xsl:text> init_widgets(); -</xsl:text> - <xsl:text> send_reset(); -</xsl:text> - <xsl:text> // show main page -</xsl:text> - <xsl:text> prepare_svg(); -</xsl:text> - <xsl:text> switch_page(default_page); -</xsl:text> - <xsl:text>}; -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>ws.onclose = function (evt) { -</xsl:text> - <xsl:text> // TODO : add visible notification while waiting for reload -</xsl:text> - <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); -</xsl:text> - <xsl:text> // TODO : re-enable auto reload when not in debug -</xsl:text> - <xsl:text> //window.setTimeout(() => location.reload(true), 10000); -</xsl:text> - <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); -</xsl:text> - <xsl:text> -</xsl:text> - <xsl:text>}; -</xsl:text> </xsl:template> </xsl:stylesheet> diff -r 4cf9ad35e6d0 -r 89c02b452717 svghmi/svghmi.js --- a/svghmi/svghmi.js Mon Mar 23 21:44:28 2020 +0100 +++ b/svghmi/svghmi.js Tue Mar 24 14:03:19 2020 +0100 @@ -86,6 +86,10 @@ if(current_subscribed_page != current_visible_page){ switch_visible_page(current_subscribed_page); } + + if(current_subscribed_page_index != current_visible_page_index){ + apply_cache(); + } apply_updates(); requestAnimationFrameID = null; } @@ -253,6 +257,8 @@ var current_visible_page; var current_subscribed_page; +var current_visible_page_index; +var current_subscribed_page_index; function prepare_svg() { for(let eltid in detachable_elements){ @@ -266,11 +272,11 @@ /* page switch already going */ /* TODO LOG ERROR */ return; - } else if(page_name == current_visible_page){ - /* already in that page */ - /* TODO LOG ERROR */ - return; - } + } + + if(page_name == undefined) + page_name = current_subscribed_page; + switch_subscribed_page(page_name, page_index); }; @@ -280,22 +286,40 @@ }; function unsubscribe(){ - widget = this; /* remove subsribers */ - for(let index of widget.indexes){ - let idx = index + widget.offset; - subscribers[idx].delete(widget); - } - widget.offset = 0; + for(let index of this.indexes){ + let idx = index + this.offset; + subscribers[idx].delete(this); + } + this.offset = 0; } function subscribe(new_offset=0){ - widget = this; /* set the offset because relative */ - widget.offset = new_offset; - /* add widget's subsribers */ - for(let index of widget.indexes){ - subscribers[index + new_offset].add(widget); + this.offset = new_offset; + /* add this's subsribers */ + for(let index of this.indexes){ + subscribers[index + new_offset].add(this); + } +} + +function unsubscribe_foreach(){ + for(let item of this.items){ + for(let widget of item) { + unsubscribe.call(widget); + } + } +} + +function subscribe_foreach(new_offset=0){ + for(let i = 0; i < this.items.length; i++) { + let item = this.items[i]; + let orig_item_index = this.index_pool[i]; + let item_index = this.index_pool[i+this.item_offset]; + let item_index_offset = item_index - orig_item_index; + for(let widget of item) { + subscribe.call(widget,new_offset + item_index_offset); + } } } @@ -323,6 +347,7 @@ update_subscriptions(); current_subscribed_page = page_name; + current_subscribed_page_index = page_index; requestHMIAnimation(); } @@ -352,18 +377,24 @@ } } + svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); + current_visible_page = page_name; +}; + +function apply_cache() { + let new_desc = page_desc[current_visible_page]; for(let widget of chain(new_desc.absolute_widgets,new_desc.relative_widgets)){ for(let index of widget.indexes){ /* dispatch current cache in newly opened page widgets */ - let cached_val = cache[index]; + let realindex = index+widget.offset; + let cached_val = cache[realindex]; if(cached_val != undefined) - dispatch_value_to_widget(widget, index, cached_val, cached_val); - } - } - - svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); - current_visible_page = page_name; -}; + dispatch_value_to_widget(widget, realindex, cached_val, cached_val); + } + } + current_visible_page_index = current_subscribed_page_index; +} + // Once connection established diff -r 4cf9ad35e6d0 -r 89c02b452717 svghmi/widget_foreach.ysl2 --- a/svghmi/widget_foreach.ysl2 Mon Mar 23 21:44:28 2020 +0100 +++ b/svghmi/widget_foreach.ysl2 Tue Mar 24 14:03:19 2020 +0100 @@ -25,9 +25,8 @@ | ], | init: function() { | /* TODO elt.setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click(evt)");*/ - | }, - | items: [ + | this.items = [ const "items_regex","concat('^',$prefix,'[0-9]+')"; const "unordered_items","$hmi_element//*[regexp:test(@inkscape:label, $items_regex)]"; foreach "$unordered_items" { @@ -35,27 +34,27 @@ const "elt","$unordered_items[@inkscape:label = $elt_label]"; const "pos","position()"; const "item_path", "$items_paths[$pos]"; - | [ /* item="«$elt_label»" path="«$item_path»" */ + | [ /* item="«$elt_label»" path="«$item_path»" */ if "count($elt)=0" error > Missing item labeled «$elt_label» in ForEach widget «$hmi_element/@id» foreach "func:refered_elements($elt)[@id = $hmi_elements/@id][not(@id = $elt/@id)]" { if "not(func:is_descendant_path(func:widget(@id)/path/@value, $item_path))" error > Widget id="«@id»" label="«@inkscape:label»" is having wrong path. Accroding to ForEach widget ancestor id="«$hmi_element/@id»", path should be descendant of «$item_path». - | hmi_widgets["«@id»"]`if "position()!=last()" > ,` + | hmi_widgets["«@id»"]`if "position()!=last()" > ,` } - | ]`if "position()!=last()" > ,` + | ]`if "position()!=last()" > ,` } - | ], + | ] + | }, + | item_offset: 0, } template "widget[@type='ForEach']", mode="widget_subscribe"{ // param "hmi_element"; | sub: function(off){ - | /*subscribe.call(this,off);*/ - /* TODO */ + | subscribe_foreach.call(this,off); | }, | unsub: function(){ - | /*unsubscribe.call(this);*/ - /* TODO */ + | unsubscribe_foreach.call(this); | }, }