changeset 28:5384b71df52a

querybuild.html added, beginning prototype.
author arussell
date Thu, 10 Dec 2015 07:26:40 -0500
parents ed8b4e3f2a73
children 2564732249b3 acc60a20582c
files query_builder/ismi.html query_builder/querybuild.html
diffstat 2 files changed, 455 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/query_builder/ismi.html	Wed Dec 02 00:53:02 2015 -0500
+++ b/query_builder/ismi.html	Thu Dec 10 07:26:40 2015 -0500
@@ -45,14 +45,14 @@
 
                 <section>
                 <div class="s2-example">
-                    <div class="row">
-                        <div class="col-sm-4 col-md-4">
+                    <div id="filter" class="row">
+                        <div class="col-sm-3 col-md-3">
                             <select class="js-example-data-array form-control"></select>
                         </div>
-                        <div class="col-sm-4 col-md-4">
+                        <div class="col-sm-3 col-md-3">
                             <select class="js-example-data-array2 form-control"></select>
                         </div>
-                        <div class="col-sm-4 col-md-4">
+                        <div class="col-sm-3 col-md-3">
                             <select class="js-example-data-array3 form-control">
                                 <option value="" disabled selected>Loading...</option>
                             </select>
@@ -69,35 +69,6 @@
 </div>
 
 <script type="text/javascript" class="js-code-data-array">
-    // Code to interrupt ajax calls if object box switched, currently doesnt work...
-    (function($) {
-        var xhrPool = [];
-        $(document).ajaxSend(function(e, jqXHR, options){
-            xhrPool.push(jqXHR);
-        });
-        $(document).ajaxComplete(function(e, jqXHR, options) {
-            xhrPool = $.grep(xhrPool, function(x){return x!=jqXHR});
-        });
-        var abort = function() {
-            $.each(xhrPool, function(idx, jqXHR) {
-                jqXHR.abort();
-                console.log("aborted");
-            });
-        };
-
-        var oldbeforeunload = window.onbeforeunload;
-        window.onbeforeunload = function() {
-            var r = oldbeforeunload ? oldbeforeunload() : undefined;
-            if (r == undefined) {
-                // only cancel requests if there is no prompt to stay on the page
-                // if there is a prompt, it will likely give the requests enough time to finish
-                abort();
-                console.log("aborted");
-            }
-            return r;
-        }
-    })(jQuery);
-
 
 
     var queryData = [];
@@ -115,11 +86,18 @@
         data: relations
     });
 
+    $(".js-example-data-array3").select2();
+
     // Ajax request function
     function ajax1(q) {
         var combinedQuery = "match (n:TEXT) return n";
         if (q !== undefined) combinedQuery = "match (n:" + q + ") return n";
         console.log(combinedQuery);
+        $.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",
@@ -146,7 +124,7 @@
                 };
             },
             success: function (res, textStatus, jqXHR) {
-                console.log(textStatus);
+                console.log(res);
                 ajaxCheck(res, textStatus);
             },
             error: function (jqXHR, textStatus, errorThrown) {
@@ -167,6 +145,7 @@
         var d = a1.data;
         // Consider implementing localStorage to avoid reloading every time
         queryData = [];
+
         for (var i= 0; i<d.length; i++) {
             var j = d[i][0].data.ismi_id;
             queryData.push({ id: j, text: d[i][0].data.label });
@@ -177,8 +156,9 @@
         });
         console.log(queryData);
 
-        // TODO: currently the queryData is being added onto the previous query data, need to remove old.
+        // TODO: currently the queryData is being added onto the previous query data, need to REMOVE OLD.
 
+        $(".js-example-data-array3").select2('data', null);
 
         $(".js-example-data-array3").select2({
             data: queryData
@@ -196,7 +176,72 @@
         ajax1(query);
     });
 
-    // TODO: ^^^^^ extend this to make a listener that will build the query dynamically
+
+    $("#plus_filter_button").click(function() {
+        console.log("add filter");
+        $("#s2-example").append("<div id='filter'/>");
+        //$(".js-example-data-array").select2({
+        //    data: filters
+        //}, console.log("created"));
+    });
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    // TODO: **** NOTE, could have each ajax fn be called with an extra parameter of what type of result to return ****
+    // TODO: (objects, attributes, attribute, relationships)
+    // TODO: in this way you could have each onchange trigger supply the appropriate result of the box it will modify.
+    // TODO: this would allow the json to be returned in the same way for each result. and we could also pass
+    // TODO: the return type param to the data gen
+
+
+    /* CURRENT IMPLEMENTATION
+       TODO: Make custom dataGen function for each dropdown type: object, attribute, display
+       TODO: we need to do this because we need to deal with the returned json differently depending on
+       TODO: what we want the dropdown to hold. This will be determined by the body.on(change function
+       TODO: and what the type of box is changing. Eg. changing the object type will trigger a change in
+       TODO: attributes and clear the display. Change in attribute will combine with object type to form
+       TODO: full query and trigger a dataGen of the appropriate info eg. TEXT.label in the display.
+       TODO: In this implementation, each box determines the next
+       TODO: Also, object changing can be used to update the list of relationships between it and any other object
+       TODO: for the next line.
+
+
+       CYPHER IMPLEMENTATION
+       TODO: Implement exactly like a cypher query.
+       TODO: first line: MATCH ---- object. relationship. object.
+       TODO: first object on choice sends ajax query to determine available relationships --> second box
+       TODO: relationship on choice determines the other types of possible objects to relate to.
+       TODO: second line: WHERE ---- object. attribute. equals.
+       TODO: the object can be either of the ones from line one. the attribute is generated by that object.
+       TODO: choose to return
+
+     */
+
+
+
+
+
+
+
 
 
 </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/query_builder/querybuild.html	Thu Dec 10 07:26:40 2015 -0500
@@ -0,0 +1,374 @@
+<!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="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>Selected object is: </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>
+                                    </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 listTolerance = 100;
+    var numFilters = 1;
+    var filters = [
+        { id: 0, text: 'has relation' },
+        { id: 1, text: 'attribute contains' }
+    ];
+    $(".filter-box"+numFilters).select2({
+        // Initialize first filter box
+        data: filters,
+        minimumResultsForSearch: Infinity
+    });
+    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="constraint-box'+classn+' form-control" >' +
+                '</select>' +
+                '</div>';
+    }
+    function submit_html(classn) {
+        return '<div class="col-sm-4 col-md-4" id="'+classn+'filter-col2">' +
+                '<div class="form-group'+classn+'">' +
+                '<textarea class="form-control" rows="1" id="attribute-text-search'+classn+'">' +
+                '</textarea>' +
+                '</div>' +
+                '</div>';
+    }
+    function select_html(selection) {
+        return '<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>Selected object is: </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> ' +
+                '</select> ' +
+                '</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() {
+        if ($("#results-container option:selected").length == 1) {
+            var selection = $(this).text();
+            console.log("add filter");
+            numFilters++;
+            $("#filters").append(select_html(selection, numFilters));
+            // Generate inner relation list
+            genRelations(sourceType, ".constraint-box"+classnum);
+
+        }
+        console.log("add filter");
+    });
+
+
+
+
+    // For use in the onchange events below
+    function genRelations(sourceNodeType, constraintBox) {
+        var query = "match (source:"+sourceNodeType+")-[rel]-target return distinct rel.type";
+        ajax1(query, constraintBox);
+    }
+    function genResults(sourceNodeType, selected, constraintBox) {
+        var query = "match (source:"+sourceNodeType+")-["+selected+"]-(target) return target.label, target.type";
+        ajax1(query, constraintBox);
+    }
+    // Can be used to return a list of all available attributes
+    // TODO: Not implemented
+    function genAttributes(sourceNodeType, constraintBox) {
+        var query = "match (n:"+sourceNodeType+") with keys(n) as collection unwind collection as attributes return distinct attributes";
+        ajax1(query, constraintBox);
+    }
+
+    // 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 selectedOption = $('.'+classname+' option:selected').text();
+
+        // 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, ".constraint-box"+classnum);
+            }
+            if (selectedOption === "attribute contains") {
+                $("#"+classnum+"filter-col2").remove();
+                $("#row"+classnum).append(submit_html(classnum));
+                //genAttributes(sourceType, ".constraint-box"+classnum);
+            }
+            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("constraint-box") > -1) {
+            //$().remove();
+            genResults(sourceType, selectedOption, "results-container");
+        }
+
+        // TODO: if the select object is changed update the sourceType var that we are looking at
+        // Change is on subject scope
+        if (classname.indexOf("select-object") > -1) {
+
+            //ajax1(query, "results-container")
+        }
+    });
+
+
+
+
+    // Ajax request function
+    function ajax1(q, resBox) {
+        console.log(q);
+        $.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) {
+        console.log(dataArr);
+        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);
+        });
+        console.log(queryData[0].text);
+
+        // Change the displayed results
+        if (resBox === "results-container") {
+            process_display_results(queryData);
+        }
+        else {
+            $(resBox).select2({
+                data: queryData
+            });
+        }
+    }
+    function process_display_results(queryData) {
+        var weights = [];
+        var resultLength = queryData.length;
+        targetTypes = [];
+
+        $('#results-container')
+                .find('option')
+                .remove()
+                .end()
+        ;
+        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.
+                $('#results-container')
+                        .append($("<option></option>")
+                                .attr("value", i)
+                                .text(queryData[i].text));
+            }
+        }
+        else {
+            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;
+                }
+            }
+            for (var i = 0; i < targetTypes.length; i++) {
+                $('#results-container')
+                        .append($("<option></option>")
+                                .text(targetTypes[i] + " [ " + weights[i] + " ]"));
+            }
+        }
+    }
+
+</script>
+
+</body>
+</html>