Completed the search function on ndn-atmos.
Additionally cleaned up the submodules.
All old code has been removed.
Change-Id: If653f3e4b8d18f3c71052c123bc7d1593e50b5d6
diff --git a/.gitmodules b/.gitmodules
index 91eb1ee..6b09fa1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,13 @@
[submodule "client/ndn-js"]
path = client/ndn-js
url = https://github.com/named-data/ndn-js.git
+ ignore = dirty
[submodule "ndn-cxx"]
path = ndn-cxx
url = https://github.com/named-data/ndn-cxx.git
+ ignnore = dirty
[submodule "ChronoSync"]
path = ChronoSync
url = https://github.com/named-data/ChronoSync.git
+ ignore = dirty
+
diff --git a/ChronoSync b/ChronoSync
index ea488fd..edaef7c 160000
--- a/ChronoSync
+++ b/ChronoSync
@@ -1 +1 @@
-Subproject commit ea488fdeea0458fa32e194ef23ff842d0f932d55
+Subproject commit edaef7c4123f6e250a741912b2747b0b7e233b8a
diff --git a/client/query/autocomplete.js b/client/query/autocomplete.js
index 6edd6d5..e918390 100644
--- a/client/query/autocomplete.js
+++ b/client/query/autocomplete.js
@@ -5,23 +5,10 @@
*
* Then simply call $('.someClass').autoComplete(getSuggestions) on it to enable auto completion.
*
- * Also add
- * ```
- * .autoComplete {
- * position: absolute;
- * top: 38px;
- * width: 229px;
- * }
- *
- * .autoComplete .list-group {
- * margin-bottom: 0;
- * border: none;
- * }
- * ```
- * to an included css file.
- *
* getSuggestions returns by calling its callback parameter with an array of valid strings.
*
+ * Autocomplete can be manually triggered by triggering the autoComplete event.
+ *
*/
(function(){
"use strict";
@@ -123,6 +110,10 @@
});
+ this.on('autoComplete', function(){
+ getSuggestions(input.val(), setAutoComplete);
+ });
+
return this;
}
diff --git a/client/query/query.html b/client/query/query.html
index 860f449..bcd98d0 100644
--- a/client/query/query.html
+++ b/client/query/query.html
@@ -44,7 +44,10 @@
<div class="col-sm-3 col-md-2 sidebar">
<div class="panel panel-default">
- <div class="panel-heading">Filter Categories</div>
+ <div class="panel-heading flex flex-row">
+ <span class="vertical-fix width-fix">Filter Categories</span>
+ <button id="searchButton" class="btn btn-primary right-fix">Search</button>
+ </div>
<div class="panel-body">
<ul id="side-menu" class="nav nav-pills nav-stacked"></ul>
</div>
@@ -58,11 +61,12 @@
<div id="filters"></div>
<form class="form-inline" id="searchBar">
<div class="form-group">
- <label for="searchBar">Search: </label>
<div class="input-group">
- <input id="search" placeholder="/cmip" type="text" class="form-control" data-toggle="tooltip" data-placement="top" title="Press Tab for autocomplete">
+ <input id="search" placeholder="Enter a path" value="/"
+ type="text" class="form-control" data-toggle="tooltip" data-placement="top"
+ title="This bar is for doing a tab completion like path completion. Press tab, enter, or click AutoComplete to begin your search.">
<div class="input-group-btn">
- <button id="searchButton" type="submit" class="btn btn-primary">Search</button>
+ <button id="autoCompleteButton" type="submit" class="btn btn-default">AutoComplete</button>
</div>
</div>
</div>
@@ -70,15 +74,27 @@
</div>
</div>
- <div class="panel panel-default">
+ <div class="panel panel-primary">
<div class="panel-heading">Results</div>
<div class="panel-body">
<nav>
- <ul id="resultPages" class="pagination"></ul>
- <span id="resultTotalPages"></span>
+ <ul class="pager">
+ <li class="previous disabled"><a href="#">← Previous</a></li>
+ <li><span class="totalResults">0 Results</span></li>
+ <li class="next disabled"><a href="#">Next →</a></li>
+ </ul>
</nav>
<table id="resultTable" class="table"></table>
</div>
+ <div class="panel-footer">
+ <nav>
+ <ul class="pager">
+ <li class="previous disabled"><a href="#">← Previous</a></li>
+ <li><span class="totalResults">0 Results</span></li>
+ <li class="next disabled"><a href="#">Next →</a></li>
+ </ul>
+ </nav>
+ </div>
</div>
</div>
diff --git a/client/query/query.js b/client/query/query.js
index 258afd8..0546cd7 100644
--- a/client/query/query.js
+++ b/client/query/query.js
@@ -26,8 +26,10 @@
//Internal variables.
this.results = [];
this.resultCount = 0;
- this.page = 1;
- this.itemsPerPage = 25;
+ this.name = null;
+ this.page = 0;
+ this.lastPage = -1;
+ //this.itemsPerPage = 25; //TODO
this.catalog = catalog;
@@ -37,6 +39,8 @@
this.filters = $('#filters');
this.searchInput = $('#search');
this.searchBar = $('#searchBar');
+ this.searchButton = $('#searchButton');
+ this.pagers = $('.pager');
var scope = this;
@@ -109,33 +113,44 @@
});
});
+ this.searchInput.autoComplete(function(field, callback){
+ scope.autoComplete(field, callback);
+ });
+
this.searchBar.submit(function(e){
e.preventDefault();
+ scope.searchInput.trigger('autoComplete');
+ });
+
+ this.searchButton.click(function(){
+ console.log("Search Button Pressed");
scope.search();
});
- this.searchInput.autoComplete(function(field, callback){
- scope.autoComplete(field, callback);
+ this.pagers.find('.next').click(function(){
+ if (!$(this).hasClass('disabled')){
+ scope.getResults(scope.page + 1);
+ }
+ });
+ this.pagers.find('.previous').click(function(){
+ if (!$(this).hasClass('disabled')){
+ scope.getResults(scope.page - 1);
+ }
});
}
Atmos.prototype.search = function(){
- var filters = this.getFilters();
- if (this.searchInput.val().length === 0 && !filters.hasOwnProperty()){
- if (!this.searchBar.hasClass('has-error')){
- this.searchBar.addClass('has-error').append('<span class="help-block">A filter or search value is required!</span>');
- }
- return;
- } else {
- this.searchBar.removeClass('has-error').find('.help-block').fadeOut(function(){$(this).remove()});
- }
+ var filters = this.getFilters();
console.log("Search started!", this.searchInput.val(), filters);
console.log("Initiating query");
+ this.results = []; //Drop any old results.
+ this.resultTable.empty();
+
var scope = this;
this.query(this.catalog, filters,
@@ -146,29 +161,10 @@
var ack = data.getName();
- var name = new Name(scope.catalog).append("query-results").append(parameters)
- .append(ack.get(ack.getComponentCount() - 3)).append(ack.getComponent(ack.getComponentCount() - 2))
+ scope.name = new Name(scope.catalog).append("query-results").append(parameters)
+ .append(ack.get(ack.getComponentCount() - 3)).append(ack.getComponent(ack.getComponentCount() - 2));
- var first = new Name(name).appendSegment(0);
-
- console.log("Next request:", first.toUri());
-
- scope.face.expressInterest(new Interest(first).setInterestLifetimeMilliseconds(10000),
- function(interest, data){ //Response
-
- var content = JSON.parse(data.getContent().toString().replace(/[\n\0]/g,""));
-
- var results = scope.results[0] = content.results;
- scope.resultCount = content.resultCount;
-
- console.log("Got results:", results);
-
- },
- function(interest){ //Timeout
- console.error("Failed to retrieve results: timeout");
- }
- );
-
+ scope.getResults(0);
}, function(interest){ //Timeout function
console.error("Request failed! Timeout");
@@ -179,6 +175,15 @@
Atmos.prototype.autoComplete = function(field, callback){
console.log("Autocomplete triggered");
+ if (this.searchInput.val().length === 0 && !filters.hasOwnProperty()){
+ if (!this.searchBar.hasClass('has-error')){
+ this.searchBar.addClass('has-error').append('<span class="help-block">A filter or search value is required!</span>');
+ }
+ return;
+ } else {
+ this.searchBar.removeClass('has-error').find('.help-block').fadeOut(function(){$(this).remove()});
+ }
+
var filters = this.getFilters();
filters["?"] = this.searchInput.val();
@@ -192,39 +197,87 @@
}
-// Atmos.prototype.onData = function(data) {
-// console.log("Recieved data", data);
+ Atmos.prototype.showResults = function(resultIndex) {
-// var payloadStr = data.content.toString().split("\n")[0];
+ var results = $('<tr><td>' + this.results[resultIndex].join('</td><td><button class="interest-button btn btn-primary btn-sm">Retrieve</button></td></tr><tr><td>') +
+ '</td><td><button class="interest-button btn btn-primary btn-sm">Retrieve</button></td></tr>'); //Fastest way to generate the table.
-// if (!payloadStr || payloadStr.length === 0){
-// this.populateResults();
-// return; //No results were returned.
-// }
+ this.resultTable.empty().append(results);
-// var queryResults = JSON.parse(payloadStr);
+ this.pagers.find('.totalResults').text(this.results[resultIndex].length + " of " + this.resultCount + " results");
-// var scope = this;
+ if (resultIndex === this.lastPage) {
+ this.pagers.find('.next').addClass('disabled');
+ }
-// $.each(queryResults, function (queryResult, field) {
+ if (resultIndex === 0){
+ this.pagers.find('.next').removeClass('disabled');
+ this.pagers.find('.previous').addClass('disabled');
+ } else if (resultIndex === 1) {
+ this.pagers.find('.previous').removeClass('disabled');
+ }
-// if (queryResult == "next") {
-// scope.populateAutocomplete(field);
-// }
+ }
-// if (queryResult == "results" && field == null){
-// return; //Sometimes the results are null. (We should skip this.)
-// }
+ Atmos.prototype.getResults = function(index){
-// $.each(field, function (entryCount, name) {
-// scope.results.push(name);
-// });
-// });http://stackoverflow.com/questions/26334964/json-parse-unexpected-token-error
+ if (this.results[index]){
+ //console.log("We already have index", index);
+ this.page = index;
+ this.showResults(index);
+ return;
+ }
-// // Calculating the current page and the view
-// this.populateResults();
+ if (this.name === null) {
+ console.error("This shouldn't be reached! We are getting results before a search has occured!");
+ throw new Error("Illegal State");
+ }
-// }
+ var first = new Name(this.name).appendSegment(index);
+
+ console.log("Requesting data index: (", index, ") at ", first);
+
+ var scope = this;
+
+ this.face.expressInterest(new Interest(first).setInterestLifetimeMilliseconds(5000),
+ function(interest, data){ //Response
+
+ if (data.getContent().length === 0){
+ console.log("Empty response.");
+ return;
+ }
+
+ if (data.getName().get(-1).equals(data.getMetaInfo().getFinalBlockId())) { //Final page.
+ scope.lastPage = index;
+ //The next buttons will be disabled by showResults.
+ }
+
+ var content = JSON.parse(data.getContent().toString().replace(/[\n\0]/g,""));
+
+ var results = scope.results[index] = content.results;
+
+ scope.resultCount = content.resultCount;
+
+ scope.pagers.find('.totalResults').text(scope.resultCount + " Results");
+
+ //console.log("Got results:", results);
+
+ scope.page = index;
+
+ if (!results){
+ console.log("No results were found!");
+ return;
+ }
+
+ scope.showResults(index);
+
+ },
+ function(interest){ //Timeout
+ console.error("Failed to retrieve results: timeout");
+ }
+ );
+
+ }
Atmos.prototype.query = function(prefix, parameters, callback, timeout) {
@@ -241,189 +294,6 @@
}
-// Atmos.prototype.expressNextInterest = function() {
-// // @todo pipelines
-// var nextName = new Name(this.state["results"]);
-// nextName.appendSegment(this.state["nextSegment"]);
-
-// var nextInterest = new Interest(nextName);
-// nextInterest.setInterestLifetimeMilliseconds(10000);
-
-// var scope = this;
-
-// this.face.expressInterest(nextInterest,
-// function(interest, data){
-// scope.onQueryResultsData(interest, data);
-// },
-// function(interest){
-// scope.onQueryResultsTimeout(interest);
-// });
-
-// this.state["nextSegment"] ++;
-// this.state["outstanding"][nextName.toUri()] = 0;
-// }
-
-// Atmos.prototype.onQueryData = function(interest, data) {
-// var name = data.getName();
-
-// delete this.state["outstanding"][interest.getName().toUri()];
-
-// this.state["version"] = name.get(this.state["prefix"].size() + 2).toVersion();
-
-// this.state["results"] = new Name(this.state["prefix"]).append("query-results").append(this.state['parameters'])
-// .appendVersion(this.state["version"]).append(name.getComponent(name.getComponentCount() - 2));
-
-// console.log("Requested URI", this.state.results.toUri());
-
-// this.expressNextInterest();
-// }
-
-// Atmos.prototype.onQueryResultsData = function(interest, data) {
-// var name = data.getName();
-// delete this.state["outstanding"][interest.getName().toUri()];
-// if (!name.get(-1).equals(data.getMetaInfo().getFinalBlockId())) {
-// this.expressNextInterest();
-// } //else {
-// //alert("found final block");
-// //}
-
-// this.state["userOnData"](data);
-// }
-
-// Atmos.prototype.onQueryTimeout = function(interest) {
-// var uri = interest.getName().toUri();
-// if (this.state["outstanding"][uri] < 1) {
-// this.state["outstanding"][uri] ++;
-// var scope = this;
-// this.face.expressInterest(interest,
-// function(interest, data){
-// scope.onQueryData(interest, data);
-// },
-// function(interest){
-// scope.onQueryTimeout(interest);
-// });
-// } else {
-// delete this.state["outstanding"][uri];
-
-// // We modify the autocomplete box here because we need to know
-// // we have all of the entries first. Fairly hacky.
-// /* TODO FIXME
-// var autocompleteFullName = this.autocompleteText.value;
-// for (var i = 0; i < dropdown.length; ++i) {
-// if (this.dropdown[i].substr(0, dropdown[i].length - 1).toUpperCase === this.autocompleteText.value.toUpperCase || dropdown.length == 1) {
-// autocompleteText.value = dropdown[i];
-// }
-// }
-// */
-// }
-// }
-
-// Atmos.prototype.onQueryResultsTimeout = function(interest) {
-// var uri = interest.getName().toUri();
-// if (this.state["outstanding"][uri] < 1) {
-// this.state["outstanding"][uri] ++;
-// var scope = this;
-// this.face.expressInterest(interest,
-// function(){
-// scope.onQueryResultsData.apply(scope, arguments);
-// },
-// function(){
-// scope.onQueryResultsTimeout.apply(scope, arguments);
-// });
-// } else {
-// delete this.state["outstanding"][uri];
-// // We modify the autocomplete box here because we need to know
-// // we have all of the entries first. Fairly hacky.
-// /* TODO FIXME
-// var autocompleteFullName = autocompleteText.value;
-// for (var i = 0; i < dropdown.length; ++i) {
-// if (dropdown[i].substr(0, dropdown[i].length - 1).toUpperCase === autocompleteText.value.toUpperCase || dropdown.length == 1) {
-// autocompleteText.value = dropdown[i];
-// }
-// }
-// */
-// }
-// }
-// **/
-
-// Atmos.prototype.populateResults = function() {
-
-// //TODO Check only for page changes and result length
-
-// this.resultTable.empty();
-
-// for (var i = startIndex; i < startIndex + 20 && i < this.results.length; ++i) {
-// this.resultTable.append('<tr><td>' + this.results[i]
-// + '</td><td><button class="interest-button btn btn-primary btn-xs">Retrieve</button></td></tr>');
-// }
-
-// if (this.results.length <= 20) {
-// this.page = 1;
-// } else {
-// this.page = startIndex / 20 + 1;
-// }
-
-// this.totalPages = Math.ceil(this.results.length / 20);
-
-// //TODO Fix the page to fit the theme.
-// var currentPage = $(".page");
-// currentPage.empty();
-// if (this.page != 1) {
-// currentPage.append('<a href="#" onclick="getPage(this.id);" id="<"><</a>');
-// }
-// // This section of code creates the paging for the results.
-// // To prevent it from having a 1000+ pages, it will only show the 5 pages before/after
-// // the current page and the total pages (expect users not to really jump around a lot).
-// for (var i = 1; i <= this.totalPages; ++i) {
-// if (i == 1 || i == this.totalPages // Min or max
-// || (i <= this.page && i + 5 >= this.page) // in our current page range
-// || (i >= this.page && i - 5 <= this.page)) { // in our current page range
-// if (i != this.page) {
-// currentPage.append(' <a href="#" onclick="getPage(' + i + ');">' + i + '</a>');
-// if (i == 1 && this.page > i + 5) {
-// currentPage.append(' ... ');
-// }
-// } else {
-// currentPage.append(' ' + i);
-// }
-// } else { // Need to skip ahead
-// if (i == this.page + 6) {
-// currentPage.append(' ... ');
-
-// currentPage.append(' <a href="#" onclick="getPage(this.id);" id=">">></a>')
-// i = this.totalPages - 1;
-// }
-// }
-// }
-// currentPage.append(' ' + this.results.length + ' results');
-// }
-
-// Atmos.prototype.getPage = function(clickedPage) {
-// console.log(clickedPage);
-
-// var nextPage = clickedPage;
-// if (clickedPage === "<") {
-// nextPage = this.page - 5;
-// } else if (clickedPage === ">") {
-// console.log("> enabled");
-
-// nextPage = this.page + 5;
-// }
-
-// nextPage--; // Need to adjust for starting at 0
-
-// if (nextPage < 0 ) {
-// nextPage = 0;
-// console.log("0 enabled");
-// } else if (nextPage > this.totalPages - 1) {
-// nextPage = this.totalPages - 1;
-// console.log("total enabled");
-// }
-
-// this.populateResults(nextPage * 20);
-// return false;
-// }
-
/**
* This function returns a map of all the categories active filters.
* @return {Object<string, string>}
diff --git a/client/query/query2.css b/client/query/query2.css
index cb94019..2119d4c 100644
--- a/client/query/query2.css
+++ b/client/query/query2.css
@@ -31,6 +31,35 @@
border: none;
}
+.flex {
+ display: flex;
+}
+
+.flex-row {
+ flex-display: row;
+}
+
+.vertical-fix {
+ line-height: 38px; /* Height of the button */
+ vertical-align: middle;
+}
+
+.width-fix {
+ flex: 1 auto;
+}
+
+.right-fix {
+ flex: auto;
+}
+
+.pager {
+ /* margin: 0; */
+}
+
+#searchBar {
+ display: inline-block;
+}
+
#filters {
margin-bottom: 10px;
visibility: visible;