Browse Source

[search] Merging dashboard and search page

Romain Rigaux 11 years ago
parent
commit
63e409e1bd

+ 27 - 20
apps/search/src/search/models.py

@@ -206,12 +206,6 @@ class CollectionManager(models.Manager):
     try:
       return self.get(name=name), False
     except Collection.DoesNotExist:
-#      id_field = ''
-#      schema_fields = SolrApi(SOLR_URL.get(), user).fields(self.name)      
-#      for name, props in schema_fields['schema']['fields'].iteritems():
-#        if props['stored'] and props['required'] and props['multiValued']:
-#          id_field = name
-
       facets = Facet.objects.create(data=json.dumps({
                    'properties': {'isEnabled': False, 'limit': 10, 'mincount': 1, 'sort': 'count'},
                    'ranges': [],
@@ -254,7 +248,7 @@ em {
     <div class="span12">%s</div>
   </div>
   <br/>
-</div>""" % ' '.join(['{{%s}}' % field for field in collection.fields(user)])
+</div>""" % ' '.join(['{{%s}}' % field['name'] for field in collection.fields_data(user)])
 
       result.update_from_post({'template': json.dumps(template)})
       result.save()
@@ -280,23 +274,30 @@ class Collection(models.Model):
   objects = CollectionManager()
 
   def get_c(self, user):
+    fields = self.fields_data(user)
+    id_field = [field.name for field in fields if field.get('isId')]
+    if not id_field:
+      id_field = 'id'
+    fields = [field.get('name') for field in self.fields_data(user)]
+  
     TEMPLATE = {
       "extracode": "<style type=\"text/css\">\nem {\n  font-weight: bold;\n  background-color: yellow;\n}</style>", "highlighting": ["body"],
       "properties": {"highlighting_enabled": True},
       "template": "{{user_screen_name}} {{user_name}} {{text}}", "isGridLayout": True,
-      "fields": ["user_screen_name", "user_name", "text"]
+      "fields": fields
     };
-    FACETS = {"dates": [], "fields": [{
-         "uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca", "verbatim": "", "isVerbatim": False, "label": "Location", 
-         "field": "to", "type": "field"
-       }
+    FACETS = {"dates": [], "fields": [
+#                                      {
+#         "uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca", "verbatim": "", "isVerbatim": False, "label": "Location", 
+#         "field": "user_location", "type": "field"
+#       }
        ],
        "charts": [], "properties": {"sort": "count", "mincount": 1, "isEnabled": True, "limit": 10, 'schemd_id_field': 'id'}, "ranges": [], "order": []
     };  
-  
+    
     m = {
       'id': self.id, 'name': self.name, 'template': TEMPLATE, 'facets': FACETS['fields'], 
-      'fields': self.fields(user)
+      'fields': fields, 'idField': id_field
     };
     
     return json.dumps(m)
@@ -314,7 +315,9 @@ class Collection(models.Model):
     schema_fields = SolrApi(SOLR_URL.get(), user).fields(self.name)
     schema_fields = schema_fields['schema']['fields']
 
-    return sorted([{'name': str(field), 'type': str(attributes.get('type', ''))}
+    return sorted([{'name': str(field), 'type': str(attributes.get('type', '')),
+                    'isId': attributes.get('stored') and attributes.get('required') and attributes.get('multiValued')
+                  }
                   for field, attributes in schema_fields.iteritems()])
 
   @property
@@ -444,13 +447,17 @@ def augment_solr_response2(response, collection, solr_query):
         normalized_facets.append(facet)
 
   # TODO HTML escape docs!
-
+  
   highlighted_fields = response.get('highlighting', [])
   if highlighted_fields:
-    for doc in response['response']['docs']:
-      # TODO: Beware, schema requires an 'id' field, silently do nothing
-      if 'message_id' in doc and doc['message_id'] in highlighted_fields:
-        doc.update(response['highlighting'][doc['message_id']])
+    id_field = collection.get('idField')    
+    if id_field:
+      for doc in response['response']['docs']:
+        if id_field in doc and doc[id_field] in highlighted_fields:
+          doc.update(response['highlighting'][doc[id_field]])
+    else:
+      response['warning'] = _("The Solr schema requires an 'id' field for performing the result highlighting")
+      
         
   response['total_pages'] = int(math.ceil((float(response['response']['numFound']) / float(solr_query['rows']))))
   response['search_time'] = response['responseHeader']['QTime']

+ 11 - 10
apps/search/src/search/templates/dashboard.mako

@@ -156,9 +156,9 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
 
   <div style="float: left; margin-left: 20px" data-bind="visible: columns().length > 0">
     <div style="font-weight: bold; color: #999; padding-left: 8px">${_('WIDGETS')}</div>
-    <div class="draggable-widget" data-bind="draggable: draggableFacet" title="${_('Text Facet')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-sort-amount-desc"></i></a></div>
-    <div class="draggable-widget" data-bind="draggable: draggableResultset" title="${_('Results')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-th-large"></i></a></div>
-    <div class="draggable-widget" data-bind="draggable: draggableBar" title="${_('Bar Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-bar-chart"></i></a></div>
+    <div class="draggable-widget" data-bind="draggable: draggableResultset" title="${_('Results')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-table"></i></a></div>    
+    <div class="draggable-widget" data-bind="draggable: draggableFacet" title="${_('Text Facet')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-sort-amount-desc"></i></a></div>    
+    <div class="draggable-widget" data-bind="draggable: draggableBar" title="${_('Timeline')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-bar-chart"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggableArea" title="${_('Area Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-area-chart"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggablePie" title="${_('Pie Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-pie-chart"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggableLine" title="${_('Line Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-line-chart"></i></a></div>
@@ -202,7 +202,7 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
     <div data-bind="template: { name: 'row-template', foreach: rows}">
     </div>
     <div style="height: 50px; padding-left: 6px" data-bind="visible: $root.isEditing">
-      <a href="javascript:void(0)" class="btn" style="margin: 4px; margin-right: 10px" data-bind="click: addEmptyRow"><i class="fa fa-table"></i> ${_('Add Row')}</a>
+      <a href="javascript:void(0)" class="btn" style="margin: 4px; margin-right: 10px" data-bind="click: addEmptyRow"><i class="fa fa-plus"></i> ${_('Add Row')}</a>
     </div>
   </div>
 </script>
@@ -215,7 +215,7 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
   </div>
   <div class="container-fluid">
     <div class="row-header" data-bind="visible: $root.isEditing">
-      <span class="muted"><i class="fa fa-table"></i> ${_('Row')}</span>
+      <span class="muted"><i class="fa fa-ellipsis-h"></i> ${_('Row')}</span>
       <div style="display: inline; margin-left: 60px">
         <a href="javascript:void(0)" data-bind="visible:$index()<$parent.rows().length-1, click: function(){moveDown($parent, this)}"><i class="fa fa-chevron-down"></i></a>
         <a href="javascript:void(0)" data-bind="visible:$index()>0, click: function(){moveUp($parent, this)}"><i class="fa fa-chevron-up"></i></a>
@@ -249,7 +249,7 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
           <li> Size: <input type="text" data-bind="value: size" /></li>
           <li> Offset: <input type="text" data-bind="value: offset" /></li>
         </ul>
-  </div>
+      </div>
     </div>
   </div>
 </script>
@@ -305,7 +305,6 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
 <script src="/static/js/jquery.blueprint.js"></script>
 
 <script type="text/javascript">
-
   ko.bindingHandlers.slideVisible = {
     init: function (element, valueAccessor) {
       var value = valueAccessor();
@@ -415,12 +414,12 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
   var Widget = function (size, name, widgetType, properties, offset) {
     var self = this;
     self.size = ko.observable(size).extend({ numeric: 0 });
-    ;
+
     self.name = ko.observable(name);
     self.widgetType = ko.observable(typeof widgetType != "undefined" && widgetType != null ? widgetType : "empty-widget");
     self.properties = ko.observable(typeof properties != "undefined" && properties != null ? properties : {});
     self.offset = ko.observable(typeof offset != "undefined" && offset != null ? offset : 0).extend({ numeric: 0 });
-    ;
+
 
     self.klass = ko.computed(function () {
       return "card card-widget span" + self.size() + (self.offset() * 1 > 0 ? " offset" + self.offset() : "");
@@ -443,7 +442,7 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
     self.remove = function (row, widget) {
       row.widgets.remove(widget);
     }
-  }
+  };
 
   Widget.prototype.clone = function () {
     return new Widget(this.size(), this.name(), this.widgetType());
@@ -504,6 +503,8 @@ ${ commonheader(_('Search'), "search", user, "70px") | n,unicode }
   }
 
   $(document).ready(function () {
+    fullLayout();
+    viewModel.isEditing(true);
   });
 </script>
 

+ 304 - 59
apps/search/src/search/templates/search2.mako

@@ -26,10 +26,110 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
 <script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/search/static/js/search.utils.js" type="text/javascript" charset="utf-8"></script>
 
+<style type="text/css">
+  .dashboard .container-fluid {
+    padding: 10px;
+  }
+
+  .row-container {
+    width: 100%;
+    min-height: 70px;
+  }
+
+  .ui-sortable {
+    background-color: #F6F6F6;
+  }
+
+  .ui-sortable h2 {
+    padding-left: 10px!important;
+  }
+
+  .ui-sortable h2 ul {
+    float: left;
+    margin-right: 10px;
+    font-size: 14px;
+  }
+
+  .ui-sortable:not(.ui-sortable-disabled) h2 {
+    cursor: move;
+  }
+
+  .ui-sortable-disabled {
+    background-color: #FFF;
+  }
+
+  .card-column {
+    border: none;
+    min-height: 400px!important;
+  }
+
+  .card-widget {
+    padding-top: 0;
+  }
+
+  .card-toolbar {
+    margin: 0;
+    padding: 4px;
+    padding-top: 10px;
+  }
+
+  .row-header {
+    background-color: #F6F6F6;
+    display: inline;
+    padding: 4px;
+  }
+
+  #emptyDashboard {
+    position: absolute;
+    right: 14px;
+    top: 80px;
+    color: #666;
+    font-size: 20px;
+  }
+
+  .emptyRow {
+    margin-top: 10px;
+    margin-left: 140px;
+    color: #666;
+    font-size: 18px;
+  }
+
+  .preview-row {
+    background-color: #DDD;
+    min-height: 400px!important;
+    margin-top: 30px;
+  }
+
+  .draggable-widget {
+    width: 100px;
+    text-align: center;
+    float: left;
+    border: 1px solid #CCC;
+    margin-top: 10px;
+    margin-right: 10px;
+    cursor: move;
+  }
+  .draggable-widget a {
+    font-size: 58px;
+    line-height: 76px;
+    cursor: move;
+  }
+
+  .unselectable {
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+  }
+
+</style>
 
 <div class="search-bar">
   % if user.is_superuser:
     <div class="pull-right" style="margin-top: 6px; margin-right: 40px">
+      <button type="button" data-bind="click: toggleEditing, css: {'btn': true, 'btn-inverse': isEditing}"><i class="fa fa-pencil"></i></button>    
       <a class="change-settings" href="#"><i class="fa fa-save"></i> ${ _('Save') }</a> &nbsp;&nbsp;
       <a href="${ url('search:admin_collections') }"><i class="fa fa-sitemap"></i> ${ _('Collection manager') }</a>
     </div>
@@ -55,27 +155,125 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
   </form>
 </div>
 
-    <div>
-      <ul class="nav nav-tabs">
-        <li class="active"><a href="#tourStep1" class="tourStep">${ _('Search') }</a></li>
-        <li><a href="#tourStep2" class="tourStep">${ _('Visualization') }</a></li>
-        <li><a id="tourLastStep" href="#tourStep3" class="tourStep">${ _('Data') }</a></li>
-      </ul>
+<div class="card card-toolbar" data-bind="slideVisible: isEditing">
+  <div style="float: left">
+    <div style="font-weight: bold; color: #999; padding-left: 8px">${_('LAYOUT')}</div>
+    <a href="javascript: oneThirdLeftLayout()" onmouseover="viewModel.previewColumns('oneThirdLeft')" onmouseout="viewModel.previewColumns('')"><img src="/search/static/art/layout_onethirdleft.png" /></a>
+    <a href="javascript: oneThirdRightLayout()" onmouseover="viewModel.previewColumns('oneThirdRight')" onmouseout="viewModel.previewColumns('')"><img src="/search/static/art/layout_onethirdright.png" /></a>
+    <a href="javascript: fullLayout()" onmouseover="viewModel.previewColumns('full')" onmouseout="viewModel.previewColumns('')"><img src="/search/static/art/layout_full.png" /></a>
+  </div>
+
+  <div style="float: left; margin-left: 20px" data-bind="visible: columns().length > 0">
+    <div style="font-weight: bold; color: #999; padding-left: 8px">${_('WIDGETS')}</div>
+    <div class="draggable-widget" data-bind="draggable: draggableResultset" title="${_('Results')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-table"></i></a></div>    
+    <div class="draggable-widget" data-bind="draggable: draggableFacet" title="${_('Text Facet')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-sort-amount-desc"></i></a></div>    
+    <div class="draggable-widget" data-bind="draggable: draggableBar" title="${_('Timeline')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-bar-chart"></i></a></div>
+    <div class="draggable-widget" data-bind="draggable: draggableArea" title="${_('Area Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-area-chart"></i></a></div>
+    <div class="draggable-widget" data-bind="draggable: draggablePie" title="${_('Pie Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-pie-chart"></i></a></div>
+    <div class="draggable-widget" data-bind="draggable: draggableLine" title="${_('Line Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-line-chart"></i></a></div>
+    <div class="draggable-widget" data-bind="draggable: draggableMap" title="${_('Map')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-map-chart"></i></a></div>
+  </div>
+  <div class="clearfix"></div>
+</div>
+
+<div id="emptyDashboard" data-bind="visible: !isEditing() && columns().length == 0">
+  <div style="float:left; padding-top: 90px; margin-right: 20px; text-align: center; width: 260px">${_('Click on the pencil to get started with your dashboard!')}</div>
+  <img src="/search/static/art/hint_arrow.png" />
+</div>
+
+<div data-bind="visible: isEditing() && previewColumns() != '' && columns().length == 0">
+  <div class="container-fluid">
+    <div class="row-fluid" data-bind="visible: previewColumns() == 'oneThirdLeft'">
+      <div class="span3 preview-row"></div>
+      <div class="span9 preview-row"></div>
+    </div>
+    <div class="row-fluid" data-bind="visible: previewColumns() == 'oneThirdRight'">
+      <div class="span9 preview-row"></div>
+      <div class="span3 preview-row"></div>
+    </div>
+    <div class="row-fluid" data-bind="visible: previewColumns() == 'full'">
+      <div class="span12 preview-row">
+      </div>
+    </div>
+  </div>
+</div>
+
+<div data-bind="css: {'dashboard': true, 'unselectable': isEditing()}">
+  <div class="container-fluid">
+    <div class="row-fluid" data-bind="template: { name: 'column-template', foreach: columns}">
     </div>
-    
-    <div class="tourSteps">
-      <div id="tourStep1" class="tourStepDetails">
+    <div class="clearfix"></div>
+  </div>
+</div>
 
-<div class="container results">
+<script type="text/html" id="column-template">
+  <div data-bind="css: klass">
+    <div data-bind="template: { name: 'row-template', foreach: rows}">
+    </div>
+    <div style="height: 50px; padding-left: 6px" data-bind="visible: $root.isEditing">
+      <a href="javascript:void(0)" class="btn" style="margin: 4px; margin-right: 10px" data-bind="click: addEmptyRow"><i class="fa fa-plus"></i> ${_('Row')}</a>
+    </div>
+  </div>
+</script>
 
-  <div id="mainContent" class="row">
-    <div class="span2 results">
-      <div class="row">
-        <span class="pull-right">
-          <a id="showAddFacetModal" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
-        </span>
+<script type="text/html" id="row-template">
+  <div class="emptyRow" data-bind="visible: widgets().length == 0 && $index()==0 && $root.isEditing() && $parent.size() > 4">
+    <img src="/search/static/art/hint_arrow_flipped.png" style="float:left; margin-right: 10px"/>
+    <div style="float:left; text-align: center; width: 260px">${_('Drag any of the widgets inside your empty row')}</div>
+    <div class="clearfix"></div>
+  </div>
+  <div class="container-fluid">
+    <div class="row-header" data-bind="visible: $root.isEditing">
+      <span class="muted"><i class="fa fa-minus"></i> ${_('Row')}</span>
+      <div style="display: inline; margin-left: 60px">
+        <a href="javascript:void(0)" data-bind="visible:$index()<$parent.rows().length-1, click: function(){moveDown($parent, this)}"><i class="fa fa-chevron-down"></i></a>
+        <a href="javascript:void(0)" data-bind="visible:$index()>0, click: function(){moveUp($parent, this)}"><i class="fa fa-chevron-up"></i></a>
+        <a href="javascript:void(0)" data-bind="visible:$parent.rows().length > 1, click: function(){remove($parent, this)}"><i class="fa fa-times"></i></a>
       </div>
-      <div class="row" data-bind="foreach: norm_facets">
+    </div>
+    <div class="row-fluid row-container" data-bind="sortable: { template: 'widget-template', data: widgets, isEnabled: $root.isEditing}">
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="widget-template">
+  <div data-bind="css: klass">
+    <h2 class="card-heading simple">
+      <ul class="inline" data-bind="visible: $root.isEditing">
+        <li><a href="javascript:void(0)" data-bind="click: function(){remove($parent, this)}"><i class="fa fa-times"></i></a></li>
+        <li><a href="javascript:void(0)" data-bind="click: compress, visible: size() > 1"><i class="fa fa-step-backward"></i></a></li>
+        <li><a href="javascript:void(0)" data-bind="click: expand, visible: size() < 12"><i class="fa fa-step-forward"></i></a></li>
+      </ul>
+      <span data-bind="text: name"></span>
+    </h2>
+    <div class="card-body">
+      <p>
+        <div data-bind="template: { name: function() { return widgetType(); }, data: properties }"></div>
+      </p>
+      <div data-bind="visible: $root.isEditing()">
+        This is visible just when we edit.<br/>
+        Common properties:
+        <ul>
+          <li> Name: <input type="text" data-bind="value: name" /></li>
+          <li> Size: <input type="text" data-bind="value: size" /></li>
+          <li> Offset: <input type="text" data-bind="value: offset" /></li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="empty-widget">
+  This is an empty widget.
+</script>
+
+<script type="text/html" id="facet-widget">
+      <span class="pull-right">
+        <a id="showAddFacetModal" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
+      </span>
+
+      ## Need to pick the facet ID from norm_facets instead of looping on all
+      <div class="row" data-bind="foreach: $root.norm_facets">
         <span class="pull-right">
           <a href="javascript:void(0)" data-bind="click: editFacet"><i class="fa fa-pencil"></i></a>
           <a href="javascript:void(0)" data-bind="click: $root.removeFacet"><i class="fa fa-times"></i></a>
@@ -94,23 +292,17 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
             </a>
           </div>
         </div>
-      </div>            
-      
-    </div> 
-    
-    <div class="span10 results">
-      <div class="row">
-        <span class="pull-right">
-          <i class="fa fa-plus" id="edit-timeline"></i>
-        </span>
-      </div>
-    
+      </div>    
+
+</script>
+
+<script type="text/html" id="resultset-widget">
       <div class="row">
         <span class="pull-right">
-          <a id="edit-template" href="javascript:void(0)"><i class="fa fa-pencil"></i></a>
+          <a data-bind="click: editTemplate" href="javascript:void(0)"><i class="fa fa-pencil"></i></a>
         </span>
       </div>
-      
+  
       <!-- ko if: $root.collection.template.isGridLayout() -->
       <table id="result-container">        
         <thead>
@@ -118,7 +310,7 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
             <th data-bind="text: $data"></th>
           </tr>
         </thead>
-        <tbody data-bind="foreach: results">
+        <tbody data-bind="foreach: $root.results">
           <tr class="result-row" data-bind="foreach: $data">
             <td data-bind="html: $data"></td>
           </tr>
@@ -126,35 +318,37 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
       </table>
       <!-- /ko -->
       <!-- ko if: ! $root.collection.template.isGridLayout() -->
-      <div id="result-container" data-bind="foreach: results">
+      <div id="result-container" data-bind="foreach: $root.results">
         <div class="result-row" data-bind="html: $data"></div>
       </div>
-      <!-- /ko -->
-      
-      </div>
-    </div>
+      <!-- /ko -->  
+</script>
 
-  </div>
-</div>
-        </div>
-      </div>      
-
-
-## unsused
-<script type="text/html" id="error-template">
-<div class="container-fluid">
-  <div class="row-fluid">
-    <div class="span12">
-      <div class="alert alert-error">
-        <h4 data-bind="text: title"></h4>
-        <br/>
-        <span class="decodeError" data-bind="text: message"></span>
-      </div>
-    </div>
-  </div>
-</div>
+<script type="text/html" id="timeline-widget">
+  This is the timeline widget
+</script>
+
+<script type="text/html" id="bar-widget">
+  This is the bar widget
+</script>
+
+<script type="text/html" id="pie-widget">
+  This is the pie widget
+</script>
+
+<script type="text/html" id="area-widget">
+  This is the area widget
 </script>
 
+<script type="text/html" id="line-widget">
+  This is the line widget
+</script>
+
+<script type="text/html" id="map-widget">
+  This is the map widget
+</script>
+
+
 <style type="text/css">
   em {
     font-weight: bold; 
@@ -241,17 +435,68 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/mustache.js"></script>
+<script src="/static/ext/js/jquery/plugins/jquery-ui-draggable-droppable-sortable-1.8.23.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/search/static/js/knockout-sortable.min.js" type="text/javascript" charset="utf-8"></script>
+
+<link href="/static/ext/css/leaflet.css" rel="stylesheet">
+<link href="/static/ext/css/hue-charts.css" rel="stylesheet">
+
+<script src="/static/ext/js/jquery/plugins/jquery.flot.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/jquery/plugins/jquery.flot.categories.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/leaflet/leaflet.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/js/jquery.blueprint.js"></script>
+
 <script src="/search/static/js/search.ko.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
 var viewModel;
 
 $(document).ready(function () {
+
+  ko.bindingHandlers.slideVisible = {
+    init: function (element, valueAccessor) {
+      var value = valueAccessor();
+      $(element).toggle(ko.unwrap(value));
+    },
+    update: function (element, valueAccessor) {
+      var value = valueAccessor();
+      ko.unwrap(value) ? $(element).slideDown(100) : $(element).slideUp(100);
+    }
+  };
+
+  ko.extenders.numeric = function (target, precision) {
+    var result = ko.computed({
+      read: target,
+      write: function (newValue) {
+        var current = target(),
+                roundingMultiplier = Math.pow(10, precision),
+                newValueAsNum = isNaN(newValue) ? 0 : parseFloat(+newValue),
+                valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;
+
+        if (valueToWrite !== current) {
+          target(valueToWrite);
+        } else {
+          if (newValue !== current) {
+            target.notifySubscribers(valueToWrite);
+          }
+        }
+      }
+    }).extend({ notify: 'always' });
+    result(target());
+    return result;
+  };
+
+
   viewModel = new SearchViewModel(${ hue_collection.get_c(user) | n,unicode }, ${ hue_query | n,unicode });
   ko.applyBindings(viewModel);
   
+  
+  fullLayout();
+  viewModel.isEditing(true);
+  viewModel.columns()[0].rows()[0].addWidget(viewModel.draggableResultset());  
   viewModel.search();
-
+  
+  
   $("#showAddFacetModal").click(function() {
     $("#addFacetModal").modal("show");
   });
@@ -261,11 +506,11 @@ $(document).ready(function () {
     $('#addFacetModal').modal("hide");
     viewModel.search();
   });
+});
 
-  $("#edit-template").click(function() {
+  function editTemplate(facet) {
     $("#editTemplateModal").modal("show");
-  });
-});
+  };
 
   function editFacet(facet) {
     viewModel.selectSingleFacet(facet);

+ 1 - 1
apps/search/src/search/urls.py

@@ -18,7 +18,7 @@
 from django.conf.urls.defaults import patterns, url
 
 urlpatterns = patterns('search.views',
-  url(r'^$', 'index', name='index'),
+  url(r'^$', 'index2', name='index'),
   url(r'^index2$', 'index2', name='index2'),
   url(r'^query2$', 'index2', name='query2'),
   url(r'^search$', 'search', name='search'),

+ 0 - 1
apps/search/src/search/views.py

@@ -129,7 +129,6 @@ def search(request):
   print request.POST
   print collection
     
- 
   if collection:
     solr_query = {}    
     try:

+ 165 - 10
apps/search/static/js/search.ko.js

@@ -14,6 +14,154 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+
+// Start dashboard lib to move out
+
+var Column = function (size, rows) {
+    var self = this;
+    self.size = ko.observable(size);
+    self.rows = ko.observableArray(rows);
+    self.klass = ko.computed(function () {
+      return "card card-home card-column span" + self.size();
+    });
+    self.addEmptyRow = function () {
+      self.addRow();
+    };
+    self.addRow = function (row) {
+      if (typeof row == "undefined" || row == null) {
+        row = new Row([]);
+      }
+      self.rows.push(row);
+    };
+  }
+
+  var Row = function (widgets) {
+    var self = this;
+    self.widgets = ko.observableArray(widgets);
+
+    self.addWidget = function (widget) {
+      self.widgets.push(widget);
+    };
+
+    self.move = function (from, to) {
+      try {
+        viewModel.columns()[to].addRow(self);
+        viewModel.columns()[from].rows.remove(self);
+      }
+      catch (exception) {
+      }
+    }
+
+    self.moveDown = function (col, row) {
+      var _i = col.rows().indexOf(row);
+      if (_i < col.rows().length - 1) {
+        var _arr = col.rows();
+        col.rows.splice(_i, 2, _arr[_i + 1], _arr[_i]);
+      }
+    }
+
+    self.moveUp = function (col, row) {
+      var _i = col.rows().indexOf(row);
+      if (_i >= 1) {
+        var _arr = col.rows();
+        col.rows.splice(_i - 1, 2, _arr[_i], _arr[_i - 1]);
+      }
+    }
+
+    self.remove = function (col, row) {
+      col.rows.remove(row);
+    }
+  }
+
+  var Widget = function (size, name, widgetType, properties, offset) {
+    var self = this;
+    self.size = ko.observable(size).extend({ numeric: 0 });
+
+    self.name = ko.observable(name);
+    self.widgetType = ko.observable(typeof widgetType != "undefined" && widgetType != null ? widgetType : "empty-widget");
+    self.properties = ko.observable(typeof properties != "undefined" && properties != null ? properties : {});
+    self.offset = ko.observable(typeof offset != "undefined" && offset != null ? offset : 0).extend({ numeric: 0 });
+
+
+    self.klass = ko.computed(function () {
+      return "card card-widget span" + self.size() + (self.offset() * 1 > 0 ? " offset" + self.offset() : "");
+    });
+
+    self.expand = function () {
+      self.size(self.size() + 1);
+    }
+    self.compress = function () {
+      self.size(self.size() - 1);
+    }
+
+    self.moveLeft = function () {
+      self.offset(self.offset() - 1);
+    }
+    self.moveRight = function () {
+      self.offset(self.offset() + 1);
+    }
+
+    self.remove = function (row, widget) {
+      row.widgets.remove(widget);
+    }
+  };
+
+  Widget.prototype.clone = function () {
+    return new Widget(this.size(), this.name(), this.widgetType());
+  };
+
+  function fullLayout() {
+    setLayout([12]);
+  }
+
+  function oneThirdLeftLayout() {
+    setLayout([3, 9]);
+  }
+
+  function oneThirdRightLayout() {
+    setLayout([9, 3]);
+  }
+
+  function setLayout(colSizes) {
+    // save previous widgets
+    var _allRows = [];
+    $(viewModel.columns()).each(function (cnt, col) {
+      var _tRows = [];
+      $(col.rows()).each(function (icnt, row) {
+        if (row.widgets().length > 0) {
+          _tRows.push(row);
+        }
+      });
+      _allRows = _allRows.concat(_tRows);
+    });
+
+    var _cols = [];
+    var _highestCol = {
+      idx: -1,
+      size: -1
+    };
+    $(colSizes).each(function (cnt, size) {
+      _cols.push(new Column(size, []));
+      if (size > _highestCol.size) {
+        _highestCol.idx = cnt;
+        _highestCol.size = size;
+      }
+    });
+    if (_allRows.length > 0 && _highestCol.idx > -1) {
+      _cols[_highestCol.idx].rows(_allRows);
+    }
+
+    $(_cols).each(function (cnt, col) {
+      if (col.rows().length == 0) {
+        col.rows([new Row([])]);
+      }
+    });
+
+    viewModel.columns(_cols);
+  }
+
+// End dashboard lib
+  
 var Query = function (vm, query) {
   var self = this;
 
@@ -31,22 +179,13 @@ var Query = function (vm, query) {
   }
 };
 
-var FieldFacet = function(vm, props) {
-  self.id = props.id;  
-  self.label = props.name;
-  self.field = props.name;
-  self.type = "field";
-}
-
-// FieldListFacet
-// RangeFacet
-
 
 var Collection = function (vm, collection) {
   var self = this;
 
   self.id = collection.id;
   self.name = collection.name;
+  self.idField = collection.idField;
   self.template = ko.mapping.fromJS(collection.template);
   self.template.fields.subscribe(function() {
 	vm.search();
@@ -99,11 +238,27 @@ var SearchViewModel = function (collection_json, query_json) {
   
   self.selectedFacet = ko.observable();
 
+  self.previewColumns = ko.observable("");
+  self.columns = ko.observableArray([]); // load back? 
+  self.isEditing = ko.observable(false);
+  self.draggableFacet = ko.observable(new Widget(12, "Facet", "facet-widget"));
+  self.draggableResultset = ko.observable(new Widget(12, "Results", "resultset-widget"));
+  self.draggableBar = ko.observable(new Widget(12, "Bar Chart", "bar-widget"));
+  self.draggableArea = ko.observable(new Widget(12, "Area Chart", "area-widget"));
+  self.draggableMap = ko.observable(new Widget(12, "Map", "map-widget"));
+  self.draggableLine = ko.observable(new Widget(12, "Line Chart", "line-widget"));
+  self.draggablePie = ko.observable(new Widget(12, "Pie Chart", "pie-widget"));
+  self.toggleEditing = function () {
+    self.isEditing(!self.isEditing());
+  };  
+  
+  
   self.search = function () {
 	$(".jHueNotify").hide();
     $.post("/search/search", {
         collection: ko.mapping.toJSON(self.collection),
         query: ko.mapping.toJSON(self.query),
+        layout: ko.mapping.toJSON(self.columns)
       }, function (data) {
        self.response(data); // If error we should probably update only the facets
    	   self.results.removeAll();