merge svghmi
authorEdouard Tisserant
Mon, 11 May 2020 11:33:00 +0200
branchsvghmi
changeset 2957 c0ea5015e46f
parent 2955 64e603b84aa3 (diff)
parent 2956 6b01738308bb (current diff)
child 2958 895d3f2b1786
child 2964 a7b11c9f468e
merge
--- a/svghmi/detachable_pages.ysl2	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/detachable_pages.ysl2	Mon May 11 11:33:00 2020 +0200
@@ -131,7 +131,7 @@
            ancestor-or-self::*[@id = $detachable_elements/@id]""";
 
     |   "«$desc/arg[1]/@value»": {
-    |     widget: hmi_widgets["«@id»"],
+    //|     widget: hmi_widgets["«@id»"],
     |     bbox: [«$p/@x», «$p/@y», «$p/@w», «$p/@h»],
     if "$desc/path/@value" {
         if "count($desc/path/@index)=0"
--- a/svghmi/gen_index_xhtml.xslt	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt	Mon May 11 11:33:00 2020 +0200
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:str="http://exslt.org/strings" xmlns:func="http://exslt.org/functions" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:debug="debug" xmlns:preamble="preamble" xmlns:declarations="declarations" xmlns:definitions="definitions" xmlns:epilogue="epilogue" xmlns:ns="beremiz" version="1.0" extension-element-prefixes="ns func exsl regexp str dyn" exclude-result-prefixes="ns func exsl regexp str dyn debug preamble epilogue declarations definitions">
-  <xsl:output cdata-section-elements="xhtml:script" method="xml"/>
+<xsl:stylesheet xmlns:ns="beremiz" xmlns:definitions="definitions" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:func="http://exslt.org/functions" xmlns:epilogue="epilogue" xmlns:preamble="preamble" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:svg="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:str="http://exslt.org/strings" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:exsl="http://exslt.org/common" xmlns:declarations="declarations" xmlns:debug="debug" exclude-result-prefixes="ns func exsl regexp str dyn debug preamble epilogue declarations definitions" extension-element-prefixes="ns func exsl regexp str dyn" version="1.0">
+  <xsl:output method="xml" cdata-section-elements="xhtml:script"/>
   <xsl:variable name="svg" select="/svg:svg"/>
   <xsl:variable name="hmi_elements" select="//svg:*[starts-with(@inkscape:label, 'HMI:')]"/>
   <xsl:variable name="hmitree" select="ns:GetHMITree()"/>
@@ -19,6 +19,14 @@
   <xsl:variable name="indexed_hmitree" select="exsl:node-set($_indexed_hmitree)"/>
   <preamble:hmi-tree/>
   <xsl:template match="preamble:hmi-tree">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var hmi_hash = [</xsl:text>
     <xsl:value-of select="$hmitree/@hash"/>
     <xsl:text>];
@@ -51,6 +59,8 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:template mode="index" match="*">
     <xsl:param name="index" select="0"/>
@@ -215,6 +225,14 @@
   </xsl:template>
   <debug:hmi-tree/>
   <xsl:template match="debug:hmi-tree">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>Raw HMI tree
 </xsl:text>
     <xsl:apply-templates mode="testtree" select="$hmitree"/>
@@ -229,10 +247,20 @@
 </xsl:text>
     <xsl:copy-of select="_parsed_widgets"/>
     <xsl:apply-templates mode="testtree" select="$parsed_widgets"/>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:variable name="geometry" select="ns:GetSVGGeometry()"/>
   <debug:geometry/>
   <xsl:template match="debug:geometry">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>ID, x, y, w, h
 </xsl:text>
     <xsl:for-each select="$geometry">
@@ -249,6 +277,8 @@
       <xsl:text>
 </xsl:text>
     </xsl:for-each>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <func:function name="func:intersect_1d">
     <xsl:param name="a0"/>
@@ -322,10 +352,20 @@
   <xsl:template match="preamble:default-page">
     <xsl:text>
 </xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var default_page = "</xsl:text>
     <xsl:value-of select="$default_page"/>
     <xsl:text>";
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:variable name="keypads_descs" select="$parsed_widgets/widget[@type = 'Keypad']"/>
   <xsl:variable name="keypads" select="$hmi_elements[@id = $keypads_descs/@id]"/>
@@ -387,6 +427,14 @@
   <xsl:template match="declarations:detachable-elements">
     <xsl:text>
 </xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var detachable_elements = {
 </xsl:text>
     <xsl:for-each select="$detachable_elements">
@@ -405,6 +453,8 @@
     </xsl:for-each>
     <xsl:text>}
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:variable name="forEach_widgets_ids" select="$parsed_widgets/widget[@type = 'ForEach']/@id"/>
   <xsl:variable name="forEach_widgets" select="$hmi_elements[@id = $forEach_widgets_ids]"/>
@@ -422,10 +472,6 @@
     <xsl:value-of select="$desc/arg[1]/@value"/>
     <xsl:text>": {
 </xsl:text>
-    <xsl:text>    widget: hmi_widgets["</xsl:text>
-    <xsl:value-of select="@id"/>
-    <xsl:text>"],
-</xsl:text>
     <xsl:text>    bbox: [</xsl:text>
     <xsl:value-of select="$p/@x"/>
     <xsl:text>, </xsl:text>
@@ -531,17 +577,35 @@
   <xsl:template match="declarations:page-desc">
     <xsl:text>
 </xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var page_desc = {
 </xsl:text>
     <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
     <xsl:text>}
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:template mode="per_page_widget_template" match="*"/>
   <debug:detachable-pages/>
   <xsl:template match="debug:detachable-pages">
     <xsl:text>
 </xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>DETACHABLES:
 </xsl:text>
     <xsl:for-each select="$detachable_elements">
@@ -558,6 +622,8 @@
       <xsl:text>
 </xsl:text>
     </xsl:for-each>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:template mode="inline_svg" match="@* | node()">
     <xsl:if test="not(@id = $discardable_elements/@id)">
@@ -672,17 +738,35 @@
   <xsl:variable name="result_svg_ns" select="exsl:node-set($result_svg)"/>
   <preamble:inline-svg/>
   <xsl:template match="preamble:inline-svg">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>let id = document.getElementById.bind(document);
 </xsl:text>
     <xsl:text>var svg_root = id("</xsl:text>
     <xsl:value-of select="$svg/@id"/>
     <xsl:text>");
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <debug:clone-unlinking/>
   <xsl:template match="debug:clone-unlinking">
     <xsl:text>
 </xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>Unlinked :
 </xsl:text>
     <xsl:for-each select="$to_unlink">
@@ -690,99 +774,222 @@
       <xsl:text>
 </xsl:text>
     </xsl:for-each>
-  </xsl:template>
-  <xsl:template mode="hmi_elements" match="svg:*">
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="hmi_widgets" match="svg:*">
     <xsl:variable name="widget" select="func:widget(@id)"/>
     <xsl:variable name="eltid" select="@id"/>
+    <xsl:variable name="args">
+      <xsl:for-each select="$widget/arg">
+        <xsl:text>"</xsl:text>
+        <xsl:value-of select="@value"/>
+        <xsl:text>"</xsl:text>
+        <xsl:if test="position()!=last()">
+          <xsl:text>,</xsl:text>
+        </xsl:if>
+      </xsl:for-each>
+    </xsl:variable>
+    <xsl:variable name="indexes">
+      <xsl:for-each select="$widget/path">
+        <xsl:choose>
+          <xsl:when test="not(@index)">
+            <xsl:message terminate="no">
+              <xsl:text>Widget </xsl:text>
+              <xsl:value-of select="$widget/@type"/>
+              <xsl:text> id="</xsl:text>
+              <xsl:value-of select="$eltid"/>
+              <xsl:text>" : No match for path "</xsl:text>
+              <xsl:value-of select="@value"/>
+              <xsl:text>" in HMI tree</xsl:text>
+            </xsl:message>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:value-of select="@index"/>
+            <xsl:if test="position()!=last()">
+              <xsl:text>,</xsl:text>
+            </xsl:if>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:for-each>
+    </xsl:variable>
     <xsl:text>  "</xsl:text>
     <xsl:value-of select="@id"/>
-    <xsl:text>": {
-</xsl:text>
-    <xsl:text>    type: "</xsl:text>
+    <xsl:text>": new </xsl:text>
     <xsl:value-of select="$widget/@type"/>
-    <xsl:text>",
-</xsl:text>
-    <xsl:text>    args: [
-</xsl:text>
-    <xsl:for-each select="$widget/arg">
-      <xsl:text>        "</xsl:text>
-      <xsl:value-of select="@value"/>
-      <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>    offset: 0,
-</xsl:text>
-    <xsl:text>    indexes: [
-</xsl:text>
-    <xsl:for-each select="$widget/path">
-      <xsl:choose>
-        <xsl:when test="not(@index)">
-          <xsl:message terminate="no">
-            <xsl:text>Widget </xsl:text>
-            <xsl:value-of select="$widget/@type"/>
-            <xsl:text> id="</xsl:text>
-            <xsl:value-of select="$eltid"/>
-            <xsl:text>" : No match for path "</xsl:text>
-            <xsl:value-of select="@value"/>
-            <xsl:text>" in HMI tree</xsl:text>
-          </xsl:message>
-        </xsl:when>
-        <xsl:otherwise>
-          <xsl:text>        </xsl:text>
-          <xsl:value-of select="@index"/>
-          <xsl:text> /* </xsl:text>
-          <xsl:value-of select="@value"/>
-          <xsl:text> */ </xsl:text>
-          <xsl:if test="position()!=last()">
-            <xsl:text>,</xsl:text>
-          </xsl:if>
-          <xsl:text>
-</xsl:text>
-        </xsl:otherwise>
-      </xsl:choose>
-    </xsl:for-each>
-    <xsl:text>    ],
-</xsl:text>
-    <xsl:text>    element: id("</xsl:text>
+    <xsl:text>Widget ("</xsl:text>
     <xsl:value-of select="@id"/>
-    <xsl:text>"),
+    <xsl:text>",[</xsl:text>
+    <xsl:value-of select="$args"/>
+    <xsl:text>],[</xsl:text>
+    <xsl:value-of select="$indexes"/>
+    <xsl:text>],{
 </xsl:text>
     <xsl:apply-templates mode="widget_defs" select="$widget">
       <xsl:with-param name="hmi_element" select="."/>
     </xsl:apply-templates>
-    <xsl:apply-templates mode="widget_subscribe" select="$widget">
-      <xsl:with-param name="hmi_element" select="."/>
-    </xsl:apply-templates>
-    <xsl:text>  }</xsl:text>
+    <xsl:text>  })</xsl:text>
     <xsl:if test="position()!=last()">
       <xsl:text>,</xsl:text>
     </xsl:if>
     <xsl:text>
 </xsl:text>
   </xsl:template>
+  <func:function name="func:unique_types">
+    <xsl:param name="elts_with_type"/>
+    <xsl:choose>
+      <xsl:when test="count($elts_with_type) &gt; 1">
+        <xsl:variable name="prior_results" select="func:unique_types($elts_with_type[position()!=last()])"/>
+        <xsl:choose>
+          <xsl:when test="$elts_with_type[last()][@type = $prior_results/@type]">
+            <func:result select="$prior_results"/>
+          </xsl:when>
+          <xsl:otherwise>
+            <func:result select="$prior_results | $elts_with_type[last()]"/>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:when>
+      <xsl:otherwise>
+        <func:result select="$elts_with_type"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </func:function>
+  <preamble:widget-base-class/>
+  <xsl:template match="preamble:widget-base-class">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>class Widget {
+</xsl:text>
+    <xsl:text>    constructor(elt_id,args,indexes,members){
+</xsl:text>
+    <xsl:text>        this.element = id(elt_id);
+</xsl:text>
+    <xsl:text>        this.args = args;
+</xsl:text>
+    <xsl:text>        this.indexes = indexes;
+</xsl:text>
+    <xsl:text>        this.offset = 0;
+</xsl:text>
+    <xsl:text>        Object.keys(members).forEach(prop =&gt; this[prop]=members[prop]);
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>    unsub(){
+</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>    sub(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>        need_cache_apply.push(this); 
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    apply_cache() {
+</xsl:text>
+    <xsl:text>        for(let index of this.indexes){
+</xsl:text>
+    <xsl:text>            /* dispatch current cache in newly opened page widgets */
+</xsl:text>
+    <xsl:text>            let realindex = index+this.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(this, realindex, cached_val, cached_val);
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
+  <preamble:hmi-classes/>
+  <xsl:template match="preamble:hmi-classes">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:variable name="used_widget_types" select="func:unique_types($parsed_widgets/widget)"/>
+    <xsl:apply-templates mode="widget_class" select="$used_widget_types"/>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
+  <xsl:template mode="widget_class" match="widget">
+    <xsl:text>class </xsl:text>
+    <xsl:value-of select="@type"/>
+    <xsl:text>Widget extends Widget{
+</xsl:text>
+    <xsl:text>    /* empty class, as </xsl:text>
+    <xsl:value-of select="@type"/>
+    <xsl:text> widget didn't provide any */
+</xsl:text>
+    <xsl:text>}
+</xsl:text>
+  </xsl:template>
+  <xsl:variable name="excluded_types" select="str:split('Page Lang List')"/>
+  <xsl:variable name="excluded_ids" select="$parsed_widgets/widget[not(@type = $excluded_types)]/@id"/>
   <preamble:hmi-elements/>
   <xsl:template match="preamble:hmi-elements">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var hmi_widgets = {
 </xsl:text>
-    <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/>
+    <xsl:apply-templates mode="hmi_widgets" select="$hmi_elements[@id = $excluded_ids]"/>
     <xsl:text>}
 </xsl:text>
-  </xsl:template>
-  <xsl:template mode="widget_subscribe" match="widget">
-    <xsl:text>    sub: subscribe,
-</xsl:text>
-    <xsl:text>    unsub: unsubscribe,
-</xsl:text>
-    <xsl:text>    apply_cache: widget_apply_cache,
-</xsl:text>
-  </xsl:template>
-  <xsl:template mode="widget_subscribe" match="widget[@type='Page']"/>
+    <xsl:text>
+</xsl:text>
+  </xsl:template>
   <xsl:template name="defs_by_labels">
     <xsl:param name="labels" select="''"/>
     <xsl:param name="mandatory" select="'yes'"/>
@@ -1658,125 +1865,114 @@
 </xsl:text>
     <xsl:text>    item_offset: 0,
 </xsl:text>
-    <xsl:text>    on_click: foreach_onclick,
-</xsl:text>
-  </xsl:template>
-  <xsl:template mode="widget_subscribe" match="widget[@type='ForEach']">
-    <xsl:text>    sub: foreach_subscribe,
-</xsl:text>
-    <xsl:text>    unsub: foreach_unsubscribe,
-</xsl:text>
-    <xsl:text>    apply_cache: foreach_apply_cache,
-</xsl:text>
-  </xsl:template>
-  <definitions:foreach/>
-  <xsl:template match="definitions:foreach">
-    <xsl:text>function foreach_unsubscribe(){
-</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:template>
+  <xsl:template mode="widget_class" match="widget[@type='ForEach']">
+    <xsl:text>class ForEachWidget extends Widget{
+</xsl:text>
+    <xsl:text>    unsub(){
+</xsl:text>
+    <xsl:text>        for(let item of this.items){
+</xsl:text>
+    <xsl:text>            for(let widget of item) {
+</xsl:text>
+    <xsl:text>                widget.unsub();
+</xsl:text>
+    <xsl:text>            }
 </xsl:text>
     <xsl:text>        }
 </xsl:text>
+    <xsl:text>        this.offset = 0;
+</xsl:text>
     <xsl:text>    }
 </xsl:text>
-    <xsl:text>    this.offset = 0;
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    foreach_widgets_do(new_offset, todo){
+</xsl:text>
+    <xsl:text>        this.offset = new_offset;
+</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>                todo(widget).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>    sub(new_offset=0){
+</xsl:text>
+    <xsl:text>        this.foreach_widgets_do(new_offset, w=&gt;w.sub);
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    apply_cache() {
+</xsl:text>
+    <xsl:text>        this.foreach_widgets_do(this.offset, w=&gt;w.apply_cache);
+</xsl:text>
+    <xsl:text>    }
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>    on_click(opstr, evt) {
+</xsl:text>
+    <xsl:text>        let new_item_offset = eval(String(this.item_offset)+opstr);
+</xsl:text>
+    <xsl:text>        if(new_item_offset + this.items.length &gt; this.index_pool.length) {
+</xsl:text>
+    <xsl:text>            if(this.item_offset + this.items.length == this.index_pool.length)
+</xsl:text>
+    <xsl:text>                new_item_offset = 0;
+</xsl:text>
+    <xsl:text>            else
+</xsl:text>
+    <xsl:text>                new_item_offset = this.index_pool.length - this.items.length;
+</xsl:text>
+    <xsl:text>        } else if(new_item_offset &lt; 0) {
+</xsl:text>
+    <xsl:text>            if(this.item_offset == 0)
+</xsl:text>
+    <xsl:text>                new_item_offset = this.index_pool.length - this.items.length;
+</xsl:text>
+    <xsl:text>            else
+</xsl:text>
+    <xsl:text>                new_item_offset = 0;
+</xsl:text>
+    <xsl:text>        }
+</xsl:text>
+    <xsl:text>        this.item_offset = new_item_offset;
+</xsl:text>
+    <xsl:text>        this.unsub();
+</xsl:text>
+    <xsl:text>        this.sub(this.offset);
+</xsl:text>
+    <xsl:text>        update_subscriptions();
+</xsl:text>
+    <xsl:text>        need_cache_apply.push(this);
+</xsl:text>
+    <xsl:text>        jumps_need_update = true;
+</xsl:text>
+    <xsl:text>        requestHMIAnimation();
+</xsl:text>
+    <xsl:text>    }
 </xsl:text>
     <xsl:text>}
 </xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function foreach_widgets_do(new_offset, todo){
-</xsl:text>
-    <xsl:text>    this.offset = new_offset;
-</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>            todo.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 foreach_subscribe(new_offset=0){
-</xsl:text>
-    <xsl:text>    foreach_widgets_do.call(this, new_offset, subscribe);
-</xsl:text>
-    <xsl:text>}
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function foreach_apply_cache() {
-</xsl:text>
-    <xsl:text>    foreach_widgets_do.call(this, this.offset, widget_apply_cache);
-</xsl:text>
-    <xsl:text>}
-</xsl:text>
-    <xsl:text>
-</xsl:text>
-    <xsl:text>function foreach_onclick(opstr, evt) {
-</xsl:text>
-    <xsl:text>    new_item_offset = eval(String(this.item_offset)+opstr)
-</xsl:text>
-    <xsl:text>    if(new_item_offset + this.items.length &gt; this.index_pool.length) {
-</xsl:text>
-    <xsl:text>        if(this.item_offset + this.items.length == this.index_pool.length)
-</xsl:text>
-    <xsl:text>            new_item_offset = 0;
-</xsl:text>
-    <xsl:text>        else
-</xsl:text>
-    <xsl:text>            new_item_offset = this.index_pool.length - this.items.length;
-</xsl:text>
-    <xsl:text>    } else if(new_item_offset &lt; 0) {
-</xsl:text>
-    <xsl:text>        if(this.item_offset == 0)
-</xsl:text>
-    <xsl:text>            new_item_offset = this.index_pool.length - this.items.length;
-</xsl:text>
-    <xsl:text>        else
-</xsl:text>
-    <xsl:text>            new_item_offset = 0;
-</xsl:text>
-    <xsl:text>    }
-</xsl:text>
-    <xsl:text>    this.item_offset = new_item_offset;
-</xsl:text>
-    <xsl:text>    off = this.offset;
-</xsl:text>
-    <xsl:text>    foreach_unsubscribe.call(this);
-</xsl:text>
-    <xsl:text>    foreach_subscribe.call(this,off);
-</xsl:text>
-    <xsl:text>    update_subscriptions();
-</xsl:text>
-    <xsl:text>    need_cache_apply.push(this);
-</xsl:text>
-    <xsl:text>    jumps_need_update = true;
-</xsl:text>
-    <xsl:text>    requestHMIAnimation();
-</xsl:text>
-    <xsl:text>}
-</xsl:text>
-    <xsl:text>
-</xsl:text>
   </xsl:template>
   <xsl:template mode="widget_defs" match="widget[@type='Input']">
     <xsl:param name="hmi_element"/>
@@ -2003,45 +2199,22 @@
       <xsl:text>        this.inactive_elt_style = this.inactive_elt.getAttribute("style");
 </xsl:text>
     </xsl:if>
-    <xsl:if test="$have_disability">
-      <xsl:text>        this.disabled_elt_style = this.disabled_elt.getAttribute("style");
-</xsl:text>
-    </xsl:if>
-    <xsl:text>    },
-</xsl:text>
-  </xsl:template>
-  <xsl:template mode="widget_subscribe" match="widget[@type='Jump']">
-    <xsl:param name="hmi_element"/>
-    <xsl:variable name="activity">
-      <xsl:call-template name="jump_widget_activity">
-        <xsl:with-param name="hmi_element" select="$hmi_element"/>
-      </xsl:call-template>
-    </xsl:variable>
-    <xsl:variable name="have_activity" select="string-length($activity)&gt;0"/>
-    <xsl:variable name="disability">
-      <xsl:call-template name="jump_widget_disability">
-        <xsl:with-param name="hmi_element" select="$hmi_element"/>
-      </xsl:call-template>
-    </xsl:variable>
-    <xsl:variable name="have_disability" select="$have_activity and string-length($disability)&gt;0"/>
     <xsl:choose>
       <xsl:when test="$have_disability">
-        <xsl:text>    sub: subscribe,
-</xsl:text>
-        <xsl:text>    unsub: unsubscribe,
-</xsl:text>
-        <xsl:text>    apply_cache: widget_apply_cache,
+        <xsl:text>        this.disabled_elt_style = this.disabled_elt.getAttribute("style");
 </xsl:text>
       </xsl:when>
       <xsl:otherwise>
-        <xsl:text>    sub: function(){},
-</xsl:text>
-        <xsl:text>    unsub: function(){},
-</xsl:text>
-        <xsl:text>    apply_cache: function(){},
+        <xsl:text>        this.sub = function(){};
+</xsl:text>
+        <xsl:text>        this.unsub = function(){};
+</xsl:text>
+        <xsl:text>        this.apply_cache = function(){};
 </xsl:text>
       </xsl:otherwise>
     </xsl:choose>
+    <xsl:text>    },
+</xsl:text>
   </xsl:template>
   <xsl:template mode="per_page_widget_template" match="widget[@type='Jump']">
     <xsl:param name="page_desc"/>
@@ -2083,6 +2256,14 @@
   </xsl:template>
   <declarations:jump/>
   <xsl:template match="declarations:jump">
+    <xsl:text>
+</xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var jumps_need_update = false;
 </xsl:text>
     <xsl:text>var jump_history = [[default_page, undefined]];
@@ -2099,11 +2280,21 @@
 </xsl:text>
     <xsl:text>
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <declarations:keypad/>
   <xsl:template match="declarations:keypad">
     <xsl:text>
 </xsl:text>
+    <xsl:text>/* </xsl:text>
+    <xsl:value-of select="local-name()"/>
+    <xsl:text> */
+</xsl:text>
+    <xsl:text>
+</xsl:text>
+    <xsl:text>
+</xsl:text>
     <xsl:text>var keypads = {
 </xsl:text>
     <xsl:for-each select="$keypads_descs">
@@ -2124,6 +2315,8 @@
     </xsl:for-each>
     <xsl:text>}
 </xsl:text>
+    <xsl:text>
+</xsl:text>
   </xsl:template>
   <xsl:template mode="widget_defs" match="widget[@type='Keypad']">
     <xsl:param name="hmi_element"/>
@@ -2453,15 +2646,10 @@
     <xsl:comment>
       <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
     </xsl:comment>
-    <xsl:for-each select="document('')/*/debug:*">
-      <xsl:comment>
-        <xsl:value-of select="local-name()"/>
-        <xsl:text> :
-</xsl:text>
-        <xsl:apply-templates select="."/>
-      </xsl:comment>
-    </xsl:for-each>
-    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <xsl:comment>
+      <xsl:apply-templates select="document('')/*/debug:*"/>
+    </xsl:comment>
+    <html xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/1999/xhtml">
       <head/>
       <body style="margin:0;overflow:hidden;">
         <xsl:copy-of select="$result_svg"/>
@@ -3152,74 +3340,6 @@
 </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>    need_cache_apply.push(this); 
-</xsl:text>
-          <xsl:text>}
-</xsl:text>
-          <xsl:text>
-</xsl:text>
-          <xsl:text>function widget_apply_cache() {
-</xsl:text>
-          <xsl:text>    for(let index of this.indexes){
-</xsl:text>
-          <xsl:text>        /* dispatch current cache in newly opened page widgets */
-</xsl:text>
-          <xsl:text>        let realindex = index+this.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(this, realindex, cached_val, cached_val);
-</xsl:text>
-          <xsl:text>    }
-</xsl:text>
-          <xsl:text>}
-</xsl:text>
-          <xsl:text>
-</xsl:text>
           <xsl:text>function switch_visible_page(page_name) {
 </xsl:text>
           <xsl:text>
--- a/svghmi/gen_index_xhtml.ysl2	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/gen_index_xhtml.ysl2	Mon May 11 11:33:00 2020 +0200
@@ -7,7 +7,11 @@
 decl emit(*name) alias - {
     *name;
     template *name {
+        |
+        | /* «local-name()» */
+        |
         content;
+        |
     }
 };
 
@@ -56,12 +60,7 @@
         comment > Made with SVGHMI. https://beremiz.org
 
         // all debug output from included definitions, as comments
-        foreach "document('')/*/debug:*" {
-            comment {
-                | «local-name()» :
-                apply ".";
-            }
-        }
+        comment apply "document('')/*/debug:*";
 
         html xmlns="http://www.w3.org/1999/xhtml"
              xmlns:svg="http://www.w3.org/2000/svg"
--- a/svghmi/svghmi.js	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/svghmi.js	Mon May 11 11:33:00 2020 +0200
@@ -325,40 +325,6 @@
     return true;
 };
 
-function* chain(a,b){
-    yield* a;
-    yield* b;
-};
-
-function unsubscribe(){
-    /* remove subsribers */
-    for(let index of this.indexes){
-        let idx = index + this.offset;
-        subscribers[idx].delete(this);
-    }
-    this.offset = 0;
-}
-
-function subscribe(new_offset=0){
-    /* set the offset because relative */
-    this.offset = new_offset;
-    /* add this's subsribers */
-    for(let index of this.indexes){
-        subscribers[index + new_offset].add(this);
-    }
-    need_cache_apply.push(this); 
-}
-
-function widget_apply_cache() {
-    for(let index of this.indexes){
-        /* dispatch current cache in newly opened page widgets */
-        let realindex = index+this.offset;
-        let cached_val = cache[realindex];
-        if(cached_val != undefined)
-            dispatch_value_to_widget(this, realindex, cached_val, cached_val);
-    }
-}
-
 function switch_visible_page(page_name) {
 
     let old_desc = page_desc[current_visible_page];
--- a/svghmi/widget_foreach.ysl2	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/widget_foreach.ysl2	Mon May 11 11:33:00 2020 +0200
@@ -45,69 +45,62 @@
     |         ]
     |     },
     |     item_offset: 0,
-    |     on_click: foreach_onclick,
 }
 
-template "widget[@type='ForEach']", mode="widget_subscribe"{
-    // param "hmi_element";
-    |     sub: foreach_subscribe,
-    |     unsub: foreach_unsubscribe,
-    |     apply_cache: foreach_apply_cache,
-}
+template "widget[@type='ForEach']", mode="widget_class"
+||
+class ForEachWidget extends Widget{
+    unsub(){
+        for(let item of this.items){
+            for(let widget of item) {
+                widget.unsub();
+            }
+        }
+        this.offset = 0;
+    }
 
-emit "definitions:foreach"
-||
-function foreach_unsubscribe(){
-    for(let item of this.items){
-        for(let widget of item) {
-            unsubscribe.call(widget);
+    foreach_widgets_do(new_offset, todo){
+        this.offset = new_offset;
+        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) {
+                todo(widget).call(widget, new_offset + item_index_offset);
+            }
         }
     }
-    this.offset = 0;
-}
 
-function foreach_widgets_do(new_offset, todo){
-    this.offset = new_offset;
-    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) {
-            todo.call(widget, new_offset + item_index_offset);
+    sub(new_offset=0){
+        this.foreach_widgets_do(new_offset, w=>w.sub);
+    }
+
+    apply_cache() {
+        this.foreach_widgets_do(this.offset, w=>w.apply_cache);
+    }
+
+    on_click(opstr, evt) {
+        let new_item_offset = eval(String(this.item_offset)+opstr);
+        if(new_item_offset + this.items.length > this.index_pool.length) {
+            if(this.item_offset + this.items.length == this.index_pool.length)
+                new_item_offset = 0;
+            else
+                new_item_offset = this.index_pool.length - this.items.length;
+        } else if(new_item_offset < 0) {
+            if(this.item_offset == 0)
+                new_item_offset = this.index_pool.length - this.items.length;
+            else
+                new_item_offset = 0;
         }
+        this.item_offset = new_item_offset;
+        this.unsub();
+        this.sub(this.offset);
+        update_subscriptions();
+        need_cache_apply.push(this);
+        jumps_need_update = true;
+        requestHMIAnimation();
     }
 }
+||
 
-function foreach_subscribe(new_offset=0){
-    foreach_widgets_do.call(this, new_offset, subscribe);
-}
-
-function foreach_apply_cache() {
-    foreach_widgets_do.call(this, this.offset, widget_apply_cache);
-}
-
-function foreach_onclick(opstr, evt) {
-    new_item_offset = eval(String(this.item_offset)+opstr)
-    if(new_item_offset + this.items.length > this.index_pool.length) {
-        if(this.item_offset + this.items.length == this.index_pool.length)
-            new_item_offset = 0;
-        else
-            new_item_offset = this.index_pool.length - this.items.length;
-    } else if(new_item_offset < 0) {
-        if(this.item_offset == 0)
-            new_item_offset = this.index_pool.length - this.items.length;
-        else
-            new_item_offset = 0;
-    }
-    this.item_offset = new_item_offset;
-    off = this.offset;
-    foreach_unsubscribe.call(this);
-    foreach_subscribe.call(this,off);
-    update_subscriptions();
-    need_cache_apply.push(this);
-    jumps_need_update = true;
-    requestHMIAnimation();
-}
-
-||
--- a/svghmi/widget_jump.ysl2	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/widget_jump.ysl2	Mon May 11 11:33:00 2020 +0200
@@ -81,33 +81,19 @@
     |         this.active_elt_style = this.active_elt.getAttribute("style");
     |         this.inactive_elt_style = this.inactive_elt.getAttribute("style");
     }
-    if "$have_disability" {
+    choose {
+        when "$have_disability" {
     |         this.disabled_elt_style = this.disabled_elt.getAttribute("style");
+        }
+        otherwise {
+    |         this.sub = function(){};
+    |         this.unsub = function(){};
+    |         this.apply_cache = function(){};
+        }
     }
     |     },
 }
 
-// default : normal subscribing
-template "widget[@type='Jump']", mode="widget_subscribe" {
-    param "hmi_element";
-    const "activity" call "jump_widget_activity" with "hmi_element", "$hmi_element";
-    const "have_activity","string-length($activity)>0";
-    const "disability" call "jump_widget_disability" with "hmi_element", "$hmi_element";
-    const "have_disability","$have_activity and string-length($disability)>0";
-    choose {
-        when "$have_disability" {
-    |     sub: subscribe,
-    |     unsub: unsubscribe,
-    |     apply_cache: widget_apply_cache,
-        }
-        otherwise {
-    |     sub: function(){},
-    |     unsub: function(){},
-    |     apply_cache: function(){},
-        }
-    }
-}
-
 template "widget[@type='Jump']", mode="per_page_widget_template"{
     param "page_desc";
     /* check that given path is compatible with page's reference path */
--- a/svghmi/widgets_common.ysl2	Mon May 11 11:10:38 2020 +0200
+++ b/svghmi/widgets_common.ysl2	Mon May 11 11:33:00 2020 +0200
@@ -21,49 +21,111 @@
     }
 };
 
-template "svg:*", mode="hmi_elements" {
+template "svg:*", mode="hmi_widgets" {
     const "widget", "func:widget(@id)";
     const "eltid","@id";
-    |   "«@id»": {
-    |     type: "«$widget/@type»",
-    |     args: [
-    foreach "$widget/arg"
-    |         "«@value»"`if "position()!=last()" > ,`
-    |     ],
-    |     offset: 0,
-    |     indexes: [
-    foreach "$widget/path" {
+    const "args" foreach "$widget/arg" > "«@value»"`if "position()!=last()" > ,`
+    const "indexes" foreach "$widget/path" {
         choose {
             when "not(@index)" {
                 warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree
             }
             otherwise {
-    |         «@index» /* «@value» */ `if "position()!=last()" > ,`
+                > «@index»`if "position()!=last()" > ,`
             }
         }
     }
-    |     ],
-    |     element: id("«@id»"),
+
+    |   "«@id»": new «$widget/@type»Widget ("«@id»",[«$args»],[«$indexes»],{
     apply "$widget", mode="widget_defs" with "hmi_element",".";
-    apply "$widget", mode="widget_subscribe" with "hmi_element",".";
-    |   }`if "position()!=last()" > ,`
+    |   })`if "position()!=last()" > ,`
 }
 
+def "func:unique_types" {
+    param "elts_with_type";
+    choose {
+        when "count($elts_with_type) > 1" {
+            const "prior_results","func:unique_types($elts_with_type[position()!=last()])";
+            choose {
+                when "$elts_with_type[last()][@type = $prior_results/@type]"{
+                    // type already in
+                    result "$prior_results";
+                }
+                otherwise {
+                    result "$prior_results | $elts_with_type[last()]";
+                }
+            }
+        }
+        otherwise {
+            result "$elts_with_type";
+        }
+    }
+}
+
+emit "preamble:widget-base-class" {
+    ||    
+    class Widget {
+        constructor(elt_id,args,indexes,members){
+            this.element = id(elt_id);
+            this.args = args;
+            this.indexes = indexes;
+            this.offset = 0;
+            Object.keys(members).forEach(prop => this[prop]=members[prop]);
+        }
+        unsub(){
+            /* remove subsribers */
+            for(let index of this.indexes){
+                let idx = index + this.offset;
+                subscribers[idx].delete(this);
+            }
+            this.offset = 0;
+        }
+
+        sub(new_offset=0){
+            /* set the offset because relative */
+            this.offset = new_offset;
+            /* add this's subsribers */
+            for(let index of this.indexes){
+                subscribers[index + new_offset].add(this);
+            }
+            need_cache_apply.push(this); 
+        }
+
+        apply_cache() {
+            for(let index of this.indexes){
+                /* dispatch current cache in newly opened page widgets */
+                let realindex = index+this.offset;
+                let cached_val = cache[realindex];
+                if(cached_val != undefined)
+                    dispatch_value_to_widget(this, realindex, cached_val, cached_val);
+            }
+        }
+
+    }
+    ||
+}
+
+emit "preamble:hmi-classes" {
+    const "used_widget_types", "func:unique_types($parsed_widgets/widget)";
+    apply "$used_widget_types", mode="widget_class";
+}
+
+template "widget", mode="widget_class"
+||
+class «@type»Widget extends Widget{
+    /* empty class, as «@type» widget didn't provide any */
+}
+||
+
+const "excluded_types", "str:split('Page Lang List')";
+const "excluded_ids","$parsed_widgets/widget[not(@type = $excluded_types)]/@id";
+
 emit "preamble:hmi-elements" {
     | var hmi_widgets = {
-    apply "$hmi_elements", mode="hmi_elements";
+    apply  "$hmi_elements[@id = $excluded_ids]", mode="hmi_widgets";
     | }
 }
 
-// default : normal subscribing
-template "widget", mode="widget_subscribe" {
-    |     sub: subscribe,
-    |     unsub: unsubscribe,
-    |     apply_cache: widget_apply_cache,
-}
-// page aren't subscribers
-template "widget[@type='Page']", mode="widget_subscribe";
-
 function "defs_by_labels" {
     param "labels","''";
     param "mandatory","'yes'";