forked from geonetwork/core-geonetwork
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Facet / Add hierarchical facet support.
Hierarchical facet support was added in geonetwork#680 providing server code to handle facet hierarchy based on thesaurus broader relations. This commit add the client side support for it, which means: * add directive to handle facet response using the dimension format ie. * example for flat facet ``` <summary count="3949" type="local"> <dimension name="type" label="types"> <category value="dataset" label="Dataset" count="3043"/> <category value="series" label="Series" count="408"/> ... ``` * example for hierachical facet ``` <category value="http://vocab.nerc.ac.uk/collection/C19/current/SVX00025/" label="World" count="110"> <category value="http://vocab.nerc.ac.uk/collection/C19/current/SVX00005/" label="Atlantic Ocean" count="13"> <category value="http://vocab.nerc.ac.uk/collection/C19/current/1/" label="North Atlantic Ocean" count="13"> <category value="http://vocab.nerc.ac.uk/collection/C19/current/SVX00015/" label="Northeast Atlantic Ocean (40W)" count="13"> <category value="http://vocab.nerc.ac.uk/collection/C19/current/1_7/" label="English Channel" count="12"/> <category value="http://vocab.nerc.ac.uk/collection/C19/current/1_8/" label="Bay of Biscay" count="3"/> .... ``` * old facet format is still supported but it sounds relevant to deprecate it and progressively move to the dimension format. Search and Editor board app are migrated to the dimension format. * add example to know how to configure hierarchical facet using the regions thesaurus provided by GeoNetwork or the GEMET thesaurus.
- Loading branch information
François Prunayre
committed
Aug 6, 2015
1 parent
a896987
commit 0326576
Showing
23 changed files
with
588 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
259 changes: 259 additions & 0 deletions
259
web-ui/src/main/resources/catalog/components/search/facets/FacetDimensionDirective.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
(function() { | ||
goog.provide('gn_facets_dimension_directive'); | ||
goog.require('gn_utility_service'); | ||
|
||
var module = angular.module('gn_facets_dimension_directive', | ||
['gn_utility_service']); | ||
|
||
module.directive('gnFacetDimensionList', [ | ||
'gnFacetConfigService', '$timeout', | ||
function(gnFacetConfigService, $timeout) { | ||
return { | ||
restrict: 'A', | ||
templateUrl: '../../catalog/components/search/facets/' + | ||
'partials/dimension-facet-list.html', | ||
scope: { | ||
dimension: '=gnFacetDimensionList', | ||
facetType: '=', | ||
params: '=' | ||
}, | ||
link: function(scope, element) { | ||
scope.facetQuery = scope.params['facet.q']; | ||
scope.facetConfig = null; | ||
|
||
scope.collapseAll = function() { | ||
$timeout(function() { | ||
element.parent().find('div.gn-facet > h4').click(); | ||
}); | ||
}; | ||
|
||
// Facet is collapsed if not in current search criteria | ||
scope.isFacetsCollapse = function(facetKey) { | ||
return !angular.isDefined(scope.params[facetKey]); | ||
}; | ||
|
||
// Load facet configuration to know which index field | ||
// correspond to which dimension. | ||
gnFacetConfigService.loadConfig(scope.facetType). | ||
then(function(data) { | ||
scope.facetConfig = { | ||
config: data, | ||
map: {} | ||
}; | ||
|
||
angular.forEach(scope.facetConfig.config, function(key) { | ||
scope.facetConfig.map[key.label] = key.name; | ||
}); | ||
}); | ||
} | ||
}; | ||
}]); | ||
|
||
module.directive('gnFacetDimensionCategory', [ | ||
'gnFacetConfigService', 'RecursionHelper', '$parse', | ||
function(gnFacetConfigService, RecursionHelper, $parse) { | ||
return { | ||
restrict: 'A', | ||
templateUrl: '../../catalog/components/search/facets/' + | ||
'partials/dimension-facet-category.html', | ||
scope: { | ||
category: '=gnFacetDimensionCategory', | ||
categoryKey: '=', | ||
path: '=', | ||
params: '=', | ||
facetConfig: '=' | ||
}, | ||
compile: function(element) { | ||
// Use the compile function from the RecursionHelper, | ||
// And return the linking function(s) which it returns | ||
return RecursionHelper.compile(element, | ||
function(scope, element, attrs) { | ||
var initialMaxItems = 5; | ||
scope.initialMaxItems = initialMaxItems; | ||
scope.maxItems = initialMaxItems; | ||
scope.toggleAll = function() { | ||
scope.maxItems = (scope.maxItems == Infinity) ? | ||
initialMaxItems : Infinity; | ||
}; | ||
|
||
// Facet drill down is based on facet.q parameter. | ||
// The facet.q parameter contains a list of comma separated | ||
// dimensions | ||
// <dimension_name>{"/"<category_value>} | ||
// Note that drill down paths use '/' as the separator | ||
// between categories in the path, so embedded '/' characters | ||
// in categories should be escaped using %2F or alternatively, | ||
// each category in the path url encoded in addition to | ||
// normal parameter encoding. | ||
// | ||
// Multiple drill down queries can be specified by providing | ||
// multiple facet.q parameters or by combining drill down | ||
// queries in one facet.q parameter using '&' | ||
// appropriately encoded. | ||
// | ||
// http://localhost:8080/geonetwork/srv/fre/q? | ||
// resultType=hierarchy& | ||
// facet.q=gemetKeyword/http%253A%252F%252Fwww.eionet.europa.eu | ||
// %252Fgemet%252Fconcept%252F2643 | ||
// %2F | ||
// http%253A%252F%252Fwww.eionet.europa.eu | ||
// %252Fgemet%252Fconcept%252F2641 | ||
|
||
/** | ||
* Build the drill down path based on current category value | ||
* and its parent. | ||
* @param {Object} category | ||
* @return {boolean|*} | ||
*/ | ||
scope.buildPath = function(category) { | ||
category.path = | ||
(scope.path === undefined ? '' : scope.path + '/') + | ||
encodeURIComponent(category['@value']); | ||
return category.path; | ||
}; | ||
|
||
|
||
/** | ||
* Build the facet.q paramaeter | ||
*/ | ||
scope.filter = function(category, $event) { | ||
if (!scope.facetConfig) { | ||
return; // Facet configuration not yet loaded. | ||
} | ||
|
||
var checked = $event.currentTarget.checked; | ||
|
||
|
||
// Extract facet.q info | ||
if (angular.isUndefined(scope.params['facet.q'])) { | ||
scope.params['facet.q'] = ''; | ||
} | ||
var facetQParam = scope.params['facet.q']; | ||
var dimensionList = | ||
facetQParam.split('&'); | ||
var categoryList = []; | ||
$.each(dimensionList, function(idx) { | ||
// Dimension filter contains the dimension name first | ||
// and then the drilldown path. User may uncheck | ||
// an element in the middle of the path. In such case | ||
// only activate the parent node. | ||
var dimensionFilter = dimensionList[idx].split('/'); | ||
|
||
// Dimension but not in that category path. Add filter. | ||
if (dimensionFilter[1] && | ||
dimensionFilter[0] != | ||
scope.facetConfig.map[scope.categoryKey]) { | ||
categoryList.push({ | ||
dimension: dimensionFilter[0], | ||
value: dimensionFilter.slice(1, dimensionFilter.length) | ||
}); | ||
} else if (dimensionFilter[1] && | ||
dimensionFilter.length > 2 && | ||
dimensionFilter[0] == | ||
scope.facetConfig.map[scope.categoryKey]) { | ||
|
||
var filteredElementInPath = | ||
$.inArray( | ||
encodeURIComponent(category['@value']), | ||
dimensionFilter); | ||
// Restrict the path to its parent | ||
if (filteredElementInPath !== -1) { | ||
categoryList.push({ | ||
dimension: scope.categoryKey, | ||
value: dimensionFilter. | ||
slice(1, filteredElementInPath). | ||
join('/') | ||
}); | ||
} | ||
} | ||
}); | ||
// Add or remove new category | ||
if (checked) { | ||
categoryList.push({ | ||
dimension: scope.categoryKey, | ||
value: category.path | ||
}); | ||
} else { | ||
|
||
} | ||
|
||
// Build facet.q | ||
facetQParam = ''; | ||
$.each(categoryList, function(idx) { | ||
if (categoryList[idx].value) { | ||
facetQParam = facetQParam + | ||
(scope.facetConfig.map[categoryList[idx].dimension] || | ||
categoryList[idx].dimension) + | ||
'/' + | ||
categoryList[idx].value + | ||
(idx < categoryList.length - 1 ? '&' : ''); | ||
} | ||
}); | ||
scope.params['facet.q'] = facetQParam; | ||
|
||
scope.$emit('resetSearch', scope.params); | ||
$event.preventDefault(); | ||
}; | ||
|
||
|
||
/** | ||
* Check that current category is already used | ||
* in current filter. | ||
* @param {Object} category | ||
* @return {boolean|*} | ||
*/ | ||
scope.isOnDrillDownPath = function(category) { | ||
// Is selected if the category value is defined in the | ||
// facet.q parameter (ie. combination of | ||
// dim1/dim1val/dim1val2&dim2/dim2Val...). | ||
category.isSelected = | ||
angular.isUndefined(scope.params['facet.q']) ? | ||
false : | ||
$.inArray( | ||
encodeURIComponent(category['@value']), | ||
scope.params['facet.q'].split(/&|\//)) !== -1; | ||
return category.isSelected; | ||
}; | ||
|
||
scope.isInFilter = function(category) { | ||
if (!scope.facetConfig) { | ||
return false; | ||
// Facet configuration not yet loaded | ||
} | ||
|
||
var facetQParam = scope.params['facet.q']; | ||
if (facetQParam === undefined) { | ||
return false; | ||
} | ||
var dimensionList = | ||
facetQParam.split('&'); | ||
var categoryList = []; | ||
for (var i = 0; i < dimensionList.length; i++) { | ||
var dimensionFilter = dimensionList[i].split('/'); | ||
if (dimensionFilter[0] == | ||
scope.facetConfig.map[scope.categoryKey] && | ||
$.inArray( | ||
encodeURIComponent(category['@value']), | ||
dimensionFilter) !== -1) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; | ||
|
||
scope.toggleNode = function(evt) { | ||
el = evt ? | ||
$(evt.currentTarget).parent() : | ||
element.find('span.fa'); | ||
el.find('.fa').first() | ||
.toggleClass('fa-minus-square') | ||
.toggleClass('fa-plus-square'); | ||
el.children('div').toggleClass('hidden'); | ||
!evt || evt.preventDefault(); | ||
return false; | ||
}; | ||
}); | ||
} | ||
}; | ||
}]); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
...rc/main/resources/catalog/components/search/facets/partials/dimension-facet-category.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<ul class="list-group"> | ||
<li data-ng-repeat="c in category" | ||
data-ng-init="path = buildPath(c); isSelected = isOnDrillDownPath(c)" | ||
data-ng-if="$index < maxItems" | ||
class="list-group-item"> | ||
<span data-ng-click="toggleNode($event)" | ||
class="fa fa-fw gn-facet-toggle" | ||
data-ng-class="c.category?(isSelected?'fa-minus-square':'fa-plus-square'):'invisible'" ></span> | ||
<label title="{{c['@label'] || c['@value']}}" | ||
data-ng-class="{'gn-facet-active': isSelected}"> | ||
<input type="checkbox" | ||
data-ng-checked="isInFilter(c)" | ||
data-ng-click="filter(c, $event)"/> | ||
{{(c['@label'] || c['@value']) | characters:30}} | ||
<span class="gn-facet-count">({{c['@count']}})</span> | ||
</label> | ||
<div data-gn-facet-dimension-category="c.category" | ||
data-category-key="categoryKey" | ||
data-facet-config="facetConfig" | ||
data-path="path" | ||
data-params="params" | ||
data-ng-class="isSelected ? '' : 'hidden'"></div> | ||
</li> | ||
<li class="gn-facet-moreorless" | ||
data-ng-if="category.length - initialMaxItems > 0"> | ||
<button data-ng-click="toggleAll()" | ||
class="btn btn-link"> | ||
{{category.length - initialMaxItems}} | ||
{{(category.length >= maxItems) ? ('more' | translate) : ('less' | translate)}} | ||
</button> | ||
</li> | ||
</ul> |
23 changes: 23 additions & 0 deletions
23
...ui/src/main/resources/catalog/components/search/facets/partials/dimension-facet-list.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<div class="gn-facet-list"> | ||
<div data-ng-repeat="f in dimension" | ||
class="gn-facet" | ||
data-ng-show="f.category"> | ||
<a class="gn-facet-collapseall" | ||
data-ng-if="$index === 0" | ||
data-ng-click="collapseAll()" | ||
title="{{'collapseAllFacet' | translate}}"> | ||
<i class="fa fa-fw fa-angle-double-left"></i> | ||
</a> | ||
|
||
<h4 data-gn-collapse="{{collapsed}}"> | ||
<i class="fa fa-fw" data-ng-class="collapsed ? 'fa-angle-left' : ''"></i> | ||
{{f['@label'] | translate}} | ||
</h4> | ||
<div data-gn-facet-dimension-category="f.category" | ||
data-category-key="f['@label']" | ||
data-params="params" | ||
data-facet-config="facetConfig" | ||
data-ng-class="{'collapse': collapsed}"> | ||
</div> | ||
</div> | ||
</div> |
3 changes: 1 addition & 2 deletions
3
web-ui/src/main/resources/catalog/components/search/facets/partials/facet-item.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.