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