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