Merge "Prefetches Collaborators"
[pharos-tools.git] / dashboard / src / templates / dashboard / multiple_select_filter_widget.html
1 <style>
2 .object_class_wrapper {
3     display: grid;
4     grid-template-columns: 1fr 1fr 1fr;
5     border: 0px;
6 }
7
8 .class_grid_wrapper {
9     border: 0px;
10     text-align: center;
11     border-right: 1px;
12     border-style: solid;
13     border-color: grey;
14 }
15
16 .class_grid_wrapper:last-child {
17     border-right: none;
18 }
19
20 .grid_wrapper {
21     display: grid;
22     grid-template-columns: 1fr 1fr;
23 }
24
25 .grid-item {
26     cursor: pointer;
27     border: 1px solid #cccccc;
28     border-radius: 5px;
29     margin: 20px;
30     height: 200px;
31     padding: 7px;
32     transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
33     box-shadow: 0 1px 1px rgba(0,0,0,.075);
34 }
35
36 .selected_node {
37     border-color: #40c640;
38     box-shadow: 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(109, 243, 76, 0.6);
39     transition: border-color ease-in-out .1s,box-shadow ease-in-out .1s;
40 }
41
42 .disabled_node {
43     cursor: not-allowed;
44     background-color: #EFEFEF;
45 }
46
47 .disabled_node:hover {}
48
49 .cleared_node {
50     background-color: #FFFFFF;
51 }
52
53 .grid-item-header {
54     font-weight: bold;
55     font-size: 20px;
56     margin-top: 10px;
57 }
58
59 .dropdown_item {
60     border: 1px;
61     border-style: solid;
62     border-color: lightgray;
63     border-radius: 5px;
64     margin: 20px;
65     padding: 2px;
66     grid-column: 1;
67     display: grid;
68     grid-template-columns: 1fr 3fr 1fr;
69     justify-items: center;
70 }
71
72 .dropdown_item > button {
73     margin: 2px;
74     justify-self: end;
75 }
76
77 .dropdown_item > h5 {
78     margin: auto;
79 }
80
81 .dropdown_item > input {
82     padding: 7px;
83     margin: 2px;
84     width: 90%;
85 }
86
87 #dropdown_wrapper {
88     display: grid;
89     grid-template-columns: 4fr 5fr;
90 }
91 </style>
92
93 <input name="filter_field" id="filter_field" type="hidden"/>
94 <div id="grid_wrapper" class="grid_wrapper">
95 {% for object_class, object_list in display_objects %}
96     <div class="class_grid_wrapper">
97         <div style="display:inline-block;margin:auto">
98             <h4>{{object_class}}</h4>
99         </div>
100         <div id="{{object_class}}" class="object_class_wrapper">
101         {% for obj in object_list %}
102             <div id="{{ obj.id|default:'not_provided' }}" class="grid-item">
103                 <p class="grid-item-header">{{obj.name}}</p>
104                 <p class="grid-item-description">{{obj.description}}</p>
105                 <button type="button" class="btn btn-success grid-item-select-btn" onclick="processClick(
106                     '{{obj.id}}');">{% if obj.multiple %}Add{% else %}Select{% endif %}</button>
107             </div>
108         {% endfor %}
109         </div>
110     </div>
111 {% endfor %}
112 </div>
113
114 <div id="dropdown_wrapper">
115 </div>
116
117 <script>
118 var initialized = false;
119 var inputs = [];
120 var graph_neighbors = {{ neighbors|safe }};
121 var filter_items = {{ filter_items|safe }};
122 var result = {};
123 var dropdown_count = 0;
124
125 {% if initial_value %}
126
127 var initial_value = {{ initial_value|safe }};
128
129
130 function make_selection( initial_data ){
131     try_init();
132     for(var item_class in initial_data) {
133         var selected_items = initial_data[item_class];
134         for( var node_id in selected_items ){
135             var node = filter_items[node_id];
136             var selection_data = selected_items[node_id]
137             if( selection_data.selected ) {
138                 select(node);
139                 markAndSweep(node);
140                 updateResult(node);
141             }
142             if(node['multiple']){
143                 make_multiple_selection(node, selection_data);
144             }
145         }
146     }
147 }
148
149 function make_multiple_selection(node, selection_data){
150     prepop_data = selection_data.values;
151     for(var k in prepop_data){
152         var div = add_item_prepopulate(node, prepop_data[k]);
153         updateObjectResult(node, div.id, prepop_data[k]);
154     }
155 }
156
157 make_selection({{initial_value|safe}});
158
159 {% endif %}
160
161 function markAndSweep(root){
162     for(var i in filter_items) {
163         node = filter_items[i];
164         node['marked'] = true; //mark all nodes
165     }
166
167     toCheck = [root];
168     while(toCheck.length > 0){
169         node = toCheck.pop();
170         if(!node['marked']) {
171             //already visited, just continue
172             continue;
173         }
174         node['marked'] = false; //mark as visited
175         if(node['follow'] || node == root){ //add neighbors if we want to follow this node
176             var neighbors = graph_neighbors[node.id];
177             for(var i in neighbors) {
178                 var neighId = neighbors[i];
179                 var neighbor = filter_items[neighId];
180                 toCheck.push(neighbor);
181             }
182         }
183     }
184
185     //now remove all nodes still marked
186     for(var i in filter_items){
187         node = filter_items[i];
188         if(node['marked']){
189             disable_node(node);
190         }
191     }
192 }
193
194 function process(node) {
195     if(node['selected']) {
196         markAndSweep(node);
197     }
198     else {  //TODO: make this not dumb
199         var selected = []
200         //remember the currently selected, then reset everything and reselect one at a time
201         for(var nodeId in filter_items) {
202             node = filter_items[nodeId];
203             if(node['selected']) {
204                 selected.push(node);
205             }
206             clear(node);
207         }
208         for(var i=0; i<selected.length; i++) {
209             node = selected[i];
210             select(node);
211             markAndSweep(selected[i]);
212         }
213     }
214 }
215
216 function select(node) {
217     elem = document.getElementById(node['id']);
218     node['selected'] = true;
219     elem.classList.remove('cleared_node');
220     elem.classList.remove('disabled_node');
221     elem.classList.add('selected_node');
222 }
223
224 function clear(node) {
225     elem = document.getElementById(node['id']);
226     node['selected'] = false;
227     node['selectable'] = true;
228     elem.classList.add('cleared_node')
229     elem.classList.remove('disabled_node');
230     elem.classList.remove('selected_node');
231 }
232
233 function disable_node(node) {
234     elem = document.getElementById(node['id']);
235     node['selected'] = false;
236     node['selectable'] = false;
237     elem.classList.remove('cleared_node');
238     elem.classList.add('disabled_node');
239     elem.classList.remove('selected_node');
240 }
241
242 function processClick(id){
243     try_init();
244     var node = filter_items[id];
245     if(!node['selectable'])
246         return;
247
248     if(node['multiple']){
249         return processClickMultiple(node);
250     } else {
251         return processClickSingle(node);
252     }
253 }
254
255 function processClickSingle(node){
256     node['selected'] = !node['selected']; //toggle on click
257
258     if(node['selected']) {
259         select(node);
260     } else {
261         clear(node);
262     }
263     process(node);
264     updateResult(node);
265 }
266
267 function processClickMultiple(node){
268     select(node);
269     var div = add_node(node);
270     process(node);
271     updateObjectResult(node, div.id, "");
272 }
273
274 function add_node(node){
275     return add_item_prepopulate(node, false);
276 }
277
278 function restrictchars(input){
279     if( input.validity.patternMismatch ){
280         input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed");
281         input.reportValidity();
282     }
283     input.value = input.value.replace(/([^A-Za-z0-9-_.])+/g, "");
284     checkunique(input);
285 }
286
287 function checkunique(tocheck){
288     val = tocheck.value;
289     for( var i = 0; i < inputs.length; i++ )
290     {
291         if( inputs[i].value == val && inputs[i] != tocheck)
292         {
293             tocheck.setCustomValidity("All hostnames must be unique");
294             tocheck.reportValidity();
295             return;
296         }
297     }
298     tocheck.setCustomValidity("");
299 }
300
301 function make_remove_button(div, node){
302     var button = document.createElement("BUTTON");
303     button.type = "button";
304     button.appendChild(document.createTextNode("Remove"));
305     button.classList.add("btn-danger");
306     button.classList.add("btn");
307     button.onclick = function(){
308         remove_dropdown(div.id, node.id);
309     }
310     return button;
311 }
312
313 function make_input(div, node, prepopulate){
314     var input = document.createElement("INPUT");
315     input.type = node.form.type;
316     input.name = node.id + node.form.name
317     input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})";
318     input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"
319     input.placeholder = node.form.placeholder;
320     inputs.push(input);
321     input.onchange = function() { updateObjectResult(node, div.id, input.value); restrictchars(this); };
322     input.oninput = function() { restrictchars(this); };
323     if(prepopulate)
324         input.value = prepopulate;
325     return input;
326 }
327
328 function add_item_prepopulate(node, prepopulate){
329     var div = document.createElement("DIV");
330     div.id = "dropdown_" + dropdown_count;
331     div.classList.add("dropdown_item");
332     dropdown_count++;
333     var label = document.createElement("H5")
334     label.appendChild(document.createTextNode(node['name']))
335     div.appendChild(label);
336     div.appendChild(make_input(div, node, prepopulate));
337     div.appendChild(make_remove_button(div, node));
338     document.getElementById("dropdown_wrapper").appendChild(div);
339     return div;
340 }
341
342 function remove_dropdown(div_id, node_id){
343     var div = document.getElementById(div_id);
344     var node = filter_items[node_id]
345     var parent = div.parentNode;
346     div.parentNode.removeChild(div);
347     delete result[node.class][node.id]['values'][div.id];
348
349     //checks if we have removed last item in class
350     if(jQuery.isEmptyObject(result[node.class][node.id]['values'])){
351         delete result[node.class][node.id];
352         clear(node);
353     }
354 }
355 function updateResult(node){
356     try_init();
357     if(!node['multiple']){
358         result[node.class][node.id] = {selected: node.selected, id: node.model_id}
359         if(!node.selected)
360             delete result[node.class][node.id];
361     }
362 }
363
364 function updateObjectResult(node, childKey, childValue){
365     try_init();
366     if(!result[node.class][node.id])
367         result[node.class][node.id] = {selected: true, id: node.model_id, values: {}}
368
369     result[node.class][node.id]['values'][childKey] = childValue;
370 }
371
372 function try_init() {
373     if(initialized) return;
374     for(nodeId in filter_items) {
375         var element = document.getElementById(nodeId);
376         var node = filter_items[nodeId];
377         result[node.class] = {}
378         }
379     initialized = true;
380 }
381
382 </script>