Move JS to external file 91/68091/7
authorParker Berberian <pberberian@iol.unh.edu>
Fri, 21 Jun 2019 21:08:56 +0000 (17:08 -0400)
committerParker Berberian <pberberian@iol.unh.edu>
Tue, 25 Jun 2019 17:52:33 +0000 (13:52 -0400)
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 <pberberian@iol.unh.edu>
dashboard/src/static/js/dashboard.js [new file with mode: 0644]
dashboard/src/templates/booking/quick_deploy.html
dashboard/src/templates/dashboard/multiple_select_filter_widget.html
dashboard/src/templates/resource/steps/define_hardware.html

diff --git a/dashboard/src/static/js/dashboard.js b/dashboard/src/static/js/dashboard.js
new file mode 100644 (file)
index 0000000..66e95d0
--- /dev/null
@@ -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);
+    }
+}
index ea80af4..07f3d89 100644 (file)
@@ -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) {
     }
 
     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;
index 3a7e148..bfcbed6 100644 (file)
@@ -1,3 +1,6 @@
+<script src="/static/js/dashboard.js">
+</script>
+
 <style>
 .object_class_wrapper {
     display: grid;
             <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 %}
 
 <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>
index 77df5a2..57078e9 100644 (file)
@@ -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);