- vuejs solr faceted search

- with extra display for active filter items
This commit is contained in:
Arno Kaimbacher 2019-10-10 12:58:13 +02:00
parent c596a620cc
commit a95282e49e
15 changed files with 261 additions and 120 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,14 +21,7 @@ section.search {
/* Seasrch items */ /* Seasrch items */
.left-bar .panel-title {
text-transform: capitalize;
margin-top: 8px;
margin-bottom: 2px;
color: white;
display: block;
font-weight: bold;
}
.search-items { .search-items {
@ -116,6 +109,15 @@ section.search {
/* filter items in the left bar */ /* filter items in the left bar */
/*-------------------------------------------------- */ /*-------------------------------------------------- */
.left-bar .panel-title {
text-transform: capitalize;
margin-top: 8px;
margin-bottom: 2px;
color: white;
display: block;
font-weight: bold;
}
.overflowing { .overflowing {
color: #444444; color: #444444;
list-style: none; list-style: none;
@ -128,7 +130,7 @@ section.search {
cursor: pointer; cursor: pointer;
} }
ul.filterItems li { ul.filter-items li {
min-height: 15px; min-height: 15px;
color: #444; color: #444;
font-size: 12px; font-size: 12px;
@ -148,21 +150,21 @@ ul.filterItems li {
margin-left: 1em; margin-left: 1em;
} }
.filterItems.limited li:nth-of-type(1n+6) { .filter-items.limited li:nth-of-type(1n+3) {
display: none; display: none;
} }
ul.filterItems .active { /* ul.filter-items .active {
background-color: lightgray; background-color: lightgray;
} } */
ul.filterItems li a { ul.filter-items li a {
cursor: pointer; cursor: pointer;
color: lightgray; color: lightgray;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
} }
/* ul.filterItems li a:hover { /* ul.filter-items li a:hover {
color: #0099cc; color: #0099cc;
cursor: pointer; cursor: pointer;
} */ } */
@ -319,7 +321,7 @@ font-size: 16px;
list-style: outside none none; list-style: outside none none;
padding: 0px 0px 12px; padding: 0px 0px 12px;
margin: 0px; margin: 0px;
max-height: 240px; /* max-height: 240px; */
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;
} }
@ -342,7 +344,6 @@ font-size: 16px;
width: 1px; width: 1px;
white-space: nowrap; white-space: nowrap;
} }
.css-w1gpbi:checked + label::before { .css-w1gpbi:checked + label::before {
animation: 0s ease 0s 1 normal none running none; animation: 0s ease 0s 1 normal none running none;
background-color: rgb(255, 255, 255); background-color: rgb(255, 255, 255);
@ -396,3 +397,30 @@ vertical-align: middle;
cursor: pointer; cursor: pointer;
} }
.active-filter-items a {
flex-wrap: wrap;
margin: 2px 3px;
padding: 5px 8px;
font-size: 0.95rem;
position: relative;
}
.filter-link {
display: inline-flex;
-moz-box-pack: center;
justify-content: center;
-moz-box-align: center;
align-items: center;
border-radius: 3px;
border: 1px solid transparent;
min-height: 30px;
overflow-wrap: break-word;
padding: 5px 12px;
line-height: 1.2rem;
background-color: rgb(238, 238, 238);
color: rgb(66, 66, 66);
cursor: pointer;
-moz-user-select: none;
transition: all 0.3s ease 0s;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,35 +1,41 @@
<template> <template>
<div class="search-container row"> <div class="search-container row">
<div class="four columns left-bar"> <div class="four columns left-bar">
<div id="left-bar" class="sidebar left-bar"> <div id="left-bar" class="sidebar left-bar">
<h2 class="indexheader">DataXplore</h2> <h2 class="indexheader">DataXplore</h2>
<!-- <div class="card" v-for="item in facets.language" :key="item.id">
<span>{{ item }}</span>
</div> -->
<!-- <facet-list v-bind:data="facets"></facet-list> --> <!-- <div class="card" v-for="item in facets.language" :key="item.id">
<div class="card" v-for="(valueArray, filterName, index) in facets" :key="index"> <span>{{ item }}</span>
<facet-list :data="valueArray" :filterName="filterName"></facet-list> </div>-->
</div>
<!-- <facet-list v-bind:data="facets"></facet-list> -->
<div class="card" v-for="(item, index) in facets" :key="index">
<facet-list :data="item.values" :filterName="item.filterName" @filter="onFilter"></facet-list>
</div>
</div> </div>
</div> </div>
<div class="eight columns right-bar"> <div class="eight columns right-bar">
<div id="right-bar" class="sidebar right-bar"> <div id="right-bar" class="sidebar right-bar">
<!-- Search input section --> <!-- Search input section -->
<div class="row"> <div class="row">
<div class="twelve columns"> <div class="twelve columns">
<vs-input @search="onSearch"></vs-input> <vs-input @search="onSearch"></vs-input>
</div> </div>
</div> </div>
<div class="row">
<div class="active-filter-items twelve columns">
<a class="filter-link" v-for="(value, key, index) in activeFilterItems" :key="index">
<span>{{ key + ": " }}</span>
<span v-if="value && value.length > 0">{{ value.join(', ') }}</span>
</a>
</div>
</div>
<!-- Results section --> <!-- Results section -->
<vs-results v-bind:data="results"></vs-results> <vs-results v-bind:data="results"></vs-results>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,9 +3,10 @@ import VsInput from './text-search/vs-input.vue';
import VsResults from './search-results/vs-results.vue'; import VsResults from './search-results/vs-results.vue';
import FacetList from './search-results/facet-list.vue' import FacetList from './search-results/facet-list.vue'
import rdrApi from './search-results/dataservice'; import rdrApi from './search-results/dataservice';
import FilterItem from './models/filter-item';
@Component({ @Component({
components: { components: {
VsInput, VsInput,
VsResults, VsResults,
FacetList FacetList
@ -15,16 +16,67 @@ export default class App extends Vue {
results = []; results = [];
facets = []; facets = [];
bar = 'bar'; searchTerm = '';
activeFilterItems = {};
async onFilter(filter) {
// console.log(filter.value);
// if (!this.activeFilterItems.some(e => e.value === filter.value)) {
// this.activeFilterItems.push(filter);
if (!this.activeFilterItems.hasOwnProperty(filter.Category)) {
this.activeFilterItems[filter.Category] = [];
}
if (!this.activeFilterItems[filter.Category].some(e => e === filter.value)) {
this.activeFilterItems[filter.Category].push(filter.value);
var res = await rdrApi.search(this.searchTerm, this.activeFilterItems);
this.results = res.response.docs;
// this.facets = res.facet_counts.facet_fields;
this.facets = [];
var facet_fields = res.facet_counts.facet_fields;
for (var prop in facet_fields) {
var facetValues = facet_fields[prop].map((facet, i) => {
if (i % 2 === 0) {
// var rObj = { value: facet, count: facet_fields[prop][i + 1] };
var rObj = new FilterItem(facet, facet_fields[prop][i + 1]);
return rObj;
}
}).filter(function (el) {
return el != null && el.count > 0;
});
this.facets.push({ filterName: prop, values: facetValues });
}
}
}
async onSearch(term) { async onSearch(term) {
console.log(term); // console.log(term);
// this.results = await rdrApi.search(term); // while (this.activeFilterItems.length > 0) {
var res = await rdrApi.search(term); // this.activeFilterItems.pop();
// }
this.activeFilterItems = {};
while (this.facets.length > 0) {
this.facets.pop();
}
this.searchTerm = term;
var res = await rdrApi.search(this.searchTerm, this.activeFilterItems);
this.results = res.response.docs; this.results = res.response.docs;
this.facets = res.facet_counts.facet_fields; var facet_fields = res.facet_counts.facet_fields;
for (var prop in facet_fields) {
var facetValues = facet_fields[prop].map((facet, i) => {
if (i % 2 === 0) {
//var rObj = { value: facet, count: facet_fields[prop][i + 1] };
var rObj = new FilterItem(facet, facet_fields[prop][i + 1])
return rObj;
}
}).filter(function (el) {
return el != null && el.count > 0;
});
this.facets.push({ filterName: prop, values: facetValues });
}
// console.log(this.facets.toString());
} }
mounted() { mounted() {
// console.log('Component mounted.') // console.log('Component mounted.')

View File

@ -0,0 +1,25 @@
export default class FilterItem {
category;
value;
count;
active;
constructor(value, count) {
// this.category = category;
this.value = value;
this.count = count;
this.active = false;
this.category = "";
}
get Category() {
return this.category;
}
set Category(theCategory) {
this.category = theCategory;
}
set Active(isActive) {
this.active = isActive;
}
}

View File

@ -2,17 +2,31 @@ import axios from "axios";
export default { export default {
async search(term) { async search(term, filterItems) {
// solr endpoint // solr endpoint
// const host = 'http://voyagerdemo.com/'; // const host = 'http://voyagerdemo.com/';
const host = 'https://repository.geologie.ac.at/'; const host = 'https://repository.geologie.ac.at/';
const path = 'solr/rdr_data/select'; const path = 'solr/rdr_data/select';
const fields = 'id,server_date_published,abstract_output,title_output,title_additional,author,subject'; // fields we want returned const fields = 'id,server_date_published,abstract_output,title_output,title_additional,author,subject'; // fields we want returned
const dismaxFields = "title^3 abstract^2 subject^1"; const dismaxFields = "title^3 abstract^2 subject^1";
const facetFields = "facet.field=language&facet.field={!key=datatype}doctype";//&fq=year:(2019)";//&facet.query=year:2018"; const facetFields = "facet.field=language&facet.field={!key=datatype}doctype&facet.field=subject";//&fq=year:(2019)";//&facet.query=year:2018";
var filterFields = "";
// filterItems.forEach(function (item) {
// console.log(item.value + " " + item.category);
// filterFields += "&fq=" + item.category +":("+ item.value + ")";
// });
Object.entries(filterItems).forEach(([key, valueArray]) => {
// console.log(`${key} ${valueArray}`);
valueArray.forEach(function (value) {
filterFields += "&fq=" + key +":("+ value + ")";
});
});
// $dismax->setQueryFields('title^3 abstract^2 subject^1'); // $dismax->setQueryFields('title^3 abstract^2 subject^1');
const api = `${host}${path}?defType=dismax&q=${term}&fl=${fields}&qf=${dismaxFields}&facet=on&${facetFields}&wt=json&rows=20&indent=on`; const api = `${host}${path}?defType=dismax&q=${term}&fl=${fields}&qf=${dismaxFields}&facet=on&${facetFields}&${filterFields}&wt=json&rows=20&indent=on`;
const res = await axios.get(api); const res = await axios.get(api);
return res.data;//.response;//.docs; return res.data;//.response;//.docs;

View File

@ -1,67 +1,79 @@
import { Component, Vue, Prop, Provide } from 'vue-property-decorator'; import { Component, Vue, Prop, Provide } from 'vue-property-decorator';
@Component @Component
export default class FacetList extends Vue { export default class FacetList extends Vue {
ITEMS_PER_FILTER = 5; ITEMS_PER_FILTER = 2;
bar = 'bar'; bar = '';
// filterItems = []; collapsed = true;
// filterItems = [];
@Prop() @Prop()
data; data;
@Prop() @Prop([String])
filterName; filterName;
@Prop([String])
get myLanguageFilters() { // alias;
// console.log(this.filterName);
// console.log(this.data); get alias() {
var facetValues = this.data.map((facet, i) => { return this.filterName == 'datatype' ? 'doctype' : this.filterName
if (i % 2 === 0) { }
// var rObj = {}; // get filterItems() {
// rObj['value'] = facet; // var facetValues = this.data.map((facet, i) => {
// rObj['count'] = solrArray[i +1]; // if (i % 2 === 0) {
var rObj = { value: facet, count: this.data[i + 1] }; // // var rObj = {};
return rObj; // // rObj['value'] = facet;
} // // rObj['count'] = solrArray[i +1];
}).filter(function (el) { // var rObj = { value: facet, count: this.data[i + 1], category: this.alias };
return el != null && el.count > 0; // return rObj;
}); // }
// var facetValues = this.data.language.filter(function(facet, i) { // }).filter(function (el) {
// return i % 2 === 0; // return el != null && el.count > 0;
// }).map(function (facet, i) { // });
// var rObj = { value: facet, count: this.data.language[i + 1] }; // // var facetValues = this.data.language.filter(function(facet, i) {
// return rObj; // // return i % 2 === 0;
// }, this); // // }).map(function (facet, i) {
return facetValues; // // var rObj = { value: facet, count: this.data.language[i + 1] };
}; // // return rObj;
// // }, this);
get facets() { // return facetValues;
return this.data; // }
};
get filterItems() { get filterItems() {
var facetValues = this.data.map((facet, i) => { return this.data;
if (i % 2 === 0) { }
// var rObj = {};
// rObj['value'] = facet;
// rObj['count'] = solrArray[i +1];
var rObj = { value: facet, count: this.data[i + 1] };
return rObj;
}
}).filter(function (el) {
return el != null && el.count > 0;
});
// var facetValues = this.data.language.filter(function(facet, i) {
// return i % 2 === 0;
// }).map(function (facet, i) {
// var rObj = { value: facet, count: this.data.language[i + 1] };
// return rObj;
// }, this);
return facetValues;
};
mounted() { get overflowing() {
}; //ko.observable(self.filterItems().length - self.activeFilterItems().length > ITEMS_PER_FILTER);
return (this.filterItems.length) > this.ITEMS_PER_FILTER;
}
get uncollapseLabelText() {
if (this.collapsed == true) {
// return myLabels.viewer.sidePanel.more; //"More results";
return "More results";
}
else {
// return myLabels.viewer.sidePanel.collapse; //"Collapse";
return "Collapse";
}
}
toggle = function () {
if (this.collapsed == true) {
this.collapsed = false;
}
else if (this.collapsed == false) {
this.collapsed = true;
//list.children("li:gt(4)").hide();
}
}
activateItem = function (filterItem) {
filterItem.Category = this.alias;
filterItem.Active = true;
this.$emit("filter", filterItem);
}
mounted() {
}
} }

View File

@ -5,28 +5,24 @@
<div class="panel panel-primary"> <div class="panel panel-primary">
<h3 class="panel-title filterViewModelName">{{ filterName }}</h3> <h3 class="panel-title filterViewModelName">{{ filterName }}</h3>
<!-- e.g.language --> <!-- e.g.language -->
<ul <ul class="filter-items" v-bind:class="{'limited':filterItems.length > 1 && collapsed }">
class="filter-items" <li v-for="(item, index) in filterItems" :key="index" class="list-group-item">
v-for="(value, index) in myLanguageFilters" <!-- <input
:key="index"
v-bind:class="{'limited':filterItems.length > 1}"
>
<li class="active" role="radio">
<input
class="css-w1gpbi" class="css-w1gpbi"
name="language" name="language"
v-bind:id="value.value" v-bind:id="item.value"
type="radio" type="radio"
v-bind:value="value.value" v-bind:value="item.value"
/> />
<label :for="value.value"> <label :for="item.value">
<span>{{ value.value }} ({{ value.count }})</span> <span click: @click="activateItem(item)">{{ item.value }} ({{ item.count }})</span>
</label> </label>-->
<a :class="Active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.value }} ({{ item.count }})</a>
</li> </li>
</ul> </ul>
<ul class="overflowing"> <ul class="overflowing" v-if="overflowing == true">
<li> <li>
<span @click="toggle()"></span> <span @click="toggle()">{{ uncollapseLabelText }}</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -35,4 +31,12 @@
<script lang="ts"> <script lang="ts">
import FacetList from "./facet-list-class"; import FacetList from "./facet-list-class";
export default FacetList; export default FacetList;
</script> </script>
<style scoped>
/* local styles */
.disabled {
color: lightgrey;
pointer-events: none;
}
</style>