Skip to content

Commit 43fbdb7

Browse files
authored
Merge pull request #20 from nbonfils/search-interface
Search interface
2 parents a5930c6 + 33c03b2 commit 43fbdb7

7 files changed

+259
-54
lines changed

package-lock.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"dependencies": {
2121
"@ajusa/lit": "^1.1.0",
22+
"@tarekraafat/autocomplete.js": "10.2.7",
2223
"alpinejs": "^3.13.0",
2324
"graphology": "0.25.4",
2425
"graphology-components": "^1.5.4",

src/index.css

+36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@
22
text-align: center;
33
}
44

5+
.center-btn {
6+
display: block;
7+
margin: auto;
8+
}
9+
10+
.concept-radio-or {
11+
margin-left: 20px;
12+
}
13+
14+
.concept-radio-and {
15+
margin-left: 10px;
16+
}
17+
18+
.concept-list {
19+
margin-top: 1.5em;
20+
padding: 0;
21+
}
22+
23+
.concept-list li {
24+
display: inline;
25+
list-style: none;
26+
}
27+
28+
.concept-list-or li + li::before {
29+
content: " OR ";
30+
}
31+
32+
.concept-list-and li + li::before {
33+
content: " AND ";
34+
}
35+
36+
.concept-list .card {
37+
padding: 0.5em;
38+
margin-right: 0.3em;
39+
}
40+
541
.corpus-too-big {
642
border: red 2px solid;
743
border-radius: 5px;

src/index.html

+116-40
Original file line numberDiff line numberDiff line change
@@ -57,46 +57,7 @@ <h1 class="center" >Bibliograph 2</h1>
5757
investigate, refine your corpus, select the filtering
5858
thresholds and explore your bibliographic landscape.
5959
</p>
60-
<br />
61-
<form @submit.prevent="
62-
loading('corpus', 'Fetching corpus from OpenAlex API');
63-
({count, works} = await fetchWorks(query, queryconcept, fromYear, toYear, maxWorks));
64-
done();" >
65-
<div>
66-
<label for="query" >
67-
Search for words in works' title, abstract and full-text
68-
</label>
69-
<input id="query"
70-
class="card w-100"
71-
placeholder="Type words to be searched in works"
72-
type="search"
73-
x-model="query" />
74-
</div>
75-
<div>
76-
<label for="queryconcept" >
77-
Search for works with the concept IDs. (to enter multiple concepts, separate with '|')
78-
</label>
79-
<input id="queryconcept"
80-
class="card w-100"
81-
placeholder="E.g. C11012388|C41008148|..."
82-
type="search"
83-
x-model="queryconcept" />
84-
</div>
85-
<div>
86-
<span>Only select works published between</span>
87-
<input
88-
id="from-year"
89-
class="card"
90-
type="number"
91-
x-model="fromYear" />
92-
<span>and</span>
93-
<input
94-
id="to-year"
95-
class="card"
96-
type="number"
97-
x-model="toYear" />
98-
</div>
99-
<div>
60+
<div>
10061
<span>Fetch a corpus of up to (max 10'000)</span>
10162
<input
10263
id="max-fetch"
@@ -106,7 +67,122 @@ <h1 class="center" >Bibliograph 2</h1>
10667
max="10000"
10768
x-model="maxWorks" />
10869
<span>most cited works</span>
70+
</div>
71+
<br />
72+
<form @submit.prevent="
73+
loading('corpus', 'Fetching corpus from OpenAlex API');
74+
({count, works} = await fetchWorks(params, maxWorks));
75+
done();" >
76+
<div class="card">
77+
<template x-for="(param, index) in params">
78+
<div>
79+
<label :for="`type-${index}`" >
80+
Type:
81+
</label>
82+
<select :id="`type-${index}`" x-model="param.type">
83+
<option value="date" >Filter by date range</option>
84+
<option value="title" >Search in title</option>
85+
<option value="titleabs" >Search in title and abstract</option>
86+
<option value="titleabsfull" >Search in title, abstract and full text</option>
87+
<option value="concept" >Filter by concept</option>
88+
</select>
89+
<button type="button" @click.prevent="params.splice(index, 1)">X</button>
90+
91+
<template x-if="param.type === 'date'">
92+
<div>
93+
<span>From</span>
94+
<input
95+
:id="`from-${index}`"
96+
class="card"
97+
placeholder="Enter a year. E.g. 2008"
98+
type="number"
99+
x-model="param.fromYear" />
100+
<span>to</span>
101+
<input
102+
:id="`to-${index}`"
103+
class="card"
104+
placeholder="Enter a year. E.g. 2008"
105+
type="number"
106+
x-model="param.toYear" />
107+
</div>
108+
</template>
109+
<template x-if="param.type === 'title'">
110+
<div>
111+
<input :id="`value-${index}`"
112+
class="card w-100"
113+
placeholder="Search for words in works' title"
114+
type="search"
115+
x-model="param.value" />
116+
<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>
117+
</div>
118+
</template>
119+
<template x-if="param.type === 'titleabs'">
120+
<div>
121+
<input :id="`value-${index}`"
122+
class="card w-100"
123+
placeholder="Search for words in works' title and abstract"
124+
type="search"
125+
x-model="param.value" />
126+
<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>
127+
</div>
128+
</template>
129+
<template x-if="param.type === 'titleabsfull'">
130+
<div>
131+
<input :id="`value-${index}`"
132+
class="card w-100"
133+
placeholder="Search for words in works' title, abstract and full-text"
134+
type="search"
135+
x-model="param.value" />
136+
<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>
137+
</div>
138+
</template>
139+
<template x-if="param.type === 'concept'">
140+
<div x-data="{ ac: null, str: '' }"
141+
x-init="param.concepts = param.concepts || [];
142+
param.op = param.op || 'or';
143+
$nextTick(() => ac = new autoComplete({selector: `#search-${index}`, ...autoCompleteConceptConfig}))">
144+
<input :id="`search-${index}`"
145+
class="card w-100"
146+
type="search"
147+
x-model="str"
148+
@selection="param.concepts.push($event.detail.selection.value); str='';" />
149+
<input :id="`radio-or-${index}`"
150+
:name="`radio-${index}`"
151+
class="concept-radio-or"
152+
type="radio"
153+
value="or"
154+
x-model="param.op" />
155+
<label :for="`radio-or-${index}`" >OR</label>
156+
<input :id="`radio-and-${index}`"
157+
:name="`radio-${index}`"
158+
class="concept-radio-and"
159+
type="radio"
160+
value="and"
161+
x-model="param.op" />
162+
<label :for="`radio-and-${index}`" >AND</label>
163+
164+
<ul :class="param.op === 'or' ? 'concept-list-or' : 'concept-list-and'" class="concept-list">
165+
<template x-for="(concept, i) in param.concepts">
166+
<li>
167+
<span class="card">
168+
<span x-text="concept.display_name"></span>
169+
|
170+
<a href="#" @click.prevent='param.concepts.splice(i, 1)'>x</a>
171+
</span>
172+
</li>
173+
</template>
174+
</ul>
175+
</div>
176+
</template>
177+
</div>
178+
</template>
179+
180+
<button type="button" class="btn center-btn"
181+
@click.prevent="params.push({type: 'date', fromYear: 1900, toYear: 2100})">
182+
Add query param
183+
</button>
109184
</div>
185+
110186
<button class="btn primary" >
111187
Fetch Corpus
112188
</button>

src/index.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import '@tarekraafat/autocomplete.js/dist/css/autoComplete.02.css';
12
import '@ajusa/lit/src/lit.css';
23
import './index.css';
34

45
import Alpine from 'alpinejs';
6+
import autoComplete from '@tarekraafat/autocomplete.js';
57

8+
import { autoCompleteConceptConfig } from './lib/autocomplete.js';
69
import { fetchWorks } from './lib/fetch.js';
710
import { processWorks, getFilters, filterData, generateJSONDataURL } from './lib/processing.js';
811
import { generateGraph, generateGexfURL } from './lib/graph.js';
912

13+
window.autoCompleteConceptConfig = autoCompleteConceptConfig;
1014
window.fetchWorks = fetchWorks;
1115
window.processWorks = processWorks;
1216
window.getFilters = getFilters;
@@ -16,11 +20,8 @@ window.generateGexfURL = generateGexfURL;
1620
window.generateJSONDataURL = generateJSONDataURL;
1721

1822
Alpine.data('App', () => ({
19-
query: '',
20-
queryconcept: '',
21-
fromYear: 1900,
22-
toYear: 2100,
23-
maxWorks: 10000,
23+
params: [{type: 'titleabs', value: ''}],
24+
maxWorks: 1000,
2425
state: 'search',
2526
nextState: '',
2627
loadingMsg: '',
@@ -44,5 +45,7 @@ Alpine.data('App', () => ({
4445
window.Alpine = Alpine;
4546
Alpine.start();
4647

48+
window.autoComplete = autoComplete;
49+
4750
if (!window.IS_PRODUCTION)
4851
new EventSource('/esbuild').addEventListener('change', () => location.reload());

src/lib/autocomplete.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const autoCompleteConceptConfig = {
2+
placeHolder: 'Search for Concepts...',
3+
data: {
4+
src: async (query) => {
5+
try {
6+
const response = await fetch(
7+
'https://api.openalex.org/autocomplete/concepts?' + new URLSearchParams({
8+
q: query,
9+
mailto: `****@****.com`,
10+
}));
11+
if (!response.ok) {
12+
throw new Error('Network response was not OK');
13+
}
14+
const data = await response.json();
15+
return data.results;
16+
} catch (e) {
17+
console.error(`Error while fetching concepts to autocomplete:\n\t${e}`);
18+
return e;
19+
}
20+
},
21+
keys: ['display_name'],
22+
cache: false,
23+
},
24+
debounce: 300,
25+
resultItem: {
26+
tabSelect: true,
27+
noResults: true,
28+
highlight: true,
29+
},
30+
events: {
31+
input: {
32+
navigate: (event) => {
33+
const selection = event.detail.selection.value;
34+
event.target.value = selection.display_name;
35+
}
36+
}
37+
},
38+
};

0 commit comments

Comments
 (0)