瀏覽代碼

HUE-1570 [search] Timeline display

Added new Graphical Facet
Added Show more on limited facets
Added pagination with numbers
Enrico Berti 12 年之前
父節點
當前提交
14c5a11

+ 21 - 4
apps/search/src/search/models.py

@@ -38,7 +38,7 @@ LOG = logging.getLogger(__name__)
 
 
 class Facet(models.Model):
-  _ATTRIBUTES = ['properties', 'fields', 'ranges', 'dates', 'order']
+  _ATTRIBUTES = ['properties', 'fields', 'ranges', 'dates', 'charts', 'order']
 
   enabled = models.BooleanField(default=True)
   data = models.TextField()
@@ -63,8 +63,8 @@ class Facet(models.Model):
 
     params = (
         ('facet', properties.get('isEnabled') and 'true' or 'false'),
-        ('facet.limit', properties.get('limit')),
         ('facet.mincount', properties.get('mincount')),
+        ('facet.limit', -1),
         ('facet.sort', properties.get('sort')),
     )
 
@@ -72,6 +72,10 @@ class Facet(models.Model):
       field_facets = tuple([('facet.field', field_facet['field']) for field_facet in data_dict['fields']])
       params += field_facets
 
+    if data_dict.get('charts'):
+      field_facets = tuple([('facet.field', field_facet['field']) for field_facet in data_dict['charts']])
+      params += field_facets
+
     if data_dict.get('ranges'):
       for field_facet in data_dict['ranges']:
         range_facets = tuple([
@@ -310,6 +314,10 @@ def get_facet_field_label(field, type, facets):
     for fld in facets['dates']:
       if fld['field'] == field:
         label = fld['label']
+  elif type == 'chart':
+    for fld in facets['charts']:
+      if fld['field'] == field:
+        label = fld['label']
   return label
 
 def get_facet_field_uuid(field, type, facets):
@@ -328,6 +336,13 @@ def get_facet_field_uuid(field, type, facets):
         uuid = fld['uuid']
   return uuid
 
+def is_chart_field(field, charts):
+  found = False
+  for fld in charts:
+    if field == fld['field']:
+      found = True
+  return found
+
 
 def augment_solr_response(response, facets):
   augmented = response
@@ -336,13 +351,15 @@ def augment_solr_response(response, facets):
   normalized_facets = {}
   default_facets = []
 
+  chart_facets = facets.get('charts', [])
+
   if response and response.get('facet_counts'):
     if response['facet_counts']['facet_fields']:
       for cat in response['facet_counts']['facet_fields']:
         facet = {
           'field': cat,
-          'type': 'field',
-          'label': get_facet_field_label(cat, 'field', facets),
+          'type': is_chart_field(cat, chart_facets) and 'chart' or 'field',
+          'label': get_facet_field_label(cat, is_chart_field(cat, chart_facets) and 'chart' or 'field', facets),
           'counts': response['facet_counts']['facet_fields'][cat],
         }
         uuid = get_facet_field_uuid(cat, 'field', facets)

+ 97 - 6
apps/search/src/search/templates/admin_collection_facets.mako

@@ -59,7 +59,8 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
           <li><a href="#step2" class="step">${ _('Step 2: Field Facets') }</a></li>
           <li><a href="#step3" class="step">${ _('Step 3: Range Facets') }</a></li>
           <li><a href="#step4" class="step">${ _('Step 4: Date Facets') }</a></li>
-          <li><a href="#step5" class="step">${ _('Step 5: Facets Order') }</a></li>
+          <li><a href="#step5" class="step">${ _('Step 5: Graphical Facet') }</a></li>
+          <li><a href="#step6" class="step">${ _('Step 6: Facets Order') }</a></li>
         </ul>
 
         <div id="step1" class="stepDetails">
@@ -88,7 +89,7 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 
         <div id="step2" class="stepDetails hide">
           <div data-bind="visible: fieldFacets().length == 0" style="padding-left: 10px;margin-bottom: 20px">
-            <em>${_('There are currently no field Facets defined.')}</em>
+            <em>${_('There are currently no field facets defined.')}</em>
           </div>
           <div data-bind="foreach: fieldFacets">
             <div class="bubble">
@@ -112,7 +113,7 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 
       <div id="step3" class="stepDetails hide">
         <div data-bind="visible: rangeFacets().length == 0" style="padding-left: 10px;margin-bottom: 20px">
-          <em>${_('There are currently no Range Facets defined.')}</em>
+          <em>${_('There are currently no range facets defined.')}</em>
         </div>
         <div data-bind="foreach: rangeFacets">
           <div class="bubble">
@@ -146,7 +147,7 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 
       <div id="step4" class="stepDetails hide">
         <div data-bind="visible: dateFacets().length == 0" style="padding-left: 10px;margin-bottom: 20px">
-          <em>${_('There are currently no Date Facets defined.')}</em>
+          <em>${_('There are currently no date facets defined.')}</em>
         </div>
         <div data-bind="foreach: dateFacets">
           <div class="bubble">
@@ -188,7 +189,32 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
         </div>
       </div>
 
-      <div id="step5" class="stepDetails hide">
+        <div id="step5" class="stepDetails hide">
+          <div data-bind="visible: chartFacets().length == 0" style="padding-left: 10px;margin-bottom: 20px">
+            <em>${_('There is currently no graphical facet defined. Remember, you can add just one field as graphical facet.')}</em>
+          </div>
+          <div data-bind="foreach: chartFacets">
+            <div class="bubble">
+              <strong><span data-bind="editable: label"></span></strong>
+              <span style="color:#666;font-size: 12px">(<span data-bind="text: field"></span>)</span>
+              <a class="btn btn-small" data-bind="click: $root.removeChartFacet"><i class="fa fa-trash-o"></i></a>
+            </div>
+          </div>
+          <div class="clearfix"></div>
+          <div class="miniform">
+            ${_('Field')}
+            <select id="select-chart-facet" data-bind="options: chartFacetsList, value: selectedChartFacet"></select>
+            &nbsp;${_('Label')}
+            <input id="selectedChartLabel" type="text" data-bind="value: selectedChartLabel" class="input" />
+            <br/>
+            <br/>
+            <a class="btn" data-bind="click: $root.addChartFacet, css:{disabled: $root.chartFacets().length == 1}"><i class="fa fa-plus-circle"></i> ${_('Set as Graphical Facet')}</a>
+            &nbsp;<span id="chart-facet-error" class="label label-important hide">${_('You can add just one field as graphical facet')}</span>
+            <span id="chart-facet-error-wrong-field-type" class="label label-important hide">${_('You can add just one field as graphical facet')}</span>
+          </div>
+        </div>
+
+      <div id="step6" class="stepDetails hide">
         <div data-bind="visible: sortableFacets().length == 0" style="padding-left: 10px;margin-bottom: 20px">
           <em>${_('There are currently no Facets defined.')}</em>
         </div>
@@ -566,6 +592,10 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
     return new Facet({type: "field", field: obj.field, label: obj.label, uuid: obj.uuid});
   }
 
+  var ChartFacet = function (obj) {
+    return new Facet({type: "chart", field: obj.field, label: obj.label, uuid: obj.uuid});
+  }
+
   var RangeFacet = function (obj) {
     return new Facet({type: "range", field: obj.field, label: obj.label, start: obj.start, end: obj.end, gap: obj.gap, uuid: obj.uuid});
   }
@@ -668,6 +698,16 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       }
     });
 
+    self.chartFacets = ko.observableArray(ko.utils.arrayMap(${ hue_collection.facets.data | n,unicode }.charts, function (obj) {
+      return new ChartFacet(obj);
+    }));
+
+    // Remove already selected fields
+    self.chartFacetsList = ko.observableArray(${ hue_collection.fields(user) | n,unicode });
+    $.each(self.chartFacets(), function(index, field) {
+      self.chartFacetsList.remove(field.field);
+    });
+
     // List of all facets sorted by UUID
     self.sortableFacets = ko.observableArray(self.fieldFacets().concat(self.rangeFacets()).concat(self.dateFacets()));
     self.sortableFacets.sort(function(left, right) {
@@ -704,6 +744,12 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
         new DateMath({frequency: 1, unit: 'DAYS'})
     ]);
 
+    self.selectedChartFacet = ko.observable();
+    self.selectedChartFacet.subscribe(function (newValue) {
+      $("#selectedChartLabel").prop("placeholder", newValue);
+    });
+    self.selectedChartLabel = ko.observable("");
+
 
     self.removeFieldFacet = function (facet) {
       self.fieldFacets.remove(facet);
@@ -728,6 +774,15 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       self.isSaveBtnVisible(true);
     };
 
+    self.removeChartFacet = function (facet) {
+      self.chartFacets.remove(facet);
+      self.sortableFacets.remove(facet);
+      self.chartFacetsList.push(facet.field);
+      self.chartFacetsList.sort();
+      self.updateSortableFacets();
+      self.isSaveBtnVisible(true);
+    };
+
     self.updateSortableFacets = function () {
       self.isSaveBtnVisible(true);
     };
@@ -805,6 +860,32 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       self.isSaveBtnVisible(true);
     };
 
+
+    self.addChartFacet = function () {
+      if (self.chartFacets().length == 0){
+        var found = false;
+        ko.utils.arrayForEach(self.chartFacets(), function(facet) {
+          if (facet.field == self.selectedChartFacet()){
+            found = true;
+          }
+        });
+        if (!found){
+          if (self.selectedChartLabel() == ""){
+            self.selectedChartLabel(self.selectedChartFacet());
+          }
+          var newFacet = new ChartFacet({field: self.selectedChartFacet(), label: self.selectedChartLabel()});
+          self.chartFacets.push(newFacet);
+          self.selectedChartLabel("");
+          self.chartFacetsList.remove(self.selectedChartFacet());
+          self.properties().isEnabled(true);
+          self.isSaveBtnVisible(true);
+        }
+      }
+      else {
+        $("#chart-facet-error").show();
+      }
+    };
+
     self.submit = function () {
       $.ajax("${ url('search:admin_collection_facets', collection_id=hue_collection.id) }", {
         data: {
@@ -812,8 +893,9 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
           'fields': ko.toJSON(self.fieldFacets),
           'ranges': ko.toJSON(self.rangeFacets),
           'dates': ko.toJSON(self.dateFacets),
+          'charts': ko.toJSON(self.chartFacets),
           'order': ko.toJSON(ko.utils.arrayMap(self.sortableFacets(), function (obj) {
-                      return obj.uuid;
+              return obj.uuid;
            }))
         },
         contentType: 'application/json',
@@ -843,6 +925,10 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       $("#field-facet-error").hide();
     });
 
+    $("#select-chart-facet").click(function(){
+      $("#chart-facet-error").hide();
+    });
+
     var currentStep = "step1";
 
     routie({
@@ -868,6 +954,11 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
         if (validateStep("step1") && validateStep("step2")) {
           showStep("step5");
         }
+      },
+      "step6":function () {
+        if (validateStep("step1") && validateStep("step2")) {
+          showStep("step6");
+        }
       }
     });
 

+ 205 - 56
apps/search/src/search/templates/search.mako

@@ -18,15 +18,17 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 import urllib
+import math
 %>
 
 <%namespace name="macros" file="macros.mako" />
 
-${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
+${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
 
 <link rel="stylesheet" href="/search/static/css/search.css">
 <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>
+<script src="/static/js/jquery.blueprint.js" type="text/javascript" charset="utf-8"></script>
 
 <%
   if "q" not in solr_query:
@@ -43,7 +45,7 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 
 <div class="search-bar">
   % if user.is_superuser:
-    <div class="pull-right" style="margin-top: 4px">
+    <div class="pull-right" style="margin-top: 6px; margin-right: 40px">
       <a class="change-settings" href="#"><i class="fa fa-edit"></i> ${ _('Customize this collection') }</a> &nbsp;&nbsp;
       <a href="${ url('search:admin_collections') }"><i class="fa fa-sitemap"></i> ${ _('Collection manager') }</a>
     </div>
@@ -69,11 +71,18 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
     </div>
   </form>
 </div>
-<div class="well">
+
+<div id="loader" class="row" style="text-align: center;margin-top: 20px">
+  <!--[if lte IE 9]>
+      <img src="/static/art/spinner-big.gif" />
+  <![endif]-->
+  <!--[if !IE]> -->
+    <i class="fa fa-spinner fa-spin" style="font-size: 60px; color: #DDD"></i>
+  <!-- <![endif]-->
+</div>
+
+
 <div class="container results">
-  <div id="loader" class="row" style="text-align: center">
-    <img src="/static/art/spinner.gif" />
-  </div>
   <div id="mainContent" class="row hide">
     % if error:
     <div class="span12 results">
@@ -85,45 +94,64 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
     % if response and 'response' in response and 'docs' in response['response'] and len(response['response']['docs']) > 0 and 'normalized_facets' in response:
     <div class="span2 results">
       <ul class="facet-list">
+        ## Force chart facets first
         % for fld in response['normalized_facets']:
-          % if fld['type'] == 'date':
-            <li class="nav-header dateFacetHeader">${fld['label']}</li>
-          % else:
-            <li class="nav-header">${fld['label']}</li>
+          % if fld['type'] == 'chart':
+            <%
+            found_value = ""
+            for fq in solr_query['fq'].split('|'):
+              if fq and fq.split(':')[0] == fld['field']:
+                found_value = fq[fq.find(":") + 1:]
+                remove_list = solr_query['fq'].split('|')
+                remove_list.remove(fq)
+            %>
+            %if found_value != "":
+              <li class="nav-header">${fld['label']}</li>
+              <li><strong>${ found_value }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
+            %endif
           % endif
-
-          <%
-          found_value = ""
-          for fq in solr_query['fq'].split('|'):
-            if fq and fq.split(':')[0] == fld['field']:
-              found_value = fq[fq.find(":") + 1:]
-              remove_list = solr_query['fq'].split('|')
-              remove_list.remove(fq)
-          %>
-          % for group, count in macros.pairwise(fld['counts']):
-            % if count > 0 and group != "" and found_value == "":
-              % if fld['type'] == 'field':
-                <li><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:"${ urllib.quote_plus(group.encode('ascii', 'xmlcharrefreplace')) }"${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${group}</a> <span class="counter">(${ count })</span></li>
-              % endif
-              % if fld['type'] == 'range':
-                <li><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:["${ group }" TO "${ str(int(group) + int(fld['gap']) - 1) }"]${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${ group } - ${ str(int(group) + int(fld['gap']) - 1) }</a> <span class="counter">(${ count })</span></li>
-              % endif
-              % if fld['type'] == 'date':
-                <li class="dateFacetItem"><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:[${ group } TO ${ group }${ urllib.quote_plus(fld['gap']) }]${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'><span class="dateFacet" data-format="${fld['format']}">${ group }<span class="dateFacetGap hide">${ fld['gap'] }</span></span></a> <span class="counter">(${ count })</span></li>
-              % endif
+        % endfor
+        % for fld in response['normalized_facets']:
+          % if fld['type'] != 'chart':
+            % if fld['type'] == 'date':
+              <li class="nav-header facetHeader dateFacetHeader">${fld['label']}</li>
+            % else:
+              <li class="nav-header facetHeader">${fld['label']}</li>
             % endif
-            % if found_value != "":
-              % if fld['type'] == 'field' and '"' + group + '"' == found_value:
-                <li><strong>${ group }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
-              % endif
-              % if fld['type'] == 'range' and '["' + group + '" TO "' + str(int(group) + int(fld['gap']) - 1) + '"]' == found_value:
-                <li><strong>${ group } - ${ str(int(group) + int(fld['gap']) - 1) }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
+
+            <%
+            found_value = ""
+            for fq in solr_query['fq'].split('|'):
+              if fq and fq.split(':')[0] == fld['field']:
+                found_value = fq[fq.find(":") + 1:]
+                remove_list = solr_query['fq'].split('|')
+                remove_list.remove(fq)
+            %>
+            % for group, count in macros.pairwise(fld['counts']):
+              % if count > 0 and group != "" and found_value == "":
+                % if fld['type'] == 'field':
+                  <li class="facetItem"><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:"${ urllib.quote_plus(group.encode('ascii', 'xmlcharrefreplace')) }"${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${group}</a> <span class="counter">(${ count })</span></li>
+                % endif
+                % if fld['type'] == 'range':
+                  <li class="facetItem"><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:["${ group }" TO "${ str(int(group) + int(fld['gap']) - 1) }"]${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${ group } - ${ str(int(group) + int(fld['gap']) - 1) }</a> <span class="counter">(${ count })</span></li>
+                % endif
+                % if fld['type'] == 'date':
+                  <li class="facetItem dateFacetItem"><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:[${ group } TO ${ group }${ urllib.quote_plus(fld['gap']) }]${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'><span class="dateFacet" data-format="${fld['format']}">${ group }<span class="dateFacetGap hide">${ fld['gap'] }</span></span></a> <span class="counter">(${ count })</span></li>
+                % endif
               % endif
-              % if fld['type'] == 'date' and found_value.startswith('[' + group + ' TO'):
-                <li><strong><span class="dateFacet" data-format="${fld['format']}">${ group }<span class="dateFacetGap hide">${ fld['gap'] }</span></span></strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
+              % if found_value != "":
+                % if fld['type'] == 'field' and '"' + group + '"' == found_value:
+                  <li class="facetItem"><strong>${ group }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
+                % endif
+                % if fld['type'] == 'range' and '["' + group + '" TO "' + str(int(group) + int(fld['gap']) - 1) + '"]' == found_value:
+                  <li class="facetItem"><strong>${ group } - ${ str(int(group) + int(fld['gap']) - 1) }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
+                % endif
+                % if fld['type'] == 'date' and found_value.startswith('[' + group + ' TO'):
+                  <li class="facetItem"><strong><span class="dateFacet" data-format="${fld['format']}">${ group }<span class="dateFacetGap hide">${ fld['gap'] }</span></span></strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="fa fa-times"></i></a></li>
+                % endif
               % endif
-            % endif
-          % endfor
+            % endfor
+          % endif
         % endfor
       </ul>
     </div>
@@ -137,9 +165,11 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       % endif
       <ul class="breadcrumb">
         <li class="pull-right">
-          <select class="sort-by">
-            <option value="">${ _('Sort by') }</option>
-          </select>
+          <div id="sortBy" style="display: inline" class="dropdown">
+            Sort by <a data-toggle="dropdown" href="#"><strong></strong> <i class="fa fa-caret-down"></i></a>
+            <ul class="dropdown-menu">
+            </ul>
+          </div>
         </li>
         <li class="active">
         <%
@@ -147,10 +177,21 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
           if end_record > int(response['response']['numFound']):
             end_record = response['response']['numFound'];
         %>
-        ${_('Showing %s - %s of %s results') % (int(solr_query["start"]) + 1, end_record, response['response']['numFound'])}
+          ${_('Page %s of %s. Showing %s results (%s seconds)') % (solr_query["current_page"], solr_query["total_pages"], response['response']['numFound'], float(solr_query["search_time"])/1000)}
         </li>
       </ul>
 
+      % for fld in response['normalized_facets']:
+        %if fld['type'] == 'chart':
+          <%
+            values = ""
+            for group, count in macros.pairwise(fld['counts']):
+              values += "['" + group + "'," + str(count) + "],"
+          %>
+          <div class="chartComponent" data-values="[${values[:-1]}]" data-label="${fld['label']}" data-field="${fld['field']}"></div>
+        %endif
+      % endfor
+
       <script src="/static/ext/js/mustache.js"></script>
 
       <div id="result-container"></div>
@@ -178,18 +219,37 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       <div class="pagination">
         <ul>
           <%
+            pages_to_show = 5 # always use an odd number since we do it symmetrically
+
             beginning = 0
             previous = int(solr_query["start"]) - int(solr_query["rows"])
             next = int(solr_query["start"]) + int(solr_query["rows"])
+
+            pages_after = (pages_to_show - 1) / 2
+            pages_total = solr_query['total_pages']+1
+            real_pages_after =  pages_total - solr_query["current_page"]
+            symmetric_start = solr_query["current_page"] < pages_total - pages_after
+            symmetric_end = solr_query["current_page"] > pages_after
+
+            pagination_start = solr_query["current_page"] > (pages_to_show - 1)/2 and (symmetric_start and solr_query["current_page"] - (pages_to_show - 1)/2 or solr_query["current_page"] - pages_to_show + real_pages_after ) or 1
+            pagination_end = solr_query["current_page"] < solr_query['total_pages']+1-(pages_to_show - 1)/2 and (symmetric_end and solr_query["current_page"] + (pages_to_show - 1)/2 + 1 or solr_query["current_page"] + (pages_to_show - solr_query["current_page"]) + 1) or solr_query['total_pages']+1
           %>
           % if int(solr_query["start"]) > 0:
             <li>
-              <a title="${_('Beginning of List')}" href="?collection=${ current_collection }&query=${solr_query["q"]}&fq=${urllib.quote_plus(solr_query["fq"])}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}&rows=${solr_query["rows"]}&start=${beginning}">&larr; ${_('Beginning of List')}</a>
-            </li>
-            <li>
-              <a title="Previous Page" href="?collection=${ current_collection }&query=${solr_query["q"]}&fq=${urllib.quote_plus(solr_query["fq"])}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}&rows=${solr_query["rows"]}&start=${previous}">${_('Previous Page')}</a>
+              <a title="${_('Previous Page')}" href="?collection=${ current_collection }&query=${solr_query["q"]}&fq=${urllib.quote_plus(solr_query["fq"])}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}&rows=${solr_query["rows"]}&start=${previous}">${_('Previous Page')}</a>
             </li>
           % endif
+          % for page in range(pagination_start, pagination_end):
+            %if page > 0 and page < pages_total:
+            <li
+             %if solr_query["current_page"] == page:
+               class="active"
+             %endif
+                >
+              <a href="?collection=${ current_collection }&query=${solr_query["q"]}&fq=${urllib.quote_plus(solr_query["fq"])}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}&rows=${solr_query["rows"]}&start=${(int(page)-1)*int(solr_query["rows"])}">${page}</a>
+            </li>
+            %endif
+          % endfor
           % if end_record < int(response["response"]["numFound"]):
             <li>
               <a title="Next page" href="?collection=${ current_collection }&query=${solr_query["q"]}&fq=${urllib.quote_plus(solr_query["fq"])}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}&rows=${solr_query["rows"]}&start=${next}">${_('Next Page')}</a>
@@ -208,7 +268,6 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
   </div>
   % endif
 </div>
-</div>
 
 % if hue_collection:
   ${ hue_collection.result.get_extracode() | n,unicode }
@@ -229,6 +288,36 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
       $("#mainContent").addClass("hide");
     };
 
+    var collectionProperties = ${ hue_collection.facets.data | n,unicode }.properties;
+    $(".facetHeader").each(function(cnt, section){
+      var _added = 0;
+      var _showMore = false;
+      var _lastSection = null;
+      $(section).nextUntil(".facetHeader").each(function(cnt, item){
+        if (cnt < collectionProperties.limit*1){ // it's a string, *1 -> number
+          $(item).show();
+          _added++;
+          if (cnt == (collectionProperties.limit*1) - 1){
+            _lastSection = $(item);
+          }
+        }
+        else {
+          _showMore = true;
+        }
+      });
+      if (_added == 0){
+        $(section).hide();
+      }
+      if (_showMore){
+        $("<li>").addClass("facetShowMore").html('<a href="javascript:void(0)">${_('More...')}</a>').insertAfter(_lastSection);
+      }
+    });
+
+    $(document).on("click", ".facetShowMore", function(){
+      $(this).hide();
+      $(this).nextUntil(".facetHeader").show();
+    });
+
     $(".dateFacet").each(function () {
       var _m = moment($(this).text());
       var _em = moment($(this).text());
@@ -297,7 +386,9 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
     var sortingData = ${ hue_collection and hue_collection.sorting.data or '[]' | n,unicode };
     if (sortingData && sortingData.fields && sortingData.fields.length > 0) {
       $.each(sortingData.fields, function (index, item) {
-        $("<option>").attr("value", item.label).text(item.label).data("field", item.field).data("asc", item.asc).appendTo($(".sort-by"));
+        var _dropDownOption= $("<li>");
+        _dropDownOption.html('<a href="#" class="dropdown-sort" data-field="'+ item.field +'" data-asc="'+ item.asc +'">'+ item.label +'</a>');
+        _dropDownOption.appendTo($("#sortBy .dropdown-menu"));
       });
       var activeSorting = "${solr_query.get("sort", "")}";
       if (activeSorting == ""){
@@ -311,24 +402,25 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
           }
         });
         if (_defaultSortingCnt == 1){
-          $(".sort-by").val(_defaultSorting);
+          $("#sortBy strong").text(_defaultSorting);
         }
       }
       if (activeSorting != "" && activeSorting.indexOf(" ") > -1) {
         $.each(sortingData.fields, function (index, item) {
           if (item.field == activeSorting.split(" ")[0] && item.asc == (activeSorting.split(" ")[1] == "asc")) {
-            $(".sort-by").val(item.label);
+            $("#sortBy strong").text(item.label);
           }
         });
       }
     }
     else {
-      $(".sort-by").hide();
+      $("#sortBy").hide();
     }
 
-    $(".sort-by").change(function () {
-      var _sort = $(".sort-by option:selected").data("field") + "+" + ($(".sort-by option:selected").data("asc") ? "asc" : "desc");
-      if ($(".sort-by").val() == "") {
+    $(document).on("click", "#sortBy li a", function () {
+      var _this = $(this);
+      var _sort = _this.data("field") + "+" + (_this.data("asc") ? "asc" : "desc");
+      if (typeof _this.data("field") == "undefined") {
         _sort = "";
       }
       location.href = "?query=${solr_query["q"]}&fq=${solr_query["fq"]}&rows=${solr_query["rows"]}&start=${solr_query["start"]}" + (_sort != "" ? "&sort=" + _sort : "");
@@ -377,6 +469,63 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
     });
     % endif
 
+    function getFq(existing, currentField, currentValue) {
+      if (existing.indexOf(currentField) > -1) {
+        var _pieces = existing.split("|");
+        var _newPieces = [];
+        $(_pieces).each(function (cnt, item) {
+          if (item.indexOf(currentField) > -1) {
+            _newPieces.push(currentField + currentValue);
+          }
+          else {
+            // !!! High trickery. Uses jquery to reconvert all html entities to text
+            _newPieces.push($("<span>").html(item).text());
+          }
+        });
+        return _newPieces.join("|");
+      }
+      else {
+        return decodeURIComponent(existing) + "|" + currentField + currentValue;
+      }
+    }
+
+    var _chartData = eval($(".chartComponent").data("values"));
+
+    // test the content of _chartData to see if it can be rendered as chart
+    if (_chartData.length > 0) {
+      if ($.isArray(_chartData[0])) {
+        if ($.isNumeric(_chartData[0][0])) {
+          $(".chartComponent").jHueBlueprint({
+            data: _chartData,
+            label: $(".chartComponent").data("label"),
+            type: $.jHueBlueprint.TYPES.BARCHART,
+            color: $.jHueBlueprint.COLORS.BLUE,
+            fill: true,
+            enableSelection: true,
+            height: 100,
+            onSelect: function (range) {
+              location.href = '?collection=${ current_collection }&query=${ solr_query['q'] }&fq=' + getFq("${ solr_query['fq'] }", $(".chartComponent").data("field"), ':["' + Math.floor(range.xaxis.from) + '" TO "' + Math.ceil(range.xaxis.to) + '"]') + '${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}';
+            },
+            onItemClick: function (pos, item) {
+              if (item) {
+                $(".chartComponent").data("plotObj").highlight(item.series, item.datapoint);
+                location.href = '?collection=${ current_collection }&query=${ solr_query['q'] }&fq=' + getFq("${ solr_query['fq'] }", $(".chartComponent").data("field"), ':' + item.datapoint[0]) + '${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}';
+              }
+            }
+          });
+        }
+        else {
+          $(".chartComponent").addClass("alert").text("${_('The graphical facets works just with numbers. Please choose another field.')}")
+        }
+      }
+      else {
+        $(".chartComponent").addClass("alert").text("${_('There was an error initializing the graphical facet component.')}")
+      }
+    }
+    else {
+      $(".chartComponent").hide();
+    }
+
   });
 </script>
 

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

@@ -21,6 +21,7 @@ except ImportError:
   import simplejson as json
 
 import logging
+import math
 
 from django.core.urlresolvers import reverse
 from django.http import HttpResponse
@@ -37,6 +38,7 @@ from search.forms import QueryForm, CollectionForm, HighlightingForm
 from search.models import Collection, augment_solr_response
 from search.search_controller import SearchController
 
+
 LOG = logging.getLogger(__name__)
 
 
@@ -70,11 +72,16 @@ def index(request):
     solr_query['rows'] = search_form.cleaned_data['rows'] or 15
     solr_query['start'] = search_form.cleaned_data['start'] or 0
     solr_query['facets'] = search_form.cleaned_data['facets'] or 1
+    solr_query['current_page'] = int(math.ceil((float(solr_query['start'])+1)/float(solr_query['rows'])))
+    solr_query['total_pages'] = 0
+    solr_query['search_time'] = 0
 
     try:
       hue_collection = Collection.objects.get(id=collection_id)
       solr_query['collection'] = hue_collection.name
       response = SolrApi(SOLR_URL.get(), request.user).query(solr_query, hue_collection)
+      solr_query['total_pages'] = int(math.ceil((float(response['response']['numFound'])/float(solr_query['rows']))))
+      solr_query['search_time'] = response['responseHeader']['QTime']
     except Exception, e:
       error['message'] = unicode(str(e), "utf8")
   else:

+ 23 - 4
apps/search/static/css/search.css

@@ -16,6 +16,10 @@
  limitations under the License.
 */
 
+body {
+  background-color: #FFFFFF;
+}
+
 .container {
   margin-left: 20px!important;
 }
@@ -30,7 +34,7 @@
 }
 
 .search-bar {
-  background-color: #fafafa;
+  background-color: #F5F5F5;
   border-bottom: 1px solid #e3e3e3;
   -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
   -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
@@ -40,6 +44,10 @@
   padding-left: 20px;
   padding-right: 20px;
   text-align: left;
+  width: 100%;
+  position: fixed;
+  top: 28px;
+  z-index: 1000;
 }
 
 .search-bar input {
@@ -72,7 +80,6 @@
   list-style: none outside none;
   padding: 0;
   margin: 0;
-  border-right: 1px solid #e3e3e3;
 }
 
 .facet-list li {
@@ -90,12 +97,10 @@
 }
 
 .pagination {
-  background-color: #F5F5F5;
   border-radius: 4px 4px 4px 4px;
   list-style: none outside none;
   margin: 0 0 20px;
   padding: 8px 15px;
-  text-align: center;
 }
 
 .results.container {
@@ -116,4 +121,18 @@
 
 .errorlist {
   display: none;
+}
+
+.breadcrumb {
+  background-color: #FFFFFF;
+  padding-left: 0;
+  margin-bottom: 0;
+}
+
+#result-container {
+  margin-top: 20px;
+}
+
+.facetItem {
+  display: none;
 }

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

@@ -1486,4 +1486,13 @@ a, a:hover, a:active, a:focus {
   text-align: center;
   vertical-align: middle;
   padding-top: 70px;
+}
+
+#jHueBlueprintTooltip {
+  position: absolute;
+  display: none;
+  border: 1px solid #61AE36;
+  padding: 2px;
+  background-color: #C2E0B0;
+  opacity: 0.80;
 }

文件差異過大導致無法顯示
+ 78 - 0
desktop/core/static/ext/js/jquery/plugins/jquery.flot.selection.min.js


文件差異過大導致無法顯示
+ 8 - 0
desktop/core/static/ext/js/jquery/plugins/jquery.flot.time.min.js


+ 82 - 7
desktop/core/static/js/jquery.blueprint.js

@@ -60,7 +60,14 @@ var FLOT_LOADED, FLOT_PIE_LOADED = false;
         type: TYPES.LINECHART,
         fill: false,
         width: "100%",
-        height: 200
+        height: 200,
+        borderWidth: 0,
+        tooltips: true,
+        enableSelection: false,
+        onSelect: function () {
+        },
+        onItemClick: function () {
+        }
       };
 
   function Plugin(element, options) {
@@ -82,7 +89,14 @@ var FLOT_LOADED, FLOT_PIE_LOADED = false;
         });
       }
       else {
-        flot(_this);
+        if (_this.options.enableSelection) {
+          $.cacheScript("/static/ext/js/jquery/plugins/jquery.flot.selection.min.js", function (script, textStatus) {
+            flot(_this);
+          });
+        }
+        else {
+          flot(_this);
+        }
       }
     });
   };
@@ -106,19 +120,69 @@ var FLOT_LOADED, FLOT_PIE_LOADED = false;
       }
       else {
         _series.push(getSerie(serie));
-        var _plot = $.plot(element, _series);
+        var _plot = $.plot(element, _series, { grid: { borderWidth: element.data('plugin_' + pluginName).options.borderWidth } });
         $(element).data("plotObj", _plot);
         $(element).data("plotSeries", _series);
       }
     }
   };
 
+  function showTooltip(x, y, contents) {
+    $("<div id='jHueBlueprintTooltip'>" + contents + "</div>").css({
+      top: y + 5,
+      left: x + 5
+    }).appendTo("body").fadeIn(200);
+  }
+
 
   function flot(plugin) {
     var _this = plugin;
     $(_this.element).width(_this.options.width).height(_this.options.height);
     var _serie = getSerie(_this.options);
-    var _plot = $.plot(_this.element, [_serie]);
+    var _options = {
+      grid: {
+        borderWidth: _this.options.borderWidth,
+        clickable: true
+      }
+    }
+    if (_this.options.tooltips) {
+      _options.grid.hoverable = true;
+    }
+    if (_this.options.enableSelection) {
+      _options.selection = {
+        mode: "x",
+        color: COLORS.GREEN
+      }
+    }
+    var _plot = $.plot(_this.element, [_serie], _options);
+    $(_this.element).bind("plotselected", function (event, ranges) {
+      _this.options.onSelect(ranges);
+    });
+
+    if (_this.options.tooltips) {
+      var previousPoint = null;
+      $(_this.element).bind("plothover", function (event, pos, item) {
+
+        if (item) {
+          if (previousPoint != item.dataIndex) {
+
+            previousPoint = item.dataIndex;
+
+            $("#jHueBlueprintTooltip").remove();
+            var x = item.datapoint[0].toFixed(2),
+                y = item.datapoint[1].toFixed(2);
+
+            showTooltip(item.pageX, item.pageY, x);
+          }
+        } else {
+          $("#jHueBlueprintTooltip").remove();
+          previousPoint = null;
+        }
+      });
+    }
+    $(_this.element).bind("plotclick", function (event, pos, item) {
+      _this.options.onItemClick(pos, item);
+    });
     $(_this.element).data("plotObj", _plot);
     $(_this.element).data("plotSeries", [_serie]);
   }
@@ -126,27 +190,38 @@ var FLOT_LOADED, FLOT_PIE_LOADED = false;
   function flot_pie(plugin) {
     var _this = plugin;
     $(_this.element).width(_this.options.width).height(_this.options.height);
-    var _plot = $.plot(_this.element, _this.options.data, {
+    var _options = {
+      grid: {
+        borderWidth: _this.options.borderWidth,
+        clickable: true
+      },
       series: {
         pie: {
           show: true
         }
       }
-    });
+    };
+    if (_this.options.tooltips) {
+      _options.grid.hoverable = true;
+    }
+    var _plot = $.plot(_this.element, _this.options.data, _options);
     $(_this.element).data("plotObj", _plot);
   }
 
 
   function getSerie(options) {
     var _flotSerie = {
-      data: options.data
+      data: options.data,
+      label: options.label
     }
 
     if (options.type == TYPES.LINECHART) {
       _flotSerie.lines = {show: true, fill: options.fill }
+      _flotSerie.points = {show: true}
     }
     if (options.type == TYPES.BARCHART) {
       _flotSerie.bars = {show: true, fill: options.fill }
+      _flotSerie.points = {show: true}
     }
     if (options.type == TYPES.POINTCHART) {
       _flotSerie.points = {show: true}

部分文件因文件數量過多而無法顯示