Mercurial > hg > NetworkVis
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>