aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/static/js/dashboard.js244
-rw-r--r--src/templates/booking/quick_deploy.html8
-rw-r--r--src/templates/dashboard/multiple_select_filter_widget.html274
-rw-r--r--src/templates/resource/steps/define_hardware.html15
4 files changed, 259 insertions, 282 deletions
diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js
new file mode 100644
index 0000000..66e95d0
--- /dev/null
+++ b/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/src/templates/booking/quick_deploy.html b/src/templates/booking/quick_deploy.html
index ea80af4..07f3d89 100644
--- a/src/templates/booking/quick_deploy.html
+++ b/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/src/templates/dashboard/multiple_select_filter_widget.html b/src/templates/dashboard/multiple_select_filter_widget.html
index 3a7e148..bfcbed6 100644
--- a/src/templates/dashboard/multiple_select_filter_widget.html
+++ b/src/templates/dashboard/multiple_select_filter_widget.html
@@ -1,3 +1,6 @@
+<script src="/static/js/dashboard.js">
+</script>
+
<style>
.object_class_wrapper {
display: grid;
@@ -102,7 +105,7 @@
<div id="{{ obj.id|default:'not_provided' }}" class="grid-item">
<p class="grid-item-header">{{obj.name}}</p>
<p class="grid-item-description">{{obj.description}}</p>
- <button type="button" class="btn btn-success grid-item-select-btn" onclick="processClick(
+ <button type="button" class="btn btn-success grid-item-select-btn" onclick="multi_filter_widget.processClick(
'{{obj.id}}');">{% if obj.multiple %}Add{% else %}Select{% endif %}</button>
</div>
{% endfor %}
@@ -113,270 +116,15 @@
<div id="dropdown_wrapper">
</div>
-
<script>
-var initialized = false;
-var inputs = [];
-var graph_neighbors = {{ neighbors|safe }};
-var filter_items = {{ filter_items|safe }};
-var result = {};
-var dropdown_count = 0;
-
-{% if initial_value %}
-
-var initial_value = {{ initial_value|safe }};
-
-
-function make_selection( initial_data ){
- try_init();
- for(var item_class in initial_data) {
- var selected_items = initial_data[item_class];
- for( var node_id in selected_items ){
- var node = filter_items[node_id];
- var selection_data = selected_items[node_id]
- if( selection_data.selected ) {
- select(node);
- markAndSweep(node);
- updateResult(node);
- }
- if(node['multiple']){
- make_multiple_selection(node, selection_data);
- }
- }
- }
-}
-
-function make_multiple_selection(node, selection_data){
- prepop_data = selection_data.values;
- for(var k in prepop_data){
- var div = add_item_prepopulate(node, prepop_data[k]);
- updateObjectResult(node, div.id, prepop_data[k]);
- }
-}
-
-make_selection({{initial_value|safe}});
-
-{% endif %}
-
-function markAndSweep(root){
- for(var i in filter_items) {
- node = filter_items[i];
- node['marked'] = true; //mark all nodes
- }
-
- toCheck = [root];
- while(toCheck.length > 0){
- node = toCheck.pop();
- if(!node['marked']) {
- //already visited, just continue
- continue;
- }
- node['marked'] = false; //mark as visited
- if(node['follow'] || node == root){ //add neighbors if we want to follow this node
- var neighbors = graph_neighbors[node.id];
- for(var i in neighbors) {
- var neighId = neighbors[i];
- var neighbor = filter_items[neighId];
- toCheck.push(neighbor);
- }
- }
- }
-
- //now remove all nodes still marked
- for(var i in filter_items){
- node = filter_items[i];
- if(node['marked']){
- disable_node(node);
- }
- }
-}
-
-function process(node) {
- if(node['selected']) {
- markAndSweep(node);
- }
- else { //TODO: make this not dumb
- var selected = []
- //remember the currently selected, then reset everything and reselect one at a time
- for(var nodeId in filter_items) {
- node = filter_items[nodeId];
- if(node['selected']) {
- selected.push(node);
- }
- clear(node);
- }
- for(var i=0; i<selected.length; i++) {
- node = selected[i];
- select(node);
- markAndSweep(selected[i]);
- }
- }
-}
-
-function select(node) {
- elem = document.getElementById(node['id']);
- node['selected'] = true;
- elem.classList.remove('cleared_node');
- elem.classList.remove('disabled_node');
- elem.classList.add('selected_node');
-}
-
-function clear(node) {
- elem = document.getElementById(node['id']);
- node['selected'] = false;
- node['selectable'] = true;
- elem.classList.add('cleared_node')
- elem.classList.remove('disabled_node');
- elem.classList.remove('selected_node');
-}
-
-function disable_node(node) {
- elem = document.getElementById(node['id']);
- node['selected'] = false;
- node['selectable'] = false;
- elem.classList.remove('cleared_node');
- elem.classList.add('disabled_node');
- elem.classList.remove('selected_node');
-}
-
-function processClick(id){
- try_init();
- var node = filter_items[id];
- if(!node['selectable'])
- return;
-
- if(node['multiple']){
- return processClickMultiple(node);
- } else {
- return processClickSingle(node);
- }
-}
-
-function processClickSingle(node){
- node['selected'] = !node['selected']; //toggle on click
-
- if(node['selected']) {
- select(node);
- } else {
- clear(node);
- }
- process(node);
- updateResult(node);
-}
-
-function processClickMultiple(node){
- select(node);
- var div = add_node(node);
- process(node);
- updateObjectResult(node, div.id, "");
-}
-
-function add_node(node){
- return add_item_prepopulate(node, false);
-}
-
-function 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, "");
- checkunique(input);
-}
-
-function checkunique(tocheck){
- val = tocheck.value;
- for( var i = 0; i < inputs.length; i++ )
- {
- if( inputs[i].value == val && inputs[i] != tocheck)
- {
- tocheck.setCustomValidity("All hostnames must be unique");
- tocheck.reportValidity();
- return;
- }
- }
- tocheck.setCustomValidity("");
-}
-
-function make_remove_button(div, node){
- var button = document.createElement("BUTTON");
- button.type = "button";
- button.appendChild(document.createTextNode("Remove"));
- button.classList.add("btn-danger");
- button.classList.add("btn");
- button.onclick = function(){
- remove_dropdown(div.id, node.id);
- }
- return button;
-}
-
-function make_input(div, node, prepopulate){
- var 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;
- inputs.push(input);
- input.onchange = function() { updateObjectResult(node, div.id, input.value); restrictchars(this); };
- input.oninput = function() { restrictchars(this); };
- if(prepopulate)
- input.value = prepopulate;
- return input;
-}
-
-function add_item_prepopulate(node, prepopulate){
- var div = document.createElement("DIV");
- div.id = "dropdown_" + dropdown_count;
- div.classList.add("dropdown_item");
- dropdown_count++;
- var label = document.createElement("H5")
- label.appendChild(document.createTextNode(node['name']))
- div.appendChild(label);
- div.appendChild(make_input(div, node, prepopulate));
- div.appendChild(make_remove_button(div, node));
- document.getElementById("dropdown_wrapper").appendChild(div);
- return div;
-}
-
-function remove_dropdown(div_id, node_id){
- var div = document.getElementById(div_id);
- var node = filter_items[node_id]
- var parent = div.parentNode;
- div.parentNode.removeChild(div);
- delete result[node.class][node.id]['values'][div.id];
-
- //checks if we have removed last item in class
- if(jQuery.isEmptyObject(result[node.class][node.id]['values'])){
- delete result[node.class][node.id];
- clear(node);
- }
-}
-function updateResult(node){
- try_init();
- if(!node['multiple']){
- result[node.class][node.id] = {selected: node.selected, id: node.model_id}
- if(!node.selected)
- delete result[node.class][node.id];
- }
-}
-
-function updateObjectResult(node, childKey, childValue){
- try_init();
- if(!result[node.class][node.id])
- result[node.class][node.id] = {selected: true, id: node.model_id, values: {}}
-
- result[node.class][node.id]['values'][childKey] = childValue;
-}
+function multipleSelectFilterWidgetEntry() {
+ const graph_neighbors = {{ neighbors|safe }};
+ const filter_items = {{ filter_items|safe }};
+ const initial_value = {{ initial_value|default_if_none:"{}"|safe }};
-function try_init() {
- if(initialized) return;
- for(nodeId in filter_items) {
- var element = document.getElementById(nodeId);
- var node = filter_items[nodeId];
- result[node.class] = {}
- }
- initialized = true;
+ //global variable
+ multi_filter_widget = new MultipleSelectFilterWidget(graph_neighbors, filter_items, initial_value);
}
+multipleSelectFilterWidgetEntry();
</script>
diff --git a/src/templates/resource/steps/define_hardware.html b/src/templates/resource/steps/define_hardware.html
index 77df5a2..57078e9 100644
--- a/src/templates/resource/steps/define_hardware.html
+++ b/src/templates/resource/steps/define_hardware.html
@@ -15,20 +15,7 @@ with your current configuration will become unavailable.</p>
</form>
{% endblock content %}
{% block onleave %}
-var normalize = function(data){
- //converts the top level keys in data to map to lists
- var normalized = {}
- for( var key in data ){
- normalized[key] = [];
- for( var subkey in data[key] ){
- normalized[key].push(data[key][subkey]);
- }
- }
- return normalized;
-}
-var data = result;
-data = JSON.stringify(data);
-document.getElementById("filter_field").value = data;
+multi_filter_widget.finish();
var formData = $("#define_hardware_form").serialize();
req = new XMLHttpRequest();
req.open('POST', '/wf/workflow/', false);