Prechádzať zdrojové kódy

[search] Added new field selection modal

Enrico Berti 11 rokov pred
rodič
commit
a9fb00d989

+ 90 - 42
apps/search/src/search/templates/search2.mako

@@ -86,6 +86,7 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
   <img src="/search/static/art/hint_arrow.png" />
 </div>
 
+
 <div data-bind="visible: isEditing() && previewColumns() != '' && columns().length == 0, css:{'with-top-margin': isEditing()}">
   <div class="container-fluid">
     <div class="row-fluid" data-bind="visible: previewColumns() == 'oneThirdLeft'">
@@ -184,7 +185,7 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
 
 <script type="text/html" id="hit-widget">
   <!-- ko ifnot: $root.getFacetFromQuery(id) -->
-    <a data-bind="click: showAddFacetModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
+    <a data-bind="click: showAddFacetDemiModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
   <!-- /ko -->
 
   <!-- ko if: $root.getFacetFromQuery(id) -->
@@ -202,7 +203,7 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
 
 <script type="text/html" id="facet-widget">
   <!-- ko ifnot: $root.getFacetFromQuery(id) -->
-    <a data-bind="click: showAddFacetModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
+    <a data-bind="click: showAddFacetDemiModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
   <!-- /ko -->
 
   <!-- ko if: $root.getFacetFromQuery(id) -->
@@ -313,8 +314,8 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
         </tr>
       </tbody>
     </table>
-    <div style="padding: 10px" data-bind="visible: $root.isRetrievingResults()">
-      <!--[if !IE]> --><i class="fa fa-spinner fa-spin" style="font-size:20px;color: #999"></i><!-- <![endif]-->
+    <div class="widget-spinner" data-bind="visible: $root.isRetrievingResults()">
+      <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
       <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
     </div>
 	  <!-- /ko -->
@@ -332,9 +333,14 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
 
 <script type="text/html" id="histogram-widget">
   <!-- ko ifnot: $root.getFacetFromQuery(id) -->
-    <a data-bind="click: showAddFacetModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
+    <a data-bind="click: showAddFacetDemiModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
   <!-- /ko -->
 
+  <div class="widget-spinner" data-bind="visible: isLoading()">
+    <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
+    <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+  </div>
+
   <!-- ko if: $root.getFacetFromQuery(id) -->
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
@@ -350,16 +356,21 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
     <a href="javascript:void(0)"><i class="fa fa-plus"></i></a>
     <a href="javascript:void(0)"><i class="fa fa-minus"></i></a>
 
-    <div data-bind="timelineChart: {data: counts, field: field, label: label, transformer: timelineChartDataTransformer, onClick: function(d){viewModel.query.selectFacet({count: d.y,selected: false,value: d.x,cat: field})}}" />
+    <div data-bind="timelineChart: {data: counts, field: field, label: label, transformer: timelineChartDataTransformer, onClick: function(d){viewModel.query.selectFacet({count: d.y,selected: false,value: d.x,cat: field})}, onComplete: function(){viewModel.getWidgetById(id).isLoading(false)}}" />
   </div>
   <!-- /ko -->
 </script>
 
 <script type="text/html" id="bar-widget">
   <!-- ko ifnot: $root.getFacetFromQuery(id) -->
-    <a data-bind="click: showAddFacetModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
+    <a data-bind="click: showAddFacetDemiModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
   <!-- /ko -->
 
+  <div class="widget-spinner" data-bind="visible: isLoading()">
+    <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
+    <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+  </div>
+
   <!-- ko if: $root.getFacetFromQuery(id) -->
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
@@ -371,62 +382,70 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
       ${ _('Gap') }: <input type="text" data-bind="value: properties.gap" />
     </div> 
 
-    <div data-bind="barChart: {data: counts, field: field, label: label, transformer: barChartDataTransformer, onClick: function(d){viewModel.query.selectFacet({count: d.y,selected: false,value: d.x,cat: field})}}" />
+    <div data-bind="barChart: {data: counts, field: field, label: label, transformer: barChartDataTransformer, onClick: function(d){viewModel.query.selectFacet({count: d.y,selected: false,value: d.x,cat: field})}, onComplete: function(){viewModel.getWidgetById(id).isLoading(false)}}" />
   </div>
   <!-- /ko -->
 </script>
 
 <script type="text/html" id="pie-widget">
   <!-- ko ifnot: $root.getFacetFromQuery(id) -->
-    <a data-bind="click: showAddFacetModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
+    <a data-bind="click: showAddFacetDemiModal" class="btn" href="javascript:void(0)"><i class="fa fa-plus"></i></a>
   <!-- /ko -->
 
+
   <!-- ko if: $root.getFacetFromQuery(id) -->
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
-    <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
+    <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">
       ${ _('Label') }: <input type="text" data-bind="value: label" />
-      ${ _('Field') }: <input type="text" data-bind="value: field" />    
-    </div>  
+      ${ _('Field') }: <input type="text" data-bind="value: field" />
+    </div>
 
-    <div data-bind="pieChart: {data: counts, transformer: pieChartDataTransformer, onClick: function(d){viewModel.query.selectFacet(d.data.obj)}}" />
+    <div data-bind="pieChart: {data: counts, transformer: pieChartDataTransformer, onClick: function(d){viewModel.query.selectFacet(d.data.obj)}, onComplete: function(){viewModel.getWidgetById(id).isLoading(false)}}" />
   </div>
   <!-- /ko -->
+  <div class="widget-spinner" data-bind="visible: isLoading()">
+    <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
+    <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+  </div>
 </script>
 
 <script type="text/html" id="area-widget">
   This is the area widget
+  <div class="widget-spinner" data-bind="visible: isLoading()">
+    <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
+    <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+  </div>
 </script>
 
 <script type="text/html" id="filter-widget">
-  
+  <div class="widget-spinner" data-bind="visible: isLoading()">
+    <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
+    <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+  </div>
 </script>
 
 <script type="text/html" id="map-widget">
   This is the map widget
+  <div class="widget-spinner" data-bind="visible: isLoading()">
+    <!--[if !IE]> --><i class="fa fa-spinner fa-spin"></i><!-- <![endif]-->
+    <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+  </div>
 </script>
 
-
-<div id="addFacetModal" class="modal hide fade">
+<div id="addFacetDemiModal" class="demi-modal fade hide" data-backdrop="false">
   <div class="modal-header">
     <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-    <h3>${_('Add facet')}</h3>
+    <h3>${_('Choose field')}</h3>
   </div>
   <div class="modal-body">
     <p>
-      <div class="clearfix"></div>
-      <div style="margin-top: 20px">
-        <div class="input-append">
-          <input id="facetName" type="text">
-          <input id="widgetId" type="hidden">
-          <input id="widgetType" type="hidden">
-        </div>
-      </div>
+      <input type="text" data-bind="value: $root.collection.template.fieldsModalFilter, valueUpdate:'afterkeydown'" placeholder="${_('Filter fields')}" class="input-xlarge" />
+      <ul data-bind="foreach: $root.collection.template.filteredFieldsAttributes().sort(function (l, r) { return l.name() > r.name() ? 1 : -1 })" class="unstyled inline fields-chooser">
+        <li data-bind="click: addFacetDemiModalFieldClick"><span class="badge badge-info"><i class="fa fa-file-text-o"></i> <span data-bind="text: name()"></span> </span></li>
+      </ul>
+      <div class="alert alert-info" data-bind="visible: $root.collection.template.filteredFieldsAttributes().length == 0">${_('There are no fields matching your search term.')}</div>
     </p>
   </div>
-  <div class="modal-footer">
-    <a href="#" data-dismiss="modal" class="btn">${_('Back')}</a>
-    <a data-bind="click: submitAddFacetModal" class="btn btn-primary disable-feedback">${_('Ok')}</a>
-  </div>
 </div>
 
 
@@ -585,6 +604,23 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
     fill-opacity: .225!important;
   }
 
+  .fields-chooser li {
+    cursor: pointer;
+    margin-bottom: 10px;
+  }
+
+  .fields-chooser li .badge {
+    font-weight: normal;
+    font-size: 12px;
+  }
+
+  .widget-spinner {
+    padding: 10px;
+    font-size: 80px;
+    color: #CCC;
+    text-align: center;
+  }
+
 </style>
 
 <script type="text/javascript" charset="utf-8">
@@ -685,26 +721,38 @@ $(document).ready(function () {
   function toggleGridFieldsSelection() {
     if (viewModel.collection.template.fields().length > 0) {
       viewModel.collection.template.fieldsSelected([])
-    } else {
+    }
+    else {
       var _fields = [];
       $.each(viewModel.collection.fields(), function (index, field) {
         _fields.push(field.name());
       });
       viewModel.collection.template.fieldsSelected(_fields);
-    };
-  };  
+    }
+  }
 
-  function showAddFacetModal(widget) {
-    $("#widgetId").val(widget.id());
-    $("#widgetType").val(widget.widgetType());        
-    $("#addFacetModal").modal("show");
-  };
+  var selectedWidget = null;
+  function showAddFacetDemiModal(widget) {
+    viewModel.collection.template.fieldsModalFilter("");
+    selectedWidget = widget;
+    $("#addFacetDemiModal").modal("show");
+    $("#addFacetDemiModal input[type='text']").focus();
+  }
+
+
+  function addFacetDemiModalFieldClick(field) {
+    var _existingFacet = viewModel.collection.getFacetById(selectedWidget.id());
+    if (selectedWidget != null) {
+      selectedWidget.isLoading(true);
+      viewModel.collection.addFacet({'name': field.name(), 'widget_id': selectedWidget.id(), 'widgetType': selectedWidget.widgetType()});
+      if (_existingFacet != null) {
+        _existingFacet.label(field.name());
+        _existingFacet.field(field.name());
+      }
+      viewModel.search();
+    }
+  }
 
-  function submitAddFacetModal() {
-    viewModel.collection.addFacet({'name': $("#facetName").val(), 'widget_id': $("#widgetId").val(), 'widgetType': $("#widgetType").val()});
-    $('#addFacetModal').modal("hide");
-    viewModel.search();
-  };
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 3 - 1
apps/search/static/js/charts.ko.js

@@ -35,6 +35,7 @@ ko.bindingHandlers.pieChart = {
 
         _d3.datum(_data)
                 .transition().duration(350)
+                .each("end", _options.onComplete)
                 .call(_chart);
         nv.utils.windowResize(_chart.update);
         $(element).height($(element).width());
@@ -91,7 +92,6 @@ function barChart(element, options, isTimeline) {
     else {
       _chart = nv.models.multiBarChart();
     }
-
     _chart.margin({bottom: 100})
         .transitionDuration(300);
 
@@ -117,6 +117,8 @@ function barChart(element, options, isTimeline) {
             values: _data
           }
         ])
+        .transition().duration(350)
+        .each("end", options.onComplete)
         .call(_chart);
 
     nv.utils.windowResize(_chart.update);

+ 38 - 9
apps/search/static/js/search.ko.js

@@ -83,6 +83,7 @@ var Widget = function (size, id, name, widgetType, properties, offset) {
   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.isLoading = ko.observable(false);
 
 
   self.klass = ko.computed(function () {
@@ -275,8 +276,8 @@ var Collection = function (vm, collection) {
       }
     });
     return _facet;
-  }  
-  
+  }
+
   self.template.fields = ko.computed(function () {
     var _fields = [];
     $.each(self.template.fieldsAttributes(), function (index, field) {
@@ -286,8 +287,23 @@ var Collection = function (vm, collection) {
       }      
     });
     return _fields;
-  });    
-  
+  });
+
+  self.template.fieldsModalFilter = ko.observable("");
+  self.template.filteredFieldsAttributes = ko.observableArray(self.template.fieldsAttributes());
+
+  self.template.fieldsModalFilter.subscribe(function(value){
+    var _fields = [];
+    $.each(self.template.fieldsAttributes(), function (index, field) {
+      if (field.name().toLowerCase().indexOf(self.template.fieldsModalFilter().toLowerCase()) > -1){
+        _fields.push(field);
+      }
+    });
+    self.template.filteredFieldsAttributes(_fields);
+  });
+
+
+
   self.addDynamicFields = function () { // + Adding merge smartly if schema updated
     $.post("/search/index/" + self.id + "/fields/dynamic", {
       }, function (data) {
@@ -380,10 +396,9 @@ var SearchViewModel = function (collection_json, query_json) {
   self.draggableFilter = ko.observable(new Widget(12, UUID(), "Filter Bar", "filter-widget"));  
 
   self.init = function () {
-  //self.collection.addDynamicFields();
-
-  self.isEditing(true);
-  self.search();  
+    //self.collection.addDynamicFields();
+    self.isEditing(true);
+    self.search();
   }
 
   self.search = function () {
@@ -436,7 +451,21 @@ var SearchViewModel = function (collection_json, query_json) {
   self.removeWidget = function (widget_json) {
     self.collection.removeFacet(widget_json.id); 
     self.search();
-  }  
+  }
+
+  self.getWidgetById = function (widget_id) {
+    var _widget = null;
+    $.each(self.columns(), function (i, col) {
+      $.each(col.rows(), function (j, row) {
+        $.each(row.widgets(), function (z, widget) {
+          if (widget.id() == widget_id){
+            _widget = widget;
+          }
+        });
+      });
+    });
+    return _widget;
+  }
 
   self.save = function () {
     $.post("/search/save", {

+ 58 - 0
desktop/core/static/css/hue3.css

@@ -1579,4 +1579,62 @@ a, a:hover, a:active, a:focus {
 
 body.modal-open {
     overflow: hidden;
+}
+
+
+.demi-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 1050;
+  width: 100%;
+  margin: -1px;
+  padding: 0;
+  background-color: #ffffff;
+  min-height: 210px;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, 0.3);
+  *border: 1px solid #999;
+  -webkit-border-bottom-right-radius: 6px;
+-webkit-border-bottom-left-radius: 6px;
+-moz-border-radius-bottomright: 6px;
+-moz-border-radius-bottomleft: 6px;
+border-bottom-right-radius: 6px;
+border-bottom-left-radius: 6px;
+  outline: none;
+  -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+     -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+          box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -webkit-background-clip: padding-box;
+     -moz-background-clip: padding-box;
+          background-clip: padding-box;
+}
+
+.demi-modal.fade {
+  top: -25%;
+  -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
+     -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
+       -o-transition: opacity 0.3s linear, top 0.3s ease-out;
+          transition: opacity 0.3s linear, top 0.3s ease-out;
+}
+
+.demi-modal.fade.in {
+  top: 0;
+}
+
+.demi-modal .modal-header {
+  padding: 9px 15px;
+  border-bottom: 1px solid #eee;
+}
+
+.demi-modal .modal-header .close {
+  margin-top: 2px;
+}
+
+
+.demi-modal .modal-body {
+  position: relative;
+  max-height: 400px;
+  padding: 15px;
+  overflow-y: auto;
 }