svghmi/widget_foreach.ysl2
changeset 3302 c89fc366bebd
parent 3241 fe945f1f48b7
child 3603 f1a00aa8cb3b
equal deleted inserted replaced
2744:577118ebd179 3302:c89fc366bebd
       
     1 // widget_foreach.ysl2
       
     2 
       
     3 widget_desc("ForEach") {
       
     4 
       
     5     longdesc
       
     6     ||
       
     7     ForEach widget is used to span a small set of widget over a larger set of
       
     8     repeated HMI_NODEs. 
       
     9 
       
    10     Idea is somewhat similar to relative page, but it all happens inside the
       
    11     ForEach widget, no page involved.
       
    12 
       
    13     Together with relative Jump widgets it can be used to build a menu to reach
       
    14     relative pages covering many identical HMI_NODES siblings.
       
    15 
       
    16     ForEach widget takes a HMI_CLASS name as argument and a HMI_NODE path as
       
    17     variable.
       
    18 
       
    19     Direct sub-elements can be either groups of widget to be spanned, labeled
       
    20     "ClassName:offset", or buttons to control the spanning, labeled
       
    21     "ClassName:+/-number".
       
    22     ||
       
    23 
       
    24     shortdesc > span widgets over a set of repeated HMI_NODEs
       
    25 
       
    26     arg name="class_name" accepts="string" > HMI_CLASS name
       
    27 
       
    28     path name="root" accepts="HMI_NODE" >  where to find HMI_NODEs whose HMI_CLASS is class_name
       
    29 }
       
    30 
       
    31 widget_defs("ForEach") {
       
    32 
       
    33     if "count(path) != 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given.
       
    34     if "count(arg) != 1" error > ForEach widget «$hmi_element/@id» must have one argument given : a class name.
       
    35 
       
    36     const "class","arg[1]/@value";
       
    37 
       
    38     const "base_path","path/@value";
       
    39     const "hmi_index_base", "$indexed_hmitree/*[@hmipath = $base_path]";
       
    40     const "hmi_tree_base", "$hmitree/descendant-or-self::*[@path = $hmi_index_base/@path]";
       
    41     const "hmi_tree_items", "$hmi_tree_base/*[@class = $class]";
       
    42     const "hmi_index_items", "$indexed_hmitree/*[@path = $hmi_tree_items/@path]"; 
       
    43     const "items_paths", "$hmi_index_items/@hmipath"; 
       
    44     |     index_pool: [
       
    45     foreach "$hmi_index_items" {
       
    46     |       «@index»`if "position()!=last()" > ,`
       
    47     }
       
    48     |     ],
       
    49     |     init: function() {
       
    50     const "prefix","concat($class,':')";
       
    51     const "buttons_regex","concat('^',$prefix,'[+\-][0-9]+')";
       
    52     const "buttons", "$hmi_element/*[regexp:test(@inkscape:label, $buttons_regex)]"; 
       
    53     foreach "$buttons" {
       
    54         const "op","substring-after(@inkscape:label, $prefix)";
       
    55     |         id("«@id»").setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click('«$op»', evt)");
       
    56     }
       
    57     |
       
    58     |         this.items = [
       
    59     const "items_regex","concat('^',$prefix,'[0-9]+')";
       
    60     const "unordered_items","$hmi_element//*[regexp:test(@inkscape:label, $items_regex)]";
       
    61     foreach "$unordered_items" {
       
    62         const "elt_label","concat($prefix, string(position()))"; 
       
    63         const "elt","$unordered_items[@inkscape:label = $elt_label]";
       
    64         const "pos","position()";
       
    65         const "item_path", "$items_paths[$pos]";
       
    66     |           [ /* item="«$elt_label»" path="«$item_path»" */
       
    67         if "count($elt)=0" error > Missing item labeled «$elt_label» in ForEach widget «$hmi_element/@id»
       
    68         foreach "func:refered_elements($elt)[@id = $hmi_elements/@id][not(@id = $elt/@id)]" {
       
    69             if "not(func:is_descendant_path(func:widget(@id)/path/@value, $item_path))"
       
    70                 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»".
       
    71     |             hmi_widgets["«@id»"]`if "position()!=last()" > ,`
       
    72         }
       
    73     |           ]`if "position()!=last()" > ,`
       
    74     }
       
    75     |         ]
       
    76     |     },
       
    77     |     item_offset: 0,
       
    78 }
       
    79 
       
    80 widget_class("ForEach")
       
    81 ||
       
    82 
       
    83     unsub_items(){
       
    84         for(let item of this.items){
       
    85             for(let widget of item) {
       
    86                 widget.unsub();
       
    87             }
       
    88         }
       
    89     }
       
    90 
       
    91     unsub(){
       
    92         this.unsub_items();
       
    93         this.offset = 0;
       
    94         this.relativeness = undefined;
       
    95     }
       
    96 
       
    97     sub_items(){
       
    98         for(let i = 0; i < this.items.length; i++) {
       
    99             let item = this.items[i];
       
   100             let orig_item_index = this.index_pool[i];
       
   101             let item_index = this.index_pool[i+this.item_offset];
       
   102             let item_index_offset = item_index - orig_item_index;
       
   103             if(this.relativeness[0])
       
   104                 item_index_offset += this.offset;
       
   105             for(let widget of item) {
       
   106                 /* all variables of all widgets in a ForEach are all relative. 
       
   107                    Really.
       
   108 
       
   109                    TODO: allow absolute variables in ForEach widgets
       
   110                 */
       
   111                 widget.sub(item_index_offset, widget.indexes.map(_=>true));
       
   112             }
       
   113         }
       
   114     }
       
   115 
       
   116     sub(new_offset=0, relativeness=[]){
       
   117         this.offset = new_offset;
       
   118         this.relativeness = relativeness;
       
   119         this.sub_items();
       
   120     }
       
   121 
       
   122     apply_cache() {
       
   123         this.items.forEach(item=>item.forEach(widget=>widget.apply_cache()));
       
   124     }
       
   125 
       
   126     on_click(opstr, evt) {
       
   127         let new_item_offset = eval(String(this.item_offset)+opstr);
       
   128         if(new_item_offset + this.items.length > this.index_pool.length) {
       
   129             if(this.item_offset + this.items.length == this.index_pool.length)
       
   130                 new_item_offset = 0;
       
   131             else
       
   132                 new_item_offset = this.index_pool.length - this.items.length;
       
   133         } else if(new_item_offset < 0) {
       
   134             if(this.item_offset == 0)
       
   135                 new_item_offset = this.index_pool.length - this.items.length;
       
   136             else
       
   137                 new_item_offset = 0;
       
   138         }
       
   139         this.item_offset = new_item_offset;
       
   140         this.unsub_items();
       
   141         this.sub_items();
       
   142         update_subscriptions();
       
   143         need_cache_apply.push(this);
       
   144         jumps_need_update = true;
       
   145         requestHMIAnimation();
       
   146     }
       
   147 ||
       
   148