22const ENDPOINT = "https://graph.dhi-roma.it/query" ;
33
44const 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
614const LANGUAGE = "en" ;
715const LIMIT = 1000 ;
816const 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+
1130const statusEl = document . querySelector ( "#status" ) ;
1231const contentEl = document . querySelector ( "#content" ) ;
1332const classTreeEl = document . querySelector ( "#class-tree" ) ;
@@ -16,7 +35,13 @@ const classUriEl = document.querySelector("#class-uri");
1635const commentBoxEl = document . querySelector ( "#comment-box" ) ;
1736const commentTextEl = document . querySelector ( "#comment-text" ) ;
1837const 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 " ) ;
2045let classes = [ ] ;
2146let 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
115147searchEl . 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+
129158async 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
235304function 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+
246342function renderClassTree ( entries ) {
247343 classTreeEl . innerHTML = "" ;
248344
0 commit comments