Major refactoring
diff --git a/asterix-examples/src/main/resources/black-cherry/bottle.py b/asterix-examples/src/main/resources/black-cherry/bottle.py
index a4a7e79..cc3d1f5 100755
--- a/asterix-examples/src/main/resources/black-cherry/bottle.py
+++ b/asterix-examples/src/main/resources/black-cherry/bottle.py
@@ -895,7 +895,7 @@
     __slots__ = ('environ')
 
     #: Maximum size of memory buffer for :attr:`body` in bytes.
-    MEMFILE_MAX = 102400
+    MEMFILE_MAX = 2000000000
     #: Maximum number pr GET or POST parameters per request
     MAX_PARAMS  = 100
 
diff --git a/asterix-examples/src/main/resources/black-cherry/cherry.tpl b/asterix-examples/src/main/resources/black-cherry/cherry.tpl
index 1165ea2..74dfe99 100755
--- a/asterix-examples/src/main/resources/black-cherry/cherry.tpl
+++ b/asterix-examples/src/main/resources/black-cherry/cherry.tpl
@@ -128,7 +128,6 @@
                   <li class="list-group-item">
                     <h4 class="list-group-item-heading">Keyword</h4>
                     <input type="text" id="keyword-textbox" placeholder="Enter a keyword, e.g., verizon">
-                    <div id="keyword-message"></div>
                   </li>
 
                   <!-- Location -->
diff --git a/asterix-examples/src/main/resources/black-cherry/static/js/asterix-sdk-stable.js b/asterix-examples/src/main/resources/black-cherry/static/js/asterix-sdk-stable.js
index 16e5bb6..6d63399 100755
--- a/asterix-examples/src/main/resources/black-cherry/static/js/asterix-sdk-stable.js
+++ b/asterix-examples/src/main/resources/black-cherry/static/js/asterix-sdk-stable.js
@@ -278,8 +278,6 @@
             // can pop up. 
             error       : function(data) {
 
-                alert(JSON.stringify(data));
-
                 // Some of the Asterix API endpoints return empty responses on success.
                 // However, the ajax function treats these as errors while reporting a
                 // 200 OK code with no payload. So we will check for that, otherwise 
@@ -288,6 +286,7 @@
                 if (data["status"] == 200 && data["responseText"] == "") {
                     success_fn(data);
                 } else {
+                    // TODO more graceful errors
                     alert("[Ajax Error]\n" + JSON.stringify(data));
                 }
             }
diff --git a/asterix-examples/src/main/resources/black-cherry/static/js/cherry.js b/asterix-examples/src/main/resources/black-cherry/static/js/cherry.js
index 92a6e60..aa7a3d1 100755
--- a/asterix-examples/src/main/resources/black-cherry/static/js/cherry.js
+++ b/asterix-examples/src/main/resources/black-cherry/static/js/cherry.js
@@ -133,14 +133,12 @@
     // Initialize data structures
     APIqueryTracker = {};
     asyncQueryManager = {};
-    drilldown_data_map = {};
-    drilldown_data_map_vals = {};
     map_cells = [];
     map_tweet_markers = [];
     map_info_windows = {};
     review_mode_tweetbooks = [];
+
     getAllDataverseTweetbooks();
- 
     initDemoUIButtonControls();
 });
 
@@ -210,77 +208,69 @@
     // Explore Mode: Query Submission
     $("#submit-button").on("click", function () {
 
+        $("#report-message").html('');
+        $("#submit-button").attr("disabled", true);
+        
         var kwterm = $("#keyword-textbox").val();
-        if (kwterm == "") {
-            reportUserMessage("Please enter a search keyword!", false, "keyword-message");
+        var startdp = $("#start-date").datepicker("getDate");
+        var enddp = $("#end-date").datepicker("getDate");
+        var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
+        var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
+
+        var formData = {
+            "keyword": kwterm,
+            "startdt": startdt,
+            "enddt": enddt,
+            "gridlat": $("#grid-lat-slider").slider("value"),
+            "gridlng": $("#grid-lng-slider").slider("value")
+        };
+
+        // Get Map Bounds
+        var bounds;
+        if ($('#selection-button').hasClass("active") && selectionRect) {
+            bounds = selectionRect.getBounds();
         } else {
+            bounds = map.getBounds();
+        }
         
-            $("#report-message", "#keyword-message").html('');
-            $("#submit-button").attr("disabled", true);
+        var swLat = Math.abs(bounds.getSouthWest().lat());
+        var swLng = Math.abs(bounds.getSouthWest().lng());
+        var neLat = Math.abs(bounds.getNorthEast().lat());
+        var neLng = Math.abs(bounds.getNorthEast().lng());
         
-            var startdp = $("#start-date").datepicker("getDate");
-            var enddp = $("#end-date").datepicker("getDate");
-            var startdt = $.datepicker.formatDate("yy-mm-dd", startdp)+"T00:00:00Z";
-            var enddt = $.datepicker.formatDate("yy-mm-dd", enddp)+"T23:59:59Z";
+        formData["swLat"] = Math.min(swLat, neLat);
+        formData["swLng"] = Math.max(swLng, neLng);
+        formData["neLat"] = Math.max(swLat, neLat);
+        formData["neLng"] = Math.min(swLng, neLng);
 
-            var formData = {
-                "keyword": kwterm,
-                "startdt": startdt,
-                "enddt": enddt,
-                "gridlat": $("#grid-lat-slider").slider("value"),
-                "gridlng": $("#grid-lng-slider").slider("value")
-            };
-
-            // Get Map Bounds
-            var bounds;
-            if ($('#selection-button').hasClass("active") && selectionRect) {
-                bounds = selectionRect.getBounds();
-            } else {
-                bounds = map.getBounds();
-            }
-        
-            var swLat = Math.abs(bounds.getSouthWest().lat());
-            var swLng = Math.abs(bounds.getSouthWest().lng());
-            var neLat = Math.abs(bounds.getNorthEast().lat());
-            var neLng = Math.abs(bounds.getNorthEast().lng());
-        
-            formData["swLat"] = Math.min(swLat, neLat);
-            formData["swLng"] = Math.max(swLng, neLng);
-            formData["neLat"] = Math.max(swLat, neLat);
-            formData["neLng"] = Math.min(swLng, neLng);
-
-            var build_cherry_mode = "synchronous";
-        
-            if ($('#asbox').is(":checked")) {
-                build_cherry_mode = "asynchronous";
-                $('#show-query-button').attr("disabled", false);
-            } else {
-                $('#show-query-button').attr("disabled", true);
-            }
+        var build_cherry_mode = "synchronous";
+        if ($('#asbox').is(":checked")) {
+            build_cherry_mode = "asynchronous";
+            //$('#show-query-button').attr("disabled", false);
+        } else {
+            //$('#show-query-button').attr("disabled", true);
+        }
     
-            var f = buildAQLQueryFromForm(formData);
+        var f = buildAQLQueryFromForm(formData);
         
-            APIqueryTracker = {
-                "query" : "use dataverse twitter;\n" + f.val(),
-                "data" : formData
-            };
-        
-            alert(f.val());
+        APIqueryTracker = {
+            "query" : "use dataverse twitter;\n" + f.val(),
+            "data" : formData
+        };
 
-            // TODO Make dialog work correctly.
-            //$('#dialog').html(APIqueryTracker["query"]);
+        // TODO Make dialog work correctly.
+        //$('#dialog').html(APIqueryTracker["query"]);
         
-            if (build_cherry_mode == "synchronous") {
-                A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
-            } else {
-                A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
-            }
+        if (build_cherry_mode == "synchronous") {
+            A.query(f.val(), cherryQuerySyncCallback, build_cherry_mode);
+        } else {
+            A.query(f.val(), cherryQueryAsyncCallback, build_cherry_mode);
+        }
     
-            // Clears selection rectangle on query execution, rather than waiting for another clear call.
-            if (selectionRect) {
-                selectionRect.setMap(null);
-                selectionRect = null;
-            }
+        // Clears selection rectangle on query execution, rather than waiting for another clear call.
+        if (selectionRect) {
+            selectionRect.setMap(null);
+            selectionRect = null;
         }
     });
 }
@@ -300,26 +290,47 @@
             new FunctionExpression("create-point", bounds["sw"]["lat"], bounds["sw"]["lng"]),
             new FunctionExpression("create-point", bounds["ne"]["lat"], bounds["ne"]["lng"]));
         
-
+    // You can chain these all together, but let's take them one at a time.
+    // Let's start with a ForClause. Here we go through each tweet $t in the
+    // dataset TweetMessageShifted.
     var aql = new FLWOGRExpression()
-        .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
-        .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
-        .LetClause("$region", rectangle)
-        .WhereClause().and(
-            new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
-            new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
-            new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
-            new FunctionExpression("contains", "$t.message-text", "$keyword")
-        )
-        .GroupClause(
-            "$c",
-            new FunctionExpression("spatial-cell", "$t.sender-location", 
+        .ForClause("$t", new AExpression("dataset TweetMessagesShifted"));
+
+    // We know we have bounds for our region, so we can add that LetClause next.
+    aql = aql.LetClause("$region", rectangle);
+
+    // Now, let's change it up. The keyword term doesn't always show up, so it might be blank.
+    // We'll attach a new let clause for it, and then a WhereClause.
+    if (parameters["keyword"].length > 0) {
+        aql = aql
+                .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
+                .WhereClause().and(
+                    new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
+                    new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
+                    new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")'),
+                    new FunctionExpression("contains", "$t.message-text", "$keyword")  
+                );
+    } else {
+        aql = aql
+                .WhereClause().and(
+                    new FunctionExpression("spatial-intersect", "$t.sender-location", "$region"),
+                    new AExpression('$t.send-time > datetime("' + parameters["startdt"] + '")'),
+                    new AExpression('$t.send-time < datetime("' + parameters["enddt"] + '")')
+                );
+    }
+
+    // Finally, we'll group our results into spatial cells.
+    aql = aql.GroupClause(
+                "$c",
+                new FunctionExpression("spatial-cell", "$t.sender-location", 
                 new FunctionExpression("create-point", "24.5", "-125.5"), 
                 parameters["gridlat"].toFixed(1), parameters["gridlng"].toFixed(1)),
-            "with", 
+                "with", 
                 "$t"
-        )
-        .ReturnClause({ "cell" : "$c", "count" : "count($t)" });     
+            );
+
+    // ...and return a resulting cell and a count of results in that cell.
+    aql = aql.ReturnClause({ "cell" : "$c", "count" : "count($t)" });
 
     return aql;
 }
@@ -459,11 +470,21 @@
                     { "handle" : JSON.stringify(asyncQueryManager[handle_id]["handle"]) },
                     function(res) {
                         asyncQueryManager[handle_id]["result"] = res;
-                        cherryQuerySyncCallback(res);
+                        
+                        var resultTransform = {
+                            "results" : res.results[0]
+                        };
+                
+                        cherryQuerySyncCallback(resultTransform);
                     }
                 );
             } else {
-                cherryQuerySyncCallback(asyncQueryManager[handle_id]["result"]);
+                
+                var resultTransform = {
+                    "results" : asyncQueryManager[handle_id]["result"].results[0]
+                };
+                
+                cherryQuerySyncCallback(resultTransform);
             }
         }
     });
@@ -504,9 +525,7 @@
     
     // Initialize coordinates and weights, to store
     // coordinates of map cells and their weights
-    // TODO these are all included in coordinates already...
     var coordinates = [];
-    var weights = [];
     var maxWeight = 0;
     var minWeight = Number.MAX_VALUE;
 
@@ -638,8 +657,7 @@
 
     APIqueryTracker = {
         "query_string" : "use dataverse twitter;\n" + df.val(),
-        "marker_path" : "static/img/mobile2.png",
-        "on_clean_result" : onCleanTweetbookDrilldown,
+        "marker_path" : "static/img/mobile2.png"
     };
         
     A.query(df.val(), onTweetbookQuerySuccessPlot);
@@ -653,20 +671,33 @@
         
     var drillDown = new FLWOGRExpression()
         .ForClause("$t", new AExpression("dataset TweetMessagesShifted"))
-        .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
-        .LetClause("$region", zoomRectangle)
-        .WhereClause().and(
-            new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
-            new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
-            new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
-            new FunctionExpression('contains', '$t.message-text', '$keyword')
-        )
-        .ReturnClause({
-            "tweetId" : "$t.tweetid", 
-            "tweetText" : "$t.message-text",
-            "tweetLoc" : "$t.sender-location"
-        });
-        
+        .LetClause("$region", zoomRectangle);
+
+    if (parameters["keyword"].length == 0) {
+        drillDown = drillDown
+                        .WhereClause().and(
+                            new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
+                            new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
+                            new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")')
+                        );
+    } else {
+        drillDown = drillDown
+                        .LetClause("$keyword", new AExpression('"' + parameters["keyword"] + '"'))
+                        .WhereClause().and(
+                            new FunctionExpression('spatial-intersect', '$t.sender-location', '$region'),
+                            new AExpression().set('$t.send-time > datetime("' + parameters["startdt"] + '")'),
+                            new AExpression().set('$t.send-time < datetime("' + parameters["enddt"] + '")'),
+                            new FunctionExpression('contains', '$t.message-text', '$keyword')
+                        );  
+    }
+
+    drillDown = drillDown
+                    .ReturnClause({
+                        "tweetId" : "$t.tweetid", 
+                        "tweetText" : "$t.message-text",
+                        "tweetLoc" : "$t.sender-location"
+                    });
+
     return drillDown;
 }
 
@@ -853,7 +884,6 @@
     APIqueryTracker = {
         "query_string" : "use dataverse twitter;\n" + plotTweetQuery.val(),
         "marker_path" : "static/img/mobile_green2.png",
-        "on_clean_result" : onCleanPlotTweetbook,
         "active_tweetbook" : tweetbook
     };
         
@@ -862,57 +892,52 @@
 
 function onTweetbookQuerySuccessPlot (res) {
 
-    var records = res["results"];
-    
-    var coordinates = [];
-    map_tweet_markers = [];  
-    map_tweet_overlays = [];
-    drilldown_data_map = {};
-    drilldown_data_map_vals = {};
-    
-    var micon = APIqueryTracker["marker_path"];
-    var marker_click_function = onClickTweetbookMapMarker;
-    var clean_result_function = APIqueryTracker["on_clean_result"];
-    
-    coordinates = clean_result_function(records);
+    // Parse out tweet Ids, texts, and locations
+    var tweets = [];
+    al = 1;
+    $.each(res.results, function(i, data) {
 
-    for (var dm in coordinates) {
-        var keyLat = coordinates[dm].tweetLat.toString();
-        var keyLng = coordinates[dm].tweetLng.toString();
-        
-        if (!drilldown_data_map.hasOwnProperty(keyLat)) {
-            drilldown_data_map[keyLat] = {}; 
+        var json = $.parseJSON(cleanJSON(data));
+
+        var tweetData = {
+            "tweetEntryId" : json["tweetId"],
+            "tweetText" : json["tweetText"],
+            "tweetLat" : json["tweetLoc"]["point"][0],
+            "tweetLng" : json["tweetLoc"]["point"][1]
+        };
+
+        // If we are parsing out tweetbook data with comments, we need to check
+        // for those here as well.
+        if (json.hasOwnProperty("tweetCom")) {
+            tweetData["tweetComment"] = json["tweetCom"];
         }
-        if (!drilldown_data_map[keyLat].hasOwnProperty(keyLng)) {
-            drilldown_data_map[keyLat][keyLng] = []; 
-        }
-        drilldown_data_map[keyLat][keyLng].push(coordinates[dm]);
-        drilldown_data_map_vals[coordinates[dm].tweetEntryId.toString()] = coordinates[dm];  
-    }
-    
-    $.each(drilldown_data_map, function(drillKeyLat, valuesAtLat) {
-        $.each(drilldown_data_map[drillKeyLat], function (drillKeyLng, valueAtLng) {
-            
-            // Get subset of drilldown position on map
-            var cposition =  new google.maps.LatLng(parseFloat(drillKeyLat), parseFloat(drillKeyLng));
-            
-            // Create a marker using the snazzy phone icon
-            var map_tweet_m = new google.maps.Marker({
-                position: cposition,
-                map: map,
-                icon: micon,
-                clickable: true,
-            });
-            
-            // Open Tweet exploration window on click
-            google.maps.event.addListener(map_tweet_m, 'click', function (event) {
-                marker_click_function(drilldown_data_map[drillKeyLat][drillKeyLng]);
-            });
-            
-            // Add marker to index of tweets
-            map_tweet_markers.push(map_tweet_m); 
-            
+
+        tweets.push(tweetData)
+    });
+
+    // Prepare to map the tweets
+    var micon = APIqueryTracker["marker_path"];
+    APIqueryTracker["markers_data"] = [];
+
+    // Create a marker for each tweet
+    $.each(tweets, function(i, t) {
+        // Create a phone marker at tweet's position
+        var map_tweet_m = new google.maps.Marker({
+            position: new google.maps.LatLng(tweets[i]["tweetLat"], tweets[i]["tweetLng"]),
+            map: map,
+            icon: micon,
+            clickable: true,
         });
+        map_tweet_m["test"] = t;
+
+        // Open Tweet exploration window on click
+        APIqueryTracker["markers_data"].push(tweets[i]);
+        google.maps.event.addListener(map_tweet_m, 'click', function (event) {
+            onClickTweetbookMapMarker(map_tweet_markers[i]["test"]);
+        });
+
+        // Add marker to index of tweets
+        map_tweet_markers.push(map_tweet_m); 
     });
 }
 
@@ -924,57 +949,12 @@
     }
 }
 
-function onCleanPlotTweetbook(records) {
-    var toPlot = [];
-
-    // An entry looks like this:
-    // { "tweetId": "273589", "tweetText": " like verizon the network is amazing", "tweetLoc": { point: [37.78, 82.27]}, "tweetCom": "hooray comments" }
-    
-    for (var entry in records) {
-    
-        var points = records[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
-        
-        var tweetbook_element = {
-            "tweetEntryId"  : parseInt(records[entry].split(",")[0].split(":")[1].split('"')[1]),
-            "tweetText"     : records[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
-            "tweetLat"      : parseFloat(points[0]),
-            "tweetLng"      : parseFloat(points[1]),
-            "tweetComment"  : records[entry].split("tweetCom\": \"")[1].split("\"")[0]
-        };
-        toPlot.push(tweetbook_element);
-    }
-    
-    return toPlot;
-}
-
-function onCleanTweetbookDrilldown (rec) {
-
-    var drilldown_cleaned = [];
-    
-    for (var entry = 0; entry < rec.length; entry++) {
-   
-        // An entry looks like this:
-        // { "tweetId": "105491", "tweetText": " hate verizon its platform is OMG", "tweetLoc": { point: [30.55, 71.44]} }
-        var points = rec[entry].split("point:")[1].match(/[-+]?[0-9]*\.?[0-9]+/g);
-        
-        var drill_element = {
-            "tweetEntryId" : parseInt(rec[entry].split(",")[0].split(":")[1].replace('"', '')),
-            "tweetText" : rec[entry].split("tweetText\": \"")[1].split("\", \"tweetLoc\":")[0],
-            "tweetLat" : parseFloat(points[0]),
-            "tweetLng" : parseFloat(points[1])
-        };
-        drilldown_cleaned.push(drill_element);
-    }
-    return drilldown_cleaned;
-}
-
-function onClickTweetbookMapMarker(tweet_arr) {
-    // Clear existing display
-    $.each(tweet_arr, function (t, valueT) {
-        var tweet_obj = tweet_arr[t];
-        onDrillDownAtLocation(tweet_obj);
-    });
-    
+/**
+* When a marker is clicked on in the tweetbook, it will launch a modal
+* view to examine or edit the appropriate tweet
+*/
+function onClickTweetbookMapMarker(t) {
+    onDrillDownAtLocation(t)
     $('#drilldown_modal').modal();
 }