From ac0fe888b57a2cba9b2884590dc38e6680f54d22 Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Fri, 21 Jun 2019 17:08:56 -0400 Subject: Move JS to external file This is mostly a proof of concept to move all JS to external files to pave the way for future re-architecting Change-Id: I3b6f00bff7325b85a75d37f554892fa5283d9f4b Signed-off-by: Parker Berberian --- dashboard/src/static/js/dashboard.js | 244 ++++++++++++++++++ dashboard/src/templates/booking/quick_deploy.html | 8 +- .../dashboard/multiple_select_filter_widget.html | 274 +-------------------- .../templates/resource/steps/define_hardware.html | 15 +- 4 files changed, 259 insertions(+), 282 deletions(-) create mode 100644 dashboard/src/static/js/dashboard.js diff --git a/dashboard/src/static/js/dashboard.js b/dashboard/src/static/js/dashboard.js new file mode 100644 index 0000000..66e95d0 --- /dev/null +++ b/dashboard/src/static/js/dashboard.js @@ -0,0 +1,244 @@ +class MultipleSelectFilterWidget { + + constructor(neighbors, items, initial) { + this.inputs = []; + this.graph_neighbors = neighbors; + this.filter_items = items; + this.result = {}; + this.dropdown_count = 0; + + for(let nodeId in this.filter_items) { + const node = this.filter_items[nodeId]; + this.result[node.class] = {} + } + + this.make_selection(initial); + } + + make_selection( initial_data ){ + if(!initial_data || jQuery.isEmptyObject(initial_data)) + return; + for(let item_class in initial_data) { + const selected_items = initial_data[item_class]; + for( let node_id in selected_items ){ + const node = this.filter_items[node_id]; + const selection_data = selected_items[node_id] + if( selection_data.selected ) { + this.select(node); + this.markAndSweep(node); + this.updateResult(node); + } + if(node['multiple']){ + this.make_multiple_selection(node, selection_data); + } + } + } + } + + make_multiple_selection(node, selection_data){ + const prepop_data = selection_data.values; + for(let k in prepop_data){ + const div = this.add_item_prepopulate(node, prepop_data[k]); + this.updateObjectResult(node, div.id, prepop_data[k]); + } + } + + markAndSweep(root){ + for(let i in this.filter_items) { + const node = this.filter_items[i]; + node['marked'] = true; //mark all nodes + } + + const toCheck = [root]; + while(toCheck.length > 0){ + const node = toCheck.pop(); + if(!node['marked']) { + continue; //already visited, just continue + } + node['marked'] = false; //mark as visited + if(node['follow'] || node == root){ //add neighbors if we want to follow this node + const neighbors = this.graph_neighbors[node.id]; + for(let neighId of neighbors) { + const neighbor = this.filter_items[neighId]; + toCheck.push(neighbor); + } + } + } + + //now remove all nodes still marked + for(let i in this.filter_items){ + const node = this.filter_items[i]; + if(node['marked']){ + this.disable_node(node); + } + } + } + + process(node) { + if(node['selected']) { + this.markAndSweep(node); + } + else { //TODO: make this not dumb + const selected = [] + //remember the currently selected, then reset everything and reselect one at a time + for(let nodeId in this.filter_items) { + node = this.filter_items[nodeId]; + if(node['selected']) { + selected.push(node); + } + this.clear(node); + } + for(let node of selected) { + this.select(node); + this.markAndSweep(selected[i]); + } + } + } + + select(node) { + const elem = document.getElementById(node['id']); + node['selected'] = true; + elem.classList.remove('disabled_node', 'cleared_node'); + elem.classList.add('selected_node'); + } + + clear(node) { + const elem = document.getElementById(node['id']); + node['selected'] = false; + node['selectable'] = true; + elem.classList.add('cleared_node') + elem.classList.remove('disabled_node', 'selected_node'); + } + + disable_node(node) { + const elem = document.getElementById(node['id']); + node['selected'] = false; + node['selectable'] = false; + elem.classList.remove('cleared_node', 'selected_node'); + elem.classList.add('disabled_node'); + } + + processClick(id){ + const node = this.filter_items[id]; + if(!node['selectable']) + return; + + if(node['multiple']){ + return this.processClickMultiple(node); + } else { + return this.processClickSingle(node); + } + } + + processClickSingle(node){ + node['selected'] = !node['selected']; //toggle on click + if(node['selected']) { + this.select(node); + } else { + this.clear(node); + } + this.process(node); + this.updateResult(node); + } + + processClickMultiple(node){ + this.select(node); + const div = this.add_item_prepopulate(node, false); + this.process(node); + this.updateObjectResult(node, div.id, ""); + } + + restrictchars(input){ + if( input.validity.patternMismatch ){ + input.setCustomValidity("Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed"); + input.reportValidity(); + } + input.value = input.value.replace(/([^A-Za-z0-9-_.])+/g, ""); + this.checkunique(input); + } + + checkunique(tocheck){ //TODO: use set + const val = tocheck.value; + for( let input of this.inputs ){ + if( input.value == val && input != tocheck){ + tocheck.setCustomValidity("All hostnames must be unique"); + tocheck.reportValidity(); + return; + } + } + tocheck.setCustomValidity(""); + } + + make_remove_button(div, node){ + const button = document.createElement("BUTTON"); + button.type = "button"; + button.appendChild(document.createTextNode("Remove")); + button.classList.add("btn", "btn-danger"); + const that = this; + button.onclick = function(){ that.remove_dropdown(div.id, node.id); } + return button; + } + + make_input(div, node, prepopulate){ + const input = document.createElement("INPUT"); + input.type = node.form.type; + input.name = node.id + node.form.name + input.pattern = "(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})"; + input.title = "Only alphanumeric characters (a-z, A-Z, 0-9), underscore(_), and hyphen (-) are allowed" + input.placeholder = node.form.placeholder; + this.inputs.push(input); + const that = this; + input.onchange = function() { that.updateObjectResult(node, div.id, input.value); that.restrictchars(this); }; + input.oninput = function() { that.restrictchars(this); }; + if(prepopulate) + input.value = prepopulate; + return input; + } + + add_item_prepopulate(node, prepopulate){ + const div = document.createElement("DIV"); + div.id = "dropdown_" + this.dropdown_count; + div.classList.add("dropdown_item"); + this.dropdown_count++; + const label = document.createElement("H5") + label.appendChild(document.createTextNode(node['name'])) + div.appendChild(label); + div.appendChild(this.make_input(div, node, prepopulate)); + div.appendChild(this.make_remove_button(div, node)); + document.getElementById("dropdown_wrapper").appendChild(div); + return div; + } + + remove_dropdown(div_id, node_id){ + const div = document.getElementById(div_id); + const node = this.filter_items[node_id] + const parent = div.parentNode; + div.parentNode.removeChild(div); + delete this.result[node.class][node.id]['values'][div.id]; + + //checks if we have removed last item in class + if(jQuery.isEmptyObject(this.result[node.class][node.id]['values'])){ + delete this.result[node.class][node.id]; + this.clear(node); + } + } + + updateResult(node){ + if(!node['multiple']){ + this.result[node.class][node.id] = {selected: node.selected, id: node.model_id} + if(!node.selected) + delete this.result[node.class][node.id]; + } + } + + updateObjectResult(node, childKey, childValue){ + if(!this.result[node.class][node.id]) + this.result[node.class][node.id] = {selected: true, id: node.model_id, values: {}} + + this.result[node.class][node.id]['values'][childKey] = childValue; + } + + finish(){ + document.getElementById("filter_field").value = JSON.stringify(this.result); + } +} diff --git a/dashboard/src/templates/booking/quick_deploy.html b/dashboard/src/templates/booking/quick_deploy.html index ea80af4..07f3d89 100644 --- a/dashboard/src/templates/booking/quick_deploy.html +++ b/dashboard/src/templates/booking/quick_deploy.html @@ -83,7 +83,7 @@ function submit_form() { //formats data for form submission - document.getElementById("filter_field").value = JSON.stringify(result); + multi_filter_widget.finish(); } function hide_dropdown(drop_id) { @@ -104,10 +104,8 @@ } function get_selected_value(key){ - for( var attr in result[key] ){ - if( attr in {} ) - continue; - else + for( var attr in multi_filter_widget.result[key] ){ + if(!(attr in {}) ) return attr; } return null; diff --git a/dashboard/src/templates/dashboard/multiple_select_filter_widget.html b/dashboard/src/templates/dashboard/multiple_select_filter_widget.html index 3a7e148..bfcbed6 100644 --- a/dashboard/src/templates/dashboard/multiple_select_filter_widget.html +++ b/dashboard/src/templates/dashboard/multiple_select_filter_widget.html @@ -1,3 +1,6 @@ + +