Faceted Search
by ninty9notout
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<div class="results results--hidden">
<div class="filters">
<div class="filters_inner">
<!-- intentionally empty -->
</div>
</div>
<div class="content">
<div class="content__inner">
</div>
</div>
</div>
<script type="text/html" id="facet-group">
<div class="facet-group">
<h3 class="facet-group__title"><%= title %></h3>
<div class="facet-list">
<% _.forEach(options, (option) => { %>
<label class="facet-item">
<input type="checkbox" class="checkbox checkbox-facet" name="<%= title %>" value="<%= option.title %>">
<span class="label"><%= option.title %></span>
</label>
<% }) %>
</div>
</div>
</script>
<script type="text/html" id="result-item">
<div class="result-item">
<h2 class="result-item__title"><strong><%= make %>:</strong> <%= model %></h2>
</div>
</script>
CSS
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
background-color: #34495E;
font-family: "Lato", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
color: #ECF0F1;
}
.spinner {
margin: 100px auto 0;
width: 70px;
text-align: center;
}
.spinner--hidden {
display: none;
}
.spinner > div {
width: 18px;
height: 18px;
background-color: #ECF0F1;
border-radius: 100%;
display: inline-block;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
animation-delay: -0.32s;
}
.spinner .bounce2 {
animation-delay: -0.16s;
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1.0);
}
}
.results {
display: flex;
min-height: 100vh;
}
.results--hidden {
display: none;
}
.filters {
flex: 1 0 auto;
max-width: 175px;
padding-left: 1em;
}
.content {
flex: 1 0 auto;
}
.facet-group {
padding: 0.5em;
}
.facet-group + .facet-group {
margin-top: 0.75em;
padding-top: 0.75em;
border-top: 1px dashed rgba(255, 255, 255, 0.15);
}
.facet-group__title {
margin: 0 0 0.25em;
}
.facet-list {
max-height: 144px;
overflow-y: auto;
background-image: radial-gradient(ellipse farthest-corner at center top, rgba(0, 0, 0, 0.15) 0, transparent 75%), radial-gradient(ellipse farthest-corner at center bottom, rgba(0, 0, 0, 0.15) 0, transparent 75%);
background-size: 100% 10px;
background-repeat: no-repeat;
background-attachment: scroll;
background-position: top, bottom;
}
.facet-item:first-child,
.facet-item:last-child {
background-size: 100% 100%;
background-repeat: no-repeat;
background-attachment: scroll;
}
.facet-item:first-child {
background-image: linear-gradient(180deg, #34495E 50%, hsla(0, 0%, 94%, 0));
background-position: top;
}
.facet-item:last-child {
background-image:...
JavaScript
const map = _.map;
const forEach = _.forEach;
const isFunction = _.isFunction;
const isEqual = _.isEqual;
const clone = _.clone;
const get = _.get;
const has = _.has;
const values = _.values;
const template = _.template;
const reject = _.reject;
const intersectionWith = _.intersectionWith;
const SELECTORS = {
LOADER: '.spinner',
RESULTS: '.results',
FILTER_INNER: '.filters_inner',
CONTENT_INNER: '.content__inner',
};
const CLASSES = {
LOADER_HIDDEN: 'spinner--hidden',
RESULTS_HIDDEN: 'results--hidden',
};
const ELEMENTS = {
LOADER: document.querySelector(SELECTORS.LOADER),
RESULTS: document.querySelector(SELECTORS.RESULTS),
FILTER_INNER: document.querySelector(SELECTORS.FILTER_INNER),
CONTENT_INNER: document.querySelector(SELECTORS.CONTENT_INNER),
};
const TEMPLATES = {
FILTER_GROUP: template( document.querySelector('#facet-group').innerHTML ),
RESULT_ITEM: template( document.querySelector('#result-item').innerHTML ),
};
const facet_keys = {
make: 'Make',
model: 'Model',
gender: 'Gender',
state: 'State',
};
const getData = () => fetch('https://api.myjson.com/bins/1h96xq').then(response => {
return response.json();
});
const getProcessedData = () => {
return getData().then((data) => {
return data.map((item) => {
item.facets = map(facet_keys, (title, key) => {
return {
group: title,
option: item[key],
};
});
return item;
});
});
}
const generateFacets = data => {
return map(facet_keys, (title, key) => {
let options = data.map(item => {
return get(item, key, null);
});
options = options.filter(item => {
return item !== null;
});
options = options.reduce((carry, item) => {
if (has(carry, item) === false) {
carry[item] = {
title: item,
count: 0,
};
}
carry[item].count++;
return carry;
}, {});
return {
title: title,
key: key,
options: values(options),
};
});
};
let loading = true;
let facets = [];
let results = [];
let appliedFacets =...