Move JS to external file
[pharos-tools.git] / dashboard / src / static / js / dashboard.js
1 class MultipleSelectFilterWidget {
2
3     constructor(neighbors, items, initial) {
4         this.inputs = [];
5         this.graph_neighbors = neighbors;
6         this.filter_items = items;
7         this.result = {};
8         this.dropdown_count = 0;
9
10         for(let nodeId in this.filter_items) {
11             const node = this.filter_items[nodeId];
12             this.result[node.class] = {}
13         }
14
15         this.make_selection(initial);
16     }
17
18     make_selection( initial_data ){
19         if(!initial_data || jQuery.isEmptyObject(initial_data))
20             return;
21         for(let item_class in initial_data) {
22             const selected_items = initial_data[item_class];
23             for( let node_id in selected_items ){
24                 const node = this.filter_items[node_id];
25                 const selection_data = selected_items[node_id]
26                 if( selection_data.selected ) {
27                     this.select(node);
28                     this.markAndSweep(node);
29                     this.updateResult(node);
30                 }
31                 if(node['multiple']){
32                     this.make_multiple_selection(node, selection_data);
33                 }
34             }
35         }
36     }
37
38     make_multiple_selection(node, selection_data){
39         const prepop_data = selection_data.values;
40         for(let k in prepop_data){
41             const div = this.add_item_prepopulate(node, prepop_data[k]);
42             this.updateObjectResult(node, div.id, prepop_data[k]);
43         }
44     }
45
46     markAndSweep(root){
47         for(let i in this.filter_items) {
48             const node = this.filter_items[i];
49             node['marked'] = true; //mark all nodes
50         }
51
52         const toCheck = [root];
53         while(toCheck.length > 0){
54             const node = toCheck.pop();
55             if(!node['marked']) {
56                 continue; //already visited, just continue
57             }
58             node['marked'] = false; //mark as visited
59             if(node['follow'] || node == root){ //add neighbors if we want to follow this node
60                 const neighbors = this.graph_neighbors[node.id];
61                 for(let neighId of neighbors) {
62                     const neighbor = this.filter_items[neighId];
63                     toCheck.push(neighbor);
64                 }
65             }
66         }
67
68         //now remove all nodes still marked
69         for(let i in this.filter_items){
70             const node = this.filter_items[i];
71             if(node['marked']){
72                 this.disable_node(node);
73             }
74         }
75     }
76
77     process(node) {
78         if(node['selected']) {
79             this.markAndSweep(node);
80         }
81         else {  //TODO: make this not dumb
82             const selected = []
83             //remember the currently selected, then reset everything and reselect one at a time
84             for(let nodeId in this.filter_items) {
85                 node = this.filter_items[nodeId];
86                 if(node['selected']) {
87                     selected.push(node);
88                 }
89                 this.clear(node);
90             }
91             for(let node of selected) {
92                 this.select(node);
93                 this.markAndSweep(selected[i]);
94             }
95         }
96     }
97
98     select(node) {
99         const elem = document.getElementById(node['id']);
100         node['selected'] = true;
101         elem.classList.remove('disabled_node', 'cleared_node');
102         elem.classList.add('selected_node');
103     }
104
105     clear(node) {
106         const elem = document.getElementById(node['id']);
107         node['selected'] = false;
108         node['selectable'] = true;
109         elem.classList.add('cleared_node')
110         elem.classList.remove('disabled_node', 'selected_node');
111     }
112
113     disable_node(node) {
114         const elem = document.getElementById(node['id']);
115         node['selected'] = false;
116         node['selectable'] = false;
117         elem.classList.remove('cleared_node', 'selected_node');
118         elem.classList.add('disabled_node');
119     }
120
121     processClick(id){
122         const node = this.filter_items[id];
123         if(!node['selectable'])
124             return;
125
126         if(node['multiple']){
127             return this.processClickMultiple(node);
128         } else {
129             return this.processClickSingle(node);
130         }
131     }
132
133     processClickSingle(node){
134         node['selected'] = !node['selected']; //toggle on click
135         if(node['selected']) {
136             this.select(node);
137         } else {
138             this.clear(node);
139         }
140         this.process(node);
141         this.updateResult(node);
142     }
143
144     processClickMultiple(node){
145         this.select(node);
146         const div = this.add_item_prepopulate(node, false);
147         this.process(node);
148         this.updateObjectResult(node, div.id, "");
149     }
150
151     restrictchars(input){
152         if( input.validity.patternMismatch ){
153             input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed");
154             input.reportValidity();
155         }
156         input.value = input.value.replace(/([^A-Za-z0-9-_.])+/g, "");
157         this.checkunique(input);
158     }
159
160     checkunique(tocheck){ //TODO: use set
161         const val = tocheck.value;
162         for( let input of this.inputs ){
163             if( input.value == val && input != tocheck){
164                 tocheck.setCustomValidity("All hostnames must be unique");
165                 tocheck.reportValidity();
166                 return;
167             }
168         }
169         tocheck.setCustomValidity("");
170     }
171
172     make_remove_button(div, node){
173         const button = document.createElement("BUTTON");
174         button.type = "button";
175         button.appendChild(document.createTextNode("Remove"));
176         button.classList.add("btn", "btn-danger");
177         const that = this;
178         button.onclick = function(){ that.remove_dropdown(div.id, node.id); }
179         return button;
180     }
181
182     make_input(div, node, prepopulate){
183         const input = document.createElement("INPUT");
184         input.type = node.form.type;
185         input.name = node.id + node.form.name
186         input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})";
187         input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"
188         input.placeholder = node.form.placeholder;
189         this.inputs.push(input);
190         const that = this;
191         input.onchange = function() { that.updateObjectResult(node, div.id, input.value); that.restrictchars(this); };
192         input.oninput = function() { that.restrictchars(this); };
193         if(prepopulate)
194             input.value = prepopulate;
195         return input;
196     }
197
198     add_item_prepopulate(node, prepopulate){
199         const div = document.createElement("DIV");
200         div.id = "dropdown_" + this.dropdown_count;
201         div.classList.add("dropdown_item");
202         this.dropdown_count++;
203         const label = document.createElement("H5")
204         label.appendChild(document.createTextNode(node['name']))
205         div.appendChild(label);
206         div.appendChild(this.make_input(div, node, prepopulate));
207         div.appendChild(this.make_remove_button(div, node));
208         document.getElementById("dropdown_wrapper").appendChild(div);
209         return div;
210     }
211
212     remove_dropdown(div_id, node_id){
213         const div = document.getElementById(div_id);
214         const node = this.filter_items[node_id]
215         const parent = div.parentNode;
216         div.parentNode.removeChild(div);
217         delete this.result[node.class][node.id]['values'][div.id];
218
219         //checks if we have removed last item in class
220         if(jQuery.isEmptyObject(this.result[node.class][node.id]['values'])){
221             delete this.result[node.class][node.id];
222             this.clear(node);
223         }
224     }
225
226     updateResult(node){
227         if(!node['multiple']){
228             this.result[node.class][node.id] = {selected: node.selected, id: node.model_id}
229             if(!node.selected)
230                 delete this.result[node.class][node.id];
231         }
232     }
233
234     updateObjectResult(node, childKey, childValue){
235         if(!this.result[node.class][node.id])
236             this.result[node.class][node.id] = {selected: true, id: node.model_id, values: {}}
237
238         this.result[node.class][node.id]['values'][childKey] = childValue;
239     }
240
241     finish(){
242         document.getElementById("filter_field").value = JSON.stringify(this.result);
243     }
244 }