UNPKG

solid-ui

Version:

UI library for Solid applications

199 lines • 10 kB
/* Form field for doing autocompleete */ import ns from '../../../ns'; import { store } from 'solid-logic'; import * as widgets from '../../../widgets'; import { style } from '../../../style'; import { renderAutocompleteControl } from './autocompleteBar'; import { st } from 'rdflib'; /** * Render a autocomplete form field * * The autocomplete form searches for an object in a definitive public database, * and allows the user to search for it by name, displaying a list of objects whose names match * the input to date, and letting the user either click on one of the list, * or just go on untill there is only one. The process then returns two values, * the URiI of the object and its name. * * @param dom The HTML Document object aka Document Object Model * @param container If present, the created widget will be appended to this * @param already A hash table of (form, subject) kept to prevent recursive forms looping * @param subject The thing about which the form displays/edits data * @param form The form or field to be rendered * @param doc The web document in which the data is * @param callbackFunction Called when data is changed so other parts can be refreshed. * * Form properties: * @param ui:property The property to store the object itself * @param ui:labelProperty The property used to store the name of the object * @param ui:category The class of objects to be searched, if fixed (else dep on class of subject) * * @returns The HTML widget created */ export function autocompleteField(dom, container, already, subject, form, doc, callbackFunction) { var _a; async function addOneIdAndRefresh(result, name) { var _a; if (!name) { throw new Error('autocompleteField: No name set.'); } const oldValue = kb.the(subject, property, null, doc); if (oldValue) { const oldName = kb.any(oldValue, labelProperty, null, doc); if (oldValue.equals(result) && oldName && oldName.sameTerm(name)) { // console.log('No change: same values.') return; } } const deletables = oldValue ? kb.statementsMatching(subject, property, oldValue, doc) .concat(kb.statementsMatching(oldValue, labelProperty, null, doc)) : []; // console.log('autocompleteField Deletables ' + deletables.map(st => st.toNT())) const insertables = [st(subject, property, result, doc), st(result, labelProperty, name, doc)]; // @@ track the language of the name too! // console.log(`AC form: ${deletables.length} to delete and ${insertables.length} to insert`) try { // console.log('@@@ AC updating ', deletables, insertables) await ((_a = kb.updater) === null || _a === void 0 ? void 0 : _a.updateMany(deletables, insertables)); } catch (err) { callbackFunction(false, err); box.appendChild(widgets.errorMessageBlock(dom, 'Autocomplete form data update error:' + err, null, err)); return; } callbackFunction(true, ''); } async function deleteOne(_result, _name) { var _a; const oldValue = kb.the(subject, property, null, doc); if (!oldValue) { callbackFunction(false, 'NO data to elete'); box.appendChild(widgets.errorMessageBlock(dom, 'Autocomplete delete: no old data!')); return; } // const oldName = kb.any(oldValue as any, labelProperty as any, null, doc) const deletables = kb.statementsMatching(subject, property, oldValue, doc) .concat(kb.statementsMatching(oldValue, labelProperty, null, doc)); // console.log('autocompleteField Deletables ' + deletables.map(st => st.toNT())) const insertables = []; // console.log(`AC form delete: ${deletables.length} to delete and ${insertables.length} to insert`) try { // console.log('@@@ AC updating ', deletables, insertables) await ((_a = kb.updater) === null || _a === void 0 ? void 0 : _a.updateMany(deletables, insertables)); } catch (err) { const e2 = new Error('Autocomplete form data delete error:' + err); callbackFunction(false, err); box.appendChild(widgets.errorMessageBlock(dom, e2, null, err)); return; } callbackFunction(true, ''); // changed } if (subject.termType !== 'NamedNode') { throw new Error('Sorry this field only works on NamedNode subjects (for editable)'); } const kb = store; const formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know const box = dom.createElement('div'); if (container) container.appendChild(box); const lhs = dom.createElement('div'); lhs.setAttribute('class', 'formFieldName'); lhs.setAttribute('style', style.formFieldNameBoxStyle); box.appendChild(lhs); const rhs = dom.createElement('div'); rhs.setAttribute('class', 'formFieldValue'); box.appendChild(rhs); const property = kb.any(form, ns.ui('property')); if (!property) { return box.appendChild(widgets.errorMessageBlock(dom, 'Error: No property given for autocomplete field: ' + form)); } const labelProperty = kb.any(form, ns.ui('labelProperty')) || ns.schema('name'); // Parse the data source into query options const dataSource = kb.any(form, ns.ui('dataSource')); if (!dataSource) { // console.log('@@ connectedStatements ACF ', kb.connectedStatements(form).map(x => x.toNT()).join('\n')) return box.appendChild(widgets.errorMessageBlock(dom, 'Error: No data source given for autocomplete field: ' + form)); } const queryParams = { // targetClass: kb.any(dataSource, ns.ui('targetClass'), null, dataSource.doc()) as NamedNode | undefined, label: kb.anyJS(dataSource, ns.schema('name'), null, dataSource.doc()), logo: (kb.any(dataSource, ns.schema('logo'), null, dataSource.doc())) }; // @@ Should we pass the target class in from the data source definition or use a current type of the subject const targetClass = (kb.any(form, ns.ui('targetClass'), null, form.doc()) || // class in form takes pecedence kb.any(dataSource, ns.ui('targetClass'), null, dataSource.doc())); if (targetClass) { queryParams.targetClass = targetClass; } queryParams.objectURIBase = (kb.any(dataSource, ns.ui('objectURIBase'), null, dataSource.doc()) || undefined); /* if (!queryParams.targetClass) { const klass = kb.any(subject, ns.rdf('type')) as NamedNode | undefined // @@ be more selective of which class if many // @@ todo: Take ALL classes, and compare them with those the data source knows about // with translation where necessary. Find most specific of the classes for the search. if (!klass) throw new Error('Autocomplete: No class specified or is current type of' + subject) queryParams.targetClass = klass } */ const endpoint = kb.anyJS(dataSource, ns.ui('endpoint'), null, dataSource.doc()); if (endpoint) { // SPARQL queryParams.endpoint = endpoint; queryParams.searchByNameQuery = kb.anyJS(dataSource, ns.ui('searchByNameQuery'), null, dataSource.doc()); if (!queryParams.searchByNameQuery) { return box.appendChild(widgets.errorMessageBlock(dom, 'Error: No searchByNameQuery given for endpoint data Source: ' + form)); } queryParams.insitituteDetailsQuery = kb.anyJS(dataSource, ns.ui('insitituteDetailsQuery'), null, dataSource.doc()); } else { // return box.appendChild( // widgets.errorMessageBlock(dom, 'Error: No SPARQL endpoint given for autocomplete field: ' + form)) const searchByNameURI = kb.anyJS(dataSource, ns.ui('searchByNameURI')); if (!searchByNameURI) { return box.appendChild(widgets.errorMessageBlock(dom, 'Error: No searchByNameURI OR sparql endpoint given for dataSource: ' + dataSource)); } queryParams.searchByNameURI = searchByNameURI; } // It can be cleaner to just remove empty fields if you can't edit them anyway const suppressEmptyUneditable = kb.anyJS(form, ns.ui('suppressEmptyUneditable'), null, formDoc); const editable = (_a = kb.updater) === null || _a === void 0 ? void 0 : _a.editable(doc.uri); const autocompleteOptions = { permanent: true, targetClass: queryParams.targetClass, // @@ simplify? queryParams }; autocompleteOptions.size = kb.anyJS(form, ns.ui('size'), null, formDoc) || undefined; let obj = kb.any(subject, property, undefined, doc); if (!obj) { obj = kb.any(form, ns.ui('default')); if (obj) { autocompleteOptions.currentObject = obj; autocompleteOptions.currentName = kb.any(autocompleteOptions.currentObject, labelProperty, null, doc); } else { // No data or default. Should we suprress the whole field? if (suppressEmptyUneditable && !editable) { box.style.display = 'none'; // clutter removal return box; } } } else { // get object and name from target data: autocompleteOptions.currentObject = obj; autocompleteOptions.currentName = kb.any(autocompleteOptions.currentObject, labelProperty, null, doc); } lhs.appendChild(widgets.fieldLabel(dom, property, form)); const barOptions = { editable, dbLookup: true }; renderAutocompleteControl(dom, subject, barOptions, autocompleteOptions, addOneIdAndRefresh, deleteOne).then((control) => { rhs.appendChild(control); }, (err) => { rhs.appendChild(widgets.errorMessageBlock(dom, `Error rendering autocomplete ${form}: ${err}`, '#fee', err)); // }); return box; } // ends //# sourceMappingURL=autocompleteField.js.map