Skip to content

Commit 0471475

Browse files
committed
pagination and search via server
1 parent a436c86 commit 0471475

2 files changed

Lines changed: 175 additions & 68 deletions

File tree

docs/browse/classes.js

Lines changed: 145 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,31 @@
22
const ENDPOINT = "https://graph.dhi-roma.it/query";
33

44
const ONTOLOGY_GRAPH = "http://www.w3.org/2002/07/owl#";
5+
const HIDDEN_CLASSES = [
6+
"http://www.w3.org/2002/07/owl#Thing"
7+
];
8+
9+
const HIDDEN_CLASS_BRANCHES = [
10+
"http://xmlns.com/foaf/0.1/Agent",
11+
"https://semantic-html.org/vocab#Semantics"
12+
];
513

614
const LANGUAGE = "en";
715
const LIMIT = 1000;
816
const RESOURCE_VIEWER =
917
location.pathname.replace(/\/$/, "") + "/resource";
1018

19+
const searchEl = document.querySelector("#instance-search");
20+
const instanceCountEl = document.querySelector("#instance-count");
21+
const pageInfoEl = document.querySelector("#page-info");
22+
const prevPageEl = document.querySelector("#prev-page");
23+
const nextPageEl = document.querySelector("#next-page");
24+
25+
const PAGE_SIZE = 100;
26+
let currentPage = 0;
27+
let currentSearch = "";
28+
let currentTotal = 0;
29+
1130
const statusEl = document.querySelector("#status");
1231
const contentEl = document.querySelector("#content");
1332
const classTreeEl = document.querySelector("#class-tree");
@@ -16,7 +35,13 @@ const classUriEl = document.querySelector("#class-uri");
1635
const commentBoxEl = document.querySelector("#comment-box");
1736
const commentTextEl = document.querySelector("#comment-text");
1837
const instancesEl = document.querySelector("#instances");
38+
const hiddenClasses = HIDDEN_CLASSES
39+
.map((iri) => `<${escapeSparqlIri(iri)}>`)
40+
.join(",\n ");
1941

42+
const hiddenBranches = HIDDEN_CLASS_BRANCHES
43+
.map((iri) => `<${escapeSparqlIri(iri)}>`)
44+
.join("\n ");
2045
let classes = [];
2146
let classByUri = new Map();
2247

@@ -91,8 +116,16 @@ async function renderCurrentClass() {
91116
showStatus("Loading Instances …");
92117

93118
try {
94-
const data = await queryInstances(selected.uri);
119+
const [countData, data] = await Promise.all([
120+
queryInstanceCount(selected.uri, currentSearch),
121+
queryInstances(selected.uri, currentSearch, currentPage)
122+
]);
123+
124+
currentTotal = Number(countData.results.bindings[0].count.value);
125+
95126
renderInstances(data.results.bindings);
127+
renderPagination();
128+
96129
hideStatus();
97130
contentEl.hidden = false;
98131
} catch (error) {
@@ -109,23 +142,19 @@ function getClassUriFromLocation() {
109142
return new URLSearchParams(location.search).get("class");
110143
}
111144

112-
const searchEl =
113-
document.querySelector("#instance-search");
145+
let searchTimer;
114146

115147
searchEl.addEventListener("input", () => {
148+
clearTimeout(searchTimer);
116149

117-
const q =
118-
searchEl.value.toLowerCase();
119-
120-
for (const row of document.querySelectorAll("tbody tr")) {
121-
122-
row.hidden =
123-
!row.textContent
124-
.toLowerCase()
125-
.includes(q);
126-
}
150+
searchTimer = setTimeout(() => {
151+
currentSearch = searchEl.value;
152+
currentPage = 0;
153+
renderCurrentClass();
154+
}, 250);
127155
});
128156

157+
129158
async function queryClasses() {
130159
const graphOpen = ONTOLOGY_GRAPH ? `GRAPH <${ONTOLOGY_GRAPH}> {` : "";
131160
const graphClose = ONTOLOGY_GRAPH ? "}" : "";
@@ -160,24 +189,19 @@ async function queryClasses() {
160189
}
161190
${graphClose}
162191
163-
FILTER(?class NOT IN (
164-
<http://www.w3.org/2002/07/owl#Thing>,
165-
<http://xmlns.com/foaf/0.1/Agent>,
166-
<https://w3id.org/grace/ontology/anything>,
167-
<https://w3id.org/grace/ontology/description>
168-
))
169-
170-
FILTER NOT EXISTS {
171-
${graphOpen}
172-
?class rdfs:subClassOf+ ?blocked .
173-
${graphClose}
174-
175-
VALUES ?blocked {
176-
<http://www.w3.org/2002/07/owl#Thing>
177-
<http://xmlns.com/foaf/0.1/Agent>
178-
<https://w3id.org/grace/ontology/description>
179-
}
180-
}
192+
FILTER(?class NOT IN (
193+
${hiddenClasses}
194+
))
195+
196+
FILTER NOT EXISTS {
197+
${graphOpen}
198+
?class rdfs:subClassOf+ ?blocked .
199+
${graphClose}
200+
201+
VALUES ?blocked {
202+
${hiddenBranches}
203+
}
204+
}
181205
182206
FILTER EXISTS {
183207
?instance a ?instanceClass .
@@ -192,44 +216,89 @@ FILTER NOT EXISTS {
192216
return sparql(query);
193217
}
194218

195-
async function queryInstances(classUri) {
219+
async function sparql(query) {
220+
const response = await fetch(ENDPOINT, {
221+
method: "POST",
222+
headers: {
223+
"Content-Type": "application/sparql-query",
224+
"Accept": "application/sparql-results+json"
225+
},
226+
body: query
227+
});
228+
229+
if (!response.ok) {
230+
throw new Error(`SPARQL endpoint returned ${response.status}`);
231+
}
232+
233+
return response.json();
234+
}
235+
236+
async function queryInstanceCount(classUri, search = "") {
237+
const searchFilter = search.trim()
238+
? `
239+
FILTER(
240+
CONTAINS(LCASE(STR(?instance)), LCASE("${escapeSparqlString(search)}")) ||
241+
CONTAINS(LCASE(STR(?label)), LCASE("${escapeSparqlString(search)}"))
242+
)
243+
`
244+
: "";
245+
196246
const query = `
197247
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
198248
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
199249
200-
SELECT ?instance (SAMPLE(?label) AS ?instanceLabel) WHERE {
250+
SELECT (COUNT(DISTINCT ?instance) AS ?count) WHERE {
201251
?instance rdf:type/rdfs:subClassOf* <${escapeSparqlIri(classUri)}> .
252+
FILTER(isIRI(?instance))
253+
254+
OPTIONAL {
255+
?instance rdfs:label ?label .
256+
FILTER(lang(?label) = "${LANGUAGE}" || lang(?label) = "")
257+
}
258+
259+
${searchFilter}
260+
}
261+
`;
262+
263+
return sparql(query);
264+
}
265+
266+
async function queryInstances(classUri, search = "", page = 0) {
267+
const searchFilter = search.trim()
268+
? `
269+
FILTER(
270+
CONTAINS(LCASE(STR(?instance)), LCASE("${escapeSparqlString(search)}")) ||
271+
CONTAINS(LCASE(STR(?label)), LCASE("${escapeSparqlString(search)}"))
272+
)
273+
`
274+
: "";
202275

276+
const query = `
277+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
278+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
279+
280+
SELECT ?instance (SAMPLE(?label) AS ?instanceLabel) WHERE {
281+
?instance rdf:type/rdfs:subClassOf* <${escapeSparqlIri(classUri)}> .
203282
FILTER(isIRI(?instance))
204283
205284
OPTIONAL {
206285
?instance rdfs:label ?label .
207286
FILTER(lang(?label) = "${LANGUAGE}" || lang(?label) = "")
208287
}
288+
289+
${searchFilter}
209290
}
210291
GROUP BY ?instance
211292
ORDER BY LCASE(STR(COALESCE(SAMPLE(?label), ?instance)))
212-
LIMIT ${LIMIT}
293+
LIMIT ${PAGE_SIZE}
294+
OFFSET ${page * PAGE_SIZE}
213295
`;
214296

215297
return sparql(query);
216298
}
217299

218-
async function sparql(query) {
219-
const response = await fetch(ENDPOINT, {
220-
method: "POST",
221-
headers: {
222-
"Content-Type": "application/sparql-query",
223-
"Accept": "application/sparql-results+json"
224-
},
225-
body: query
226-
});
227-
228-
if (!response.ok) {
229-
throw new Error(`SPARQL endpoint returned ${response.status}`);
230-
}
231-
232-
return response.json();
300+
function escapeSparqlString(value) {
301+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
233302
}
234303

235304
function normalizeClasses(bindings) {
@@ -243,6 +312,33 @@ function normalizeClasses(bindings) {
243312
}));
244313
}
245314

315+
function renderPagination() {
316+
const totalPages = Math.max(1, Math.ceil(currentTotal / PAGE_SIZE));
317+
318+
instanceCountEl.textContent = `${currentTotal} results`;
319+
pageInfoEl.textContent = `Page ${currentPage + 1} of ${totalPages}`;
320+
321+
prevPageEl.disabled = currentPage === 0;
322+
nextPageEl.disabled = currentPage >= totalPages - 1;
323+
}
324+
325+
prevPageEl.addEventListener("click", () => {
326+
if (currentPage > 0) {
327+
currentPage--;
328+
renderCurrentClass();
329+
}
330+
});
331+
332+
nextPageEl.addEventListener("click", () => {
333+
const totalPages = Math.ceil(currentTotal / PAGE_SIZE);
334+
335+
if (currentPage < totalPages - 1) {
336+
currentPage++;
337+
renderCurrentClass();
338+
}
339+
});
340+
341+
246342
function renderClassTree(entries) {
247343
classTreeEl.innerHTML = "";
248344

docs/browse/index.html

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,41 @@ <h4>Classes</h4>
3030

3131
<section class="class-content">
3232

33-
<details open>
34-
<summary>Instances</summary>
35-
<div class="table-toolbar">
36-
<input
37-
id="instance-search"
38-
type="search"
39-
placeholder="Search instances..."
40-
>
41-
</div>
42-
<table>
43-
<thead>
44-
<tr>
45-
<th>Instance</th>
46-
<th>URI</th>
47-
</tr>
48-
</thead>
49-
<tbody id="instances"></tbody>
50-
</table>
51-
</details>
33+
<details open>
34+
<summary>Instances</summary>
35+
36+
<div class="table-toolbar">
37+
<input
38+
id="instance-search"
39+
type="search"
40+
placeholder="Search instances..."
41+
>
42+
43+
<span id="instance-count"></span>
44+
45+
<div class="pagination">
46+
<button id="prev-page" type="button">Previous</button>
47+
<span id="page-info"></span>
48+
<button id="next-page" type="button">Next</button>
49+
</div>
50+
</div>
51+
52+
<table>
53+
<thead>
54+
<tr>
55+
<th>Instance</th>
56+
<th>URI</th>
57+
</tr>
58+
</thead>
59+
<tbody id="instances"></tbody>
60+
</table>
61+
</details>
5262
</section>
5363
</div>
5464
</section>
5565
</main>
5666

67+
5768
<script src="classes.js"></script>
5869
</body>
5970
</html>

0 commit comments

Comments
 (0)