Mercurial > hg > NetworkVis
comparison query_builder/querybuild.html @ 28:5384b71df52a
querybuild.html added, beginning prototype.
| author | arussell |
|---|---|
| date | Thu, 10 Dec 2015 07:26:40 -0500 |
| parents | |
| children | acc60a20582c |
comparison
equal
deleted
inserted
replaced
| 27:ed8b4e3f2a73 | 28:5384b71df52a |
|---|---|
| 1 <!DOCTYPE html> | |
| 2 <html> | |
| 3 <head> | |
| 4 <meta http-equiv="Content-Type" content="text/html" charset="UTF-8"> | |
| 5 <title>Query Builder</title> | |
| 6 <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"> | |
| 7 <link rel="stylesheet" href="select2-4.0.1/dist/css/select2.min.css"> | |
| 8 | |
| 9 <script type="text/javascript" src="js/d3.min.js"></script> | |
| 10 <script type="text/javascript" src="select2-4.0.1/vendor/jquery-2.1.0.js"></script> | |
| 11 <script type="text/javascript" src="select2-4.0.1/dist/js/select2.full.min.js"></script> | |
| 12 | |
| 13 | |
| 14 </head> | |
| 15 <body style="background:none;"> | |
| 16 <div role="navigation" class="navbar navbar-default navbar-static-top"> | |
| 17 <div class="container"> | |
| 18 <div class="row"> | |
| 19 <div class="col-sm-6 col-md-6"> | |
| 20 <ul class="nav navbar-nav"> | |
| 21 </ul> | |
| 22 </div> | |
| 23 <div class="navbar-header col-sm-6 col-md-6" style="height: auto;"> | |
| 24 <div class="col-md-6"> | |
| 25 <div class="navbar-brand"> | |
| 26 <div class="brand">ISMI Query Builder</div> | |
| 27 </div> | |
| 28 </div> | |
| 29 <div class="col-md-offset-6"> | |
| 30 <div class="logo-well" style="height: 60%; width: 60%;"> | |
| 31 <a href="//neo4j.com/developer-resources"> | |
| 32 <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%"> | |
| 33 </a> | |
| 34 </div> | |
| 35 </div> | |
| 36 </div> | |
| 37 </div> | |
| 38 </div> | |
| 39 </div> | |
| 40 <div class="container"> | |
| 41 <div class="row" style="width: 95%"> | |
| 42 <div class="col-lg"> | |
| 43 <div class="panel panel-default"> | |
| 44 <div class="panel-heading" id="title">Query Builder</div> | |
| 45 | |
| 46 <section> | |
| 47 <div class="s2-example"> | |
| 48 <div id="filters"> | |
| 49 <div class="row" id="startrow" style="margin-top: 15px"> | |
| 50 <div class="col-sm-4 col-md-4" id="select-col1"> | |
| 51 <select class="selected-object form-control"> | |
| 52 <option selected>Selected object is: </option> | |
| 53 </select> | |
| 54 </div> | |
| 55 <div class="col-sm-4 col-md-4" id="select-col2"> | |
| 56 <select class="select-object1 form-control"> | |
| 57 <option selected>TEXT</option> | |
| 58 <option>WITNESS</option> | |
| 59 <option>PERSON</option> | |
| 60 </select> | |
| 61 </div> | |
| 62 </div> | |
| 63 <div class="row" id="row1" style="margin-top: 15px"> | |
| 64 <div class="col-sm-4 col-md-4" id="1filter-col1"> | |
| 65 <select class="filter-box1 form-control"> | |
| 66 <option value="" disabled selected>Choose filter...</option> | |
| 67 </select> | |
| 68 </div> | |
| 69 </div> | |
| 70 </div> | |
| 71 <div align="left" style="margin-top: 30px"><button id="minus_filter_button" class="btn btn-warning btn-md" type="button"> - </button> | |
| 72 <button id="plus_filter_button" class="btn btn-warning btn-md" type="button"> + </button> | |
| 73 </div> | |
| 74 </div> | |
| 75 | |
| 76 | |
| 77 <div class="container-fluid" style="margin-top: 15px"> | |
| 78 <form role="form"> | |
| 79 <div class="form-group"> | |
| 80 <label for="results-container">Results list</label> | |
| 81 <select multiple class="form-control" id="results-container" style="min-height: 300px;"> | |
| 82 </select> | |
| 83 </div> | |
| 84 </form> | |
| 85 | |
| 86 </div> | |
| 87 | |
| 88 <!-- <pre data-fill-from=".js-code-data-array"></pre> --> | |
| 89 </section> | |
| 90 | |
| 91 </div> | |
| 92 </div> | |
| 93 </div> | |
| 94 </div> | |
| 95 | |
| 96 | |
| 97 | |
| 98 | |
| 99 | |
| 100 | |
| 101 <script type="text/javascript" class="js-code-data-array"> | |
| 102 //var queryData = []; | |
| 103 | |
| 104 // Defining initial variables and helper functions | |
| 105 var sourceType = "TEXT"; | |
| 106 var targets = []; | |
| 107 var targetTypes = []; | |
| 108 var listTolerance = 100; | |
| 109 var numFilters = 1; | |
| 110 var filters = [ | |
| 111 { id: 0, text: 'has relation' }, | |
| 112 { id: 1, text: 'attribute contains' } | |
| 113 ]; | |
| 114 $(".filter-box"+numFilters).select2({ | |
| 115 // Initialize first filter box | |
| 116 data: filters, | |
| 117 minimumResultsForSearch: Infinity | |
| 118 }); | |
| 119 function filter_html(n) { | |
| 120 return '<div class="row" id="row'+n+'" style="margin-top: 15px">' + | |
| 121 '<div class="col-sm-4 col-md-4" id="'+n+'filter-col1">' + | |
| 122 '<select class="filter-box'+n+' form-control">' + | |
| 123 '<option value="" disabled selected>Choose filter...</option>' + | |
| 124 '</select>' + | |
| 125 '</div>' + | |
| 126 '</div>'; | |
| 127 } | |
| 128 function constraint_html(classn) { | |
| 129 return '<div class="col-sm-4 col-md-4" id="'+classn+'filter-col2">' + | |
| 130 '<select class="constraint-box'+classn+' form-control" >' + | |
| 131 '</select>' + | |
| 132 '</div>'; | |
| 133 } | |
| 134 function submit_html(classn) { | |
| 135 return '<div class="col-sm-4 col-md-4" id="'+classn+'filter-col2">' + | |
| 136 '<div class="form-group'+classn+'">' + | |
| 137 '<textarea class="form-control" rows="1" id="attribute-text-search'+classn+'">' + | |
| 138 '</textarea>' + | |
| 139 '</div>' + | |
| 140 '</div>'; | |
| 141 } | |
| 142 function select_html(selection) { | |
| 143 return '<div class="row" id="startrow" style="margin-top: 15px">' + | |
| 144 '<div class="col-sm-4 col-md-4" id="select-col1"> ' + | |
| 145 '<select class="selected-object form-control"> ' + | |
| 146 '<option selected>Selected object is: </option> ' + | |
| 147 '</select> ' + | |
| 148 '</div> ' + | |
| 149 '<div class="col-sm-4 col-md-4" id="select-col2"> ' + | |
| 150 '<select class="select-object1 form-control"> ' + | |
| 151 '<option selected>TEXT</option> ' + | |
| 152 '<option>WITNESS</option> ' + | |
| 153 '<option>PERSON</option> ' + | |
| 154 '</select> ' + | |
| 155 '</div> ' + | |
| 156 '</div>'; | |
| 157 } | |
| 158 | |
| 159 | |
| 160 | |
| 161 | |
| 162 // Button filters | |
| 163 $("#plus_filter_button").click(function() { | |
| 164 console.log("add filter"); | |
| 165 numFilters++; | |
| 166 $("#filters").append(filter_html(numFilters)); | |
| 167 $(".filter-box"+numFilters).select2({ | |
| 168 data: filters, | |
| 169 minimumResultsForSearch: Infinity | |
| 170 }); | |
| 171 }); | |
| 172 $("#minus_filter_button").click(function() { | |
| 173 console.log("minus filter"); | |
| 174 $("#row"+numFilters).remove(); | |
| 175 numFilters--; | |
| 176 }); | |
| 177 $("#results-container").dblclick(function() { | |
| 178 if ($("#results-container option:selected").length == 1) { | |
| 179 var selection = $(this).text(); | |
| 180 console.log("add filter"); | |
| 181 numFilters++; | |
| 182 $("#filters").append(select_html(selection, numFilters)); | |
| 183 // Generate inner relation list | |
| 184 genRelations(sourceType, ".constraint-box"+classnum); | |
| 185 | |
| 186 } | |
| 187 console.log("add filter"); | |
| 188 }); | |
| 189 | |
| 190 | |
| 191 | |
| 192 | |
| 193 // For use in the onchange events below | |
| 194 function genRelations(sourceNodeType, constraintBox) { | |
| 195 var query = "match (source:"+sourceNodeType+")-[rel]-target return distinct rel.type"; | |
| 196 ajax1(query, constraintBox); | |
| 197 } | |
| 198 function genResults(sourceNodeType, selected, constraintBox) { | |
| 199 var query = "match (source:"+sourceNodeType+")-["+selected+"]-(target) return target.label, target.type"; | |
| 200 ajax1(query, constraintBox); | |
| 201 } | |
| 202 // Can be used to return a list of all available attributes | |
| 203 // TODO: Not implemented | |
| 204 function genAttributes(sourceNodeType, constraintBox) { | |
| 205 var query = "match (n:"+sourceNodeType+") with keys(n) as collection unwind collection as attributes return distinct attributes"; | |
| 206 ajax1(query, constraintBox); | |
| 207 } | |
| 208 | |
| 209 // Change Events | |
| 210 $('body').on('change', function(event){ | |
| 211 var classname = (event.target.className).substr(0, (event.target.className).indexOf(" ")); | |
| 212 var classnum = (event.target.className.match(/\d+\.\d+|\d+\b|\d+(?=\w)/g) || [] ) | |
| 213 .map(function (v) {return +v;}).shift(); | |
| 214 var selectedOption = $('.'+classname+' option:selected').text(); | |
| 215 | |
| 216 // Change is on the filter box | |
| 217 if (classname.indexOf("filter-box") > -1) { | |
| 218 if (selectedOption === "has relation") { | |
| 219 // Remove any other boxes in the row | |
| 220 $("#"+classnum+"filter-col2").remove(); | |
| 221 $("#row"+classnum).append(constraint_html(classnum)); | |
| 222 // Generate inner relation list | |
| 223 genRelations(sourceType, ".constraint-box"+classnum); | |
| 224 } | |
| 225 if (selectedOption === "attribute contains") { | |
| 226 $("#"+classnum+"filter-col2").remove(); | |
| 227 $("#row"+classnum).append(submit_html(classnum)); | |
| 228 //genAttributes(sourceType, ".constraint-box"+classnum); | |
| 229 } | |
| 230 if (selectedOption === "return") { | |
| 231 $("#"+classnum+"filter-col2").remove(); | |
| 232 $("#row"+classnum).append(constraint_html(classnum)); | |
| 233 genAttributes(targetTypes, ".constraint-box"+classnum); | |
| 234 } | |
| 235 } | |
| 236 | |
| 237 // Change is on the relationship constraint box | |
| 238 if (classname.indexOf("constraint-box") > -1) { | |
| 239 //$().remove(); | |
| 240 genResults(sourceType, selectedOption, "results-container"); | |
| 241 } | |
| 242 | |
| 243 // TODO: if the select object is changed update the sourceType var that we are looking at | |
| 244 // Change is on subject scope | |
| 245 if (classname.indexOf("select-object") > -1) { | |
| 246 | |
| 247 //ajax1(query, "results-container") | |
| 248 } | |
| 249 }); | |
| 250 | |
| 251 | |
| 252 | |
| 253 | |
| 254 // Ajax request function | |
| 255 function ajax1(q, resBox) { | |
| 256 console.log(q); | |
| 257 $.ajaxSetup({ | |
| 258 headers: { | |
| 259 "Authorization": 'Basic ' + window.btoa("neo4j"+":"+"neo5j") | |
| 260 } | |
| 261 }); | |
| 262 return $.ajax({ | |
| 263 type: "POST", | |
| 264 url: "https://ismi-dev.mpiwg-berlin.mpg.de/neo4j-ismi/db/data/cypher", | |
| 265 accepts: "application/json", | |
| 266 dataType: "json", | |
| 267 data: { | |
| 268 "query": q, | |
| 269 "params": {} | |
| 270 }, | |
| 271 beforeSend: function (xhr) { | |
| 272 xhr.setRequestHeader ("Authorization", "Basic " + btoa('neo4j'+":"+'neo5j')); | |
| 273 }, | |
| 274 processResults: function (data, params) { | |
| 275 // parse the results into the format expected by Select2 | |
| 276 // since we are using custom formatting functions we do not need to | |
| 277 // alter the remote JSON data, except to indicate that infinite | |
| 278 // scrolling can be used | |
| 279 params.page = params.page || 1; | |
| 280 return { | |
| 281 pagination: { | |
| 282 more: (params.page * 30) < data.length | |
| 283 } | |
| 284 }; | |
| 285 }, | |
| 286 success: function (res, textStatus, jqXHR) { | |
| 287 ajaxCheck(res, resBox, textStatus); | |
| 288 console.log(textStatus); | |
| 289 }, | |
| 290 error: function (jqXHR, textStatus, errorThrown) { | |
| 291 console.log(textStatus); | |
| 292 } | |
| 293 }); | |
| 294 } | |
| 295 // Check if request was successful | |
| 296 function ajaxCheck(a1, resBox, status){ | |
| 297 if (status === 'error') console.log("error: bad ajax request"); | |
| 298 //else dataGen(a1); | |
| 299 dataGen(a1.data, resBox); | |
| 300 } | |
| 301 // On success, generate new data | |
| 302 // TODO: might want to just always return nodes and then deal with the queryData different for each type of return | |
| 303 function dataGen(dataArr, resBox) { | |
| 304 console.log(dataArr); | |
| 305 var d = dataArr; | |
| 306 targets = []; | |
| 307 | |
| 308 // Consider implementing localStorage to avoid reloading every time | |
| 309 var queryData = []; | |
| 310 for (var i= 0; i<d.length; i++) { | |
| 311 var j = d[i]; | |
| 312 queryData.push({ id: j, text: j[0] }); | |
| 313 targets.push(j[1]); | |
| 314 } | |
| 315 // Now that queryData array has been defined we will sort it by its label value and initialize the option. | |
| 316 queryData.sort(function(a,b){ | |
| 317 var c = a.text.replace(/<|>/g, ''); | |
| 318 var d = b.text.replace(/<|>/g, ''); | |
| 319 return c.localeCompare(d); | |
| 320 }); | |
| 321 console.log(queryData[0].text); | |
| 322 | |
| 323 // Change the displayed results | |
| 324 if (resBox === "results-container") { | |
| 325 process_display_results(queryData); | |
| 326 } | |
| 327 else { | |
| 328 $(resBox).select2({ | |
| 329 data: queryData | |
| 330 }); | |
| 331 } | |
| 332 } | |
| 333 function process_display_results(queryData) { | |
| 334 var weights = []; | |
| 335 var resultLength = queryData.length; | |
| 336 targetTypes = []; | |
| 337 | |
| 338 $('#results-container') | |
| 339 .find('option') | |
| 340 .remove() | |
| 341 .end() | |
| 342 ; | |
| 343 if (resultLength < listTolerance) { | |
| 344 for (var i = 0; i < resultLength; i++) { | |
| 345 // TODO: put this jQuery call into its own function to be called if the else statement occurs | |
| 346 // TODO: but the user actually does want to see the entire huge list. | |
| 347 $('#results-container') | |
| 348 .append($("<option></option>") | |
| 349 .attr("value", i) | |
| 350 .text(queryData[i].text)); | |
| 351 } | |
| 352 } | |
| 353 else { | |
| 354 for (var i = 0; i < targets.length; i++) { | |
| 355 if (targetTypes.indexOf(targets[i]) == -1) { | |
| 356 targetTypes.push(targets[i]); | |
| 357 weights.push(1); | |
| 358 } | |
| 359 else { | |
| 360 weights[targetTypes.indexOf(targets[i])] += 1; | |
| 361 } | |
| 362 } | |
| 363 for (var i = 0; i < targetTypes.length; i++) { | |
| 364 $('#results-container') | |
| 365 .append($("<option></option>") | |
| 366 .text(targetTypes[i] + " [ " + weights[i] + " ]")); | |
| 367 } | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 </script> | |
| 372 | |
| 373 </body> | |
| 374 </html> |
