solid-ui
Version:
UI library for Solid applications
263 lines • 10.5 kB
JavaScript
import { Literal, st } from 'rdflib';
import { solidLogicSingleton } from 'solid-logic';
import ns from '../../ns';
import { style } from '../../style';
import styleConstants from '../../styleConstants';
import { label } from '../../utils';
import { errorMessageBlock } from '../error';
import { mostSpecificClassURI } from './fieldFunction';
import { fieldParams } from './fieldParams';
const store = solidLogicSingleton.store;
/* Style and create a name, value pair
*/
export function renderNameValuePair(dom, kb, box, form, label) {
// const property = kb.any(form, ns.ui('property'))
box.style.display = 'flex';
box.style.flexDirection = 'row';
const lhs = box.appendChild(dom.createElement('div'));
lhs.style.width = styleConstants.formFieldNameBoxWidth;
const rhs = box.appendChild(dom.createElement('div'));
lhs.setAttribute('class', 'formFieldName');
lhs.setAttribute('style', style.formFieldNameBoxStyle);
rhs.setAttribute('class', 'formFieldValue');
if (label) {
lhs.appendChild(dom.createTextNode(label));
}
else if (kb.any(form, ns.ui('property'))) { // Assume more space for error on right
lhs.appendChild(fieldLabel(dom, kb.any(form, ns.ui('property')), form));
}
else {
rhs.appendChild(errorMessageBlock(dom, 'No property or label given for form field: ' + form));
lhs.appendChild(dom.createTextNode('???'));
}
return rhs;
}
/**
* Create an anchor element with a label as the anchor text.
*
* @param dom The DOM
* @param property href for the anchor element
* @param fieldInQuestion field to produce a label for
*
* @internal exporting this only for unit tests
*/
export function fieldLabel(dom, property, fieldInQuestion) {
let lab = store.any(fieldInQuestion, ns.ui('label'));
if (!lab)
lab = label(property, true); // Init capital
if (property === undefined) {
return dom.createTextNode('@@Internal error: undefined property');
}
const anchor = dom.createElement('a');
/* istanbul ignore next */
if (property.uri)
anchor.setAttribute('href', property.uri);
anchor.setAttribute('style', 'color: #3B5998; text-decoration: none;'); // Not too blue and no underline
anchor.textContent = lab;
return anchor;
}
/**
* Returns the document for the first quad that matches
* the subject and predicate provided, or default if that
* store is not editable.
*
* @param subject Subject about which we want to find an editable RDF document
* @param predicate Predicate about which we want to find an editable RDF document
* @param def default RDF document to return if none found
*
* @internal exporting this only for unit tests
*/
export function fieldStore(subject, predicate, def) {
const sts = store.statementsMatching(subject, predicate);
if (sts.length === 0)
return def; // can used default as no data yet
if (!store.updater) {
throw new Error('Store has no updater');
}
if (sts.length > 0 &&
sts[0].why.value &&
store.updater.editable(sts[0].why.value, store)) {
return store.sym(sts[0].why.value);
}
return def;
}
/**
* Render a basic form field
*
* The same function is used for many similar one-value fields, with different
* regexps used to validate.
*
* @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?
*
* @returns The HTML widget created
*/
export function basicField(dom, container, already, subject, form, doc, callbackFunction) {
const kb = store;
const formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know
const box = dom.createElement('div');
const property = kb.any(form, ns.ui('property'));
if (container)
container.appendChild(box);
if (!property) {
return box.appendChild(errorMessageBlock(dom, 'Error: No property given for text field: ' + form));
}
const rhs = renderNameValuePair(dom, kb, box, form);
// 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 uri = mostSpecificClassURI(form);
let params = fieldParams[uri];
if (params === undefined)
params = { style: '' }; // non-bottom field types can do this
const paramStyle = params.style || '';
const inputStyle = style.textInputStyle + paramStyle;
const field = dom.createElement('input');
field.style = inputStyle;
rhs.appendChild(field);
field.setAttribute('type', params.type ? params.type : 'text');
const fieldType = (field.getAttribute('type') || '').toLowerCase();
const deferWhileFocused = fieldType === 'date' || fieldType === 'datetime-local';
const size = kb.anyJS(form, ns.ui('size')) || styleConstants.textInputSize || 20;
field.setAttribute('size', size);
const maxLength = kb.any(form, ns.ui('maxLength'));
field.setAttribute('maxLength', maxLength ? '' + maxLength : styleConstants.basicMaxLength);
doc = doc || fieldStore(subject, property, doc);
let obj = kb.any(subject, property, undefined, doc);
if (!obj) {
obj = kb.any(form, ns.ui('default'));
}
if (obj && obj.value && params.uriPrefix) {
// eg tel: or mailto:
field.value = decodeURIComponent(obj.value.replace(params.uriPrefix, '')) // should have no spaces but in case
.replace(/ /g, '');
}
else if (obj) {
/* istanbul ignore next */
field.value = obj.value || obj.value || '';
}
field.setAttribute('style', inputStyle);
if (!kb.updater) {
throw new Error('kb has no updater');
}
if (!kb.updater.editable(doc.uri)) {
field.readOnly = true // was: disabled. readOnly is better
;
field.style = style.textInputStyleUneditable + paramStyle;
if (suppressEmptyUneditable && field.value === '') {
box.style.display = 'none'; // clutter
}
return box;
}
// read-write:
field.addEventListener('keyup', function (_e) {
if (params.pattern) {
field.setAttribute('style', inputStyle +
(field.value.match(params.pattern)
? 'color: green;'
: 'color: red;'));
}
}, true);
field.addEventListener('change', function (_e) {
if (deferWhileFocused && dom.activeElement === field) {
if (field.dataset) {
field.dataset.deferredChange = 'true';
}
return;
}
// i.e. lose focus with changed data
if (params.pattern && !field.value.match(params.pattern))
return;
const disabledForSave = !deferWhileFocused;
if (disabledForSave) {
field.disabled = true; // See if this stops getting two dates from fumbling, e.g., the chrome datepicker.
}
field.setAttribute('style', inputStyle + 'color: gray;'); // pending
const ds = kb.statementsMatching(subject, property); // remove any multiple values
let result;
if (params.namedNode) {
result = kb.sym(field.value);
}
else if (params.uriPrefix) {
result = encodeURIComponent(field.value.replace(/ /g, ''));
result = kb.sym(params.uriPrefix + field.value);
}
else {
if (params.dt) {
result = new Literal(field.value.trim(), undefined, ns.xsd(params.dt));
}
else {
result = new Literal(field.value);
}
}
let is = ds.map(statement => st(statement.subject, statement.predicate, result, statement.why)); // can include >1 doc
if (is.length === 0) {
// or none
is = [st(subject, property, result, doc)];
}
function updateMany(ds, is, callback) {
const docs = [];
is.forEach(st => {
if (!docs.includes(st.why.uri))
docs.push(st.why.uri);
});
ds.forEach(st => {
/* istanbul ignore next */
if (!docs.includes(st.why.uri))
docs.push(st.why.uri);
});
/* istanbul ignore next */
if (docs.length === 0) {
throw new Error('updateMany has no docs to patch');
}
if (!kb.updater) {
throw new Error('kb has no updater');
}
if (docs.length === 1) {
return kb.updater.update(ds, is, callback);
}
// return kb.updater.update(ds, is, callback)
const doc = docs.pop();
const is1 = is.filter(st => st.why.uri === doc);
const is2 = is.filter(st => st.why.uri !== doc);
const ds1 = ds.filter(st => st.why.uri === doc);
const ds2 = ds.filter(st => st.why.uri !== doc);
kb.updater.update(ds1, is1, function (uri, ok, body) {
if (ok) {
updateMany(ds2, is2, callback);
}
else {
callback(uri, ok, body);
}
});
}
updateMany(ds, is, function (uri, ok, body) {
// kb.updater.update(ds, is, function (uri, ok, body) {
if (ok) {
if (disabledForSave) {
field.disabled = false;
}
field.setAttribute('style', inputStyle);
}
else {
box.appendChild(errorMessageBlock(dom, body));
}
callbackFunction(ok, body);
});
}, true);
field.addEventListener('blur', function (_e) {
if (deferWhileFocused &&
field.dataset &&
field.dataset.deferredChange === 'true') {
delete field.dataset.deferredChange;
const event = new Event('change', { bubbles: true });
field.dispatchEvent(event);
}
}, true);
return box;
}
//# sourceMappingURL=basic.js.map