catalog, query UI: fix race condition and stabilize UI
add autocomplete, sidebar, deselect, and paging to UI
stability improvements to catalog
Change-Id: I2e873a41eaa8c10d9f9a1d605f33fb5217112d64
refs: #2634, 2638
diff --git a/client/query/query.css b/client/query/query.css
index 4708490..9489526 100644
--- a/client/query/query.css
+++ b/client/query/query.css
@@ -111,12 +111,20 @@
background: #FFEF36;
}
-.autocomplete {
+.autocomplete, /autocompleteButton {
width: 80%;
/*padding-left: 100px;
margin: 0px;*/
display: block;
- /*position: relative;*/
+ position: relative;
+}
+
+.currentSelections {
+ width: 80%;
+ /*padding-left: 100px;
+ margin: 0px;*/
+ display: block;
+ position: relative;
}
.textbox {
@@ -135,7 +143,7 @@
margin: 0px;
padding: 0px;
width: 80%;
- border: 1px solid #000000;
+ /*border: 1px solid #000000;*/
-moz-border-radius-bottomleft: 0px;
-webkit-border-bottom-left-radius: 0px;
border-bottom-left-radius: 0px;
@@ -239,4 +247,4 @@
.resultTable tr:first-child td:last-child {
border-width: 0px 0px 1px 1px;
-}
\ No newline at end of file
+}
diff --git a/client/query/query.html b/client/query/query.html
index b8f0e61..9e68518 100644
--- a/client/query/query.html
+++ b/client/query/query.html
@@ -6,122 +6,258 @@
<meta charset="UTF-8" />
- <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script>
- <script src="http://code.jquery.com/ui/1.10.1/jquery-ui.js"></script>
-
+ <script type="text/javascript" src="../jquery/jquery-latest.min.js"></script>
+ <script type="text/javascript" src="../jquery/ui/1.10.1/jquery-ui.js"></script>
+ <script type="text/javascript" src="../ndn-js/build/ndn.js"></script>
<link rel="stylesheet" href="query.css">
+ <link rel="stylesheet" href="../jquery/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script>
- var searchMenuOptions = {};
- var results = {};
+ // {@ @todo: this need to be configured before the document load
+ var catalog = "/catalog/myUniqueName";
+ var face = new Face({
+ host: "localhost",
+ port: 9696
+ });
+
+ // @}
+
+ var searchMenuOptions = {}
+ var results = [];
var resultCount = 0;
var page = 1;
var totalPages = 1;
+ var selectedSearch = {};
+ var dropdown = [];
$(document).ready(function () {
var searchMenu = $(".cssmenu");
var currentPage = $(".page");
var resultTable = $(".resultTable");
- var data = $.getJSON("sample.json", function () {
- })
- .done(function( data ) {
+ var data = $.getJSON("search_catagories.json", function () {}).done(function (data) {
$.each(data, function (pageSection, contents) {
if (pageSection == "SearchCatagories") {
-
$.each(contents, function (search, searchOptions) {
search = search.replace(/\_/g, " ");
searchMenu.append('<li id="' + search + '" onclick="getDropDown(this.id)"><a href="#">' + search + '</a></li>');
- searchMenuOptions[search] = searchOptions;
+ searchMenuOptions[String(search)] = searchOptions;
});
- } else if (pageSection == "QueryResults") {
- results = {};
- resultCount = 0;
- var view = [0, 0];
- $.each(contents, function (queryResult, field) {
- if (queryResult == "Length") {
- resultCount = field;
- } else if (queryResult == "View") {
- view = field;
- } else if (queryResult == "Results") {
- resultTable.empty();
- resultTable.append('<tr><td>Results</td></tr>');
-
- $.each(field, function (entryCount, fullResult) {
-
- $.each(fullResult, function (name, metadata) {
- resultTable.append('<tr><td onclick="getDetails(this.id)" id="' + name + '">' + name + '</td></tr>');
- results[name] = metadata;
- });
-
- });
- } else {
- console.error("Unknown field " + queryResult);
- }
- });
-
- // Calculating the current page and the view
- var diff = view[1] - view[0] + 1;
- if (diff > 0) {
- totalPages = Math.ceil(resultCount / diff);
- page = Math.ceil(view[0] / diff) + 1;
- } else {
- totalPages = 1;
- page = 1;
- }
-
- if (page != 1) {
- console.log(view[0]);
- currentPage.append('<span id="' + (view[0] - 1) + '" onclick="getPage(this.id)"><a href="#"><</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 = 0; i < totalPages; ++i) {
- if (i + 1 == 1 || i + 1 == page || i + 1 == totalPages || (i + 1 < page && i + 4 > page) || (i + 1 > page && i - 4 < page)) { // in our current page range
- currentPage.append(' ');
- currentPage.append('<span id="' + (i + 1) + '" onclick="getPage(this.id)">');
- if (i + 1 != page) {
- currentPage.append('<a href="#">' + (i + 1) + '</a>')
- } else {
- currentPage.append(i + 1);
- }
- currentPage.append('</span>');
- } else { // Need to skip ahead
- currentPage.append(' ...');
- if (i == page + 5) {
- i = totalPages - 2;
- } else if (i < page - 7) {
- i = page - 6;
- }
- }
- }
- if (page != totalPages) {
- currentPage.append(' <span id="' + (page + diff) + '" onclick="getPage(this.id)"><a href="#">></a>');
- }
}
});
- })();
+ });
});
- var state = "";
+ function onData(data) {
+ var payloadStr = data.content.toString().split("\n")[0];
+
+ var queryResults = JSON.parse(payloadStr);
+
+ var resultTable = $(".resultTable");
+ $.each(queryResults, function (queryResult, field) {
+
+ if (queryResult == "next") {
+ populateAutocomplete(field);
+ }
+
+ $.each(field, function (entryCount, name) {
+ results.push(name);
+ });
+ });
+
+ // Calculating the current page and the view
+ totalPages = Math.ceil(resultCount / 20);
+ populateResults(0);
+ }
+
+ var state = {};
+
+ function query(prefix, parameters, callback, pipeline) {
+ results = [];
+ dropdown = [];
+
+ var resultTable = $(".resultTable");
+ resultTable.empty();
+ resultTable.append('<tr><td>Results</td></tr>');
+
+ var queryPrefix = new Name(prefix);
+ queryPrefix.append("query");
+
+ queryPrefix.append(JSON.stringify(parameters));
+
+ state = {
+ prefix: new Name(prefix),
+ userOnData: callback,
+ outstanding: {},
+ nextSegment: 0,
+ };
+
+ /*if (state.hasOwnProperty("version")) {
+ console.log("state already has version");
+ }*/
+
+ var queryInterest = new Interest(queryPrefix);
+ queryInterest.setInterestLifetimeMilliseconds(10000);
+
+ face.expressInterest(queryInterest,
+ onQueryData,
+ onQueryTimeout);
+
+ state["outstanding"][queryInterest.getName().toUri()] = 0;
+ }
+
+ function expressNextInterest() {
+ // @todo pipelines
+ var nextName = new Name(state["results"]);
+ nextName.appendSegment(state["nextSegment"]);
+
+ var nextInterest = new Interest(nextName);
+ nextInterest.setInterestLifetimeMilliseconds(10000);
+
+ face.expressInterest(nextInterest,
+ onQueryResultsData,
+ onQueryResultsTimeout);
+
+ state["nextSegment"] ++;
+ state["outstanding"][nextName.toUri()] = 0;
+ }
+
+ function onQueryData(interest, data) {
+ var name = data.getName();
+
+ delete state["outstanding"][interest.getName().toUri()];
+
+ state["version"] = name.get(state["prefix"].size() + 2).toVersion();
+
+ state["results"] = new Name(state["prefix"]).append("query-results").appendVersion(state["version"]);
+
+ expressNextInterest();
+ }
+
+ function onQueryResultsData(interest, data) {
+ var name = data.getName();
+ delete state["outstanding"][interest.getName().toUri()];
+
+ if (!name.get(-1).equals(new Name.Component("END"))) {
+ expressNextInterest();
+ } else {
+ alert("found final block");
+ }
+
+ state["userOnData"](data);
+ }
+
+ function onQueryTimeout(interest) {
+ var uri = interest.getName().toUri();
+ if (state["outstanding"][uri] < 1) {
+ state["outstanding"][uri] ++;
+ face.expressInterest(interest,
+ onQueryData,
+ onQueryTimeout);
+ } else {
+ delete state["outstanding"][uri];
+
+ // We modify the autocomplete box here because we need to know
+ // we have all of the entries first. Fairly hacky.
+ 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];
+ }
+ }
+ }
+ }
+
+ function onQueryResultsTimeout(interest) {
+ var uri = interest.getName().toUri();
+ if (state["outstanding"][uri] < 1) {
+ state["outstanding"][uri] ++;
+ face.expressInterest(interest,
+ onQueryResultsData,
+ onQueryResultsTimeout);
+ } else {
+ delete state["outstanding"][uri];
+ // We modify the autocomplete box here because we need to know
+ // we have all of the entries first. Fairly hacky.
+ 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];
+ }
+ }
+ }
+ }
+
+
+ var currentViewIndex = 0;
+
+ function populateResults(startIndex) {
+ var resultTable = $(".resultTable");
+ resultTable.empty();
+ resultTable.append('<tr><td>Results</td></tr>');
+
+
+ for (var i = startIndex; i < startIndex + 20 && i < results.length; ++i) {
+ resultTable.append('<tr><td>' + results[i] + '</td></tr>');
+ }
+
+ if (results.length <= 20) {
+ page = 1;
+ } else {
+ page = startIndex / 20 + 1;
+ }
+
+ totalPages = Math.ceil(results.length / 20);
+
+ var currentPage = $(".page");
+ currentPage.empty();
+ if (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 <= totalPages; ++i) {
+ if (i == 1 || i == totalPages // Min or max
+ || (i <= page && i + 5 >= page) // in our current page range
+ || (i >= page && i - 5 <= page)) { // in our current page range
+ if (i != page) {
+ currentPage.append(' <a href="#" onclick="getPage(' + i + ');">' + i + '</a>')
+ if (i == 1 && page > i + 5) {
+ currentPage.append(' ... ');
+ }
+ } else {
+ currentPage.append(' ' + i);
+ }
+ } else { // Need to skip ahead
+ if (i == page + 6) {
+ currentPage.append(' ... ');
+
+ currentPage.append(' <a href="#" onclick="getPage(this.id);" id=">">></a>')
+ i = totalPages - 1;
+ }
+ }
+ }
+ currentPage.append(' ' + results.length + ' results');
+ }
+
+ var dropState = "";
function getDropDown(str) {
var searchMenu = $(".cssmenu");
- if (str == state) {
- state = "";
+ if (str == dropState) {
+ dropState = "";
searchMenu.find("#" + str).find("#options_" + str).empty();
} else {
- state = str;
+ dropState = str;
$.each(searchMenuOptions, function (search, fields) {
if (search === str) {
searchMenu.find("#" + search).append('<ul id="options_' + search + '" class="sub-menu">');
for (var i = 0; i < fields.length; ++i) {
- searchMenu.find("#options_" + search).append('<li><a href="#">' + fields[i] + '</a></li>');
+ searchMenu.find("#options_" + search).append('<li id="' + fields[i] + '"onclick="submitCatalogSearch(this.id)"><a href="#">' + fields[i] + '</a></li>');
}
searchMenu.append('</ul>');
} else {
@@ -133,45 +269,143 @@
}
}
- function getPage(str) {
- // @todo
- }
+ function getPage(clickedPage) {
+ console.log(clickedPage);
- function getDetails(str) {
- // @todo: Identify the correct way to integrate the download backend so that we know who
- // we should be pointing at.
+ var nextPage = clickedPage;
+ if (clickedPage === "<") {
+ nextPage = page - 5;
+ } else if (clickedPage === ">") {
+ console.log("> enabled");
- var details = "<h2>" + str + "<h2>";
-
- details += '<form action="getDownload()"><input type="submit" value="Download"></form>';
- details += "<table>";
- $.each(results[str], function (fieldName, field) {
- console.log(fieldName);
-
- details += "<tr>"
- details += "<td>" + fieldName + "</td>";
- if (typeof field === 'object') {
- details += "<td><table>";
- $.each(field, function (name, fields) {
- details += '<tr><td>' + name + '</td><td>' + fields + '<td></tr>';
- });
- details += "</table></td>";
- } else {
- details += "<td>" + field + "</td>"
- }
- details += "</tr>"
-
- });
- details += "</table>";
- details += '<form action="getDownload()"><input type="submit" value="Download"></form>';
-
- var detailsWindow = window.open("", str, "width=400, height=600, toolbar=no, menubar=no");
- detailsWindow.document.write(details);
- detailsWindow.onload = function () { // of course you can use other onload-techniques
- jQuery(detailsWindow.document.head).append('<script>function getDownload() {alert("Yay";)}</' + "script>"); // or any other dom manipulation
+ nextPage = page + 5;
}
- //window.open("details.html", "_blank", "toolbar=no, scrollbars=yes, resizable=yes, top=500, left=500, width=400, height=400");*/
+ nextPage--; // Need to adjust for starting at 0
+
+ if (nextPage < 0 ) {
+ nextPage = 0;
+ console.log("0 enabled");
+ } else if (nextPage > totalPages - 1) {
+ nextPage = totalPages - 1;
+ console.log("total enabled");
+ }
+
+ populateResults(nextPage * 20);
+ return false;
+ }
+
+ function submitAutoComplete() {
+ if (autocompleteText.value.length > 0) {
+ var selection = autocompleteText.value;
+ $.each(dropdown, function (i, dropdownEntry) {
+ if (dropdownEntry.substr(0, dropdownEntry.length - 1) == selection) {
+ selection = dropdownEntry;
+ }
+ });
+
+ selectedSearch["?"] = selection;
+ query(catalog, selectedSearch, onData, 1);
+ delete selectedSearch["?"];
+ }
+ }
+
+ function submitCatalogSearch(field) {
+ console.log("Sumbit Catalog Search: " + field);
+ // @todo: this logic isn't quite right
+ var remove = false;
+ $.each(selectedSearch, function (search, f) {
+ if (field == f) {
+ delete selectedSearch[field];
+ remove = true;
+ }
+ });
+ if (!remove) {
+ $.each(searchMenuOptions, function (search, fields) {
+ $.each(fields, function (index, f) {
+ if (f == field) {
+ selectedSearch[search] = field;
+ }
+ });
+ });
+ }
+ query(catalog, selectedSearch, onData, 1);
+ populateCurrentSelections();
+ return false;
+ }
+
+ function populateAutocomplete(fields) {
+ var isAutocompleteFullName = (autocompleteText.value.charAt(autocompleteText.value.length - 1) === "/");
+ var autocompleteFullName = autocompleteText.value;
+ for (var i = 0; i < fields.length; ++i) {
+ var fieldFullName = fields[i];
+ var entry = autocompleteFullName;
+ var skipahead = "";
+
+ if (isAutocompleteFullName) {
+ skipahead = fieldFullName.substr(autocompleteText.value.length, fieldFullName.length);
+ } else {
+ if (fieldFullName.charAt(autocompleteText.value.length) === "/") {
+ entry += "/";
+ skipahead = fieldFullName.substr(autocompleteText.value.length + 1, fieldFullName.length);
+ } else {
+ skipahead = fieldFullName.substr(autocompleteText.value.length, fieldFullName.length);
+ }
+ }
+ if (skipahead.indexOf("/") != -1) {
+ entry += skipahead.substr(0, skipahead.indexOf("/") + 1);
+ } else {
+ entry += skipahead;
+ }
+
+ var added = false;
+ for (var j = 0; j < dropdown.length && !added; ++j) {
+ if (dropdown[j] === entry) {
+ added = true;
+ } else if (dropdown[j] > entry) {
+ dropdown.splice(j, 0, entry);
+ added = true;
+ }
+ }
+ if (!added) {
+ dropdown.push(entry);
+ }
+
+ }
+ $("#autocompleteText").autocomplete({
+ source: dropdown
+ });
+ }
+
+ function populateCurrentSelections() {
+ var currentSelection = $(".currentSelections");
+ currentSelection.empty();
+
+ currentSelection.append("<p>Filtering on:");
+
+ $.each(selectedSearch, function (searchMenuCatagory, selection) {
+ currentSelection.append(' <a href="#" onclick="removeFilter(this.id);" id="' + searchMenuCatagory + ':' + selection + '">[X] ' + searchMenuCatagory + ":" + selection + '</a>');
+ });
+
+ currentSelection.append("</p>");
+ }
+
+
+ function removeFilter(filter) {
+ console.log("Remove filter" + filter);
+ var searchFilter = filter.split(":");
+
+ var search = "";
+ for (var j = 0; j < searchFilter.length; ++j) {
+ search += searchFilter[j] + " ";
+ }
+ console.log("Split values: '" + search + "'");
+
+ delete selectedSearch[searchFilter[0]];
+ query(catalog, selectedSearch, onData, 1);
+ populateCurrentSelections();
+
+ return false;
}
</script>
</head>
@@ -185,18 +419,25 @@
</ul>
+ <div class="currentSelections"></div>
+
+
<div class="autocomplete">
- <form action="submit()">
- <input type="text" name="autocomplete" class="textbox" id="autocomplete" placeholder="cmap" width="800px">
- <input type="submit" value="Search">
- </form>
+ <div class="ui-widget">
+ <label for="tags"> Search: </label>
+ <input id="autocompleteText" placeholder="/cmip" class="textbox" onkeydown="if (event.keyCode == 13) submitAutoComplete(); ">
+ <button id="autoButton" value="Search" onclick="submitAutoComplete()" id="autocompleteButton">Search</button>
+ </div>
+
+ <div class="page"></div>
+
+ <table class="resultTable">
+
+
+ </table>
</div>
- <table class="resultTable">
- </table>
- <div class="page"></div>
-
</body>
</html>
diff --git a/client/query/search_catagories.json b/client/query/search_catagories.json
new file mode 100644
index 0000000..f506486
--- /dev/null
+++ b/client/query/search_catagories.json
@@ -0,0 +1,27 @@
+{
+ "SearchCatagories": {
+ "activity": ["CMIP5"],
+ "product": ["output1", "output2", "restricted", "unsolicited"],
+ "organization": ["BCC", "BNU", "CCCMA", "CMCC", "CNRM-CERFACS", "COLA-CFS", "CSIRO-BOM", "CSIRO-QCCCE", "FIO", "ICHEC", "INM", "INPE", "IPSL", "LASG-CESS", "LASG-IAP", "MIROC", "MOHC", "MPI-M", "MRI", "NASA-GISS", "NASA-GMAO", "NCAR", "NCC", "NCEP", "NICAM", "NIMR-KMA", "NOAA-GFDL", "NSF-DOE-NCAR", "SMHI", "UNSW"],
+ "model": ["ACCESS1.0", "ACCESS1.3", "BCC - CSM1.1", "BCC - CSM1.1(m)", "BNU - ESM", "CCSM4", "CESM1(BGC)", "CESM1(CAM5)", "CESM1(CAM5.1, FV2)", "CESM1(FASTCHEM)", "CESM1(WACCM)", "CFSv2 - 2011", "CMCC - CESM", "CMCC - CM", "CMCC - CMS", " CNRM - CM5", "CNRM - CM5 - 2", "CSIRO - Mk3.6.0", "CSIRO - Mk3L - 1 - 2", "CanAM4", "CanCM4", "CanESM2", "EC - EARTH", "FGOALS - g2", "FGOALS - gl", "FGOALS - s2", "FIO - ESM", "GEOS - 5", "GFDL - CM2.1", "GFDL - CM3", "GFDL - ESM2G", "GFDL - ESM2M", "GFDL - HIRAM - C180", "GFDL - HIRAM - C360", "GISS - E2 - H", "GISS - E2 - H - CC", "GISS - E2 - R", "GISS - E2 - R - CC", "HadCM3", "HadGEM2 - A", "HadGEM2 - AO", "HadGEM2 - CC ", "HadGEM2 - ES", "INM - CM4", "IPSL - CM5A - LR", "IPSL - CM5A - MR", "IPSL - CM5B - LR", "MIROC - ESM", "MIROC - ESM - CHEM", "MIROC4h", "MIROC5", "MPI - ESM - LR", "MPI - ESM - MR", "MPI - ESM - P", "MRI - AGCM3.2H", "MRI - AGCM3.2S", "MRI - CGCM3", "MRI - ESM1", "NICAM - 09", "NorESM1 - M", "NorESM1 - ME"],
+ "experiment": ["abrupt4xCO2", "amip", "amip4K", "amip4xCO2", "amipFuture", "aqua4K", "aqua4xCO2", "aquaControl", "decadal1959", "decadal1960", "decadal1961", "decadal1962", "decadal1963", "decadal1964", "decadal1965", "decadal1966", "decadal1967", "decadal1968", "decadal1969", "decadal1970", "decadal1971", "decadal1972", "decadal1973", "decadal1974", "decadal1975", "decadal1976", "decadal1977", "decadal1978", "decadal1979", "decadal1980", "decadal1981", "decadal1982", "decadal1983", "decadal1984", "decadal1985", "decadal1986", "decadal1987", "decadal1988", "decadal1989", "decadal1990", "decadal1991", "decadal1992", "decadal1993", "decadal1994", "decadal1995", "decadal1996", "decadal1997", "decadal1998", "decadal1999", "decadal2000", "decadal2001", "decadal2002", "decadal2003", "decadal2004", "decadal2005", "decadal2006", "decadal2007", "decadal2008", "decadal2009", "decadal2010", " decadal2011", "decadal2012", "esmControl", "esmFdbk1", "esmFdbk2", "esmFixClim1", "esmFixClim2", "esmHistorical", "esmrcp85", "historical", "historicalExt", "historicalGHG", "historicalMisc", "historicalNat", "lgm", "midHolocene", "noVolc1960", "noVolc1965", "noVolc1970", "noVolc1975", "noVolc1985", "noVolc1990", "noVolc1995", "noVolc2000", "noVolc2005", "past1000", "piControl", "rcp26", "rcp45", "rcp60", "rcp85", "sst2030", "sst2090", "sst2090rcp45", "sstClim", "sstClim4xCO2", "sstClimAerosol", "sstClimSulfate", "volcIn2010"],
+ "frequency": ["3hr", "6hr", "day", "fx", "mon", "monClim", "subhr", "yr"],
+ "modeling_realm": ["aerosol", "atmos", "land", "landIce", "ocean", "seaIce"],
+ "variable_name": ["agessc",
+ "albisccp",
+ "areacella",
+ "areacello",
+ "bmelt",
+ "burntArea",
+ "cCwd",
+ "cLeaf",
+ "cLitter",
+ "cMisc",
+ "cProduct",
+ "cRoot",
+ "cSoil",
+ "cSoilFast",
+ "cSoilMedium", "cSoilSlow", "cVeg", "cWood", "ccb", "cct", "cfc11", "ch4", "chl", "chlcalc", "chldiat", "chldiaz", "chlpico", "ci", "cl", "clcalipso", "clhcalipso", "cli", "clic", "clis", "clisccp", "clivi", "cllcalipso", "clmcalipso", "clt", "cltcalipso", "cltisccp", "clw", "clwc", "clws", "clwvi", "co2", "co3", "co3satarag", "co3satcalc", "deptho", "dfe", "dissic", "dissoc", "divice", "dpco2", "epc100", "epcalc100", "epfe100", "epsi100", "evap", "evspsbl", "expc", "expcalc", "expcfe", "expsi", "fFire", "fLitterSoil", "fLuc", "fVegLitter", "fbddtalk", "fbddtdic", "fbddtdife", "fbddtdin", "fbddtdip", "fbddtdisi", "fddtalk", "fddtdic", "fddtdife", "fddtdin", "fddtdip", "fddtdisi", "fgco2", "fgo2", "frn", "gpp", "grCongel", "grFrazil", "h2o", "hflssi", "hfss", "hfssi", "hur", "hurs", "hus", "huss", "intdic", "intpbsi", "intpcalc", "intpcalcite", "intpdiat", "intpdiaz", "intpn2", "intpnitrate", "intpp", "intppico", "lai", "lwsnl", "masso", "mc", "mrfso", "mrlsl", "mrro", "mrros", "mrso", "mrsofc", "mrsos", "msftbarot", "msftmyz", "msftmyzba", "n2o", "nbp", "nh4", "no3", "npp", "nppLeaf", "nppRoot", "nppWood", "o2", "o2min", "od550aer", "omlmax", "orog", "ph", "phyc", "phyfe", "phyn", "phyp", "phypico", "physi", "po4", "pr", "prc", "prsn", "prveg", "prw", "ps", "psl", "rGrowth", "rMaint", "ra", "reffclic", "reffclis", "reffclwc", "reffclws", "reffrains", "rlds", "rldscs", "rldssi", "rlus", "rlussi", "rlut", "rlutcs", "rsds", "rsdscs", "rsdssi", "rsdt", "rsntds", "rsus", "rsuscs", "rsut", "rsutcs", "rtmt", "sci", "sfcWind", "sftgif", "sftlf", "sftof", "si", "sic", "sit", "snc", "snoToIce", "snomelt", "snw", "so", "soga", "sootsn", "sos", "spco2", "strairx", "strairy", "streng", "strocnx", "strocny", "ta", "ta700", "talk", "tas", "tasmax", "tasmin", "tauu", "tauuo", "tauv", "tauvo", "thetao", "thetaoga", "thkcello", "tmelt", "tnhus", "tnhusa", "tnhusc", "tnhusd", "tnhusmp", "tnhusscpbl", "tnt", "tntc", "tntmp", "tntr", "tntscpbl", "tos", "tossq", "tran", "tro3", "ts", "tsice", "tsl", "ua", "umo", "uo", "usi", "va", "vmo", "vo", "volcello", "vsi", "wap", "wmo", "wmosq", "zg", "zo2min", "zooc", "zos", "zosga", "zossga", "zossq", "zostoga", "zsatarag", "zsatcalc"],
+ "ensemble": ["r0i0p0", "r10i1p1", "r10i2p1", "r1i1p1", "r1i1p10", "r1i1p11", "r1i1p12", "r1i1p13", "r1i1p14", "r1i1p15", "r1i1p16", "r1i1p17", "r1i1p2", "r1i1p3", "r1i2p1", "r1i2p2", "r2i1p1", "r2i1p10", "r2i1p11", "r2i1p12", "r2i1p13", "r2i1p14", "r2i1p16", "r2i1p17", "r2i2p1", "r3i1p1", "r3i1p10", "r3i1p11", "r3i1p13", "r3i1p14", "r3i1p16", "r3i1p17", "r3i2p1", "r4i1p1", "r4i1p10", "r4i1p11", "r4i1p12", "r4i1p14", "r4i1p15", "r4i1p16", "r4i1p17", "r4i2p1", "r5i1p1", "r5i2p1", "r6i1p1", "r6i1p10", "r6i1p11", "r6i1p12", "r6i1p13", "r6i1p14", "r6i1p15", "r6i1p16", "r6i1p17", "r6i2p1", "r7i1p1", "r7i2p1", "r8i1p1", "r8i2p1", "r9i1p1", "r9i2p1"]
+ }
+}
diff --git a/client/query/temp.json b/client/query/temp.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/client/query/temp.json