Removing project content and adding a note
[laas.git] / src / static / js / dashboard.js
diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js
deleted file mode 100644 (file)
index a63c71b..0000000
+++ /dev/null
@@ -1,1664 +0,0 @@
-///////////////////
-// Global Variables
-///////////////////
-
-form_submission_callbacks = [];  //all runnables will be executed before form submission
-
-///////////////////
-// Global Functions
-///////////////////
-
-// Taken from https://docs.djangoproject.com/en/3.0/ref/csrf/
-function getCookie(name) {
-    var cookieValue = null;
-    if (document.cookie && document.cookie !== '') {
-        var cookies = document.cookie.split(';');
-        for (var i = 0; i < cookies.length; i++) {
-            var cookie = cookies[i].trim();
-            // Does this cookie string begin with the name we want?
-            if (cookie.substring(0, name.length + 1) === (name + '=')) {
-                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                break;
-            }
-        }
-    }
-    return cookieValue;
-}
-
-function update_page(response) {
-    if( response.redirect )
-    {
-        window.location.replace(response.redirect);
-        return;
-    }
-    draw_breadcrumbs(response.meta);
-    update_exit_button(response.meta);
-    update_side_buttons(response.meta);
-    $("#formContainer").html(response.content);
-}
-
-function update_side_buttons(meta) {
-    const step = meta.active;
-    const page_count = meta.steps.length;
-
-    const back_button = document.getElementById("workflow-nav-back");
-    if (step == 0) {
-        back_button.classList.add("disabled");
-        back_button.disabled = true;
-    } else {
-        back_button.classList.remove("disabled");
-        back_button.disabled = false;
-    }
-
-    const forward_btn = document.getElementById("workflow-nav-next");
-    if (step == page_count - 1) {
-        forward_btn.classList.add("disabled");
-        forward_btn.disabled = true;
-    } else {
-        forward_btn.classList.remove("disabled");
-        forward_btn.disabled = false;
-    }
-}
-
-function update_exit_button(meta) {
-    if (meta.workflow_count == 1) {
-        document.getElementById("cancel_btn").innerText = "Exit Workflow";
-    } else {
-        document.getElementById("cancel_btn").innerText = "Return to Parent";
-    }
-}
-
-function draw_breadcrumbs(meta) {
-    $("#topPagination").children().not(".page-control").remove();
-
-    for (const i in meta.steps) {
-        const step_btn = create_step(meta.steps[i], i == meta["active"]);
-        $("#topPagination li:last-child").before(step_btn);
-    }
-}
-
-function create_step(step_json, active) {
-    const step_dom = document.createElement("li");
-    // First create the dom object depending on active or not
-    step_dom.className = "topcrumb";
-    if (active) {
-        step_dom.classList.add("active");
-    }
-    $(step_dom).html(`<span class="d-flex align-items-center justify-content-center text-capitalize w-100">${step_json['title']}</span>`)
-
-    const code = step_json.valid;
-
-    let stat = "";
-    let msg = "";
-    if (code < 100) {
-        $(step_dom).children().first().append("<i class='ml-2 far fa-square'></i>")
-        stat = "";
-        msg = "";
-    } else if (code < 200) {
-        $(step_dom).children().first().append("<i class='ml-2 fas fa-minus-square'></i>")
-        stat = "invalid";
-        msg = step_json.message;
-    } else if (code < 300) {
-        $(step_dom).children().first().append("<i class='ml-2 far fa-check-square'></i>")
-        stat = "valid";
-        msg = step_json.message;
-    }
-
-    if (step_json.enabled == false) {
-        step_dom.classList.add("disabled");
-    }
-    if (active) {
-        update_message(msg, stat);
-    }
-
-    return step_dom;
-}
-
-function update_description(title, desc) {
-    document.getElementById("view_title").innerText = title;
-    document.getElementById("view_desc").innerText = desc;
-}
-
-function update_message(message, stepstatus) {
-    let color_code;
-    if (stepstatus == 'valid') {
-        color_code = 'text-success';
-    } else if (stepstatus == 'invalid') {
-        color_code = 'text-danger';
-    } else {
-        color_code = 'none';
-    }
-    document.getElementById("view_message").innerText = message;
-    document.getElementById("view_message").className = "step_message";
-    document.getElementById("view_message").classList.add("message_" + stepstatus);
-    document.getElementById("view_message").classList.add(color_code);
-}
-
-function submitStepForm(next_step = "current"){
-    run_form_callbacks();
-    const step_form_data = $("#step_form").serialize();
-    const form_data = $.param({
-        "step": next_step,
-        "step_form": step_form_data,
-        "csrfmiddlewaretoken": $("[name=csrfmiddlewaretoken]").val()
-    });
-    $.post(
-        '/workflow/manager/',
-        form_data,
-        (data) => update_page(data),
-        'json'
-    ).fail(() => alert("failure"));
-}
-
-function run_form_callbacks(){
-    for(f of form_submission_callbacks)
-        f();
-    form_submission_callbacks = [];
-}
-
-function create_workflow(type) {
-    $.ajax({
-        type: "POST",
-        url: "/workflow/create/",
-        data: {
-            "workflow_type": type
-        },
-        headers: {
-            "X-CSRFToken": getCookie('csrftoken')
-        }
-    }).done(function (data, textStatus, jqXHR) {
-        window.location = "/workflow/";
-    }).fail(function (jqxHR, textstatus) {
-        alert("Something went wrong...");
-    });
-}
-
-function add_workflow(type) {
-    data = $.ajax({
-        type: "POST",
-        url: "/workflow/add/",
-        data: {
-            "workflow_type": type
-        },
-        headers: {
-            "X-CSRFToken": getCookie('csrftoken')
-        }
-    }).done(function (data, textStatus, jqXHR) {
-        update_page(data);
-    }).fail(function (jqxHR, textstatus) {
-        alert("Something went wrong...");
-    });
-}
-
-function pop_workflow() {
-    data = $.ajax({
-        type: "POST",
-        url: "/workflow/pop/",
-        headers: {
-            "X-CSRFToken": getCookie('csrftoken')
-        }
-    }).done(function (data, textStatus, jqXHR) {
-        update_page(data);
-    }).fail(function (jqxHR, textstatus) {
-        alert("Something went wrong...");
-    });
-}
-
-function continue_workflow() {
-    window.location.replace("/workflow/");
-}
-
-///////////////////
-//Class Definitions
-///////////////////
-
-class MultipleSelectFilterWidget {
-
-    constructor(neighbors, items, initial) {
-        this.inputs = [];
-        this.graph_neighbors = neighbors;
-        this.filter_items = items;
-        this.currentLab = null;
-        this.available_resources = {};
-        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;
-
-        // Need to sort through labs first
-        let initial_lab = initial_data['lab'];
-        let initial_resources = initial_data['resource'];
-
-        for( let node_id in initial_lab) { // This should only be length one
-            const node = this.filter_items[node_id];
-            const selection_data = initial_lab[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);
-            }
-            this.currentLab = node;
-            this.available_resources = JSON.parse(node['available_resources']);
-        }
-
-        for( let node_id in initial_resources){
-            const node = this.filter_items[node_id];
-            const selection_data = initial_resources[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);
-            }
-        }
-        this.updateAvailibility();
-    }
-
-    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(node);
-            }
-        }
-    }
-
-    select(node) {
-        const elem = document.getElementById(node['id']);
-        node['selected'] = true;
-        elem.classList.remove('bg-white', 'not-allowed', 'bg-light');
-        elem.classList.add('selected_node');
-
-        if(node['class'] == 'resource')
-            this.reserveResource(node);
-
-    }
-
-    clear(node) {
-        const elem = document.getElementById(node['id']);
-        node['selected'] = false;
-        node['selectable'] = true;
-        elem.classList.add('bg-white')
-        elem.classList.remove('not-allowed', 'bg-light', 'selected_node');
-    }
-
-    disable_node(node) {
-        const elem = document.getElementById(node['id']);
-        node['selected'] = false;
-        node['selectable'] = false;
-        elem.classList.remove('bg-white', 'selected_node');
-        elem.classList.add('not-allowed', 'bg-light');
-    }
-
-    labCheck(node){
-        // if lab is not already selected update available resources
-        if(!node['selected']) {
-            this.currentLab = node;
-            this.available_resources = JSON.parse(node['available_resources']);
-            this.updateAvailibility();
-        } else {
-            // a lab is already selected, clear already selected resources
-            if(confirm('Unselecting a lab will reset all selected resources, are you sure?')) {
-                location.reload();
-                return false;
-            }
-        }
-        return true;
-    }
-
-    updateAvailibility() {
-        const lab_resources = this.graph_neighbors[this.currentLab.id];
-
-        // need to loop through and update all quantities
-        for(let i in lab_resources) {
-            const resource_node = this.filter_items[lab_resources[i]];
-            const required_resources = JSON.parse(resource_node['required_resources']);
-            let elem = document.getElementById(resource_node.id).getElementsByClassName("grid-item-description")[0];
-            let leastAvailable = 100;
-            let currCount;
-            let quantityDescription;
-            let quantityNode;
-
-            for(let resource in required_resources) {
-                currCount = Math.floor(this.available_resources[resource] / required_resources[resource]);
-                if(currCount < leastAvailable)
-                    leastAvailable = currCount;
-
-                if(!currCount || currCount < 0) {
-                    leastAvailable = 0
-                    break;
-                }
-            }
-
-            if (elem.children[0]){
-                elem.removeChild(elem.children[0]);
-            }
-
-            quantityDescription = '<br> Quantity Currently Available: ' + leastAvailable;
-            quantityNode = document.createElement('P');
-            if (leastAvailable > 0) {
-                quantityDescription = quantityDescription.fontcolor('green');
-            } else {
-                quantityDescription = quantityDescription.fontcolor('red');
-            }
-
-            quantityNode.innerHTML = quantityDescription;
-            elem.appendChild(quantityNode)
-        }
-    }
-
-    reserveResource(node){
-        const required_resources = JSON.parse(node['required_resources']);
-        let hostname = document.getElementById('id_hostname');
-        let image = document.getElementById('id_image');
-        let cnt = 0
-
-
-        for(let resource in required_resources){
-            this.available_resources[resource] -= required_resources[resource];
-            cnt += required_resources[resource];
-        }
-
-        if (cnt > 1 && hostname) {
-            hostname.readOnly = true;
-            // we only disable hostname modification because there is no sane case where you want all hosts to have the same hostname
-            // image is still allowed to be set across all hosts, but is filtered to the set of images that are commonly applicable still
-            // if no images exist that would apply to all hosts in a pod, then the user is restricted to not setting an image
-            // and the default image for each host is used
-        }
-
-        this.updateAvailibility();
-    }
-
-    releaseResource(node){
-        const required_resources = JSON.parse(node['required_resources']);
-        let hostname = document.getElementById('id_hostname');
-        let image = document.getElementById('id_image');
-
-        for(let resource in required_resources){
-            this.available_resources[resource] += required_resources[resource];
-        }
-
-        if (hostname && image) {
-            hostname.readOnly = false;
-            image.disabled = false;
-        }
-
-        this.updateAvailibility();
-    }
-
-    processClick(id){
-        let lab_check;
-        const node = this.filter_items[id];
-        if(!node['selectable'])
-            return;
-
-        // If they are selecting a lab, update accordingly
-        if (node['class'] == 'lab') {
-            lab_check = this.labCheck(node);
-            if (!lab_check)
-                return;
-        }
-
-        // Can only select a resource if a lab is selected
-        if (!this.currentLab) {
-            alert('You must select a lab before selecting a resource');
-            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.releaseResource(node); // can't do this in clear since clear removes border
-        }
-        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", "d-inline-block");
-        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.classList.add("form-control", "w-auto", "d-inline-block");
-        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("card", "flex-row", "d-flex", "mb-2");
-        this.dropdown_count++;
-        const label = document.createElement("H5")
-        label.appendChild(document.createTextNode(node['name']))
-        label.classList.add("p-1", "m-1", "flex-grow-1");
-        div.appendChild(label);
-        let remove_btn = this.make_remove_button(div, node);
-        remove_btn.classList.add("p-1", "m-1");
-        div.appendChild(remove_btn);
-        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);
-        this.result[node.class][node.id]['count']--;
-        this.releaseResource(node); // This can't be done on clear b/c clear removes border
-
-        //checks if we have removed last item in class
-        if(this.result[node.class][node.id]['count'] == 0){
-            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, count: 0}
-
-        this.result[node.class][node.id]['count']++;
-    }
-
-    finish(){
-        document.getElementById("filter_field").value = JSON.stringify(this.result);
-    }
-}
-
-class NetworkStep {
-    // expects:
-    //
-    // debug: bool
-    // resources: {
-    //     id: {
-    //         id: int,
-    //         value: {
-    //             description: string,
-    //         },
-    //         interfaces: [
-    //             id: int,
-    //             name: str,
-    //             description: str,
-    //             connections: [
-    //                 {
-    //                     network: int, [networks.id]
-    //                     tagged: bool
-    //                 }
-    //             ],
-    //         ],
-    //     }
-    // }
-    // networks: {
-    //     id: {
-    //         id: int,
-    //         name: str,
-    //         public: bool,
-    //     }
-    // }
-    //
-    constructor(debug, resources, networks, graphContainer, overviewContainer, toolbarContainer){
-        if(!this.check_support()) {
-            console.log("Aborting, browser is not supported");
-            return;
-        }
-
-        this.currentWindow = null;
-        this.netCount = 0;
-        this.netColors = ['red', 'blue', 'purple', 'green', 'orange', '#8CCDF5', '#1E9BAC'];
-        this.hostCount = 0;
-        this.lastHostBottom = 100;
-        this.networks = new Set();
-        this.has_public_net = false;
-        this.debug = debug;
-        this.editor = new mxEditor();
-        this.graph = this.editor.graph;
-
-        window.global_graph = this.graph;
-        window.network_rr_index = 5;
-
-        this.editor.setGraphContainer(graphContainer);
-        this.doGlobalConfig();
-
-        let mx_networks = {}
-
-        for(const network_id in networks) {
-            let network = networks[network_id];
-
-            mx_networks[network_id] = this.populateNetwork(network);
-        }
-
-        this.prefillHosts(resources, mx_networks);
-
-        //this.addToolbarButton(this.editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true);
-        //this.addToolbarButton(this.editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true);
-        this.addToolbarButton(this.editor, toolbarContainer, 'zoomIn', 'fa-search-plus');
-        this.addToolbarButton(this.editor, toolbarContainer, 'zoomOut', 'fa-search-minus');
-
-        if(this.debug){
-            this.editor.addAction('printXML', function(editor, cell) {
-                mxLog.write(this.encodeGraph());
-                mxLog.show();
-            }.bind(this));
-            this.addToolbarButton(this.editor, toolbarContainer, 'printXML', 'fa-file-code');
-        }
-
-        new mxOutline(this.graph, overviewContainer);
-        //sets the edge color to be the same as the network
-        this.graph.addListener(mxEvent.CELL_CONNECTED, function(sender, event) {this.cellConnectionHandler(sender, event)}.bind(this));
-        //hooks up double click functionality
-        this.graph.dblClick = function(evt, cell) {this.doubleClickHandler(evt, cell);}.bind(this);
-    }
-
-    check_support(){
-        if (!mxClient.isBrowserSupported()) {
-            mxUtils.error('Browser is not supported', 200, false);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Expects
-     * mx_interface: mxCell for the interface itself
-     * network: mxCell for the outer network
-     * tagged: bool
-     */
-    connectNetwork(mx_interface, network, tagged) {
-        var cell = new mxCell(
-            "connection from " + network + " to " + mx_interface,
-            new mxGeometry(0, 0, 50, 50));
-        cell.edge = true;
-        cell.geometry.relative = true;
-        cell.setValue(JSON.stringify({tagged: tagged}));
-
-        let terminal = this.getClosestNetworkCell(mx_interface.geometry.y, network);
-        let edge = this.graph.addEdge(cell, null, mx_interface, terminal);
-        this.colorEdge(edge, terminal, true);
-        this.graph.refresh(edge);
-    }
-
-    /**
-     * Expects:
-     *
-     * to: desired y axis position of the matching cell
-     * within: graph cell for a full network, with all child cells
-     *
-     * Returns:
-     * an mx cell, the one vertically closest to the desired value
-     *
-     * Side effect:
-     * modifies the <rr_index> on the <within> parameter
-     */
-    getClosestNetworkCell(to, within) {
-        if(window.network_rr_index === undefined) {
-            window.network_rr_index = 5;
-        }
-
-        let child_keys = within.children.keys();
-        let children = Array.from(within.children);
-        let index = (window.network_rr_index++) % children.length;
-
-        let child = within.children[child_keys[index]];
-
-        return children[index];
-    }
-
-    /** Expects
-     *
-     * hosts: {
-     *     id: {
-     *         id: int,
-     *         value: {
-     *             description: string,
-     *         },
-     *         interfaces: [
-     *             id: int,
-     *             name: str,
-     *             description: str,
-     *             connections: [
-     *                 {
-     *                     network: int, [networks.id]
-     *                     tagged: bool 
-     *                 }
-     *             ],
-     *         ],
-     *     }
-     * }
-     *
-     * network_mappings: {
-     *     <django network id>: <mxnetwork id>
-     * }
-     *
-     * draws given hosts into the mxgraph
-     */
-    prefillHosts(hosts, network_mappings){
-        for(const host_id in hosts) {
-            this.makeHost(hosts[host_id], network_mappings);
-        }
-    }
-
-    cellConnectionHandler(sender, event){
-        const edge = event.getProperty('edge');
-        const terminal = event.getProperty('terminal')
-        const source = event.getProperty('source');
-        if(this.checkAllowed(edge, terminal, source)) {
-            this.colorEdge(edge, terminal, source);
-            this.alertVlan(edge, terminal, source);
-        }
-    }
-
-    doubleClickHandler(evt, cell) {
-        if( cell != null ){
-            if( cell.getParent() != null && cell.getParent().getId().indexOf("network") > -1) {
-                cell = cell.getParent();
-            }
-            if( cell.isEdge() || cell.getId().indexOf("network") > -1 ) {
-                this.createDeleteDialog(cell.getId());
-            }
-            else {
-                this.showDetailWindow(cell);
-           }
-        }
-    }
-
-    alertVlan(edge, terminal, source) {
-        if( terminal == null || edge.getTerminal(!source) == null) {
-            return;
-        }
-        const form = document.createElement("form");
-        const tagged = document.createElement("input");
-        tagged.type = "radio";
-        tagged.name = "tagged";
-        tagged.value = "True";
-        tagged.checked = "True";
-        form.appendChild(tagged);
-        form.appendChild(document.createTextNode(" Tagged"));
-        form.appendChild(document.createElement("br"));
-
-        const untagged = document.createElement("input");
-        untagged.type = "radio";
-        untagged.name = "tagged";
-        untagged.value = "False";
-        form.appendChild(untagged);
-        form.appendChild(document.createTextNode(" Untagged"));
-        form.appendChild(document.createElement("br"));
-
-        const yes_button = document.createElement("button");
-        yes_button.onclick = function() {this.parseVlanWindow(edge.getId());}.bind(this);
-        yes_button.appendChild(document.createTextNode("Okay"));
-
-        const cancel_button = document.createElement("button");
-        cancel_button.onclick = function() {this.deleteVlanWindow(edge.getId());}.bind(this);
-        cancel_button.appendChild(document.createTextNode("Cancel"));
-
-        const error_div = document.createElement("div");
-        error_div.id = "current_window_errors";
-        form.appendChild(error_div);
-
-        const content = document.createElement('div');
-        content.appendChild(form);
-        content.appendChild(yes_button);
-        content.appendChild(cancel_button);
-        this.showWindow("Vlan Selection", content, 200, 200);
-    }
-
-    createDeleteDialog(id) {
-        const content = document.createElement('div');
-        const remove_button = document.createElement("button");
-        remove_button.style.width = '46%';
-        remove_button.onclick = function() { this.deleteCell(id);}.bind(this);
-        remove_button.appendChild(document.createTextNode("Remove"));
-        const cancel_button = document.createElement("button");
-        cancel_button.style.width = '46%';
-        cancel_button.onclick = function() { this.closeWindow();}.bind(this);
-        cancel_button.appendChild(document.createTextNode("Cancel"));
-
-        content.appendChild(remove_button);
-        content.appendChild(cancel_button);
-        this.showWindow('Do you want to delete this network?', content, 200, 62);
-    }
-
-    checkAllowed(edge, terminal, source) {
-        //check if other terminal is null, and that they are different
-        const otherTerminal = edge.getTerminal(!source);
-        if(terminal != null && otherTerminal != null) {
-            if( terminal.getParent().getId().split('_')[0] == //'host' or 'network'
-                otherTerminal.getParent().getId().split('_')[0] ) {
-                //not allowed
-                this.graph.removeCells([edge]);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    colorEdge(edge, terminal, source) {
-        if(terminal.getParent().getId().indexOf('network') >= 0) {
-            const styles = terminal.getParent().getStyle().split(';');
-            let color = 'black';
-            for(let style of styles){
-                const kvp = style.split('=');
-                if(kvp[0] == "fillColor"){
-                    color = kvp[1];
-                }
-            }
-
-            edge.setStyle('strokeColor=' + color);
-        } else {
-            console.log("Failed to color " + edge + ", " + terminal + ", " + source);
-        }
-    }
-
-    showDetailWindow(cell) {
-        const info = JSON.parse(cell.getValue());
-        const content = document.createElement("div");
-        const pre_tag = document.createElement("pre");
-        pre_tag.appendChild(document.createTextNode("Name: " + info.name + "\nDescription:\n" + info.description));
-        const ok_button = document.createElement("button");
-        ok_button.onclick = function() { this.closeWindow();};
-        content.appendChild(pre_tag);
-        content.appendChild(ok_button);
-        this.showWindow('Details', content, 400, 400);
-    }
-
-    restoreFromXml(xml, editor) {
-        const doc = mxUtils.parseXml(xml);
-        const node = doc.documentElement;
-        editor.readGraphModel(node);
-
-        //Iterate over all children, and parse the networks to add them to the sidebar
-        for( const cell of this.graph.getModel().getChildren(this.graph.getDefaultParent())) {
-            if(cell.getId().indexOf("network") > -1) {
-                const info = JSON.parse(cell.getValue());
-                const name = info['name'];
-                this.networks.add(name);
-                const styles = cell.getStyle().split(";");
-                let color = null;
-                for(const style of styles){
-                    const kvp = style.split('=');
-                    if(kvp[0] == "fillColor") {
-                        color = kvp[1];
-                        break;
-                    }
-                }
-                if(info.public){
-                    this.has_public_net = true;
-                }
-                this.netCount++;
-                this.makeSidebarNetwork(name, color, cell.getId());
-            }
-        }
-    }
-
-    deleteCell(cellId) {
-        var cell = this.graph.getModel().getCell(cellId);
-        if( cellId.indexOf("network") > -1 ) {
-            let elem = document.getElementById(cellId);
-            elem.parentElement.removeChild(elem);
-        }
-        this.graph.removeCells([cell]);
-        this.currentWindow.destroy();
-    }
-
-    newNetworkWindow() {
-        const input = document.createElement("input");
-        input.type = "text";
-        input.name = "net_name";
-        input.maxlength = 100;
-        input.id = "net_name_input";
-        input.style.margin = "5px";
-
-        const yes_button = document.createElement("button");
-        yes_button.onclick = function() {this.parseNetworkWindow();}.bind(this);
-        yes_button.appendChild(document.createTextNode("Okay"));
-
-        const cancel_button = document.createElement("button");
-        cancel_button.onclick = function() {this.closeWindow();}.bind(this);
-        cancel_button.appendChild(document.createTextNode("Cancel"));
-
-        const error_div = document.createElement("div");
-        error_div.id = "current_window_errors";
-
-        const content = document.createElement("div");
-        content.appendChild(document.createTextNode("Name: "));
-        content.appendChild(input);
-        content.appendChild(document.createElement("br"));
-        content.appendChild(yes_button);
-        content.appendChild(cancel_button);
-        content.appendChild(document.createElement("br"));
-        content.appendChild(error_div);
-
-        this.showWindow("Network Creation", content, 300, 300);
-    }
-
-    parseNetworkWindow() {
-        const net_name = document.getElementById("net_name_input").value
-        const error_div = document.getElementById("current_window_errors");
-        if( this.networks.has(net_name) ){
-            error_div.innerHTML = "All network names must be unique";
-            return;
-        }
-        this.addNetwork(net_name);
-        this.currentWindow.destroy();
-    }
-
-    addToolbarButton(editor, toolbar, action, image) {
-        const button = document.createElement('button');
-        button.setAttribute('class', 'btn btn-sm m-1');
-        if (image != null) {
-            const icon = document.createElement('i');
-            icon.setAttribute('class', 'fas ' + image);
-            button.appendChild(icon);
-        }
-        mxEvent.addListener(button, 'click', function(evt) {
-            editor.execute(action);
-        });
-        mxUtils.write(button, '');
-        toolbar.appendChild(button);
-    };
-
-    encodeGraph() {
-        const encoder = new mxCodec();
-        const xml = encoder.encode(this.graph.getModel());
-        return mxUtils.getXml(xml);
-    }
-
-    doGlobalConfig() {
-        //general graph stuff
-        this.graph.setMultigraph(false);
-        this.graph.setCellsSelectable(false);
-        this.graph.setCellsMovable(false);
-
-        //testing
-        this.graph.vertexLabelIsMovable = true;
-
-        //edge behavior
-        this.graph.setConnectable(true);
-        this.graph.setAllowDanglingEdges(false);
-        mxEdgeHandler.prototype.snapToTerminals = true;
-        mxConstants.MIN_HOTSPOT_SIZE = 16;
-        mxConstants.DEFAULT_HOTSPOT = 1;
-        //edge 'style' (still affects behavior greatly)
-        const style = this.graph.getStylesheet().getDefaultEdgeStyle();
-        style[mxConstants.STYLE_EDGE] = mxConstants.EDGESTYLE_ELBOW;
-        style[mxConstants.STYLE_ENDARROW] = mxConstants.NONE;
-        style[mxConstants.STYLE_ROUNDED] = true;
-        style[mxConstants.STYLE_FONTCOLOR] = 'black';
-        style[mxConstants.STYLE_STROKECOLOR] = 'red';
-        style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = '#FFFFFF';
-        style[mxConstants.STYLE_STROKEWIDTH] = '3';
-        style[mxConstants.STYLE_ROUNDED] = true;
-        style[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
-
-        const hostStyle = this.graph.getStylesheet().getDefaultVertexStyle();
-        hostStyle[mxConstants.STYLE_ROUNDED] = 1;
-
-        this.graph.convertValueToString = function(cell) {
-            try{
-                //changes value for edges with xml value
-                if(cell.isEdge()) {
-                    if(JSON.parse(cell.getValue())["tagged"]) {
-                        return "tagged";
-                    }
-                    return "untagged";
-                }
-                else{
-                    return JSON.parse(cell.getValue())['name'];
-                }
-            }
-            catch(e){
-                return cell.getValue();
-            }
-        };
-    }
-
-    showWindow(title, content, width, height) {
-        //create transparent black background
-        const background = document.createElement('div');
-        background.style.position = 'absolute';
-        background.style.left = '0px';
-        background.style.top = '0px';
-        background.style.right = '0px';
-        background.style.bottom = '0px';
-        background.style.background = 'black';
-        mxUtils.setOpacity(background, 50);
-        document.body.appendChild(background);
-
-        const x = Math.max(0, document.body.scrollWidth/2-width/2);
-        const y = Math.max(10, (document.body.scrollHeight ||
-                    document.documentElement.scrollHeight)/2-height*2/3);
-
-        const wnd = new mxWindow(title, content, x, y, width, height, false, true);
-        wnd.setClosable(false);
-
-        wnd.addListener(mxEvent.DESTROY, function(evt) {
-            this.graph.setEnabled(true);
-            mxEffects.fadeOut(background, 50, true, 10, 30, true);
-        }.bind(this));
-        this.currentWindow = wnd;
-
-        this.graph.setEnabled(false);
-        this.currentWindow.setVisible(true);
-    };
-
-    closeWindow() {
-        //allows the current window to be destroyed
-        this.currentWindow.destroy();
-    };
-
-    othersUntagged(edgeID) {
-        const edge = this.graph.getModel().getCell(edgeID);
-        const end1 = edge.getTerminal(true);
-        const end2 = edge.getTerminal(false);
-
-        if( end1.getParent().getId().split('_')[0] == 'host' ){
-            var netint = end1;
-        } else {
-            var netint = end2;
-        }
-
-        var edges = netint.edges;
-        for( let edge of edges) {
-            if( edge.getValue() ) {
-                var tagged = JSON.parse(edge.getValue()).tagged;
-            } else {
-                var tagged = true;
-            }
-            if( !tagged ) {
-                return true;
-            }
-        }
-
-        return false;
-    };
-
-
-    deleteVlanWindow(edgeID) {
-        const cell = this.graph.getModel().getCell(edgeID);
-        this.graph.removeCells([cell]);
-        this.currentWindow.destroy();
-    }
-
-    parseVlanWindow(edgeID) {
-        //do parsing and data manipulation
-        const radios = document.getElementsByName("tagged");
-        const edge = this.graph.getModel().getCell(edgeID);
-
-        for(let radio of radios){
-            if(radio.checked) {
-                //set edge to be tagged or untagged
-                if( radio.value == "False") {
-                    if( this.othersUntagged(edgeID) ) {
-                        document.getElementById("current_window_errors").innerHTML = "Only one untagged vlan per interface is allowed.";
-                        return;
-                    }
-                }
-                const edgeVal = {tagged: radio.value == "True"};
-                edge.setValue(JSON.stringify(edgeVal));
-                break;
-            }
-        }
-        this.graph.refresh(edge);
-        this.closeWindow();
-    }
-
-    makeMxNetwork(net_name, is_public = false) {
-        const model = this.graph.getModel();
-        const width = 10;
-        const height = 1700;
-        const xoff = 400 + (30 * this.netCount);
-        const yoff = -10;
-        let color = this.netColors[this.netCount];
-        if( this.netCount > (this.netColors.length - 1)) {
-            color = Math.floor(Math.random() * 16777215); //int in possible color space
-            color = '#' + color.toString(16).toUpperCase(); //convert to hex
-        }
-        const net_val = { name: net_name, public: is_public};
-        const net = this.graph.insertVertex(
-            this.graph.getDefaultParent(),
-            'network_' + this.netCount,
-            JSON.stringify(net_val),
-            xoff,
-            yoff,
-            width,
-            height,
-            'fillColor=' + color,
-            false
-        );
-        const num_ports = 45;
-        for(var i=0; i<num_ports; i++){
-            let port = this.graph.insertVertex(
-                net,
-                null,
-                '',
-                0,
-                (1/num_ports) * i,
-                10,
-                height / num_ports,
-                'fillColor=black;opacity=0',
-                true
-            );
-        }
-
-        const ret_val = { color: color, element_id: "network_" + this.netCount };
-
-        this.networks.add(net_name);
-        this.netCount++;
-        return ret_val;
-    }
-
-    // expects:
-    //
-    // {
-    //     id: int,
-    //     name: str,
-    //     public: bool,
-    // }
-    //
-    // returns:
-    // mxgraph id of network
-    populateNetwork(network) {
-        let mxNet = this.makeMxNetwork(network.name, network.public);
-        this.makeSidebarNetwork(network.name, mxNet.color, mxNet.element_id);
-
-        if( network.public ) {
-            this.has_public_net = true;
-        }
-
-        return mxNet.element_id;
-    }
-
-    addPublicNetwork() {
-        const net = this.makeMxNetwork("public", true);
-        this.makeSidebarNetwork("public", net['color'], net['element_id']);
-        this.has_public_net = true;
-    }
-
-    addNetwork(net_name) {
-        const ret = this.makeMxNetwork(net_name);
-        this.makeSidebarNetwork(net_name, ret.color, ret.element_id);
-    }
-
-    updateHosts(removed) {
-        const cells = []
-        for(const hostID of removed) {
-            cells.push(this.graph.getModel().getCell("host_" + hostID));
-        }
-        this.graph.removeCells(cells);
-
-        const hosts = this.graph.getChildVertices(this.graph.getDefaultParent());
-        let topdist = 100;
-        for(const i in hosts) {
-            const host = hosts[i];
-            if(host.id.startsWith("host_")){
-                const geometry = host.getGeometry();
-                geometry.y = topdist + 50;
-                topdist = geometry.y + geometry.height;
-                host.setGeometry(geometry);
-            }
-        }
-    }
-
-    makeSidebarNetwork(net_name, color, net_id){
-        const colorBlob = document.createElement("div");
-        colorBlob.className = "square-20 rounded-circle";
-        colorBlob.style['background'] = color;
-
-        const textContainer = document.createElement("span");
-        textContainer.className = "ml-2";
-        textContainer.appendChild(document.createTextNode(net_name));
-
-        const timesIcon = document.createElement("i");
-        timesIcon.classList.add("fas", "fa-times");
-
-        const deletebutton = document.createElement("button");
-        deletebutton.className = "btn btn-danger ml-auto square-20 p-0 d-flex justify-content-center";
-        deletebutton.appendChild(timesIcon);
-        deletebutton.addEventListener("click", function() { this.createDeleteDialog(net_id); }.bind(this), false);
-
-        const newNet = document.createElement("li");
-        newNet.classList.add("list-group-item", "d-flex", "bg-light");
-        newNet.id = net_id;
-        newNet.appendChild(colorBlob);
-        newNet.appendChild(textContainer);
-
-        if( net_name != "public" ) {
-            newNet.appendChild(deletebutton);
-        }
-        document.getElementById("network_list").appendChild(newNet);
-    }
-
-    /** 
-     * Expects format:
-     * {
-     *     'id': int,
-     *     'value': {
-     *         'description': string,
-     *     },
-     *     'interfaces': [
-     *          {
-     *              id: int,
-     *              name: str,
-     *              description: str,
-     *              connections: [
-     *                  {
-     *                      network: int, <django network id>,
-     *                      tagged: bool
-     *                  }
-     *              ]
-     *          }
-     *      ]
-     * }
-     *
-     * network_mappings: {
-     *     <django network id>: <mxnetwork id>
-     * }
-     */
-    makeHost(hostInfo, network_mappings) {
-        const value = JSON.stringify(hostInfo['value']);
-        const interfaces = hostInfo['interfaces'];
-        const width = 100;
-        const height = (25 * interfaces.length) + 25;
-        const xoff = 75;
-        const yoff = this.lastHostBottom + 50;
-        this.lastHostBottom = yoff + height;
-        const host = this.graph.insertVertex(
-            this.graph.getDefaultParent(),
-            'host_' + hostInfo['id'],
-            value,
-            xoff,
-            yoff,
-            width,
-            height,
-            'editable=0',
-            false
-        );
-        host.getGeometry().offset = new mxPoint(-50,0);
-        host.setConnectable(false);
-        this.hostCount++;
-
-        for(var i=0; i<interfaces.length; i++) {
-            const port = this.graph.insertVertex(
-                host,
-                null,
-                JSON.stringify(interfaces[i]),
-                90,
-                (i * 25) + 12,
-                20,
-                20,
-                'fillColor=blue;editable=0',
-                false
-            );
-            port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0);
-            const iface = interfaces[i];
-            for( const connection of iface.connections ) {
-                const network = this
-                    .graph
-                    .getModel()
-                    .getCell(network_mappings[connection.network]);
-
-                this.connectNetwork(port, network, connection.tagged);
-            }
-            this.graph.refresh(port);
-        }
-        this.graph.refresh(host);
-    }
-
-    prepareForm() {
-        const input_elem = document.getElementById("hidden_xml_input");
-        input_elem.value = this.encodeGraph(this.graph);
-    }
-}
-
-class SearchableSelectMultipleWidget {
-    constructor(format_vars, field_dataset, field_initial) {
-        this.format_vars = format_vars;
-        this.items = field_dataset;
-        this.initial = field_initial;
-
-        this.expanded_name_trie = {"isComplete": false};
-        this.small_name_trie = {"isComplete": false};
-        this.string_trie = {"isComplete": false};
-
-        this.added_items = new Set();
-
-        for( let e of ["show_from_noentry", "show_x_results", "results_scrollable", "selectable_limit", "placeholder"] )
-        {
-            this[e] = format_vars[e];
-        }
-
-        this.search_field_init();
-
-        if( this.show_from_noentry )
-        {
-            this.search("");
-        }
-    }
-
-    disable() {
-        const textfield = document.getElementById("user_field");
-        const drop = document.getElementById("drop_results");
-
-        textfield.disabled = "True";
-        drop.style.display = "none";
-
-        const btns = document.getElementsByClassName("btn-remove");
-        for( const btn of btns )
-        {
-            btn.classList.add("disabled");
-            btn.onclick = "";
-        }
-    }
-
-    search_field_init() {
-        this.build_all_tries(this.items);
-
-        for( const elem of this.initial )
-        {
-            this.select_item(elem);
-        }
-        if(this.initial.length == 1)
-        {
-            this.search(this.items[this.initial[0]]["small_name"]);
-            document.getElementById("user_field").value = this.items[this.initial[0]]["small_name"];
-        }
-    }
-
-    build_all_tries(dict)
-    {
-        for( const key in dict )
-        {
-            this.add_item(dict[key]);
-        }
-    }
-
-    add_item(item)
-    {
-        const id = item['id'];
-        this.add_to_tree(item['expanded_name'], id, this.expanded_name_trie);
-        this.add_to_tree(item['small_name'], id, this.small_name_trie);
-        this.add_to_tree(item['string'], id, this.string_trie);
-    }
-
-    add_to_tree(str, id, trie)
-    {
-        let inner_trie = trie;
-        while( str )
-        {
-            if( !inner_trie[str.charAt(0)] )
-            {
-                var new_trie = {};
-                inner_trie[str.charAt(0)] = new_trie;
-            }
-            else
-            {
-                var new_trie = inner_trie[str.charAt(0)];
-            }
-
-            if( str.length == 1 )
-            {
-                new_trie.isComplete = true;
-                if( !new_trie.ids )
-                {
-                    new_trie.ids = [];
-                }
-                new_trie.ids.push(id);
-            }
-            inner_trie = new_trie;
-            str = str.substring(1);
-        }
-    }
-
-    search(input)
-    {
-        if( input.length == 0 && !this.show_from_noentry){
-            this.dropdown([]);
-            return;
-        }
-        else if( input.length == 0 && this.show_from_noentry)
-        {
-            this.dropdown(this.items); //show all items
-        }
-        else
-        {
-            const trees = []
-            const tr1 = this.getSubtree(input, this.expanded_name_trie);
-            trees.push(tr1);
-            const tr2 = this.getSubtree(input, this.small_name_trie);
-            trees.push(tr2);
-            const tr3 = this.getSubtree(input, this.string_trie);
-            trees.push(tr3);
-            const results = this.collate(trees);
-            this.dropdown(results);
-        }
-    }
-
-    getSubtree(input, given_trie)
-    {
-        /*
-        recursive function to return the trie accessed at input
-        */
-
-        if( input.length == 0 ){
-            return given_trie;
-        }
-
-        else{
-            const substr = input.substring(0, input.length - 1);
-            const last_char = input.charAt(input.length-1);
-            const subtrie = this.getSubtree(substr, given_trie);
-
-            if( !subtrie ) //substr not in the trie
-            {
-                return {};
-            }
-
-            const indexed_trie = subtrie[last_char];
-            return indexed_trie;
-        }
-    }
-
-    serialize(trie)
-    {
-        /*
-        takes in a trie and returns a list of its item id's
-        */
-        let itemIDs = [];
-        if ( !trie )
-        {
-            return itemIDs; //empty, base case
-        }
-        for( const key in trie )
-        {
-            if(key.length > 1)
-            {
-                continue;
-            }
-            itemIDs = itemIDs.concat(this.serialize(trie[key]));
-        }
-        if ( trie.isComplete )
-        {
-            itemIDs.push(...trie.ids);
-        }
-
-        return itemIDs;
-    }
-
-    collate(trees)
-    {
-        /*
-        takes a list of tries
-        returns a list of ids of objects that are available
-        */
-        const results = [];
-        for( const tree of trees )
-        {
-            const available_IDs = this.serialize(tree);
-
-            for( const itemID of available_IDs ) {
-                results[itemID] = this.items[itemID];
-            }
-        }
-        return results;
-    }
-
-    generate_element_text(obj)
-    {
-        const content_strings = [obj.expanded_name, obj.small_name, obj.string].filter(x => Boolean(x));
-        const result = content_strings.shift();
-        if( result == null || content_strings.length < 1) {
-            return result;
-        } else {
-            return result + " (" + content_strings.join(", ") + ")";
-        }
-    }
-
-    dropdown(ids)
-    {
-        /*
-        takes in a mapping of ids to objects in  items
-        and displays them in the dropdown
-        */
-        const drop = document.getElementById("drop_results");
-        while(drop.firstChild)
-        {
-            drop.removeChild(drop.firstChild);
-        }
-
-        for( const id in ids )
-        {
-            const obj = this.items[id];
-            const result_text = this.generate_element_text(obj);
-            const result_entry = document.createElement("a");
-            result_entry.href = "#";
-            result_entry.innerText = result_text;
-            result_entry.title = result_text;
-            result_entry.classList.add("list-group-item", "list-group-item-action", "overflow-ellipsis", "flex-shrink-0");
-            result_entry.onclick = function() { searchable_select_multiple_widget.select_item(obj.id); };
-            const tooltip = document.createElement("span");
-            const tooltiptext = document.createTextNode(result_text);
-            tooltip.appendChild(tooltiptext);
-            tooltip.classList.add("d-none");
-            result_entry.appendChild(tooltip);
-            drop.appendChild(result_entry);
-        }
-
-        const scroll_restrictor = document.getElementById("scroll_restrictor");
-
-        if( !drop.firstChild )
-        {
-            scroll_restrictor.style.visibility = 'hidden';
-        }
-        else
-        {
-            scroll_restrictor.style.visibility = 'inherit';
-        }
-    }
-
-    select_item(item_id)
-    {
-        if( (this.selectable_limit > -1 && this.added_items.size < this.selectable_limit) || this.selectable_limit < 0 )
-        {
-            this.added_items.add(item_id);
-        }
-        this.update_selected_list();
-        // clear search bar contents
-        document.getElementById("user_field").value = "";
-        document.getElementById("user_field").focus();
-        this.search("");
-    }
-
-    remove_item(item_id)
-    {
-        this.added_items.delete(item_id);
-
-        this.update_selected_list()
-        document.getElementById("user_field").focus();
-    }
-
-    update_selected_list()
-    {
-        document.getElementById("added_number").innerText = this.added_items.size;
-        const selector = document.getElementById('selector');
-        selector.value = JSON.stringify([...this.added_items]);
-        const added_list = document.getElementById('added_list');
-
-        while(selector.firstChild)
-        {
-            selector.removeChild(selector.firstChild);
-        }
-        while(added_list.firstChild)
-        {
-            added_list.removeChild(added_list.firstChild);
-        }
-
-        const list_html = document.createElement("div");
-        list_html.classList.add("list-group");
-
-        for( const item_id of this.added_items )
-        {
-            const times = document.createElement("li");
-            times.classList.add("fas", "fa-times");
-
-            const deleteButton = document.createElement("a");
-            deleteButton.href = "#";
-            deleteButton.innerHTML = "<i class='fas fa-times'></i>"
-            // Setting .onclick/.addEventListener does not work,
-            // which is why I took the setAttribute approach
-            // If anyone knows why, please let me know :]
-            deleteButton.setAttribute("onclick", `searchable_select_multiple_widget.remove_item(${item_id});`);
-            deleteButton.classList.add("btn");
-            const deleteColumn = document.createElement("div");
-            deleteColumn.classList.add("col-auto");
-            deleteColumn.append(deleteButton);
-
-            const item = this.items[item_id];
-            const element_entry_text = this.generate_element_text(item);
-            const textColumn = document.createElement("div");
-            textColumn.classList.add("col", "overflow-ellipsis");
-            textColumn.innerText = element_entry_text;
-            textColumn.title = element_entry_text;
-
-            const itemRow = document.createElement("div");
-            itemRow.classList.add("list-group-item", "d-flex", "p-0", "align-items-center");
-            itemRow.append(textColumn, deleteColumn);
-
-            list_html.append(itemRow);
-        }
-        added_list.innerHTML = list_html.innerHTML;
-    }
-}