Skip to content

Commit

Permalink
Merge pull request #20 from nbonfils/search-interface
Browse files Browse the repository at this point in the history
Search interface
  • Loading branch information
nbonfils committed Feb 20, 2024
2 parents a5930c6 + 33c03b2 commit 43fbdb7
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 54 deletions.
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"dependencies": {
"@ajusa/lit": "^1.1.0",
"@tarekraafat/autocomplete.js": "10.2.7",
"alpinejs": "^3.13.0",
"graphology": "0.25.4",
"graphology-components": "^1.5.4",
Expand Down
36 changes: 36 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@
text-align: center;
}

.center-btn {
display: block;
margin: auto;
}

.concept-radio-or {
margin-left: 20px;
}

.concept-radio-and {
margin-left: 10px;
}

.concept-list {
margin-top: 1.5em;
padding: 0;
}

.concept-list li {
display: inline;
list-style: none;
}

.concept-list-or li + li::before {
content: " OR ";
}

.concept-list-and li + li::before {
content: " AND ";
}

.concept-list .card {
padding: 0.5em;
margin-right: 0.3em;
}

.corpus-too-big {
border: red 2px solid;
border-radius: 5px;
Expand Down
156 changes: 116 additions & 40 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,46 +57,7 @@ <h1 class="center" >Bibliograph 2</h1>
investigate, refine your corpus, select the filtering
thresholds and explore your bibliographic landscape.
</p>
<br />
<form @submit.prevent="
loading('corpus', 'Fetching corpus from OpenAlex API');
({count, works} = await fetchWorks(query, queryconcept, fromYear, toYear, maxWorks));
done();" >
<div>
<label for="query" >
Search for words in works' title, abstract and full-text
</label>
<input id="query"
class="card w-100"
placeholder="Type words to be searched in works"
type="search"
x-model="query" />
</div>
<div>
<label for="queryconcept" >
Search for works with the concept IDs. (to enter multiple concepts, separate with '|')
</label>
<input id="queryconcept"
class="card w-100"
placeholder="E.g. C11012388|C41008148|..."
type="search"
x-model="queryconcept" />
</div>
<div>
<span>Only select works published between</span>
<input
id="from-year"
class="card"
type="number"
x-model="fromYear" />
<span>and</span>
<input
id="to-year"
class="card"
type="number"
x-model="toYear" />
</div>
<div>
<div>
<span>Fetch a corpus of up to (max 10'000)</span>
<input
id="max-fetch"
Expand All @@ -106,7 +67,122 @@ <h1 class="center" >Bibliograph 2</h1>
max="10000"
x-model="maxWorks" />
<span>most cited works</span>
</div>
<br />
<form @submit.prevent="
loading('corpus', 'Fetching corpus from OpenAlex API');
({count, works} = await fetchWorks(params, maxWorks));
done();" >
<div class="card">
<template x-for="(param, index) in params">
<div>
<label :for="`type-${index}`" >
Type:
</label>
<select :id="`type-${index}`" x-model="param.type">
<option value="date" >Filter by date range</option>
<option value="title" >Search in title</option>
<option value="titleabs" >Search in title and abstract</option>
<option value="titleabsfull" >Search in title, abstract and full text</option>
<option value="concept" >Filter by concept</option>
</select>
<button type="button" @click.prevent="params.splice(index, 1)">X</button>

<template x-if="param.type === 'date'">
<div>
<span>From</span>
<input
:id="`from-${index}`"
class="card"
placeholder="Enter a year. E.g. 2008"
type="number"
x-model="param.fromYear" />
<span>to</span>
<input
:id="`to-${index}`"
class="card"
placeholder="Enter a year. E.g. 2008"
type="number"
x-model="param.toYear" />
</div>
</template>
<template x-if="param.type === 'title'">
<div>
<input :id="`value-${index}`"
class="card w-100"
placeholder="Search for words in works' title"
type="search"
x-model="param.value" />
<a href="https://docs.openalex.org/how-to-use-the-api/get-lists-of-entities/search-entities#boolean-searches">Note that you can use boolean search in this field</a>
</div>
</template>
<template x-if="param.type === 'titleabs'">
<div>
<input :id="`value-${index}`"
class="card w-100"
placeholder="Search for words in works' title and abstract"
type="search"
x-model="param.value" />
<a href="https://docs.openalex.org/how-to-use-the-api/get-lists-of-entities/search-entities#boolean-searches">Note that you can use boolean search in this field</a>
</div>
</template>
<template x-if="param.type === 'titleabsfull'">
<div>
<input :id="`value-${index}`"
class="card w-100"
placeholder="Search for words in works' title, abstract and full-text"
type="search"
x-model="param.value" />
<a href="https://docs.openalex.org/how-to-use-the-api/get-lists-of-entities/search-entities#boolean-searches">Note that you can use boolean search in this field</a>
</div>
</template>
<template x-if="param.type === 'concept'">
<div x-data="{ ac: null, str: '' }"
x-init="param.concepts = param.concepts || [];
param.op = param.op || 'or';
$nextTick(() => ac = new autoComplete({selector: `#search-${index}`, ...autoCompleteConceptConfig}))">
<input :id="`search-${index}`"
class="card w-100"
type="search"
x-model="str"
@selection="param.concepts.push($event.detail.selection.value); str='';" />
<input :id="`radio-or-${index}`"
:name="`radio-${index}`"
class="concept-radio-or"
type="radio"
value="or"
x-model="param.op" />
<label :for="`radio-or-${index}`" >OR</label>
<input :id="`radio-and-${index}`"
:name="`radio-${index}`"
class="concept-radio-and"
type="radio"
value="and"
x-model="param.op" />
<label :for="`radio-and-${index}`" >AND</label>

<ul :class="param.op === 'or' ? 'concept-list-or' : 'concept-list-and'" class="concept-list">
<template x-for="(concept, i) in param.concepts">
<li>
<span class="card">
<span x-text="concept.display_name"></span>
|
<a href="#" @click.prevent='param.concepts.splice(i, 1)'>x</a>
</span>
</li>
</template>
</ul>
</div>
</template>
</div>
</template>

<button type="button" class="btn center-btn"
@click.prevent="params.push({type: 'date', fromYear: 1900, toYear: 2100})">
Add query param
</button>
</div>

<button class="btn primary" >
Fetch Corpus
</button>
Expand Down
13 changes: 8 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import '@tarekraafat/autocomplete.js/dist/css/autoComplete.02.css';
import '@ajusa/lit/src/lit.css';
import './index.css';

import Alpine from 'alpinejs';
import autoComplete from '@tarekraafat/autocomplete.js';

import { autoCompleteConceptConfig } from './lib/autocomplete.js';
import { fetchWorks } from './lib/fetch.js';
import { processWorks, getFilters, filterData, generateJSONDataURL } from './lib/processing.js';
import { generateGraph, generateGexfURL } from './lib/graph.js';

window.autoCompleteConceptConfig = autoCompleteConceptConfig;
window.fetchWorks = fetchWorks;
window.processWorks = processWorks;
window.getFilters = getFilters;
Expand All @@ -16,11 +20,8 @@ window.generateGexfURL = generateGexfURL;
window.generateJSONDataURL = generateJSONDataURL;

Alpine.data('App', () => ({
query: '',
queryconcept: '',
fromYear: 1900,
toYear: 2100,
maxWorks: 10000,
params: [{type: 'titleabs', value: ''}],
maxWorks: 1000,
state: 'search',
nextState: '',
loadingMsg: '',
Expand All @@ -44,5 +45,7 @@ Alpine.data('App', () => ({
window.Alpine = Alpine;
Alpine.start();

window.autoComplete = autoComplete;

if (!window.IS_PRODUCTION)
new EventSource('/esbuild').addEventListener('change', () => location.reload());
38 changes: 38 additions & 0 deletions src/lib/autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const autoCompleteConceptConfig = {
placeHolder: 'Search for Concepts...',
data: {
src: async (query) => {
try {
const response = await fetch(
'https://api.openalex.org/autocomplete/concepts?' + new URLSearchParams({
q: query,
mailto: `****@****.com`,
}));
if (!response.ok) {
throw new Error('Network response was not OK');
}
const data = await response.json();
return data.results;
} catch (e) {
console.error(`Error while fetching concepts to autocomplete:\n\t${e}`);
return e;
}
},
keys: ['display_name'],
cache: false,
},
debounce: 300,
resultItem: {
tabSelect: true,
noResults: true,
highlight: true,
},
events: {
input: {
navigate: (event) => {
const selection = event.detail.selection.value;
event.target.value = selection.display_name;
}
}
},
};
Loading

0 comments on commit 43fbdb7

Please sign in to comment.