Skip to content

Commit 20a6728

Browse files
author
Tim Berners-Lee
committed
First successful trial of the AC field. Need more work
1 parent 5d54d91 commit 20a6728

6 files changed

Lines changed: 95 additions & 44 deletions

File tree

src/widgets/error.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
/**
22
* Create an error message block
33
* @param dom The DOM on which dom.createElement will be called
4-
* @param msg The error message string to display
4+
* @param err The error message string to display
55
* @param backgroundColor Background color. Default: '#fee'
6-
* @returns A div element with the msg string
6+
* @returns A div element with the err string
77
*/
8-
export function errorMessageBlock (dom: HTMLDocument, msg: string, backgroundColor?: string): HTMLDivElement {
8+
/* eslint-disable no-console */
9+
10+
export function errorMessageBlock (dom: HTMLDocument, err: string | Error, backgroundColor?: string): HTMLDivElement {
911
const div = dom.createElement('div')
12+
if (err instanceof Error && err.stack) {
13+
console.error(`errorMessageBlock: Error ${err} at: ${err.stack}`, err) // @@ pick one
14+
}
1015
div.setAttribute(
1116
'style',
1217
'margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: ' +
1318
(backgroundColor || '#fee') +
1419
'; color:black;'
1520
)
16-
div.textContent = msg
21+
div.textContent = err
1722
return div
1823
}

src/widgets/forms.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,13 +350,12 @@ forms.field[ns.ui('Multiple').uri] = function (
350350
* One possibility is to not actually make the link to the thing until
351351
* this callback happens to avoid widow links
352352
*/
353-
function itemDone (uri, ok, message) {
354-
debug.log(`Item ${uri} done callback for item ${object.uri.slice(-7)}`)
353+
function itemDone (ok, message) {
354+
debug.log(`Item done callback for item ${object.uri.slice(-7)}`)
355355
if (!ok) { // when does this happen? errors typically deal with upstream
356356
debug.error(' Item done callback: Error: ' + message)
357-
} else {
358-
linkDone(uri, ok, message)
359357
}
358+
callbackFunction(ok, message)
360359
}
361360
const linkDone = function (uri, ok, message) {
362361
return callbackFunction(ok, message)

src/widgets/forms/autocomplete/autocompleteBar.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const WEBID_NOUN = 'Solid ID'
1717

1818
const GREEN_PLUS = icons.iconBase + 'noun_34653_green.svg'
1919
const SEARCH_ICON = icons.iconBase + 'noun_Search_875351.svg'
20+
const EDIT_ICON = icons.iconBase + 'noun_253504+svg'
2021

2122
export async function renderAutocompleteControl (dom:HTMLDocument,
2223
person:NamedNode,
@@ -25,8 +26,14 @@ export async function renderAutocompleteControl (dom:HTMLDocument,
2526
addOneIdAndRefresh): Promise<HTMLElement> {
2627
async function autoCompleteDone (object, _name) {
2728
const webid = object.uri
28-
removeDecorated()
29-
return addOneIdAndRefresh(person, webid)
29+
if (barOptions.permanent) { // remember to set this in publicid panel
30+
setVisible(editButton, true)
31+
setVisible(acceptButton, false)
32+
setVisible(cancelButton, false)
33+
} else {
34+
removeDecorated()
35+
}
36+
return addOneIdAndRefresh(object, name)
3037
}
3138

3239
async function greenButtonHandler (_event) {
@@ -65,8 +72,23 @@ export async function renderAutocompleteControl (dom:HTMLDocument,
6572
// const queryParams = barOptions.queryParameters || wikidataParameters
6673
const acceptButton = widgets.continueButton(dom)
6774
const cancelButton = widgets.cancelButton(dom, removeDecorated) // @@ not in edit case only in temporary case
68-
if (barOptions.permanent) {
69-
var editButton = widgets.button(dom, icons.iconBase + 'noun_253504.svg', 'Edit')
75+
var editButton
76+
var editing = true
77+
78+
function setVisible (element:HTMLElement, visible:boolean) {
79+
element.style.visibility = visible ? 'visible' : 'collapse'
80+
}
81+
82+
function syncEditingStatus () {
83+
if (editing) {
84+
setVisible(editButton, false)
85+
setVisible(acceptButton, true)
86+
setVisible(cancelButton, true)
87+
} else {
88+
setVisible(editButton, true)
89+
setVisible(acceptButton, false)
90+
setVisible(cancelButton, false)
91+
}
7092
}
7193

7294
const decoration:AutocompleteDecoration = {
@@ -83,12 +105,22 @@ export async function renderAutocompleteControl (dom:HTMLDocument,
83105
if (barOptions.editable) {
84106
// creationArea.appendChild(await renderAutoComplete(dom, barOptions, autoCompleteDone)) wait for searchButton
85107
creationArea.style.width = '100%'
86-
const plus = creationArea.appendChild(widgets.button(dom, GREEN_PLUS, barOptions.idNoun, greenButtonHandler))
87-
widgets.makeDropTarget(plus, droppedURIHandler, undefined)
108+
if (barOptions.manualURIEntry) {
109+
const plus = creationArea.appendChild(widgets.button(dom, GREEN_PLUS, barOptions.idNoun, greenButtonHandler))
110+
widgets.makeDropTarget(plus, droppedURIHandler, undefined)
111+
}
88112
if (barOptions.dbLookup) {
89113
creationArea.appendChild(widgets.button(dom, SEARCH_ICON, barOptions.idNoun, searchButtonHandler))
90114
}
115+
if (barOptions.permanent && barOptions.editable) {
116+
editButton = widgets.button(dom, icons.iconBase + 'noun_253504.svg', 'Edit', _event => {
117+
editing = !editing
118+
syncEditingStatus()
119+
})
120+
creationArea.appendChild(editButton)
121+
}
91122
}
123+
syncEditingStatus()
92124
return creationArea
93125
} // renderAutocompleteControl
94126

src/widgets/forms/autocomplete/autocompleteField.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,6 @@ export function autocompleteField (
8484
const labelProperty = kb.any(form, ns.ui('labelProperty')) || ns.schema('name')
8585
const size = kb.any(form, ns.ui('size')) // may be undefined, let the widget decide
8686

87-
const searchClass = kb.any(form, ns.ui('searchClass'))
88-
if (!searchClass) {
89-
return box.appendChild(
90-
widgets.errorMessageBlock(dom, 'Error: No searchClass given for autocomplete field: ' + form)
91-
)
92-
}
9387
// Parse the data source into query options
9488

9589
const dataSource = kb.any(form, ns.ui('dataSource')) as NamedNode | undefined
@@ -99,10 +93,18 @@ export function autocompleteField (
9993
)
10094
}
10195
const queryParams:QueryParameters = {
102-
targetClass: kb.any(dataSource, ns.ui('targetClass'), null, dataSource.doc()) as NamedNode | undefined,
96+
// targetClass: kb.any(dataSource, ns.ui('targetClass'), null, dataSource.doc()) as NamedNode | undefined,
10397
label: kb.anyJS(dataSource, ns.schema('name'), null, dataSource.doc()),
10498
logo: kb.anyJS(dataSource, ns.schema('logo'), null, dataSource.doc())
10599
}
100+
101+
// @@ Should we pass the target class in from the data source definition or use a current type of the subject
102+
const targetClass = (kb.any(form, ns.ui('targetClass'), null, form.doc()) || // class in form takes pecedence
103+
kb.any(dataSource, ns.ui('targetClass'), null, dataSource.doc())) as NamedNode | undefined
104+
if (targetClass) {
105+
queryParams.targetClass = targetClass
106+
}
107+
/*
106108
if (!queryParams.targetClass) {
107109
const klass = kb.any(subject, ns.rdf('type')) as NamedNode | undefined
108110
// @@ be more selective of which class if many
@@ -111,31 +113,32 @@ export function autocompleteField (
111113
if (!klass) throw new Error('Autocomplete: No class specified or is current type of' + subject)
112114
queryParams.targetClass = klass
113115
}
114-
const endPoint = kb.anyJS(dataSource, ns.ui('endPoint'), null, dataSource.doc()) as string | undefined
115-
if (endPoint) { // SPARQL
116-
queryParams.endpoint = endPoint
116+
*/
117+
const endpoint = kb.anyJS(dataSource, ns.ui('endpoint'), null, dataSource.doc()) as string | undefined
118+
if (endpoint) { // SPARQL
119+
queryParams.endpoint = endpoint
117120

118121
queryParams.searchByNameQuery = kb.anyJS(dataSource, ns.ui('searchByNameQuery'), null, dataSource.doc())
119122
if (!queryParams.searchByNameQuery) {
120123
return box.appendChild(
121-
widgets.errorMessageBlock(dom, 'Error: No searchByNameQuery given for data Source: ' + form))
124+
widgets.errorMessageBlock(dom, 'Error: No searchByNameQuery given for endpoint data Source: ' + form))
122125
}
123126
queryParams.insitituteDetailsQuery = kb.anyJS(dataSource, ns.ui('insitituteDetailsQuery'), null, dataSource.doc())
124127
} else {
125-
return box.appendChild(
126-
widgets.errorMessageBlock(dom, 'Error: No SPARQL endPoint given for autocomplete field: ' + form))
127-
}
128-
const queryTemplate = kb.any(form, ns.ui('queryTemplate'))
129-
if (!queryTemplate) {
130-
return box.appendChild(
131-
widgets.errorMessageBlock(dom, 'Error: No queryTemplate given for autocomplete field: ' + form)
132-
)
128+
// return box.appendChild(
129+
// widgets.errorMessageBlock(dom, 'Error: No SPARQL endpoint given for autocomplete field: ' + form))
130+
const searchByNameURI = kb.anyJS(dataSource, ns.ui('searchByNameURI'))
131+
if (!searchByNameURI) {
132+
return box.appendChild(
133+
widgets.errorMessageBlock(dom, 'Error: No searchByNameURI OR sparql endpoint given for dataSource: ' + dataSource)
134+
)
135+
}
136+
queryParams.searchByNameURI = searchByNameURI
133137
}
134138
// It can be cleaner to just remove empty fields if you can't edit them anyway
135139
const suppressEmptyUneditable = kb.anyJS(form, ns.ui('suppressEmptyUneditable'), null, formDoc)
136140
const editable = kb.updater.editable((doc as NamedNode).uri)
137141

138-
// @@ Should we pass the target class in from the data source definition or use a current type of the subject
139142
const autocompleteOptions:AutocompleteOptions = { // cancelButton?: HTMLElement,
140143
// acceptButton?: HTMLElement,
141144
targetClass: queryParams.targetClass, // @@ simplify?
@@ -163,10 +166,10 @@ export function autocompleteField (
163166
const barOptions = {
164167
editable: doc && doc.uri && kb.updater.editable(doc.uri, kb),
165168
permanent: true,
166-
dbLookup: false
169+
dbLookup: true
167170
}
168171
renderAutocompleteControl(dom, subject as NamedNode, barOptions, autocompleteOptions, addOneIdAndRefresh).then((control) => {
169-
// console.log('Async load of autocomplete field control finished:' + control)
172+
// log('Async load of autocomplete field control finished:' + control)
170173
rhs.appendChild(control)
171174
}, (err) => {
172175
rhs.appendChild(widgets.errorMessageBlock(dom, `Error rendering autocomplete${form}: ${err}`))

src/widgets/forms/autocomplete/autocompletePicker.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
**
55
*/
66
import * as debug from '../../../debug'
7-
import { style } from '../../../style'
7+
import style from '../../../style'
88
import { store } from '../../../logic'
99
import widgets from '../../../widgets'
1010

@@ -73,7 +73,9 @@ export async function renderAutoComplete (dom: HTMLDocument,
7373
function complain (message) {
7474
const errorRow = table.appendChild(dom.createElement('tr'))
7575
debug.log(message)
76-
errorRow.appendChild(widgets.errorMessageBlock(dom, message, 'pink'))
76+
const err = new Error(message)
77+
errorRow.appendChild(widgets.errorMessageBlock(dom, err, 'pink'))
78+
// errorMessageBlock will log the stack to the console
7779
style.setStyle(errorRow, 'autocompleteRowStyle')
7880
errorRow.style.padding = '1em'
7981
}
@@ -87,6 +89,7 @@ export async function renderAutoComplete (dom: HTMLDocument,
8789
// remove(decoration.cancelButton)
8890
// remove(decoration.acceptButton)
8991
// remove(div)
92+
clearList()
9093
callback(object, name)
9194
}
9295
async function gotIt (object:NamedNode, name:string) {

src/widgets/forms/autocomplete/publicData.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* Logic to access public data stores
22
*
33
* including filtering resut by natural language etc
4+
* See https://solidos.solidcommunity.net/public/2021/01%20Building%20Solid%20Apps%20which%20use%20Public%20Data.html
45
*/
56
import { NamedNode, Literal, parse } from 'rdflib'
67
import * as debug from '../../../debug'
@@ -13,7 +14,9 @@ import { store } from '../../../logic'
1314
// import * as instituteDetailsWikidataQuery from './instituteDetailsWikidataQuery.js'
1415
const kb = store
1516

16-
export const AUTOCOMPLETE_LIMIT = 3000 // How many to get from server
17+
export const AUTOCOMPLETE_LIMIT = 500 // How many to get from server
18+
// With 3000 we could exceed the wikidata timeout
19+
export const LANGUAGE = 'en' // @@ To do : get user's preferred language
1720

1821
const subjectRegexp = /\$\(subject\)/g
1922

@@ -121,9 +124,9 @@ export const dbpediaParameters:QueryParameters = {
121124
endpoint: 'https://dbpedia.org/sparql/'
122125
}
123126

124-
const dbPediaTypeMap = { AcademicInsitution: 'http://umbel.org/umbel/rc/EducationalOrganization' }
127+
export const dbPediaTypeMap = { AcademicInsitution: 'http://umbel.org/umbel/rc/EducationalOrganization' }
125128

126-
const wikidataOutgoingClassMap = {
129+
export const wikidataOutgoingClassMap = {
127130
AcademicInsitution: 'http://www.wikidata.org/entity/Q4671277',
128131
Enterprise: 'http://www.wikidata.org/entity/Q6881511',
129132
Business: 'http://www.wikidata.org/entity/Q4830453',
@@ -281,7 +284,7 @@ export function ESCOResultToBindings (json: Object): Bindings {
281284
const bindings = results.map(result => {
282285
const name = result.title
283286
const uri = result.uri // like http://data.europa.eu/esco/occupation/57af9090-55b4-4911-b2d0-86db01c00b02
284-
return { name: { value: name, type: 'literal' }, uri: { type: 'IRI', value: uri } } // simulate SPARQL bindings
287+
return { name: { value: name, type: 'literal' }, subject: { type: 'IRI', value: uri } } // simulate SPARQL bindings
285288
})
286289
return bindings
287290
}
@@ -313,10 +316,16 @@ export async function queryPublicDataByName (
313316
languages: Array<string>,
314317
queryTarget: QueryParameters): Promise<Bindings> {
315318
function substituteStrings (template: string):string {
316-
return template.replace('$(name)', filter)
319+
const u1 = template.replace('$(name)', filter)
317320
.replace('$(limit)', '' + AUTOCOMPLETE_LIMIT)
318-
.replace('$(targetClass)', theClass.toNT())
321+
.replace('$(language)', language)
322+
return u1.replace('$(targetClass)', theClass.toNT())
319323
}
324+
if (!theClass) {
325+
throw new Error('queryPublicDataByName: No class provided')
326+
}
327+
const languagePrefs = await getPreferredLanguages()
328+
const language = languagePrefs[0] || 'en'
320329
if (queryTarget.searchByNameQuery) {
321330
const sparql = substituteStrings(queryTarget.searchByNameQuery)
322331
debug.log('Querying public data - sparql: ' + sparql)

0 commit comments

Comments
 (0)