|
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 |