view query_builder/querybuild.html @ 32:acc60a20582c

Temporary Changes for querybuilder. Selecting attribute and having results list filter not fully working yet. Need 'ALL' option in attributes.
author arussell
date Sat, 19 Dec 2015 09:15:43 -0500
parents 5384b71df52a
children 4bbb832c53ac
line wrap: on
line source

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
    <title>Query Builder</title>
    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="select2-4.0.1/dist/css/select2.min.css">

    <script type="text/javascript" src="js/d3.min.js"></script>
    <script type="text/javascript" src="js/underscore-min.js"></script>
    <script type="text/javascript" src="select2-4.0.1/vendor/jquery-2.1.0.js"></script>
    <script type="text/javascript" src="select2-4.0.1/dist/js/select2.full.min.js"></script>



</head>
<body style="background:none;">
<div role="navigation" class="navbar navbar-default navbar-static-top">
    <div class="container">
        <div class="row">
            <div class="col-sm-6 col-md-6">
                <ul class="nav navbar-nav">
                </ul>
            </div>
            <div class="navbar-header col-sm-6 col-md-6" style="height: auto;">
                <div class="col-md-6">
                    <div class="navbar-brand">
                        <div class="brand">ISMI Query Builder</div>
                    </div>
                </div>
                <div class="col-md-offset-6">
                    <div class="logo-well" style="height: 60%; width: 60%;">
                        <a href="//neo4j.com/developer-resources">
                            <img src="//neo4j-contrib.github.io/developer-resources/language-guides/assets/img/logo-white.svg" alt="Neo4j World's Leading Graph Database" id="logo" style="max-height: 50%; width: 50%">
                        </a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<div class="container">
    <div class="row" style="width: 95%">
        <div class="col-lg">
            <div class="panel panel-default">
                <div class="panel-heading" id="title">Query Builder</div>

                <section>
                    <div class="s2-example">
                        <div id="filters">
                            <div class="row" id="startrow" style="margin-top: 15px">
                                <div class="col-sm-4 col-md-4" id="select-col1">
                                    <select class="selected-object form-control">
                                        <option selected>Object type :  </option>
                                    </select>
                                </div>
                                <div class="col-sm-4 col-md-4" id="select-col2">
                                    <select class="select-object1 form-control">
                                        <option selected>TEXT</option>
                                        <option>WITNESS</option>
                                        <option>PERSON</option>
                                        <option>CODEX</option>
                                        <option>PLACE</option>
                                        <option>REPOSITORY</option>
                                        <option>COLLECTION</option>
                                    </select>
                                </div>
                            </div>
                            <div class="row" id="row1" style="margin-top: 15px">
                                <div class="col-sm-4 col-md-4" id="1filter-col1">
                                    <select class="filter-box1 form-control">
                                        <option value="" disabled selected>Choose filter...</option>
                                    </select>
                                </div>
                            </div>
                        </div>
                        <div align="left" style="margin-top: 30px"><button id="minus_filter_button" class="btn btn-warning btn-md" type="button"> - </button>
                            <button id="plus_filter_button" class="btn btn-warning btn-md" type="button">  +  </button>
                        </div>
                    </div>


                    <div class="container-fluid" style="margin-top: 15px">
                        <form role="form">
                            <div class="form-group">
                                <label for="results-container">Results list</label>
                                <select multiple class="form-control" id="results-container" style="min-height: 300px;">
                                </select>
                            </div>
                        </form>

                    </div>

                    <!--    <pre data-fill-from=".js-code-data-array"></pre> -->
                </section>

            </div>
        </div>
    </div>
</div>






<script type="text/javascript" class="js-code-data-array">
    //var queryData = [];

    // Defining initial variables and helper functions
    var sourceType = "TEXT";
    var targets = [];
    var targetTypes = [];
    var targetObj = []; // The current objects on the display box
    var numFilters = 1;
    var filters = [
        { id: 0, text: 'has relation' },
        { id: 1, text: 'attribute' }
    ];
    $(".filter-box"+numFilters).select2({
        // Initialize first filter box
        data: filters,
        minimumResultsForSearch: Infinity
    });
    var listTolerance = 100;
    //$("")

    function filter_html(n) {
        return '<div class="row" id="row'+n+'" style="margin-top: 15px">' +
                '<div class="col-sm-4 col-md-4" id="'+n+'filter-col1">' +
                '<select class="filter-box'+n+' form-control">' +
                '<option value="" disabled selected>Choose filter...</option>' +
                '</select>' +
                '</div>' +
                '</div>';
    }
    function constraint_html(classn) {
        return '<div class="col-sm-4 col-md-4" id="'+classn+'filter-col2">' +
                '<select class="rel-constraint-box'+classn+' form-control" >' +
                '</select>' +
                '</div>';
    }
    function attr_constraint_html(classn) {
        return '<div class="col-sm-4 col-md-4" id="'+classn+'filter-col2">' +
                '<select class="attr-constraint-box'+classn+' form-control" >' +
                '</select>' +
                '</div>';
    }
    function submit_html(classn) {
        return '<div class="col-sm-4 col-md-4" id="'+classn+'filter-col3">' +
                '<div class="form-group'+classn+'">' +
                '<form onsubmit="return false;"> ' +
                '<input type="text" name="attr-field" class="form-control" id="attr-input'+classn+'">' +
                '</input>' +
                '</form>' +
                '</div>' +
                '</div>';
    }
    function select_html(selection, n) {
        return '<div class="row" id="row'+n+'" style="margin-top: 15px">' +
                '<div class="col-sm-4 col-md-4" id="select-col1"> ' +
                '<select class="selected-object form-control"> ' +
                '<option selected>Selected object is: </option> ' +
                '</select> ' +
                '</div> ' +
                '<div class="col-sm-4 col-md-4" id="select-col2"> ' +
                '<h4>' + selection +
                '</h4>' +
                '</div> ' +
                '</div>';
    }




    // Button filters
    $("#plus_filter_button").click(function() {
        console.log("add filter");
        numFilters++;
        $("#filters").append(filter_html(numFilters));
        $(".filter-box"+numFilters).select2({
            data: filters,
            minimumResultsForSearch: Infinity
        });
    });
    $("#minus_filter_button").click(function() {
        console.log("minus filter");
        $("#row"+numFilters).remove();
        numFilters--;
    });
    $("#results-container").dblclick(function() {
        var displayChoice = $("#results-container").find("option:selected").text();
        console.log(displayChoice + ".........");
        var selection;

        _.map(targetObj, function(obj){

            if (_.values(_.values(targetObj))[1] === displayChoice) {
                selection = (_.values(_.values(obj))[0])[1];
            }
            else {
                selection = (_.values(_.values(obj))[0])[0];
            }
        });
        numFilters++;
        $("#filters").append(select_html(selection, numFilters));
        // Generate inner relation list
        //genRelations(sourceType, ".constraint-box"+numFilters);
        sourceType = selection;
        console.log("add filter");
    });
    $(function() {
        $("input").submit(function(event, data) {
            console.log($('.form-control').find("option:selected").text());
            console.log(event);
            console.log(data);
            event.preventDefault();

        });
    });


    // Change Events
    $('body').on('change', function(event){
        var classname = (event.target.className).substr(0, (event.target.className).indexOf(" "));
        var classnum = (event.target.className.match(/\d+\.\d+|\d+\b|\d+(?=\w)/g) || [] ).map(function (v) {return +v;}).shift();
        var allclass = '.'+classname;
        var selectedOption = $(allclass).find('option:selected').text();
        console.log("SEL "+ selectedOption);

        // Change is on the filter box
        if (classname.indexOf("filter-box") > -1) {
            if (selectedOption === "has relation") {
                // Remove any other boxes in the row
                $("#"+classnum+"filter-col2").remove();
                $("#row"+classnum).append(constraint_html(classnum));
                // Generate inner relation list
                genRelations(sourceType, ".rel-constraint-box"+classnum);
            }
            if (selectedOption === "attribute") {
                $("#"+classnum+"filter-col2").remove();
                $("#row"+classnum).append(attr_constraint_html(classnum));
                genAttributes(sourceType, ".attr-constraint-box"+classnum);
                //$("#row"+classnum).append(submit_html(classnum));
                $("#row"+classnum).append(submit_html(classnum));

            }

            // NOT IMPLEMENTED
            /*
            if (selectedOption === "return") {
                $("#"+classnum+"filter-col2").remove();
                $("#row"+classnum).append(constraint_html(classnum));
                genAttributes(targetTypes, ".constraint-box"+classnum);
            }
            */
        }

        // Change is on the relationship constraint box
        if (classname.indexOf("rel-constraint-box") > -1) {
            //$().remove();
            genResults(sourceType, selectedOption, "results-container");
        }

        // Change is on the attr constraint box
        if (classname.indexOf("attr-constraint-box") > -1) {
            //$().remove();
            console.log("CHANGE ON ATTR");
            console.log("changing currentqueryattr to" + selectedOption);
            currentQueryAttr = selectedOption;
            console.log("currentQueryAttr is" + currentQueryAttr);
            //genResults(sourceType, selectedOption, "results-container");
        }

        // Change is on subject scope
        if (classname.indexOf("select-object") > -1) {
            sourceType = selectedOption;
            console.log("CHANGE ON SCOPE");
            genResults(sourceType, "New Source", "results-container");

            //ajax1(query, "results-container")
        }
    });
    $("#filters").submit(function (event) {
        var inputId = $("input:text[name=attr-field]")[0].id;
        var inputValue = $("input:text[name=attr-field]").val();
        // Alter results list
        console.log("QUERY TYPE IS ATTRIBUTE NOW");
        currentQueryType = "attribute";
        console.log("SUBMITTED");
        genResults(sourceType, inputValue, "results-container")


    });
    // For use in the onchange events above
    function genRelations(sourceNodeType, constraintBox) {
        console.log("GENERATE ALL RELATIONS");
        var query = "match (source:"+sourceNodeType+")-[rel]-target return distinct rel.type";
        console.log("QUERY TYPE IS RELATION NOW");
        currentQueryType = "relation";
        ajax1(query, constraintBox);
    }
    function genAttributes(sourceNodeType, constraintBox) {
        console.log("GENERATE ALL ATTRIBUTES");
        var query = "match (n:"+sourceNodeType+") with keys(n) as collection unwind collection as attributes return distinct attributes";
        ajax1(query, constraintBox);
    }
    function genResults(sourceNodeType, selected, constraintBox) {
        // TODO: Return first the list of current values of the display box

        if (selected === "New Source") {
            console.log("NEW SOURCE - UPDATE RESULTS");
            var query = "match (source:"+sourceNodeType+") return source._n_label, source.type";
        }
        else if (currentQueryType === "attribute") {
            console.log("ATTR CHANGE - UPDATE RESULTS");
            console.log("before update currentQueryAttr is " + currentQueryAttr);
            var qAttr = currentQueryAttr;
            var query = "match (source:"+sourceNodeType+") where "+get_result_labels()+" AND source."+qAttr+"=~\"(?i).*"+selected+".*\" return distinct source._n_label, source.type limit 5";
            console.log(query);
        }
        else if (currentQueryType === "relation") {
            var query = "match (source:" + sourceNodeType + ")-[rel:" + selected + "]->(target) return distinct target._n_label, target.type";
            console.log("REL CHANGE - UPDATE RESULTS");
        }
        else {
            console.log("GENRESULTS IMPROPERLY CALLED");
            return;
        }
        ajax1(query, constraintBox);
    }
    var currentQueryType;
    var currentQueryAttr;
    var resultLabelString = "";
    function get_result_labels() {
        return resultLabelString;
    }
    function set_result_labels(currentResults) {
        resultLabelString = "(";
        for (var i = 0; i < currentResults.length; i++) {
            if (i > 0) resultLabelString += " OR ";
            resultLabelString += 'source._n_label=\"'+currentResults[i].text.substr(1)+'\"';
        }
        resultLabelString += ")"
    }
    // to use contains
    // TODO: start n = node(*) where n.Name =~ '.*SUBSTRING.*' return n.Name, n;



    // Ajax request function
    function ajax1(q, resBox) {
        $.ajaxSetup({
            headers: {
                "Authorization": 'Basic ' + window.btoa("neo4j"+":"+"neo5j")
            }
        });
        return $.ajax({
            type: "POST",
            url: "https://ismi-dev.mpiwg-berlin.mpg.de/neo4j-ismi/db/data/cypher",
            accepts: "application/json",
            dataType: "json",
            data: {
                "query": q,
                "params": {}
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader ("Authorization", "Basic " + btoa('neo4j'+":"+'neo5j'));
            },
            processResults: function (data, params) {
                // parse the results into the format expected by Select2
                // since we are using custom formatting functions we do not need to
                // alter the remote JSON data, except to indicate that infinite
                // scrolling can be used
                params.page = params.page || 1;
                return {
                    pagination: {
                        more: (params.page * 30) < data.length
                    }
                };
            },
            success: function (res, textStatus, jqXHR) {
                ajaxCheck(res, resBox, textStatus);
                console.log(textStatus);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                console.log(textStatus);
            }
        });
    }
    // Check if request was successful
    function ajaxCheck(a1, resBox, status){
        if (status === 'error') console.log("error: bad ajax request");
        //else dataGen(a1);
        dataGen(a1.data, resBox);
    }
    // On success, generate new data
    // TODO: might want to just always return nodes and then deal with the queryData different for each type of return
    function dataGen(dataArr, resBox) {
        var d = dataArr;
        targets = [];

        // Consider implementing localStorage to avoid reloading every time
        var queryData = [];
        for (var i= 0; i<d.length; i++) {
            var j = d[i];
            queryData.push({ id: j, text: j[0] });
            targets.push(j[1]);
        }
        // Now that queryData array has been defined we will sort it by its label value and initialize the option.
        queryData.sort(function(a,b){
            var c = a.text.replace(/<|>/g, '');
            var d = b.text.replace(/<|>/g, '');
            return c.localeCompare(d);
        });

        // Change the displayed results
        if (resBox === "results-container") {
            process_display_results(queryData);
        }
        // Or fill any select boxes
        else {
            $(resBox).select2({
                data: queryData
            });
        }
    }
    function process_display_results(queryData) {
        var weights = [];
        var resultLength = queryData.length;
        targetTypes = [];
        targetObj = queryData;
        set_result_labels(queryData);
        $('#results-container')
                .find('option')
                .remove()
                .end()
        ;
        for (var i = 0; i < targets.length; i++) {
            if (targetTypes.indexOf(targets[i]) == -1) {
                targetTypes.push(targets[i]);
                weights.push(1);
            }
            else {
                weights[targetTypes.indexOf(targets[i])] += 1;
            }
        }
        if (resultLength < listTolerance) {
            for (var i = 0; i < resultLength; i++) {
                // TODO: put this jQuery call into its own function to be called if the else statement occurs
                // TODO: but the user actually does want to see the entire huge list.
                // This next bit removes duplicate labels if we want to use that for things like cities
                // would this throw issues with other things??
                //$('#results-container option:contains('+queryData[i].text+')').each(function(){
                //    $(this).remove();
                //});
                $('#results-container')
                        .append($("<option></option>")
                                .attr("value", i)
                                .text(queryData[i].text));
            }
        }
        else {
            for (var i = 0; i < targetTypes.length; i++) {
                $('#results-container')
                        .append($("<option></option>")
                                .text(targetTypes[i] + " [ " + weights[i] + " ]"));
            }
        }
    }

</script>

</body>
</html>