svghmi/widget_foreach.ysl2
author Edouard Tisserant
Tue, 13 Jul 2021 16:16:58 +0200
branchsvghmi
changeset 3278 2bcfbea6a2a8
parent 3241 fe945f1f48b7
child 3603 f1a00aa8cb3b
permissions -rw-r--r--
SVGHMI: Fixed typo on session manager unregister, leading to wrong count of sessions and then exceptions when creating more session than allowed in protocol options. Also added more safety check in protocol in case session would be missing.
// widget_foreach.ysl2

widget_desc("ForEach") {

    longdesc
    ||
    ForEach widget is used to span a small set of widget over a larger set of
    repeated HMI_NODEs. 

    Idea is somewhat similar to relative page, but it all happens inside the
    ForEach widget, no page involved.

    Together with relative Jump widgets it can be used to build a menu to reach
    relative pages covering many identical HMI_NODES siblings.

    ForEach widget takes a HMI_CLASS name as argument and a HMI_NODE path as
    variable.

    Direct sub-elements can be either groups of widget to be spanned, labeled
    "ClassName:offset", or buttons to control the spanning, labeled
    "ClassName:+/-number".
    ||

    shortdesc > span widgets over a set of repeated HMI_NODEs

    arg name="class_name" accepts="string" > HMI_CLASS name

    path name="root" accepts="HMI_NODE" >  where to find HMI_NODEs whose HMI_CLASS is class_name
}

widget_defs("ForEach") {

    if "count(path) != 1" error > ForEach widget «$hmi_element/@id» must have one HMI path given.
    if "count(arg) != 1" error > ForEach widget «$hmi_element/@id» must have one argument given : a class name.

    const "class","arg[1]/@value";

    const "base_path","path/@value";
    const "hmi_index_base", "$indexed_hmitree/*[@hmipath = $base_path]";
    const "hmi_tree_base", "$hmitree/descendant-or-self::*[@path = $hmi_index_base/@path]";
    const "hmi_tree_items", "$hmi_tree_base/*[@class = $class]";
    const "hmi_index_items", "$indexed_hmitree/*[@path = $hmi_tree_items/@path]"; 
    const "items_paths", "$hmi_index_items/@hmipath"; 
    |     index_pool: [
    foreach "$hmi_index_items" {
    |       «@index»`if "position()!=last()" > ,`
    }
    |     ],
    |     init: function() {
    const "prefix","concat($class,':')";
    const "buttons_regex","concat('^',$prefix,'[+\-][0-9]+')";
    const "buttons", "$hmi_element/*[regexp:test(@inkscape:label, $buttons_regex)]"; 
    foreach "$buttons" {
        const "op","substring-after(@inkscape:label, $prefix)";
    |         id("«@id»").setAttribute("onclick", "hmi_widgets['«$hmi_element/@id»'].on_click('«$op»', evt)");
    }
    |
    |         this.items = [
    const "items_regex","concat('^',$prefix,'[0-9]+')";
    const "unordered_items","$hmi_element//*[regexp:test(@inkscape:label, $items_regex)]";
    foreach "$unordered_items" {
        const "elt_label","concat($prefix, string(position()))"; 
        const "elt","$unordered_items[@inkscape:label = $elt_label]";
        const "pos","position()";
        const "item_path", "$items_paths[$pos]";
    |           [ /* 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()" > ,`
        }
    |           ]`if "position()!=last()" > ,`
    }
    |         ]
    |     },
    |     item_offset: 0,
}

widget_class("ForEach")
||

    unsub_items(){
        for(let item of this.items){
            for(let widget of item) {
                widget.unsub();
            }
        }
    }

    unsub(){
        this.unsub_items();
        this.offset = 0;
        this.relativeness = undefined;
    }

    sub_items(){
        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;
            if(this.relativeness[0])
                item_index_offset += this.offset;
            for(let widget of item) {
                /* all variables of all widgets in a ForEach are all relative. 
                   Really.

                   TODO: allow absolute variables in ForEach widgets
                */
                widget.sub(item_index_offset, widget.indexes.map(_=>true));
            }
        }
    }

    sub(new_offset=0, relativeness=[]){
        this.offset = new_offset;
        this.relativeness = relativeness;
        this.sub_items();
    }

    apply_cache() {
        this.items.forEach(item=>item.forEach(widget=>widget.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_items();
        this.sub_items();
        update_subscriptions();
        need_cache_apply.push(this);
        jumps_need_update = true;
        requestHMIAnimation();
    }
||