Skip to content

Commit 517d173

Browse files
authored
fix for multi-select bug (SolidOS#515)
1 parent a07bb68 commit 517d173

2 files changed

Lines changed: 299 additions & 3 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2+
3+
<html xmlns="http://www.w3.org/1999/xhtml">
4+
5+
<head>
6+
<meta name="generator"
7+
content="HTML Tidy for Mac OS X (vers 31 October 2006 - Apple Inc. build 15.17), see www.w3.org" />
8+
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii" />
9+
10+
<title>Form structures 3 - Choice</title>
11+
<link type="text/css" rel="stylesheet" href="../style/demo-style.css" />
12+
13+
<!-- used for production-ready documentation -->
14+
<!-- note: one does not need whole mashlib; solid-ui is sufficient -->
15+
<script type="text/javascript" src="https://solidos.github.io/mashlib/dist/mashlib.js"></script>
16+
<!-- to use only solid-ui -->
17+
<!-- <script type="text/javascript" src="https://solidos.github.io/solid-ui/dist/main.js"></script> -->
18+
19+
<!-- used for local development -->
20+
<!-- make sure to use the latest build version by running npm run build-form-examples first -->
21+
<!--<script type="text/javascript" src="main.js"></script>-->
22+
<script type="text/javascript" src="test-form.js"></script>
23+
</head>
24+
25+
<body>
26+
27+
<h1>Role Types in preference - Classifier</h1>
28+
<pre id="Prolog">
29+
@prefix foaf: &lt;http://xmlns.com/foaf/0.1/&gt;.
30+
@prefix sched: &lt;http://www.w3.org/ns/pim/schedule#&gt;.
31+
@prefix cal: &lt;http://www.w3.org/2002/12/cal/ical#&gt;.
32+
@prefix dc: &lt;http://purl.org/dc/elements/1.1/&gt;.
33+
@prefix rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;.
34+
@prefix ui: &lt;http://www.w3.org/ns/ui#&gt;.
35+
36+
@prefix schema: &lt;http://schema.org/>.
37+
38+
@prefix trip: &lt;http://www.w3.org/ns/pim/trip#&gt;.
39+
@prefix vcard: &lt;http://www.w3.org/2006/vcard/ns#&gt;.
40+
@prefix xsd: &lt;http://www.w3.org/2001/XMLSchema#&gt;.
41+
@prefix ex: &lt;#&gt;. # Things in the examples
42+
@prefix : &lt;#&gt;.
43+
</pre>
44+
45+
<div>
46+
<table style="border-collapse: collapse; border: 0.1em solid gray"
47+
summary="Table of input data values and UI outputs generated">
48+
<tbody>
49+
<tr>
50+
<td>Form data for ui:Choice</td>
51+
<td>Raw data</td>
52+
<td>Form</td>
53+
</tr>
54+
<tr class="form-demo">
55+
<td class="input"
56+
source="https://solidos.solidcommunity.net/public/2021/solidUiFormTestData/preferenceFormText.ttl#this">
57+
</td>
58+
<td class="target"
59+
source="https://solidos.solidcommunity.net/public/2021/solidUiFormTestData/preferenceOntologyData.ttl">
60+
</td>
61+
<td class="output"></td>
62+
</tr>
63+
</tbody>
64+
</table>
65+
</div>
66+
</body>
67+
68+
</html>

src/widgets/forms.js

Lines changed: 231 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,213 @@ export function makeDescription (
13291329
return group
13301330
}
13311331

1332+
/** Make SELECT element to select options
1333+
//
1334+
// @param subject - a term, the subject of the statement(s) being edited.
1335+
// @param predicate - a term, the predicate of the statement(s) being edited
1336+
// @param possible - a list of terms, the possible value the object can take
1337+
// @param options.multiple - Boolean - Whether more than one at a time is allowed
1338+
// @param options.nullLabel - a string to be displayed as the
1339+
// option for none selected (for non multiple)
1340+
// @param options.mint - User may create thing if this sent to the prompt string eg "New foo"
1341+
// @param options.subForm - If mint, then the form to be used for minting the new thing
1342+
// @param dataDoc - The web document being edited
1343+
// @param callbackFunction - takes (boolean ok, string errorBody)
1344+
*/
1345+
export function makeSelectForClassifierOptions (
1346+
dom,
1347+
kb,
1348+
subject,
1349+
predicate,
1350+
possible,
1351+
options,
1352+
dataDoc,
1353+
callbackFunction
1354+
) {
1355+
log.debug('Select list length now ' + possible.length)
1356+
let n = 0
1357+
const uris = {} // Count them
1358+
const editable = kb.updater.editable(dataDoc.uri)
1359+
1360+
for (let i = 0; i < possible.length; i++) {
1361+
const sub = possible[i] // @@ Maybe; make this so it works with blank nodes too
1362+
if (!sub.uri) debug.warn(`makeSelectForClassifierOptions: option does not have an uri: ${sub}, with predicate: ${predicate}`)
1363+
if (!sub.uri || sub.uri in uris) continue
1364+
uris[sub.uri] = true
1365+
n++
1366+
} // uris is now the set of possible options
1367+
if (n === 0 && !options.mint) {
1368+
return errorMessageBlock(
1369+
dom,
1370+
"Can't do selector with no options, subject= " +
1371+
subject +
1372+
' property = ' +
1373+
predicate +
1374+
'.'
1375+
)
1376+
}
1377+
1378+
log.debug('makeSelectForClassifierOptions: dataDoc=' + dataDoc)
1379+
let actual
1380+
const getActual = function () {
1381+
actual = {}
1382+
if (predicate.sameTerm(ns.rdf('type'))) {
1383+
actual = kb.findTypeURIs(subject)
1384+
} else {
1385+
kb.each(subject, predicate, null, dataDoc).forEach(function (x) {
1386+
actual[x.uri] = true
1387+
})
1388+
}
1389+
return actual
1390+
}
1391+
actual = getActual()
1392+
1393+
const onChange = function (_e) {
1394+
select.disabled = true // until data written back - gives user feedback too
1395+
const ds = []
1396+
let is = []
1397+
const removeValue = function (t) {
1398+
if (kb.holds(subject, predicate, t, dataDoc)) {
1399+
ds.push($rdf.st(subject, predicate, t, dataDoc))
1400+
}
1401+
}
1402+
let newObject
1403+
for (let i = 0; i < select.options.length; i++) {
1404+
const opt = select.options[i]
1405+
if (opt.selected && opt.AJAR_mint) {
1406+
if (options.mintClass) {
1407+
const thisForm = promptForNew(
1408+
dom,
1409+
kb,
1410+
subject,
1411+
predicate,
1412+
options.mintClass,
1413+
null,
1414+
dataDoc,
1415+
function (ok, body) {
1416+
if (!ok) {
1417+
callbackFunction(ok, body, { change: 'new' }) // @@ if ok, need some form of refresh of the select for the new thing
1418+
}
1419+
}
1420+
)
1421+
select.parentNode.appendChild(thisForm)
1422+
newObject = thisForm.AJAR_subject
1423+
} else {
1424+
newObject = newThing(dataDoc)
1425+
}
1426+
is.push($rdf.st(subject, predicate, newObject, dataDoc))
1427+
if (options.mintStatementsFun) {
1428+
is = is.concat(options.mintStatementsFun(newObject))
1429+
}
1430+
}
1431+
if (!opt.AJAR_uri) continue // a prompt or mint
1432+
if (opt.selected && !(opt.AJAR_uri in actual)) {
1433+
// new class
1434+
is.push($rdf.st(subject, predicate, kb.sym(opt.AJAR_uri), dataDoc))
1435+
}
1436+
if (!opt.selected && opt.AJAR_uri in actual) {
1437+
// old class
1438+
removeValue(kb.sym(opt.AJAR_uri))
1439+
}
1440+
if (opt.selected) select.currentURI = opt.AJAR_uri
1441+
}
1442+
let sel = select.subSelect // All subclasses must also go
1443+
while (sel && sel.currentURI) {
1444+
removeValue(kb.sym(sel.currentURI))
1445+
sel = sel.subSelect
1446+
}
1447+
sel = select.superSelect // All superclasses are redundant
1448+
while (sel && sel.currentURI) {
1449+
removeValue(kb.sym(sel.currentURI))
1450+
sel = sel.superSelect
1451+
}
1452+
function doneNew (ok, _body) {
1453+
callbackFunction(ok, { widget: 'select', event: 'new' })
1454+
}
1455+
log.info('makeSelectForClassifierOptions: data doc = ' + dataDoc)
1456+
kb.updater.update(ds, is, function (uri, ok, body) {
1457+
actual = getActual() // refresh
1458+
if (ok) {
1459+
select.disabled = false // data written back
1460+
if (newObject) {
1461+
const fn = fieldFunction(dom, options.subForm)
1462+
fn(
1463+
dom,
1464+
select.parentNode,
1465+
{},
1466+
newObject,
1467+
options.subForm,
1468+
dataDoc,
1469+
doneNew
1470+
)
1471+
}
1472+
} else {
1473+
return select.parentNode.appendChild(errorMessageBlock(dom, 'Error updating data in select: ' + body))
1474+
}
1475+
if (callbackFunction) callbackFunction(ok, { widget: 'select', event: 'change' })
1476+
})
1477+
}
1478+
1479+
const select = dom.createElement('select')
1480+
select.setAttribute('style', style.formSelectSTyle)
1481+
if (options.multiple) select.setAttribute('multiple', 'true')
1482+
select.currentURI = null
1483+
1484+
select.refresh = function () {
1485+
actual = getActual() // refresh
1486+
for (let i = 0; i < select.children.length; i++) {
1487+
const option = select.children[i]
1488+
if (option.AJAR_uri) {
1489+
option.selected = option.AJAR_uri in actual
1490+
}
1491+
}
1492+
select.disabled = false // unlocked any conflict we had got into
1493+
}
1494+
1495+
for (const uri in uris) {
1496+
const c = kb.sym(uri)
1497+
const option = dom.createElement('option')
1498+
if (options.disambiguate) {
1499+
option.appendChild(dom.createTextNode(utils.labelWithOntology(c, true))) // Init. cap
1500+
} else {
1501+
option.appendChild(dom.createTextNode(utils.label(c, true))) // Init.
1502+
}
1503+
const backgroundColor = kb.any(
1504+
c,
1505+
kb.sym('http://www.w3.org/ns/ui#backgroundColor')
1506+
)
1507+
if (backgroundColor) {
1508+
option.setAttribute(
1509+
'style',
1510+
'background-color: ' + backgroundColor.value + '; '
1511+
)
1512+
}
1513+
option.AJAR_uri = uri
1514+
if (uri in actual) {
1515+
option.setAttribute('selected', 'true')
1516+
select.currentURI = uri
1517+
// dump("Already in class: "+ uri+"\n")
1518+
}
1519+
select.appendChild(option)
1520+
}
1521+
if (editable && options.mint) {
1522+
const mint = dom.createElement('option')
1523+
mint.appendChild(dom.createTextNode(options.mint))
1524+
mint.AJAR_mint = true // Flag it
1525+
select.insertBefore(mint, select.firstChild)
1526+
}
1527+
if (select.currentURI == null && !options.multiple) {
1528+
const prompt = dom.createElement('option')
1529+
prompt.appendChild(dom.createTextNode(options.nullLabel))
1530+
select.insertBefore(prompt, select.firstChild)
1531+
prompt.selected = true
1532+
}
1533+
if (editable) {
1534+
select.addEventListener('change', onChange, false)
1535+
}
1536+
return select
1537+
} // makeSelectForClassifierOptions
1538+
13321539
/** Make SELECT element to select options
13331540
//
13341541
// @param subject - a term, the subject of the statement(s) being edited.
@@ -1503,20 +1710,41 @@ export function makeSelectForCategory (
15031710
) {
15041711
const du = kb.any(category, ns.owl('disjointUnionOf'))
15051712
let subs
1713+
let multiple = false
15061714
if (!du) {
15071715
subs = kb.each(undefined, ns.rdfs('subClassOf'), category)
1716+
multiple = true
15081717
} else {
15091718
subs = du.elements
15101719
}
15111720
log.debug('Select list length ' + subs.length)
1512-
1513-
return makeSelectForOptions(
1721+
if (subs.length === 0) {
1722+
return errorMessageBlock(
1723+
dom,
1724+
"Can't do " +
1725+
(multiple ? 'multiple ' : '') +
1726+
'selector with no subclasses of category: ' +
1727+
category
1728+
)
1729+
}
1730+
if (subs.length === 1) {
1731+
return errorMessageBlock(
1732+
dom,
1733+
"Can't do " +
1734+
(multiple ? 'multiple ' : '') +
1735+
'selector with only 1 subclass of category: ' +
1736+
category +
1737+
':' +
1738+
subs[1]
1739+
)
1740+
}
1741+
return makeSelectForClassifierOptions(
15141742
dom,
15151743
kb,
15161744
subject,
15171745
ns.rdf('type'),
15181746
subs,
1519-
{ nullLabel: '* Select type *' },
1747+
{ multiple, nullLabel: '* Select type *' },
15201748
dataDoc,
15211749
callbackFunction
15221750
)

0 commit comments

Comments
 (0)