SVGHMI: ForEach now has working (un)subscribe. Fixed PageSwitch that wasn't behaving when jumping to current page with another path. svghmi
authorEdouard Tisserant
Tue, 24 Mar 2020 14:03:19 +0100
branchsvghmi
changeset 2895 89c02b452717
parent 2894 4cf9ad35e6d0
child 2896 99c5335ed59f
SVGHMI: ForEach now has working (un)subscribe. Fixed PageSwitch that wasn't behaving when jumping to current page with another path.
svghmi/gen_index_xhtml.xslt
svghmi/svghmi.js
svghmi/widget_foreach.ysl2
--- 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 &lt; 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=&gt;w.unsub());
+</xsl:text>
+    <xsl:text>        old_desc.relative_widgets.map(w=&gt;w.unsub());
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>    new_desc.absolute_widgets.map(w=&gt;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=&gt;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=&gt;w.unsub());
-</xsl:text>
-    <xsl:text>        old_desc.relative_widgets.map(w=&gt;w.unsub());
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>    new_desc.absolute_widgets.map(w=&gt;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=&gt;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(() =&gt; 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(() =&gt; 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>
--- 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
--- 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);
     |     },
 }