Skip to content

Commit ab72f42

Browse files
Merge pull request SolidOS#264 from solid/fieldFunction
Split out and clean up mostSpecificClassURI, fieldFunction
2 parents c916faf + 22d865f commit ab72f42

9 files changed

Lines changed: 233 additions & 65 deletions

File tree

src/widgets/forms.js

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
/* global alert */
77

88
import { fieldParams } from './forms/fieldParams'
9+
import { field, mostSpecificClassURI, fieldFunction } from './forms/fieldFunction'
910

1011
module.exports = {}
1112

1213
var forms = {}
1314

14-
forms.field = {} // Form field functions by URI of field type.
15+
forms.field = field // Form field functions by URI of field type.
1516

1617
var UI = {
1718
icons: require('../iconBase'),
@@ -90,20 +91,20 @@ forms.field[ns.ui('Form').uri] = forms.field[
9091
var original = []
9192
for (var i = 0; i < p2.length; i++) {
9293
var field = p2[i]
93-
var t = forms.mostSpecificClassURI(field) // Field type
94+
var t = mostSpecificClassURI(field) // Field type
9495
if (t === ui('Options').uri) {
9596
var dep = kb.any(field, ui('dependingOn'))
9697
if (dep && kb.any(subject, dep)) original[i] = kb.any(subject, dep).toNT()
9798
}
9899

99-
var fn = forms.fieldFunction(dom, field)
100+
var fn = fieldFunction(dom, field)
100101

101102
var itemChanged = function (ok, body) {
102103
if (ok) {
103104
for (var j = 0; j < p2.length; j++) {
104105
// This is really messy.
105106
var field = p2[j]
106-
var t = forms.mostSpecificClassURI(field) // Field type
107+
var t = mostSpecificClassURI(field) // Field type
107108
if (t === ui('Options').uri) {
108109
var dep = kb.any(field, ui('dependingOn'))
109110
var newOne = fn(
@@ -385,7 +386,7 @@ forms.field[ns.ui('Multiple').uri] = function (
385386
// var ins = []
386387
// var del = []
387388

388-
var fn = forms.fieldFunction(dom, element)
389+
var fn = fieldFunction(dom, element)
389390
var subField = fn(dom, null, already, object, element, store, itemDone) // p2 was: body. moving to not passing that
390391
subField.subject = object // Keep a back pointer between the DOM array and the RDF objects
391392

@@ -589,7 +590,7 @@ function basicField (
589590
return box
590591
}
591592
lhs.appendChild(forms.fieldLabel(dom, property, form))
592-
var uri = forms.mostSpecificClassURI(form)
593+
var uri = mostSpecificClassURI(form)
593594
var params = forms.fieldParams[uri]
594595
if (params === undefined) params = {} // non-bottom field types can do this
595596
var style = params.style || UI.style.textInputStyle || 'font-size: 100%; margin: 0.1em; padding: 0.1em;'
@@ -971,7 +972,7 @@ forms.field[ns.ui('Choice').uri] = function (
971972
var object = kb.any(subject, property)
972973
function addSubForm () {
973974
object = kb.any(subject, property)
974-
forms.fieldFunction(dom, subForm)(
975+
fieldFunction(dom, subForm)(
975976
dom,
976977
rhs,
977978
already,
@@ -1021,7 +1022,7 @@ forms.field[ns.ui('Comment').uri] = forms.field[
10211022
var contents = kb.any(form, ui('contents'))
10221023
if (!contents) contents = 'Error: No contents in comment field.'
10231024

1024-
var uri = forms.mostSpecificClassURI(form)
1025+
var uri = mostSpecificClassURI(form)
10251026
var params = forms.fieldParams[uri]
10261027
if (params === undefined) {
10271028
params = {}
@@ -1041,41 +1042,6 @@ forms.field[ns.ui('Comment').uri] = forms.field[
10411042
return box
10421043
}
10431044

1044-
/// ////////////// Form-related functions
1045-
1046-
/** Which class of field is this?
1047-
* @param x a field
1048-
* @returns the URI of the most specific class
1049-
*/
1050-
1051-
forms.mostSpecificClassURI = function (x) {
1052-
const kb = UI.store
1053-
var ft = kb.findTypeURIs(x)
1054-
var bot = kb.bottomTypeURIs(ft) // most specific
1055-
var bots = []
1056-
for (var b in bot) bots.push(b)
1057-
// if (bots.length > 1) throw "Didn't expect "+x+" to have multiple bottom types: "+bots
1058-
return bots[0]
1059-
}
1060-
1061-
forms.fieldFunction = function (dom, field) {
1062-
const uri = forms.mostSpecificClassURI(field) // What type
1063-
// const uri = field.uri
1064-
var fun = forms.field[uri]
1065-
UI.log.debug(
1066-
'paneUtils: Going to implement field ' + field + ' of type ' + uri
1067-
)
1068-
if (!fun) {
1069-
return function () {
1070-
return error.errorMessageBlock(
1071-
dom,
1072-
'No handler for field ' + field + ' of type ' + uri
1073-
)
1074-
}
1075-
}
1076-
return fun
1077-
}
1078-
10791045
// A button for editing a form (in place, at the moment)
10801046
//
10811047
// When editing forms, make it yellow, when editing thr form form, pink
@@ -1125,7 +1091,7 @@ forms.appendForm = function (
11251091
store,
11261092
itemDone
11271093
) {
1128-
return forms.fieldFunction(dom, form)(
1094+
return fieldFunction(dom, form)(
11291095
dom,
11301096
container,
11311097
already,
@@ -1343,7 +1309,7 @@ forms.promptForNew = function (
13431309
box.setAttribute('style', `border: 0.05em solid ${UI.style.formBorderColor}; color: ${UI.style.formBorderColor}`) // @@color?
13441310
box.innerHTML = '<h3>New ' + utils.label(theClass) + '</h3>'
13451311

1346-
var formFunction = forms.fieldFunction(dom, form)
1312+
var formFunction = fieldFunction(dom, form)
13471313
var object = forms.newThing(store)
13481314
var gotButton = false
13491315
var itemDone = function (ok, body) {
@@ -1607,7 +1573,7 @@ forms.makeSelectForOptions = function (
16071573
if (ok) {
16081574
select.disabled = false // data written back
16091575
if (newObject) {
1610-
var fn = forms.fieldFunction(dom, options.subForm)
1576+
var fn = fieldFunction(dom, options.subForm)
16111577
fn(
16121578
dom,
16131579
select.parentNode,

src/widgets/forms/comment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import store from '../../store'
22
import ns from '../../ns'
3-
import { mostSpecificClassURI } from '../forms'
3+
import { mostSpecificClassURI } from './fieldFunction'
44
import { fieldParams } from './fieldParams'
55

66
/**

src/widgets/forms/fieldFunction.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Node } from 'rdflib'
2+
import store from '../../store'
3+
import { debug } from '../../log'
4+
import { errorMessageBlock } from '../error'
5+
6+
export const field: { [classUri: string]: FieldFunction } = {} // Form field functions by URI of field type.
7+
8+
export type FieldFunction = (
9+
dom: HTMLDocument, // the DOM
10+
container: HTMLElement | undefined, // if defined, the box will be appended to it
11+
already: { }, // used to avoid looping in nested forms
12+
subject: Node, // the thing for which data will be loaded into the form element
13+
form: Node, // the RDF declaration for what the form should have
14+
doc: Node, // the online RDF document for data binding (form input values will be read/saved automatically)
15+
callbackFunction: (ok: boolean, errorMessage: string) => void // this will be called when data changes (TODO: check this with unit tests)
16+
) => HTMLElement
17+
18+
/**
19+
* Which class of field is this? Relies on http://www.w3.org/2000/01/rdf-schema#subClassOf and
20+
* https://linkeddata.github.io/rdflib.js/doc/classes/formula.html#bottomtypeuris
21+
* to find the most specific RDF type if there are multiple.
22+
*
23+
* @param x a form field, e.g. `namedNode('https://timbl.com/timbl/Public/Test/Forms/individualForm.ttl#fullNameField')`
24+
* @returns the URI of the most specific known class, e.g. `http://www.w3.org/ns/ui#SingleLineTextField`
25+
*/
26+
export function mostSpecificClassURI (x: Node): string {
27+
const kb = store
28+
const ft = kb.findTypeURIs(x)
29+
const bot = kb.bottomTypeURIs(ft) // most specific
30+
const bots: any[] = []
31+
for (const b in bot) bots.push(b)
32+
// if (bots.length > 1) throw "Didn't expect "+x+" to have multiple bottom types: "+bots
33+
return bots[0]
34+
}
35+
36+
/**
37+
* Returns a function that creates a form widget
38+
* @param dom unused
39+
* @param fieldInQuestion the field for which to create a form, e.g. namedNode('https://timbl.com/timbl/Public/Test/Forms/individualForm.ttl#fullNameField')
40+
*/
41+
export function fieldFunction (dom: any /* unused */, fieldInQuestion: Node): FieldFunction {
42+
const uri = mostSpecificClassURI(fieldInQuestion) // What type
43+
// const uri = field.uri
44+
const fun = field[uri]
45+
debug(
46+
'paneUtils: Going to implement field ' + fieldInQuestion + ' of type ' + uri
47+
)
48+
if (!fun) {
49+
return function (dom2: HTMLDocument, container?: HTMLElement): HTMLElement {
50+
const box = errorMessageBlock(
51+
dom2,
52+
'No handler for field ' + fieldInQuestion + ' of type ' + uri
53+
)
54+
if (container) container.appendChild(box)
55+
56+
return box
57+
}
58+
}
59+
return fun
60+
}

test/unit/tabs.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('tabWidget', () => {
4242
})
4343
})
4444

45-
afterAll(() => clearStore())
45+
afterAll(clearStore)
4646

4747
it('creates an element', () => {
4848
expect(tabWidgetElement.tagName).toEqual('DIV')
@@ -147,7 +147,7 @@ describe('tabWidget', () => {
147147
})
148148

149149
describe('option ordered', () => {
150-
afterAll(() => clearStore())
150+
afterAll(clearStore)
151151

152152
it('allows for tabs to be fetched from triples instead of a collection', () => {
153153
const predicate = meeting('toolList')

test/unit/widgets/buttons.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ describe('findImage', () => {
183183
const subject = sym('https://domain.tld/#test')
184184
const imageObject = sym('https://domain.tld/#image')
185185

186-
afterEach(() => clearStore())
186+
afterEach(clearStore)
187187

188188
it('exists', () => {
189189
expect(findImage).toBeInstanceOf(Function)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`fieldFunction function returned if no matching function exists appends an error block to a container 1`] = `
4+
<div>
5+
<div
6+
style="margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: #fee; color:black;"
7+
>
8+
No handler for field &lt;http://example.com/#form&gt; of type http://example.com/#unknown-type
9+
</div>
10+
</div>
11+
`;
12+
13+
exports[`fieldFunction function returned if no matching function exists returns an error block 1`] = `
14+
<div
15+
style="margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: #fee; color:black;"
16+
>
17+
No handler for field &lt;http://example.com/#form&gt; of type http://example.com/#unknown-type
18+
</div>
19+
`;
20+
21+
exports[`fieldFunction function returned if subject type undefined appends an error block to a container 1`] = `
22+
<div>
23+
<div
24+
style="margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: #fee; color:black;"
25+
>
26+
No handler for field &lt;http://example.com/#doesnt-exist&gt; of type undefined
27+
</div>
28+
</div>
29+
`;
30+
31+
exports[`fieldFunction function returned if subject type undefined returns an error block 1`] = `
32+
<div
33+
style="margin: 0.1em; padding: 0.5em; border: 0.05em solid gray; background-color: #fee; color:black;"
34+
>
35+
No handler for field &lt;http://example.com/#doesnt-exist&gt; of type undefined
36+
</div>
37+
`;

test/unit/widgets/forms/__snapshots__/index.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ exports[`ColorField runs 1`] = `
4747
</tr>
4848
`;
4949

50+
exports[`Comment runs 1`] = `
51+
<div>
52+
<undefined>
53+
&lt;http://example.com/#bla&gt;
54+
</undefined>
55+
</div>
56+
`;
57+
5058
exports[`DateField runs 1`] = `
5159
<tr>
5260
<td
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { namedNode } from 'rdflib'
2+
import ns from '../../../../src/ns'
3+
import uiStore from '../../../../src/store'
4+
5+
import {
6+
field,
7+
fieldFunction,
8+
mostSpecificClassURI
9+
} from '../../../../src/widgets/forms/fieldFunction'
10+
import { clearStore } from '../../helpers/clearStore'
11+
12+
afterEach(clearStore)
13+
14+
describe('mostSpecificClassURI', () => {
15+
it('exists', () => {
16+
expect(mostSpecificClassURI).toBeInstanceOf(Function)
17+
})
18+
it('reports the RDF type if there is only one', () => {
19+
const form = namedNode('http://example.com/#form')
20+
uiStore.add(form, ns.rdf('type'), namedNode('http://example.com/#type'), namedNode('http://example.com/'))
21+
expect(mostSpecificClassURI(form)).toEqual('http://example.com/#type')
22+
})
23+
it('reports the subtype if there are one super and one sub type', () => {
24+
const node = namedNode('http://example.com/#form')
25+
uiStore.add(node, ns.rdf('type'), namedNode('http://example.com/#human'), namedNode('http://example.com/'))
26+
uiStore.add(node, ns.rdf('type'), namedNode('http://example.com/#employee'), namedNode('http://example.com/'))
27+
uiStore.add(namedNode('http://example.com/#employee'), ns.rdfs('subClassOf'), namedNode('http://example.com/#human'), namedNode('http://example.com/'))
28+
expect(mostSpecificClassURI(node)).toEqual('http://example.com/#employee')
29+
})
30+
})
31+
32+
describe('fieldFunction', () => {
33+
it('exists', () => {
34+
expect(fieldFunction).toBeInstanceOf(Object)
35+
})
36+
it('returns the field function if it exists', () => {
37+
// create a function for type http://example.com/#type
38+
const myFunction = () => document.createElement('div')
39+
field['http://example.com/#type'] = myFunction
40+
41+
// create a field of type http://example.com/#type
42+
const form = namedNode('http://example.com/#form')
43+
uiStore.add(form, ns.rdf('type'), namedNode('http://example.com/#type'), namedNode('http://example.com/'))
44+
45+
expect(fieldFunction(undefined, form)).toEqual(myFunction)
46+
})
47+
48+
describe('function returned if subject type undefined', () => {
49+
const fn = fieldFunction(undefined, namedNode('http://example.com/#doesnt-exist'))
50+
it('returns an error block', () => {
51+
const result = fn(document, document.createElement('div'), {},
52+
namedNode('http://example.com/#subject'),
53+
namedNode('http://example.com/#form'),
54+
namedNode('http://example.com/'),
55+
() => {})
56+
expect(result).toMatchSnapshot()
57+
})
58+
it('appends an error block to a container', () => {
59+
const container = document.createElement('div')
60+
fn(document, container, {},
61+
namedNode('http://example.com/#subject'),
62+
namedNode('http://example.com/#form'),
63+
namedNode('http://example.com/'),
64+
() => {})
65+
expect(container).toMatchSnapshot()
66+
})
67+
})
68+
69+
describe('function returned if no matching function exists', () => {
70+
// create a field of type http://example.com/#unknown-type
71+
const form = namedNode('http://example.com/#form')
72+
uiStore.add(form, ns.rdf('type'), namedNode('http://example.com/#unknown-type'), namedNode('http://example.com/'))
73+
const fn = fieldFunction(undefined, form)
74+
75+
it('returns an error block', () => {
76+
const result = fn(document, undefined, {},
77+
namedNode('http://example.com/#subject'),
78+
namedNode('http://example.com/#form'),
79+
namedNode('http://example.com/'),
80+
() => {})
81+
expect(result).toMatchSnapshot()
82+
})
83+
it('appends an error block to a container', () => {
84+
const container = document.createElement('div')
85+
fn(document, container, {},
86+
namedNode('http://example.com/#subject'),
87+
namedNode('http://example.com/#form'),
88+
namedNode('http://example.com/'),
89+
() => {})
90+
expect(container).toMatchSnapshot()
91+
})
92+
})
93+
})

0 commit comments

Comments
 (0)