Merge "Make host entries prettier on Define Hardware step"
[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 .class_grid_wrapper {
8     border: 0px;
9     text-align: center;
10     border-right: 1px;
11     border-style: solid;
12     border-color: grey;
13 }
14
15 .class_grid_wrapper:last-child {
16     border-right: none;
17 }
18
19 .grid_wrapper {
20     display: grid;
21     grid-template-columns: 1fr 1fr;
22 }
23 .grid-item {
24     cursor: pointer;
25     border:2px;
26     border-style:none;
27     border-color:black;
28     border-radius: 5px;
29     margin:20px;
30     height: 200px;
31     padding: 7px;
32     box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
33     transition-property: box-shadow, background-color;
34     transition-duration: .2s;
35 }
36
37 .grid-item:hover {
38     box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75);
39     transition-property: box-shadow;
40     transition-duration: .2s;
41
42 }
43
44 .selected_node {
45     box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75);
46     background-color: #CCECD7;
47     transition-property: background-color;
48     transition-duration: .2s;
49 }
50
51 .disabled_node {
52     cursor: not-allowed;
53     background-color: #EFEFEF;
54     box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75);
55     transition-property: box-shadow;
56     transition-duration: .2s;
57 }
58
59 .disabled_node:hover {
60     box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75);
61 }
62
63 .cleared_node {
64     background-color: #FFFFFF;
65 }
66
67 .grid-item-header
68 {
69     font-weight: bold;
70     font-size: 20px;
71     margin-top: 10px;
72 }
73
74 #dropdown_wrapper > div > h5 {
75     margin: 12px;
76     display: inline-block;
77     vertical-align: middle;
78 }
79
80 #dropdown_wrapper > div > button {
81     padding: 7px;
82     margin: 2px;
83     float: right;
84     width: 80px;
85 }
86 #dropdown_wrapper > div > input {
87     padding: 7px;
88     margin: 2px;
89     float: right;
90     width: 300px;
91     width: calc(100% - 240px);
92 }
93
94 #dropdown_wrapper > div {
95     border:2px;
96     border-style:none;
97     border-color:black;
98     border-radius: 5px;
99     margin:20px;
100     padding: 2px;
101     box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
102     transition-property: box-shadow, background-color;
103     transition-duration: .2s;
104     display: inline-block;
105     vertical-align: middle;
106 }
107
108 #dropdown_wrapper {
109     display: grid;
110     grid-template-columns: 3fr 5fr;
111 }
112
113 </style>
114 <input name="filter_field" id="filter_field" type="hidden"/>
115 <div id="grid_wrapper" class="grid_wrapper">
116 {% for object_class, object_list in filter_objects %}
117 <div class="class_grid_wrapper">
118     <div style="display:inline-block;margin:auto">
119         <h4>{{object_class}}</h4>
120     </div>
121     <div id="{{object_class}}" class="object_class_wrapper">
122     {% for obj in object_list %}
123     <div id="object_parent">
124         <div id="{{ obj.id|default:'not_provided' }}" class="grid-item">
125             <p class="grid-item-header">{{obj.name}}</p>
126             <p class="grid-item-description">{{obj.description}}</p>
127             <button type="button" class="btn btn-success grid-item-select-btn" onclick="processClick('{{obj.id}}', {% if obj.multiple %}true{% else %}false{% endif %});">{% if obj.multiple %}Add{% else %}Select{% endif %}</button>
128         </div>
129         <input type="hidden" name="{{obj.id}}_selected" value="false"/>
130     </div>
131     {% endfor %}
132     </div>
133     </div>
134 {% endfor %}
135 </div>
136
137 <div id="dropdown_wrapper">
138 </div>
139
140 <script>
141 var initialized = false;
142 var mapping = {{ mapping|safe }};
143 var filter_items = {{ filter_items|safe }};
144 var result = {};
145 var selection = {{selection_data|default_if_none:"null"|safe}};
146 var dropdown_count = 0;
147
148 {% if selection_data %}
149 make_selection({{selection_data|safe}});
150 {% endif %}
151
152 function make_selection( selection_data ){
153     if(!initialized) {
154         filter_field_init();
155     }
156     for(var k in selection_data) {
157         selected_items = selection_data[k];
158         for( var selected_item in selected_items ){
159             var node = filter_items[selected_item];
160             if(!node['multiple']){
161                 var input_value = selected_items[selected_item];
162                 if( input_value != 'false' ) {
163                     select(node);
164                     markAndSweep(node);
165                 }
166                 var div = document.getElementById(selected_item)
167                 var inputs = div.parentNode.getElementsByTagName("input")
168                 var input = div.parentNode.getElementsByTagName("input")[0]
169                 input.value = input_value;
170                 updateResult(selected_item);
171             } else {
172                 make_multiple_selection(selected_items, selected_item);
173             }
174         }
175     }
176 }
177
178 function make_multiple_selection(data, item_class){
179     var node = filter_items[item_class];
180     select(node);
181     markAndSweep(node);
182     prepop_data = data[item_class];
183     for(var i=0; i<prepop_data.length; i++){
184         var div = add_item_prepopulate(node, prepop_data[i]);
185         updateObjectResult(div);
186     }
187 }
188
189 function markAndSweep(root){
190     for(var nodeId in filter_items) {
191         node = filter_items[nodeId];
192         node['marked'] = true; //mark all nodes
193         //clears grey background of everything
194     }
195
196     toCheck = [];
197     toCheck.push(root);
198
199     while(toCheck.length > 0){
200         node = toCheck.pop();
201         if(!node['marked']) {
202             //already visited, just continue
203             continue;
204         }
205         node['marked'] = false; //mark as visited
206         if(node['follow'] || node == root){ //add neighbors if we want to follow this node (labs)
207             var mappingId = node.id
208             var neighbors = mapping[mappingId];
209             for(var neighId in neighbors) {
210                 neighId = neighbors[neighId];
211                 var neighbor = filter_items[neighId];
212                 toCheck.push(neighbor);
213             }
214         }
215     }
216
217     //now remove all nodes still marked
218     for(var nodeId in filter_items){
219         node = filter_items[nodeId];
220         if(node['marked']){
221             disable_node(node);
222         }
223     }
224 }
225
226 function process(node) {
227     if(node['selected']) {
228         markAndSweep(node);
229     }
230     else {
231         var selected = []
232         //remember the currently selected, then reset everything and reselect one at a time
233         for(var nodeId in filter_items) {
234             node = filter_items[nodeId];
235             if(node['selected']) {
236                 selected.push(node);
237             }
238             clear(node);
239
240         }
241         for(var i=0; i<selected.length; i++) {
242             node = selected[i];
243             select(node);
244             markAndSweep(selected[i]);
245         }
246     }
247 }
248
249 function select(node) {
250     elem = document.getElementById(node['id']);
251     node['selected'] = true;
252     elem.classList.remove('cleared_node');
253     elem.classList.remove('disabled_node');
254     elem.classList.add('selected_node');
255     var input = elem.parentNode.getElementsByTagName("input")[0];
256     input.disabled = false;
257     input.value = true;
258 }
259
260 function clear(node) {
261     elem = document.getElementById(node['id']);
262     node['selected'] = false;
263     node['selectable'] = true;
264     elem.classList.add('cleared_node')
265     elem.classList.remove('disabled_node');
266     elem.classList.remove('selected_node');
267     elem.parentNode.getElementsByTagName("input")[0].disabled = true;
268 }
269
270 function disable_node(node) {
271     elem = document.getElementById(node['id']);
272     node['selected'] = false;
273     node['selectable'] = false;
274     elem.classList.remove('cleared_node');
275     elem.classList.add('disabled_node');
276     elem.classList.remove('selected_node');
277     elem.parentNode.getElementsByTagName("input")[0].disabled = true;
278 }
279
280 function processClick(id, multiple){
281     if(!initialized){
282         filter_field_init();
283     }
284     var element = document.getElementById(id);
285     var node = filter_items[id];
286     if(!node['selectable']){
287         return;
288     }
289     if(multiple){
290         return processClickMultipleObject(node);
291     }
292     node['selected'] = !node['selected']; //toggle on click
293
294     if(node['selected']) {
295         select(node);
296     }
297     else {
298         clear(node);
299     }
300     process(node);
301     updateResult(id);
302 }
303
304 function processClickMultipleObject(node){
305     select(node);
306     add_node(node);
307     process(node);
308 }
309
310 function add_node(node){
311     return add_item_prepopulate(node, {});
312 }
313
314 inputs = []
315
316 function restrictchars(input)
317 {
318     if( input.validity.patternMismatch )
319     {
320         input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed");
321         input.reportValidity();
322     }
323
324     input.value = input.value.replace(/([^A-Za-z0-9-_.])+/g, "");
325
326     checkunique(input);
327 }
328
329 function checkunique(tocheck)
330 {
331     val = tocheck.value;
332     for( var i = 0; i < inputs.length; i++ )
333     {
334         if( inputs[i].value == val && inputs[i] != tocheck)
335         {
336             tocheck.setCustomValidity("All hostnames must be unique");
337             tocheck.reportValidity();
338             return;
339         }
340     }
341     tocheck.setCustomValidity("");
342 }
343
344 function add_item_prepopulate(node, prepopulate){
345     inputs = [];
346     var div = document.createElement("DIV");
347     div.class = node['id'];
348     div.id = "dropdown_" + dropdown_count;
349     dropdown_count++;
350     var label = document.createElement("H5");
351     label.appendChild(document.createTextNode(node['name']));
352     div.appendChild(label);
353     button = document.createElement("BUTTON");
354     button.type = "button";
355     button.appendChild(document.createTextNode("Remove"));
356     button.classList.add("btn-danger");
357     button.classList.add("btn");
358     div.appendChild(button);
359     for(var i=0; i<node['forms'].length; i++){
360         form = node['forms'][i];
361         var input = document.createElement("INPUT");
362         input.type = form['type'];
363         input.name = form['name'];
364         input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})";
365         input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"
366         input.placeholder = form['placeholder'];
367         inputs.push(input);
368         input.onchange = function() { updateObjectResult(div); restrictchars(this); };
369         input.oninput = function() { restrictchars(this); };
370         if(form['name'] in prepopulate){
371             input.value = prepopulate[form['name']];
372         }
373         div.appendChild(input);
374     }
375     //add class id to dropdown object
376     var hiddenInput = document.createElement("INPUT");
377     hiddenInput.type = "hidden";
378     hiddenInput.name = "class";
379     hiddenInput.value = node['id'];
380     div.appendChild(hiddenInput);
381     button.onclick = function(){
382         remove_dropdown(div.id);
383     }
384     document.getElementById("dropdown_wrapper").appendChild(div);
385     var linebreak = document.createElement("BR");
386     document.getElementById("dropdown_wrapper").appendChild(linebreak);
387     updateObjectResult(div);
388     return div;
389 }
390
391 function remove_dropdown(id){
392     var div = document.getElementById(id);
393     var parent = div.parentNode;
394     div.parentNode.removeChild(div);
395     //checks if we have removed last item in class
396     var deselect_class = true;
397     var div_inputs = div.getElementsByTagName("input");
398     var div_class = div_inputs[div_inputs.length-1].value;
399     var result_class = document.getElementById(div_class).parentNode.parentNode.id;
400     delete result[result_class][div.id];
401     for(var i=0; i<parent.children.length; i++){
402         var inputs = parent.children[i].getElementsByTagName("input");
403         var object_class = "";
404         for(var k=0; k<inputs.length; k++){
405             if(inputs[k].name == "class"){
406                 object_class = inputs[k].value;
407             }
408         }
409         if(object_class == div_class){
410             deselect_class = false;
411         }
412     }
413     if(deselect_class){
414         clear(filter_items[div_class]);
415     }
416 }
417
418 function updateResult(nodeId){
419     if(!initialized){
420         filter_field_init();
421     }
422     if(!filter_items[nodeId]['multiple']){
423         var node = document.getElementById(nodeId);
424         var value = {}
425         value[nodeId] = node.parentNode.getElementsByTagName("input")[0].value;
426         result[node.parentNode.parentNode.id] = {};
427         result[node.parentNode.parentNode.id][nodeId] = value;
428     }
429 }
430
431 function updateObjectResult(parentElem){
432     node_type = document.getElementById(parentElem.class).parentNode.parentNode.id;
433     input = {};
434     inputs = parentElem.getElementsByTagName("input");
435     for(var i in inputs){
436         var e = inputs[i];
437         input[e.name] = e.value;
438     }
439     result[node_type][parentElem.id] = input;
440 }
441
442 function filter_field_init() {
443     for(nodeId in filter_items) {
444         element = document.getElementById(nodeId);
445         node = filter_items[nodeId];
446         result[element.parentNode.parentNode.id] = {}
447         }
448     initialized = true;
449 }
450
451 </script>