Skip to content

Commit

Permalink
WIP demo
Browse files Browse the repository at this point in the history
  • Loading branch information
fergiemcdowall committed Oct 23, 2023
1 parent 9160b75 commit 22b16fc
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 161 deletions.
7 changes: 4 additions & 3 deletions demo2/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@

<script src="./lib/search-index.js"></script>
<script src="./lib/stopwords.js"></script>
<script src="./lib/ui.js"></script>
<script src="./lib/autocomplete.js"></script>
<script src="./src/app.js"></script>
<title>Search-index demo</title>
Expand All @@ -96,12 +97,12 @@ <h1>Search index demo!</h1>
</header>
<div style="flex-direction: row;display: flex;">
<div style="flex-basis: 300px; background-color: green;">
<div class="refiner" data-field="year"></div>
<div class="refiner" data-field="month"></div>
<div id="year-refiner" class="refiner"></div>
<div id="month-refiner" class="refiner"></div>
</div>
<div style="flex-basis: 80%; background-color: red;flex-wrap:wrap;">
<div style="flex-direction: column;display: flex;">
<input id="searchbox"></input>
<input id="searchbox" autofocus></input>
<div id="suggestions" />
</div>
<div id="count"></div>
Expand Down
2 changes: 1 addition & 1 deletion demo2/lib/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const autocomplete = async (inp, DICTIONARY, search) => {
/*and and make the current item more visible:*/
addActive(x)
} else if (e.keyCode == 13 || e.keyCode == 9) {
/*If the ENTER key is pressed, prevent the form from being submitted,*/
/*If the ENTER/TAB key is pressed, prevent the form from being submitted,*/
e.preventDefault()
if (currentFocus > -1) {
/*and simulate a click on the "active" item:*/
Expand Down
168 changes: 168 additions & 0 deletions demo2/lib/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// should be eventually bundled in with search-index

const ui = ({
_import = '',
count = {},
hits = {},
refiners = [],
searchInput = {}
} = {}) => {
const el = (type, innerHTML = '', attributes = {}) => {
const el = document.createElement(type)
el.innerHTML = innerHTML
for (const [attr, value] of Object.entries(attributes)) {
el.setAttribute(attr, value)
}
return el
}

const countTemplate = count => `showing ${count} hits`
const hitsTemplate = doc => `<div>${JSON.stringify(doc)}</div>`

count = { template: countTemplate, elementId: 'count', ...count }
count.el = document.getElementById(count.elementId)

searchInput = {
elementId: 'searchbox',
...searchInput
}
searchInput.el = document.getElementById(searchInput.elementId)

hits = {
template: hitsTemplate,
elementId: 'hits',
...hits
}
hits.el = document.getElementById(hits.elementId)

refiners = refiners.map(refiner => ({
el: document.getElementById(refiner.elementId),
...refiner
}))
// refiners = refiners.map(({ elementId }) => document.getElementById(elementId))

// treat empty search as a special case: instead of showing nothing,
// show everything.
const emptySearchQuery = () => [
{
ALL_DOCUMENTS: true
},
{
FACETS: [
{
FIELD: ['month', 'year']
}
]
}
]

const searchQuery = () => {
// debugger
return [
[
...searchInput.el.value.split(/\s+/).filter(item => item),
...getActiveFilters()
],
{
FACETS: [
{
FIELD: ['month', 'year']
}
],
DOCUMENTS: true
}
]
}

const search = () =>
(searchInput.el.value.length + getActiveFilters().length
? si.SEARCH(...searchQuery())
: si.QUERY(...emptySearchQuery())
)
.then(result => ({
// query: q,
result // TODO: should this be nested like this?
}))
.then(displaySearch)

const getActiveFilters = () =>
[...document.querySelectorAll('.filter-select:checked')].map(e => e.name)

const displaySearch = res => {
hits.el.innerHTML = res.result.RESULT.map(({ _doc }) =>
hits.template(_doc)
).join('\n')
count.el.innerHTML = count.template(res.result.RESULT_LENGTH)
refiners.forEach(refiner => {
const activeFilters = getActiveFilters()
refiner.el.innerHTML = ''
refiner.el.appendChild(el('h2', refiner.title))
res.result.FACETS.filter(item => item.FIELD == refiner.field).forEach(
({ VALUE, _id }) => {
const checkbox = el('input', null, {
class: 'filter-select',
id: refiner.field + ':' + VALUE,
name: refiner.field + ':' + VALUE,
type: 'checkbox'
})
if (activeFilters.includes(refiner.field + ':' + VALUE))
checkbox.setAttribute('checked', true)
refiner.el.appendChild(checkbox)
refiner.el.appendChild(
el('label', VALUE + ' (' + _id.length + ')', {
for: refiner.field + ':' + VALUE
})
)
refiner.el.appendChild(el('br'))

const isThisAnActiveFilter = activeFilters.indexOf(
refiner.field + ':' + VALUE
)
if (isThisAnActiveFilter !== -1) {
activeFilters.splice(isThisAnActiveFilter, 1)
}
}
)

activeFilters
.filter(item => item.split(':')[0] == refiner.field)
.forEach(item => {
refiner.el.appendChild(
el('input', null, {
class: 'filter-select',
id: item,
name: item,
type: 'checkbox',
checked: true
})
)
refiner.el.appendChild(
el('label', item.split(':')[1] + ' (0)', {
for: item
})
)
refiner.el.appendChild(el('br'))
})

const filterCheckboxes = document.getElementsByClassName('filter-select')
for (let i = 0; i < filterCheckboxes.length; i++) {
filterCheckboxes[i].addEventListener('input', function (e) {
search()
})
}
})
}

if (_import)
fetch(_import)
.then(res => res.json())
.then(dump => si.IMPORT(dump))
.then(search)
.catch(e => console.error(_import + ' is unreachable'))

searchInput.el.addEventListener('input', function (e) {
search(this.value)
})

autocomplete(document.getElementById('searchbox'), si.DICTIONARY, search)
}
175 changes: 18 additions & 157 deletions demo2/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,168 +3,29 @@ const si = new SearchIndex.SearchIndex({
stopwords
})

const ui = ops => {
ops = {
count: 'count',
searchInput: 'searchInput',
hits: 'hits',
filters: 'refiner',
...ops
}

const searchInput = document.getElementById(ops.searchInput)
const hits = document.getElementById(ops.hits)
const count = document.getElementById(ops.count)
const filters = document.getElementsByClassName(ops.filters)

// treat empty search as a special case: instead of showing nothing,
// show everything.
const emptySearchQuery = () => [
{
ALL_DOCUMENTS: true
window.onload = function () {
ui({
_import: 'data/EarthPorn-top-search-index.json',
count: {
elementId: 'count'
},
{
FACETS: [
{
FIELD: ['month', 'year']
}
]
}
]

const searchQuery = () => {
// debugger
return [
[
...searchInput.value.split(/\s+/).filter(item => item),
...getActiveFilters()
],
{
FACETS: [
{
FIELD: ['month', 'year']
}
],
DOCUMENTS: true
}
]
}

const search = () =>
(searchInput.value.length + getActiveFilters().length
? si.SEARCH(...searchQuery())
: si.QUERY(...emptySearchQuery())
)
.then(result => ({
// query: q,
result // TODO: should this be nested like this?
}))
.then(displaySearch)

const getActiveFilters = () =>
[...document.querySelectorAll('.filter-select:checked')].map(e => e.name)

const el = (type, innerHTML, attributes = {}) => {
const el = document.createElement(type)
if (innerHTML) el.innerHTML = innerHTML
for (const [attr, value] of Object.entries(attributes)) {
el.setAttribute(attr, value)
}
return el
}

const displaySearch = res => {
hits.innerHTML = res.result.RESULT.map(
({ _doc }) =>
hits: {
elementId: 'hits',
template: doc =>
'<b>' +
_doc.title +
doc.title +
'</b><br><a href=' +
_doc.url_overridden_by_dest +
doc.url_overridden_by_dest +
' target=_blank><img src=' +
_doc.thumbnail +
doc.thumbnail +
'></a><div>' +
JSON.stringify(_doc) +
JSON.stringify(doc) +
'</div>'
).join('\n')
count.innerHTML = `showing ${res.result.RESULT_LENGTH} hits`
for (let i = 0; i < filters.length; i++) {
let field = filters[i].getAttribute('data-field')
const activeFilters = getActiveFilters()

filters[i].innerHTML = ''
filters[i].appendChild(el('h2', field.toUpperCase()))
res.result.FACETS.filter(item => item.FIELD == field).forEach(
({ VALUE, _id }) => {
const checkbox = el('input', null, {
class: 'filter-select',
id: field + ':' + VALUE,
name: field + ':' + VALUE,
type: 'checkbox'
})
if (activeFilters.includes(field + ':' + VALUE))
checkbox.setAttribute('checked', true)
filters[i].appendChild(checkbox)
filters[i].appendChild(
el('label', VALUE + ' (' + _id.length + ')', {
for: field + ':' + VALUE
})
)
filters[i].appendChild(el('br'))

const isThisAnActiveFilter = activeFilters.indexOf(
field + ':' + VALUE
)
if (isThisAnActiveFilter !== -1) {
activeFilters.splice(isThisAnActiveFilter, 1)
}
}
)

activeFilters
.filter(item => item.split(':')[0] == field)
.forEach(item => {
filters[i].appendChild(
el('input', null, {
class: 'filter-select',
id: item,
name: item,
type: 'checkbox',
checked: true
})
)
filters[i].appendChild(
el('label', item.split(':')[1] + ' (0)', {
for: item
})
)
filters[i].appendChild(el('br'))
})

const filterCheckboxes = document.getElementsByClassName('filter-select')
for (let i = 0; i < filterCheckboxes.length; i++) {
filterCheckboxes[i].addEventListener('input', function (e) {
search()
})
}
}
}

fetch('data/EarthPorn-top-search-index.json')
.then(res => res.json())
.then(dump => si.IMPORT(dump))
.then(search)

searchInput.addEventListener('input', function (e) {
search(this.value)
})

// TODO autocomplete should take a promise
autocomplete(document.getElementById('searchbox'), si.DICTIONARY, search)
}

window.onload = function () {
ui({
hits: 'hits',
searchInput: 'searchbox'
},
refiners: [
{ elementId: 'year-refiner', title: 'Yeario', field: 'year' },
{ elementId: 'month-refiner', title: 'month', field: 'month' }
],
searchInput: { elementId: 'searchbox' }
})
}

0 comments on commit 22b16fc

Please sign in to comment.