From d8478b5675381975dc86967b057b255e9a6f8174 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sun, 17 Nov 2019 16:17:11 +0000 Subject: [PATCH 01/32] eslint --- Documentation/forms-intro.html | 879 ++++++++++++++++++++++----------- README.md | 7 +- package-lock.json | 76 ++- package.json | 2 +- src/acl/acl-control.ts | 2 +- src/utils.js | 62 ++- src/widgets/forms.js | 444 ++++++++++++----- 7 files changed, 1044 insertions(+), 428 deletions(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 806184738..97073a731 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -1,283 +1,598 @@ - - -solid-ui: Introduction using forms - - - -

Using Forms in the UI ontology

- -

The User Interface ontology at http://www.w3.org/ns/ui defines RDF terms for - describing forms. The solid-ui projet provides functions to - use these forms within your web application to create a quick user interface solution. This document describes how. -

- -

The form system allows you to define a user interface - declaratively in RDF. - In your web app, you then: -

    -
  1. make sure the ontology files are loaded
  2. -
  3. load the file with the form itself
  4. -
  5. call UI.widgets.appendForm(dom, container, {}, subject, form, doc, callback)
  6. -
-

-

- where - - - - - - - - -
domis the DOM HTMLDocument object, a/k/a document
containeris a DOM element to contain the form
{}are unused at present
subjectis the RDF thing about which data will be stored
formis the RDF object in the store for the form
docis the RDF document on the web where the data will be stored. Often, subject.doc()
callbackis a function taking an error flag and a message (if the error flag is true)
- If the form is a complex form, as the user adds more data, more form UI will be created. - The data in each field is saved back to the web the moment the user has entered it. There is no general Save Button. -

-

-There is a form form for editing forms. -It is in the form ontology itself. -

- -

-You can of course go and write other implementations of the form system using -your favorite user interface language. -

-

Go to the source

-

- -

Form field types

-

Form fields may be named or blank nodes in your file; -the form system does not care. -It is often useful to name them to keep track of them. -

-

Below, all Field Classes and Properties are in the UI namespace, - http://www.w3.org/ns/ui#, except - the data types, like Integer, which are in the normal XSD namespace. -

-

- Here are some properties which you can use with any field (except the documentation fields). - - - - -
labelStringA label for the form field. This is the prompt for the user, e.g., "Name", "Employer".
propertyrdf:PropertyWhen the user enters the data, it is stored in the web as a triple with this property as its predicate.
default[according to field type] OptionalThe input control is set to this value by default. - It is easiest for the user to enter this value. (This value is not automatically stored by the form system if the user does not select or enter it in some way.
-Other properties are given for each field type. -

-

Form

-

The form itself has a collection of fields. -The parts property gives an order list of -the fields in each form. - - - - -
partsrdf:Collection (aka List, Array) of FieldThe parts of the form in the order in which they are
partField (Obsolete)A field which is a part of the form or group. This property is obsolete. Use parts.
-If you use the obsolete "part" method for listing the parts of a form, then -each field needs an additional property: - - - -
sequenceIntegerThe parts of the form in the order in which they are
- -For each part, declare its type, and the extra data that type requires, as below. -

-

Group

-

Group is a field which is just a collection of other fields. - It is in fact interchangeable with Form. - - -

Single Value fields - Numeric

- -

These prompt the user for a single value. They typically take default values, -and min and max values.

- -

BooleanField

-

A checkbox on the form, stored an RDF boolean true or false value.

- -

TriStateField

-

A checkbox on the form, stores an RDF boolean true or false value, - or no value if the box is left in its third, blank state.

- -

IntegerField

-

An RDF integer value

- -

DecimalField

-

An RDF decimal value. Useful for monetary amounts

- -

FloatField

-

A floating point number

- - -

Single Value fields: Special Types

- -

ColorField

-

A color picker is used, and genertes a string which is a CSS_compatible color in -a string like #ffeebb

- -

DateField

- -

Uses a date picker on a good browser. - Leaves an RDF date literal as is value.

-

DateTimeField

-

- -Leaves an RDF dateTime literal as is value.

-

PhoneField

-

-Leaves as its value a named node with a uri which starts 'tel:'

-

EmailField

-

- -Leaves as its value a named now with a uri which starts 'mailto:' -

-

Single Value fields - Text

- -

SingleLineTextField

-

- -

MultiLineTextField

-

- -

Complex fields

-

Group

-

- -A group is simply a static set of fields of any type. -Its properties are the same as for Form. -

-

Choice

-

The user choses an item from a class. - - - - - - -
fromrdfs:ClassThe selected thing must be a member of this class. E.g. Person.
propertyrdf:PropertyWhen the item is found, the new data links it from the subject with tis property. E.g. friend.
canMintNewxsd:BooleanIf the user doesn't find the thing they want, can they introduce a item of that class by filling in a form about it? [Boolean]
-If a new thing is minted, that will be done with a form which is a ui:creationForm for the class. -

- -

Multiple

-

- -

When the subject can have several of the same thing, -like friends, ro phone numbers, then the Multiple field -allows this. The user clicks on the green plus icon, and is prompted -for a subform for the related thing. -The user can also delete existing ones.

-

For each new thing, the system generates an arbitrary (timestamp) URI within the file -where the data is being stored. The subform is then about that thing: the subject of the subform is not -the subject of the original form. It is the field, or the address, and so on. -

-

Classifier

-

- - -
categoryrdfs:ClassThe object will already be in this class. - The user will select subclasses of this class.
- This form field leverages the ontology heavily. - It pulls the subclasses of the given class, and makes a pop-up menu -for the user to chose one. -If and only if the ontology says that the class is a disjoint union (owl:disjointUnionOf) of the subclasses, then the -user interface will only allow the user to pick one. -If the user picks a subclass, and the ontology shows that that subclass has its own subclasses, then the -user will be prompted to pick one of those, to (if they like) further refine the selection. And so on. -

- -

The classifier pops a menu to allow the user to select a set of valued to classify the subject. -

-

Options

- -

And Options field is the 'case statement' of the form system. - It will chose at runtime a subfield depending - on a property, often the type, of the subject. Often used after a classifier. -

-

- - - - - -
Options propertyrangesignificance
dependingOnrdf:PropertyThe predicate in the data used to select the case.
caseCaseA case object, with for x use y. (2 or more cases)
-and for each case: - - - - -
Case propertyrangesignificance
for[The range of the dependingOn property]The value this case applies to
useFieldsub form to be used in case the value matches the "for"
- -

- - -

Documentation fields

-

Heading

-

Help the user find parts of a long form, or just for a title of a short form. - - -
contentsStringThe text content of the heading
- -

- -

Comment

-

Use comments in the form to help users understand what is going on, -what their options are, and what the fields mean. - - - -
contentsStringThe text content of the comment. - (This should be displayed by form systems as pre-wrap mode)
-

- - -

Conclusion

-

The form language and the form implementation in solid-ui - can't do everything, but it can handle - a pretty wide selection of tasks in common -daily life at home and at work. -It can be vary efficient as developers can reuse material between forms. -Users can even generate their own forms. -

-Future directions include separate implementations of the form UI code in -for various platforms, and using various UI frameworks. -There may also be extension of the system with new field types, -more options for setting style from various sources, -

- - - + + + + + + + solid-ui: Introduction using forms + + + + +

Using Forms in the UI ontology

+ +

+ The User Interface ontology at http://www.w3.org/ns/ui defines + RDF terms for describing forms. The + solid-ui projet + provides functions to use these forms within your web application to + create a quick user interface solution. This document describes how. +

+ +

+ The form system allows you to define a user interface declaratively in + RDF. In your web app, you then: +

+ +
    +
  1. make sure the ontology files are loaded
  2. + +
  3. load the file with the form itself
  4. + +
  5. + call + UI.widgets.appendForm(dom, container, {}, subject, form, doc, + callback) +
  6. +
+ +

where

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
domis the DOM HTMLDocument object, a/k/a document
containeris a DOM element to contain the form
{}are unused at present
subjectis the RDF thing about which data will be stored
formis the RDF object in the store for the form
doc + is the RDF document on the web where the data will be stored. Often, + subject.doc() +
callback + is a function taking an error flag and a message (if the error flag is + true) +
+ + If the form is a complex form, as the user adds more data, more form UI will + be created. The data in each field is saved back to the web the moment the + user has entered it. There is no general Save Button. + +

+ There is a form form for editing forms. It is in the form ontology itself. +

+ +

+ You can of course go and write other implementations of the form system + using your favorite user interface language. +

+ +

Go to the source

+ + + +

Form field types

+ +

+ Form fields may be named or blank nodes in your file; the form system does + not care. It is often useful to name them to keep track of them. +

+ +

+ Below, all Field Classes and Properties are in the UI namespace, + http://www.w3.org/ns/ui#, except the data types, like Integer, which are in the normal + XSD + namespace. +

+ +

+ Here are some properties which you can use with any field (except the + documentation fields). +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
labelString + A label for the form field. This is the prompt for the user, e.g., + "Name", "Employer". +
propertyrdf:Property + When the user enters the data, it is stored in the web as a triple + with this property as its predicate. +
default[according to field type] Optional + The input control is set to this value by default. It is easiest for + the user to enter this value. (This value is not automatically + stored by the form system if the user does not select or enter it in + some way. +
+ + Other properties are given for each field type. + +

Form

+ +

+ The form itself has a collection of fields. The parts property + gives an order list of the fields in each form. +

+ + + + + + + + + + + + + + + + + +
partsrdf:Collection (aka List, Array) of FieldThe parts of the form in the order in which they are
partField (Obsolete) + A field which is a part of the form or group. This property is + obsolete. Use parts. +
+ + If you use the obsolete "part" method for listing the parts of a form, then + each field needs an additional property: + + + + + + + + + +
sequenceIntegerThe parts of the form in the order in which they are
+ + For each part, declare its type, and the extra data that type requires, as + below. + +

Group

+ +

+ Group is a field which is just a collection of other fields. It is in fact + interchangeable with Form. +

+ +

Single Value fields - Numeric

+ These prompt the user for a single value. They typically take default + values, and min and max values. + +

BooleanField

+ +

A checkbox on the form, stored an RDF boolean true or false value.

+ +

TriStateField

+ +

+ A checkbox on the form, stores an RDF boolean true or false value, or no + value if the box is left in its third, blank state. +

+ +

IntegerField

+ +

An RDF integer value

+ +

DecimalField

+ +

An RDF decimal value. Useful for monetary amounts

+ +

FloatField

+ +

A floating point number

+ +

Single Value fields: Special Types

+ +

ColorField

+ +

+ A color picker is used, and genertes a string which is a CSS_compatible + color in a string like #ffeebb +

+ +

DateField

+ +

+ Uses a date picker on a good browser. Leaves an RDF date literal as is + value. +

+ +

DateTimeField

+ +

Leaves an RDF dateTime literal as is value.

+ +

PhoneField

+ +

Leaves as its value a named node with a uri which starts 'tel:'

+ +

EmailField

+ +

Leaves as its value a named now with a uri which starts 'mailto:'

+ +

Single Value fields - Text

+ +

SingleLineTextField

+ +

MultiLineTextField

+ +

Complex fields

+ +

Group

+ +

+ A group is simply a static set of fields of any type. Its properties are + the same as for Form. +

+ +

Choice

+ +

The user choses an item from a class.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
fromrdfs:ClassThe selected thing must be a member of this class. E.g. Person.
propertyrdf:Property + When the item is found, the new data links it from the subject with + tis property. E.g. friend. +
canMintNewxsd:Boolean + If the user doesn't find the thing they want, can they introduce a + item of that class by filling in a form about it? [Boolean] +
+ + If a new thing is minted, that will be done with a form which is a + ui:creationForm for the class. + +

Multiple

+ +

+ When the subject can have several of the same thing, like friends, ro + phone numbers, then the Multiple field allows this. The user clicks on the + green plus icon, and is prompted for a subform for the related thing. The + user can also delete existing ones. +

+ +

+ For each new thing, the system generates an arbitrary (timestamp) URI + within the file where the data is being stored. The subform is then about + that thing: the subject of the subform is not the subject of the original + form. It is the field, or the address, and so on. +

+ +

Classifier

+ + + + + + + + + +
categoryrdfs:Class + The object will already be in this class. The user will select + subclasses of this class. +
+ + This form field leverages the ontology heavily. It pulls the subclasses of + the given class, and makes a pop-up menu for the user to chose one. If and + only if the ontology says that the class is a disjoint union + (owl:disjointUnionOf) of the subclasses, then the user interface will only + allow the user to pick one. If the user picks a subclass, and the ontology + shows that that subclass has its own subclasses, then the user will be + prompted to pick one of those, to (if they like) further refine the + selection. And so on. + +

+ The classifier pops a menu to allow the user to select a set of valued to + classify the subject. +

+ +

Options

+ +

+ And Options field is the 'case statement' of the form system. It will + chose at runtime a subfield depending on a property, often the type, of + the subject. Often used after a classifier. +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Options propertyrangesignificance
dependingOnrdf:PropertyThe predicate in the data used to select the case.
caseCaseA case object, with for x use y. (2 or more cases)
+ + and for each case: + + + + + + + + + + + + + + + + + + + + + + + + + +
Case propertyrangesignificance
for[The range of the dependingOn property]The value this case applies to
useFieldsub form to be used in case the value matches the "for"
+ +

Documentation fields

+ +

Heading

+ +

+ Help the user find parts of a long form, or just for a title of a short + form. +

+ + + + + + + + + +
contentsStringThe text content of the heading
+ +

Comment

+ +

+ Use comments in the form to help users understand what is going on, what + their options are, and what the fields mean. +

+ + + + + + + + + +
contentsString + The text content of the comment. (This should be displayed by form + systems as pre-wrap mode) +
+ +

Conclusion

+ +

+ The form language and the form implementation in solid-ui can't do + everything, but it can handle a pretty wide selection of tasks in common + daily life at home and at work. It can be vary efficient as developers can + reuse material between forms. Users can even generate their own forms. +

+ Future directions include separate implementations of the form UI code in + for various platforms, and using various UI frameworks. There may also be + extension of the system with new field types, more options for setting style + from various sources, + diff --git a/README.md b/README.md index 1be116e85..1a0754969 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,18 @@ [![NPM Package](https://img.shields.io/npm/v/solid-ui.svg)](https://www.npmjs.com/package/solid-ui) - User Interface widgets and utilities for Solid -These are HTML5 widgets which connect to a solid store. Building blocks for solid-based apps. +These are HTML5 widgets which connect to a solid store. Building blocks for solid-based apps. A selection + ``` var UI = require('solid-ui') var acl = require('solid-ui').acl ``` + The submodules at the moment include log, acl, acl-control, messageArea, etc - A login widget @@ -29,7 +30,7 @@ The submodules at the moment include log, acl, acl-control, messageArea, etc The typical style of the widgets is to know what data it has been derived from, allow users to edit it, and to automatically sync with data as it changes in the future. -TO see how these are used, see the panes which use them within the solid-app-set +TO see how these are used, see the panes which use them within the solid-app-set The level of support for this varies. diff --git a/package-lock.json b/package-lock.json index bbc7c8ecf..41e09d475 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2293,16 +2293,78 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.6.1.tgz", - "integrity": "sha512-Z0rddsGqioKbvqfohg7BwkFC3PuNLsB+GE9QkFza7tiDzuHoy0y823Y+oGNDzxNZrYyLjqkZtCTl4vCqOmEN4g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.7.0.tgz", + "integrity": "sha512-H5G7yi0b0FgmqaEUpzyBlVh0d9lq4cWG2ap0RKa6BkF3rpBb6IrAoubt1NWh9R2kRs/f0k6XwRDiDz3X/FqXhQ==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "2.6.1", + "@typescript-eslint/experimental-utils": "2.7.0", "eslint-utils": "^1.4.2", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", "tsutils": "^3.17.1" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.7.0.tgz", + "integrity": "sha512-9/L/OJh2a5G2ltgBWJpHRfGnt61AgDeH6rsdg59BH0naQseSwR7abwHq3D5/op0KYD/zFT4LS5gGvWcMmegTEg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.7.0", + "eslint-scope": "^5.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.7.0.tgz", + "integrity": "sha512-vVCE/DY72N4RiJ/2f10PTyYekX2OLaltuSIBqeHYI44GQ940VCYioInIb8jKMrK9u855OEJdFC+HmWAZTnC+Ag==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "glob": "^7.1.4", + "is-glob": "^4.0.1", + "lodash.unescape": "4.0.1", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@typescript-eslint/experimental-utils": { @@ -3028,7 +3090,7 @@ }, "debug-log": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", "dev": true }, @@ -3499,7 +3561,7 @@ "dependencies": { "doctrine": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { @@ -7314,7 +7376,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/package.json b/package.json index 637d85b75..51298f3cf 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@babel/preset-env": "^7.6.2", "@babel/preset-typescript": "^7.6.0", "@types/rdflib": "^0.20.1", - "@typescript-eslint/eslint-plugin": "^2.6.1", + "@typescript-eslint/eslint-plugin": "^2.7.0", "@typescript-eslint/parser": "^2.6.1", "eslint": "^6.6.0", "husky": "^3.0.9", diff --git a/src/acl/acl-control.ts b/src/acl/acl-control.ts index 140db5f6e..a6c10a62b 100644 --- a/src/acl/acl-control.ts +++ b/src/acl/acl-control.ts @@ -393,7 +393,7 @@ export function ACLControlBox5 ( uris.map(function (u) { return handleOneDroppedURI(u) // can add to meetingDoc but must be sync }) - ).then(function (_a) { + ).then(function () { saveAndRestoreUI() }) } diff --git a/src/utils.js b/src/utils.js index b684297dc..bd74dee27 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,3 @@ -/* global tabulator */ // Solid-UI general Utilities // ========================== // @@ -32,7 +31,8 @@ module.exports = { RDFComparePredicateSubject, shortName, stackString, - syncTableToArray + syncTableToArray, + syncTableToArrayReOrdered } var UI = { @@ -121,6 +121,7 @@ function genUuid () { * @param {function({NamedNode})} createNewRow(thing) returns a TR table row for a new thing * * Tolerates out of order elements but puts new ones in order. + * Can be used in fact for any element type - does not have to be a table and tr. */ function syncTableToArray (table, things, createNewRow) { let foundOne @@ -165,6 +166,49 @@ function syncTableToArray (table, things, createNewRow) { } } // syncTableToArray +/** Sync a DOM table with an array of things + * + * @param {DomElement} table - will have a tr for each thing + * @param {Array} things - ORDERED array of UNIQUE NamedNode objects. No duplicates + * @param {function({NamedNode})} createNewRow(thing) returns a TR table row for a new thing + * + * Ensures order matches exacly. We will re-rder existing elements if necessary + * Can be used in fact for any element type - does not have to be a table and tr. + * Any RDF node value can only appear ONCE in the array + */ +function syncTableToArrayReOrdered (table, things, createNewRow) { + const elementMap = {} + + for (let i = 0; i < table.children.length; i++) { + const row = table.children[i] + row.trashMe = true + elementMap[row.subject.toNT()] = row // More sophisticaed would be to have a bag of duplicates + } + + for (let g = 0; g < things.length; g++) { + var thing = things[g] + const row = table.children[g] + if (!row.subject.sameTerm(thing)) { + const existingRow = elementMap[thing.toNT()] + if (existingRow) { + table.remove(existingRow) + row.before(existingRow) // Insert existing ro in place of this one + } else { + const newRow = createNewRow(thing) + row.before(newRow) // Insert existing ro in place of this one + newRow.subject = thing + } + } + } // loop g + + for (let i = 0; i < table.children.length; i++) { + const row = table.children[i] + if (row.trashMe) { + table.removeChild(row) + } + } +} // syncTableToArrayReOrdered + /* Error stack to string for better diagnotsics ** ** See http://snippets.dzone.com/posts/show/6632 @@ -200,8 +244,7 @@ function stackString (e) { function emptyNode (node) { const nodes = node.childNodes const len = nodes.length - let i - for (i = len - 1; i >= 0; i--) node.removeChild(nodes[i]) + for (let i = len - 1; i >= 0; i--) node.removeChild(nodes[i]) return node } @@ -274,14 +317,12 @@ function getTerm (target) { case 'undetermined selected': return target.nextSibling ? st.predicate - : getUndeterminedSelection(statementTr, st) + : !statementTr.AJAR_inverse + ? st.object + : st.subject } } -function getUndeterminedSelection (statementTr, st) { - return !statementTr.AJAR_inverse ? st.object : st.subject -} - function include (document, linkstr) { var lnk = document.createElement('script') lnk.setAttribute('type', 'text/javascript') @@ -485,6 +526,7 @@ function label (x, initialCap) { // The tabulator labeler is more sophisticated if it exists // Todo: move it to a solid-ui option. + /* var lab if (typeof tabulator !== 'undefined' && tabulator.lb) { lab = tabulator.lb.label(x) @@ -492,7 +534,7 @@ function label (x, initialCap) { return doCap(lab.value) } } - + */ // Hard coded known label predicates // @@ TBD: Add subproperties of rdfs:label diff --git a/src/widgets/forms.js b/src/widgets/forms.js index 555e7d86e..190cfacc0 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -1,7 +1,9 @@ -/* +/* F O R M S + * + * A Vanilla Dom implementation of the form language */ -const $rdf = require('rdflib') +/* global alert */ module.exports = {} @@ -17,6 +19,7 @@ var UI = { style: require('../style'), widgets: forms } +const $rdf = require('rdflib') const error = require('./error') const buttons = require('./buttons') const utils = require('../utils') @@ -30,8 +33,20 @@ const dashCharacter = '-' /* Form Field implementations ** */ -/* Group of different fields +/** Group of different fields + ** + ** One type of form field is an ordered Group of other fields. + ** A Form is actually just the same as a group. ** + ** @param {Document} dom The HTML Document object aka Document Object Model + ** @param {Element?} container If present, the created widget will be appended to this + ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Node} subject The thing about which the form displays/edits data + ** @param {Node} form The form or field to be rendered + ** @param {Node} store The web document in which the data is + ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? + ** + ** @returns {Element} The HTML widget created */ forms.field[UI.ns.ui('Form').uri] = forms.field[ UI.ns.ui('Group').uri @@ -40,7 +55,7 @@ forms.field[UI.ns.ui('Form').uri] = forms.field[ var box = dom.createElement('div') box.setAttribute('style', 'padding-left: 2em; border: 0.05em solid brown;') // Indent a group var ui = UI.ns.ui - container.appendChild(box) + if (container) container.appendChild(box) // Prevent loops var key = subject.toNT() + '|' + form.toNT() @@ -112,9 +127,19 @@ forms.field[UI.ns.ui('Form').uri] = forms.field[ return box } -/* Options: Select one or more cases +/** Options field: Select one or more cases + ** + ** @param {Document} dom The HTML Document object aka Document Object Model + ** @param {Element?} container If present, the created widget will be appended to this + ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Node} subject The thing about which the form displays/edits data + ** @param {Node} form The form or field to be rendered + ** @param {Node} store The web document in which the data is + ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? ** + ** @returns {Element} The HTML widget created */ + forms.field[UI.ns.ui('Options').uri] = function ( dom, container, @@ -128,7 +153,7 @@ forms.field[UI.ns.ui('Options').uri] = function ( var box = dom.createElement('div') // box.setAttribute('style', 'padding-left: 2em; border: 0.05em dotted purple;') // Indent Options var ui = UI.ns.ui - container.appendChild(box) + if (container) container.appendChild(box) var dependingOn = kb.any(form, ui('dependingOn')) if (!dependingOn) { @@ -188,8 +213,17 @@ forms.field[UI.ns.ui('Options').uri] = function ( return box } -/* Multiple similar fields (unordered) +/** Multiple field: zero or more similar subFields + ** + ** @param {Document} dom The HTML Document object aka Document Object Model + ** @param {Element?} container If present, the created widget will be appended to this + ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Node} subject The thing about which the form displays/edits data + ** @param {Node} form The form or field to be rendered + ** @param {Node} store The web document in which the data is + ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? ** + ** @returns {Element} The HTML widget created */ forms.field[UI.ns.ui('Multiple').uri] = function ( dom, @@ -200,16 +234,155 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( store, callbackFunction ) { - // var plusIcon = UI.icons.originalIconBase + 'tango/22-list-add.png' // blue plus + /** Add an item to the control + * @param {Event} anyEvent if used as an event handler + * @param {Node} object The RDF object to be represented by this item. + */ + function addItem (object) { + // can be used as a event handler + async function deleteItem () { + if (ordered) { + for (let i = 0; i < list.elements.length; i++) { + if (list.elements[i].sameTerm(object)) { + list.elements.splice(i, 1) + saveListThenRefresh() + return + } + } + } else { + // unordered + if (kb.holds(subject, property, object)) { + var del = [$rdf.st(subject, property, object, store)] + kb.updater.update(del, [], function (uri, ok, message) { + if (ok) { + body.removeChild(subField) + } else { + body.appendChild( + error.errorMessageBlock( + dom, + 'Multiple: delete failed: ' + message + ) + ) + } + }) + } + } + } + + /** Move the object up or down in the ordered list + * @param {Event} anyEvent if used as an event handler + * @param {Boolean} upwards Move this up (true) or down (false). + */ + async function move (event, upwards) { + // @@ possibly, allow shift+click to do move to top or bottom? + for (var i = 0; i < list.elements.length; i++) { + // Find object in array + if (list.elements[i].sameTerm(object)) { + break + } + } + if (i === list.elements.length) { + alert('list move: not found element for ' + object) + } + if (upwards) { + if (i === 0) { + alert('@@ boop - already at top -temp message') // @@ make boop sound + return + } + list.elements.splice(i - 1, 2, list.elements[i], list.elements[i - 1]) + } else { + // downwards + if (i === list.elements.length - 1) { + alert('@@ boop - already at bottom -temp message') // @@ make boop sound + return + } + list.elements.splice(i, 2, list.elements[i + 1], list.elements[i]) + } + saveListThenRefresh() + } + /* A subField has been filled in + */ + function itemDone (uri, ok, message) { + if (ok) { + // @@@ Check IT hasnt alreday been written in + if (ordered) { + list = kb.any(subject, property, null, store) + if (!list) { + list = new $rdf.Collecion([object]) + // list.append(object) + ins = [$rdf.st(subject, property, list)] // Will this work? + } else { + const oldList = new $rdf.Collecion(list.elments) + list.append(object) + del = [$rdf.st(subject, property, oldList)] // If this doesn't work, kb.saveBack(store) + ins = [$rdf.st(subject, property, list)] + } + } else { + if (!kb.holds(subject, property, object, store)) { + ins = [$rdf.st(subject, property, object, store)] + } + kb.updater.update(del, ins, linkDone) + } + } else { + tr.appendChild( + error.errorMessageBlock(dom, 'Multiple: item failed: ' + body) + ) + callbackFunction(ok, message) + } + } + var linkDone = function (uri, ok, message) { + return callbackFunction(ok, message) + } + + if (!object) object = forms.newThing(store) + UI.log.debug('Multiple: add: ' + object) + var tr = box.insertBefore(dom.createElement('tr'), tail) + var ins = [] + var del = [] + + var fn = forms.fieldFunction(dom, element) + var subField = fn(dom, null, already, object, element, store, itemDone) // p2 was: body. moving to not passing that + subField.subject = object // Keep a back pointer between the DOM array and the RDF objects + + // delete button and move buttons + if (kb.updater.editable(store.uri)) { + buttons.deleteButtonWithCheck( + dom, + subField, + utils.label(property), + deleteItem + ) + if (ordered) { + subField.appendChild( + UI.widgets.button(dom, UI.icons.iconBase.noun_1369237.svg, 'Move Up'), + async event => move(event, true) + ) + subField.appendChild( + UI.widgets.button( + dom, + UI.icons.iconBase.noun_1369241.svg, + 'Move Down' + ), + async event => move(event, false) + ) + } + } + return subField // unused + } // addItem + var plusIconURI = UI.icons.iconBase + 'noun_19460_green.svg' // white plus in green circle var kb = UI.store kb.updater = kb.updater || new $rdf.UpdateManager(kb) var box = dom.createElement('table') - // We don't indent multiple as it is a sort of a prefix o fthe next field and has contents of one. + // We don't indent multiple as it is a sort of a prefix of the next field and has contents of one. // box.setAttribute('style', 'padding-left: 2em; border: 0.05em solid green;') // Indent a multiple var ui = UI.ns.ui - container.appendChild(box) + if (container) container.appendChild(box) + + var ordered = kb.any(form, ui('ordered')) + ordered = ordered ? $rdf.Node.toJS(ordered) : false + var property = kb.any(form, ui('property')) if (!property) { box.appendChild( @@ -218,7 +391,7 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( return box } var min = kb.any(form, ui('min')) // This is the minimum number -- default 0 - min = min ? min.value : 0 + min = min ? 0 + min.value : 0 // var max = kb.any(form, ui('max')) // This is the minimum number // max = max ? max.value : 99999999 @@ -230,65 +403,26 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( return box } - // box.appendChild(dom.createElement('h3')).textContent = "Fields:". var body = box.appendChild(dom.createElement('tr')) var tail = box.appendChild(dom.createElement('tr')) - - var addItem = function (e, object) { - UI.log.debug('Multiple add: ' + object) - // ++count - if (!object) object = forms.newThing(store) - var tr = box.insertBefore(dom.createElement('tr'), tail) - var itemDone = function (uri, ok, message) { - if (ok) { - // @@@ Check IT hasnt alreday been written in - if (!kb.holds(subject, property, object, store)) { - var ins = [$rdf.st(subject, property, object, store)] - kb.updater.update([], ins, linkDone) - } - } else { - tr.appendChild( - error.errorMessageBlock(dom, 'Multiple: item failed: ' + body) - ) - callbackFunction(ok, message) - } - } - var linkDone = function (uri, ok, message) { - return callbackFunction(ok, message) - } - - var fn = forms.fieldFunction(dom, element) - var subField = fn(dom, body, already, object, element, store, itemDone) - - // delete button - var deleteItem = function () { - if (kb.holds(subject, property, object)) { - var del = [$rdf.st(subject, property, object, store)] - kb.updater.update(del, [], function (uri, ok, message) { - if (ok) { - body.removeChild(subField) - } else { - body.appendChild( - error.errorMessageBlock( - dom, - 'Multiple: delete failed: ' + message - ) - ) - } - }) - } - } - if (kb.updater.editable(store.uri)) { - buttons.deleteButtonWithCheck( - dom, - subField, - utils.label(property), - deleteItem - ) + var list // The RDF collection which keeps the ordered version + var values // Initial values + + var unsavedList = false // Flag that + if (ordered) { + list = kb.any(subject, property) + if (!list) { + unsavedList = true + values = [] + // list = new $rdf.Collection() + // @@ save {subject property list} triple @@ + } else { + values = list.elements } + } else { + values = kb.each(subject, property) + list = null } - - var values = kb.each(subject, property) if (kb.updater.editable(store.uri)) { var img = tail.appendChild(dom.createElement('img')) img.setAttribute('src', plusIconURI) // plus sign @@ -298,16 +432,56 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( prompt.textContent = (values.length === 0 ? 'Add one or more ' : 'Add more ') + utils.label(property) - tail.addEventListener('click', addItem, true) // img.addEventListener('click', addItem, true) + tail.addEventListener('click', async _eventNotUsed => addItem(), true) } - values.map(function (obj) { - addItem(null, obj) - }) + function createListIfNecessary () { + if (unsavedList) { + list = new $rdf.Collection() + kb.add(subject, property, list, store) + } + unsavedList = false + } + + async function saveListThenRefresh () { + createListIfNecessary() + try { + await kb.fetcher.putBack(store) + } catch (err) { + box.appendChild( + error.errorMessageBlock(dom, 'Error trying to put back a list: ' + err) + ) + return + } + refresh() + } + + // values.forEach(function (obj) { addItem(obj) }) + function refresh () { + if (ordered) { + list = kb.any(subject, property) + if (!list) { + unsavedList = true + values = [] + } else { + values = list.elements + } + } else { + values = kb.each(subject, property) + list = null + } + UI.utils.syncTableToArrayReOrdered(body, values, addItem) + } + body.refresh = refresh // Allow live update + refresh() + var extra = min - values.length - for (var j = 0; j < extra; j++) { - console.log('Adding extra: min ' + min) - addItem() // Add blanks if less than minimum + if (extra > 0) { + for (var j = 0; j < extra; j++) { + console.log('Adding extra: min ' + min) + addItem() // Add blanks if less than minimum + } + saveListThenRefresh() // async } return box } @@ -395,19 +569,22 @@ forms.fieldParams[UI.ns.ui('EmailField').uri] = { } forms.fieldParams[UI.ns.ui('EmailField').uri].pattern = /^\s*.*@.*\..*\s*$/ // @@ Get the right regexp here -forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ - UI.ns.ui('EmailField').uri -] = forms.field[UI.ns.ui('ColorField').uri] = forms.field[ - UI.ns.ui('DateField').uri -] = forms.field[UI.ns.ui('DateTimeField').uri] = forms.field[ - UI.ns.ui('TimeField').uri -] = forms.field[UI.ns.ui('NumericField').uri] = forms.field[ - UI.ns.ui('IntegerField').uri -] = forms.field[UI.ns.ui('DecimalField').uri] = forms.field[ - UI.ns.ui('FloatField').uri -] = forms.field[UI.ns.ui('TextField').uri] = forms.field[ - UI.ns.ui('SingleLineTextField').uri -] = forms.field[UI.ns.ui('NamedNodeURIField').uri] = function ( +/** Render a basic form field + * + ** @param {Document} dom The HTML Document object aka Document Object Model + ** @param {Element?} container If present, the created widget will be appended to this + ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Node} subject The thing about which the form displays/edits data + ** @param {Node} form The form or field to be rendered + ** @param {Node} store The web document in which the data is + ** @param {function(ok, errorMessage)} callbackFunction Called when data is changed? + ** + ** @returns {Element} The HTML widget created + ** + ** The same function is used for many similar one-value fields, with different + ** regexps used to validate. + */ +function basicField ( dom, container, already, @@ -420,7 +597,7 @@ forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ var kb = UI.store var box = dom.createElement('tr') - container.appendChild(box) + if (container) container.appendChild(box) var lhs = dom.createElement('td') lhs.setAttribute('class', 'formFieldName') lhs.setAttribute('style', ' vertical-align: middle;') @@ -470,14 +647,14 @@ forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ field.setAttribute('style', style) if (!kb.updater.editable(store.uri)) { - field.disabled = true + field.readonly = true // was: disabled. readonly is better return box } - // read-write: + // read-write: field.addEventListener( 'keyup', - function (_event) { + function (_e) { if (params.pattern) { field.setAttribute( 'style', @@ -492,7 +669,7 @@ forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ ) field.addEventListener( 'change', - function (_event) { + function (_e) { // i.e. lose focus with changed data if (params.pattern && !field.value.match(params.pattern)) return field.disabled = true // See if this stops getting two dates from fumbling e.g the chrome datepicker. @@ -524,14 +701,10 @@ forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ function updateMany (ds, is, callback) { var docs = [] is.forEach(st => { - if (!docs.includes(st.why.uri)) { - docs.push(st.why.uri) - } + if (!docs.includes(st.why.uri)) docs.push(st.why.uri) }) ds.forEach(st => { - if (!docs.includes(st.why.uri)) { - docs.push(st.why.uri) - } + if (!docs.includes(st.why.uri)) docs.push(st.why.uri) }) if (docs.length === 0) { throw new Error('updateMany has no docs to patch') @@ -539,7 +712,8 @@ forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ if (docs.length === 1) { return kb.updater.update(ds, is, callback) } - console.log('Update many: ' + docs) + // 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) @@ -571,6 +745,20 @@ forms.field[UI.ns.ui('PhoneField').uri] = forms.field[ return box } +forms.field[UI.ns.ui('PhoneField').uri] = basicField +forms.field[UI.ns.ui('EmailField').uri] = basicField +forms.field[UI.ns.ui('ColorField').uri] = basicField +forms.field[UI.ns.ui('DateField').uri] = basicField +forms.field[UI.ns.ui('DateTimeField').uri] = basicField +forms.field[UI.ns.ui('TimeField').uri] = basicField +forms.field[UI.ns.ui('NumericField').uri] = basicField +forms.field[UI.ns.ui('IntegerField').uri] = basicField +forms.field[UI.ns.ui('DecimalField').uri] = basicField +forms.field[UI.ns.ui('FloatField').uri] = basicField +forms.field[UI.ns.ui('TextField').uri] = basicField +forms.field[UI.ns.ui('SingleLineTextField').uri] = basicField +forms.field[UI.ns.ui('NamedNodeURIField').uri] = basicField + /* Multiline Text field ** */ @@ -590,9 +778,10 @@ forms.field[UI.ns.ui('MultiLineTextField').uri] = function ( if (!property) { return error.errorMessageBlock(dom, 'No property to text field: ' + form) } - container.appendChild(forms.fieldLabel(dom, property, form)) + const box = dom.createElement('div') + box.appendChild(forms.fieldLabel(dom, property, form)) store = forms.fieldStore(subject, property, store) - var box = forms.makeDescription( + var field = forms.makeDescription( dom, kb, subject, @@ -601,12 +790,14 @@ forms.field[UI.ns.ui('MultiLineTextField').uri] = function ( callbackFunction ) // box.appendChild(dom.createTextNode('<-@@ subj:'+subject+', prop:'+property)) - container.appendChild(box) + box.appendChild(field) + if (container) container.appendChild(box) return box } /* Boolean field and Tri-state version (true/false/null) ** + ** @@ todo: remove tristate param */ function booleanField ( dom, @@ -622,9 +813,12 @@ function booleanField ( var kb = UI.store var property = kb.any(form, ui('property')) if (!property) { - return container.appendChild( - error.errorMessageBlock(dom, 'No property to boolean field: ' + form) + const errorBlock = error.errorMessageBlock( + dom, + 'No property to boolean field: ' + form ) + if (container) container.appendChild(errorBlock) + return errorBlock } var lab = kb.any(form, ui('label')) if (!lab) lab = utils.label(property, true) // Init capital @@ -637,7 +831,7 @@ function booleanField ( var ins = $rdf.st(subject, property, true, store) var del = $rdf.st(subject, property, false, store) var box = buildCheckboxForm(dom, kb, lab, del, ins, form, store, tristate) - container.appendChild(box) + if (container) container.appendChild(box) return box } forms.field[UI.ns.ui('BooleanField').uri] = function ( @@ -647,8 +841,7 @@ forms.field[UI.ns.ui('BooleanField').uri] = function ( subject, form, store, - callbackFunction, - _tristate + callbackFunction ) { return booleanField( dom, @@ -726,7 +919,7 @@ forms.field[UI.ns.ui('Classifier').uri] = function ( store, checkOptions ) - container.appendChild(box) + if (container) container.appendChild(box) return box } @@ -757,7 +950,7 @@ forms.field[UI.ns.ui('Choice').uri] = function ( var multiple = false var p var box = dom.createElement('tr') - container.appendChild(box) + if (container) container.appendChild(box) var lhs = dom.createElement('td') box.appendChild(lhs) var rhs = dom.createElement('td') @@ -800,7 +993,7 @@ forms.field[UI.ns.ui('Choice').uri] = function ( opts.disambiguate = true } var object = kb.any(subject, property) - function addSubForm (_ok, _body) { + function addSubForm () { object = kb.any(subject, property) forms.fieldFunction(dom, subForm)( dom, @@ -829,7 +1022,7 @@ forms.field[UI.ns.ui('Choice').uri] = function ( callbackFunction ) rhs.appendChild(selector) - if (object && subForm) addSubForm(true, '') + if (object && subForm) addSubForm() return box } @@ -868,7 +1061,7 @@ forms.field[UI.ns.ui('Comment').uri] = forms.field[ } // non-bottom field types can do this var box = dom.createElement('div') - container.appendChild(box) + if (container) container.appendChild(box) var p = box.appendChild(dom.createElement(params.element)) p.textContent = contents @@ -927,7 +1120,7 @@ forms.editFormButton = function ( b.innerHTML = 'Edit ' + utils.label(UI.ns.ui('Form')) b.addEventListener( 'click', - function (_event) { + function (_e) { var ff = forms.appendForm( dom, container, @@ -943,7 +1136,7 @@ forms.editFormButton = function ( ? 'background-color: #fee;' : 'background-color: #ffffe7;' ) - container.removeChild(b) + b.parentNode.removeChild(b) }, true ) @@ -1096,7 +1289,7 @@ forms.newButton = function ( b.innerHTML = 'New ' + utils.label(theClass) b.addEventListener( 'click', - function (_event) { + function (_e) { b.parentNode.appendChild( forms.promptForNew( dom, @@ -1155,7 +1348,7 @@ forms.promptForNew = function ( b.innerHTML = 'Goto ' + utils.label(theClass) b.addEventListener( 'click', - function (_event) { + function (_e) { dom.outlineManager.GotoSubject( theClass, true, @@ -1250,7 +1443,7 @@ forms.makeDescription = function ( // @@ this is the place to color the field from the user who chanaged it } } - function saveChange (_event) { + function saveChange (_e) { submit.disabled = true submit.setAttribute('style', 'visibility: hidden; float: right;') // Keep UI clean field.disabled = true @@ -1289,7 +1482,7 @@ forms.makeDescription = function ( field.addEventListener( 'keyup', - function (_event) { + function (_e) { // Green means has been changed, not saved yet field.setAttribute('style', style + 'color: green;') if (submit) { @@ -1369,7 +1562,7 @@ forms.makeSelectForOptions = function ( // var newObject = null - var onChange = function (_event) { + var onChange = function (_e) { select.disabled = true // until data written back - gives user feedback too var ds = [] var is = [] @@ -1705,15 +1898,18 @@ function buildCheckboxForm (dom, kb, lab, del, ins, form, store, tristate) { refresh() if (!editable) return box - var boxHandler = function (_event) { + var boxHandler = function (_e) { tx.style = 'color: #bbb;' // grey -- not saved yet var toDelete = input.state === true ? ins : input.state === false ? del : [] + input.newState = + input.state === null + ? true + : input.state === true + ? false + : tristate + ? null + : true - function getState (input, tristate) { - return input.state === true ? false : tristate ? null : true - } - - input.newState = input.state === null ? true : getState(input, tristate) var toInsert = input.newState === true ? ins : input.newState === false ? del : [] console.log(` Deleting ${toDelete}`) From 1298c306f0c9c49dd15518dca69f6b448019f9e3 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Mon, 9 Dec 2019 09:41:12 -0500 Subject: [PATCH 02/32] After rebase --- .eslintignore | 5 ++ package-lock.json | 106 +++++++++++++++++++++++++------------------ package.json | 4 ++ src/widgets/forms.js | 2 +- 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/.eslintignore b/.eslintignore index a65b41774..671f770cf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,6 @@ lib +*.html +*.md +*.json +Documentation +Documentation/forms-intro.html diff --git a/package-lock.json b/package-lock.json index 41e09d475..6d59efccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2619,9 +2619,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", + "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" }, "babel-plugin-dynamic-import-node": { "version": "2.3.0", @@ -2972,6 +2972,29 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -3052,6 +3075,22 @@ "parse-json": "^4.0.0" } }, + "cross-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz", + "integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==", + "requires": { + "node-fetch": "2.6.0", + "whatwg-fetch": "3.0.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + } + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -3949,9 +3988,9 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" } } }, @@ -4839,9 +4878,9 @@ } }, "jsonld": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.8.0.tgz", - "integrity": "sha512-a3bwbR0wqFstxKsGoimUIIKBdfJ+yb9kWK+WK7MpVyvfYtITMpUtF3sNoN1wG/W+jGDgya0ACRh++jtTozxtyQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-1.8.1.tgz", + "integrity": "sha512-f0rusl5v8aPKS3jApT5fhYsdTC/JpyK1PoJ+ZtYYtZXoyb1J0Z///mJqLwrfL/g4NueFSqPymDYIi1CcSk7b8Q==", "requires": { "canonicalize": "^1.0.1", "rdf-canonize": "^1.0.2", @@ -5503,9 +5542,9 @@ "dev": true }, "n3": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.2.1.tgz", - "integrity": "sha512-5va8zsh02owDul7+5bj35cGOzWU7DeJHwQK3F8NDKtiYqPgWcFTg/zLxJs1JeRF2n6j5PI/eR9DCokS7nLrevA==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.3.4.tgz", + "integrity": "sha512-Eu5EVYGncuwiTlOV1J6p3OFBNSfI84D+fW0o8o5s2aRowO3yRcM4SvqPTOKzCCJutRvaXP0J9GIzwrP6tINm2Q==" }, "nanomatch": { "version": "1.2.13", @@ -6112,9 +6151,9 @@ } }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", + "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==" }, "pump": { "version": "3.0.0", @@ -6938,16 +6977,16 @@ } }, "solid-auth-cli": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/solid-auth-cli/-/solid-auth-cli-1.0.8.tgz", - "integrity": "sha512-cZYNLM6/BDwbcywsrfFRIrGVjTPc/f3snubabqd1WRCvNwW7wkvpo0yvoHp3NQjVAfKHw5kIrZtaV8IRHPK/KQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/solid-auth-cli/-/solid-auth-cli-1.0.10.tgz", + "integrity": "sha512-fEvgpdr429peDFmXQk2FDeD14bGsHccT+4Pn7l8Gw6b3aCWYCaCXxkSEdiHkSt6N6rGx0sGiq9Yp3DB9Z/I6kg==", "requires": { "@solid/cli": "^0.1.1", "async": "^2.6.1", - "isomorphic-fetch": "^2.2.1", + "cross-fetch": "^3.0.4", "jsonld": "^1.4.0", "n3": "^1.0.3", - "solid-rest": "^1.0.7" + "solid-rest": "^1.0.9" }, "dependencies": { "async": { @@ -6983,9 +7022,9 @@ "integrity": "sha512-yQGQlTNDVtcMfzLz7OwL6Z8lJy9rN1ep0MgX28DPcja0DtA5pu1MzTpBVD9kvXl9X6eSEX73I7IYt0cUim0DrA==" }, "solid-rest": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/solid-rest/-/solid-rest-1.0.7.tgz", - "integrity": "sha512-OiNKV1nW00RVdnd88HfCVIcY+LKI6VAc6DbY0Tujy5/eiURkCnxqAkMJXLd10lKmpQZ2NNYpsfBN/QWnoIBczA==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/solid-rest/-/solid-rest-1.0.9.tgz", + "integrity": "sha512-+V8h180TkQhGK9BkLfzm4aSJZaqyEst0TmBkvlXB1bchoO2F7+yM//m6HAZBbTnDcDhf1jsrt34nfNgXf6t8tg==", "requires": { "concat-stream": "^2.0.0", "fs-extra": "^8.0.1", @@ -6993,31 +7032,10 @@ "node-fetch": "^2.6.0" }, "dependencies": { - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" - }, - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } } } }, diff --git a/package.json b/package.json index 51298f3cf..16ae9ed90 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,10 @@ "pre-push": "npm test" } }, + "eslintignore": [ + "package.json", + "*.html" + ], "lint-staged": { "src/**/*.(js|ts)": [ "eslint" diff --git a/src/widgets/forms.js b/src/widgets/forms.js index 190cfacc0..d918d88fa 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -403,7 +403,7 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( return box } - var body = box.appendChild(dom.createElement('tr')) + var body = box.appendChild(dom.createElement('tr')) // 20191207 var tail = box.appendChild(dom.createElement('tr')) var list // The RDF collection which keeps the ordered version var values // Initial values From b185dd59698b5e718c1c4d700ad92f49e9a38971 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sun, 15 Dec 2019 20:11:12 -0500 Subject: [PATCH 03/32] some bugs out, not perfect but up down arrows work --- src/create.js | 38 ++++--- src/utils.js | 37 +++--- src/widgets/buttons.js | 21 +++- src/widgets/forms.js | 251 +++++++++++++++++++++++++---------------- 4 files changed, 208 insertions(+), 139 deletions(-) diff --git a/src/create.js b/src/create.js index baf068464..f16fcd6ab 100644 --- a/src/create.js +++ b/src/create.js @@ -32,14 +32,15 @@ module.exports = { ** */ function newThingUI (createContext, dataBrowserContext, thePanes) { + if (!thePanes) throw new Error('@@ newThingUI: update API') // phase out const dom = createContext.dom const div = createContext.div if (createContext.me && !createContext.me.uri) { throw new Error('newThingUI: Invalid userid: ' + createContext.me) } - var iconStyle = 'padding: 0.7em; width: 2em; height: 2em;' // was: 'padding: 1em; width: 3em; height: 3em;' - var star = div.appendChild(dom.createElement('img')) + const iconStyle = 'padding: 0.7em; width: 2em; height: 2em;' // was: 'padding: 1em; width: 3em; height: 3em;' + const star = div.appendChild(dom.createElement('img')) var visible = false // the inividual tools tools // noun_272948.svg = black star // noun_34653_green.svg = green plus @@ -53,7 +54,23 @@ function newThingUI (createContext, dataBrowserContext, thePanes) { pre.appendChild(dom.createTextNode(message)) } - var selectNewTool = function (_event) { + function styleTheIcons (style) { + for (var i = 0; i < iconArray.length; i++) { + var st = iconStyle + style + if (iconArray[i].disabled) { + // @@ unused + st += 'opacity: 0.3;' + } + iconArray[i].setAttribute('style', st) // eg 'background-color: #ccc;' + } + } + + function selectTool (icon) { + styleTheIcons('display: none;') // 'background-color: #ccc;' + icon.setAttribute('style', iconStyle + 'background-color: yellow;') + } + + function selectNewTool (_event) { visible = !visible star.setAttribute( 'style', @@ -225,21 +242,6 @@ function newThingUI (createContext, dataBrowserContext, thePanes) { }) } }) - - var styleTheIcons = function (style) { - for (var i = 0; i < iconArray.length; i++) { - var st = iconStyle + style - if (iconArray[i].disabled) { - // @@ unused - st += 'opacity: 0.3;' - } - iconArray[i].setAttribute('style', st) // eg 'background-color: #ccc;' - } - } - var selectTool = function (icon) { - styleTheIcons('display: none;') // 'background-color: #ccc;' - icon.setAttribute('style', iconStyle + 'background-color: yellow;') - } } // Form to get the name of a new thing before we create it diff --git a/src/utils.js b/src/utils.js index bd74dee27..4694fd453 100644 --- a/src/utils.js +++ b/src/utils.js @@ -170,7 +170,7 @@ function syncTableToArray (table, things, createNewRow) { * * @param {DomElement} table - will have a tr for each thing * @param {Array} things - ORDERED array of UNIQUE NamedNode objects. No duplicates - * @param {function({NamedNode})} createNewRow(thing) returns a TR table row for a new thing + * @param {function({NamedNode})} createNewRow(thing) returns a rendering of a new thing * * Ensures order matches exacly. We will re-rder existing elements if necessary * Can be used in fact for any element type - does not have to be a table and tr. @@ -181,31 +181,34 @@ function syncTableToArrayReOrdered (table, things, createNewRow) { for (let i = 0; i < table.children.length; i++) { const row = table.children[i] - row.trashMe = true elementMap[row.subject.toNT()] = row // More sophisticaed would be to have a bag of duplicates } for (let g = 0; g < things.length; g++) { var thing = things[g] - const row = table.children[g] - if (!row.subject.sameTerm(thing)) { - const existingRow = elementMap[thing.toNT()] - if (existingRow) { - table.remove(existingRow) - row.before(existingRow) // Insert existing ro in place of this one + if (g >= table.children.length) { // table needs extending + const newRow = createNewRow(thing) + newRow.subject = thing + table.appendChild(newRow) + } else { + const row = table.children[g] + if (row.subject.sameTerm(thing)) { } else { - const newRow = createNewRow(thing) - row.before(newRow) // Insert existing ro in place of this one - newRow.subject = thing + const existingRow = elementMap[thing.toNT()] + if (existingRow) { + table.removeChild(existingRow) + table.insertBefore(existingRow, row) // Insert existing ro in place of this one + } else { + const newRow = createNewRow(thing) + row.before(newRow) // Insert existing ro in place of this one + newRow.subject = thing + } } } } // loop g - - for (let i = 0; i < table.children.length; i++) { - const row = table.children[i] - if (row.trashMe) { - table.removeChild(row) - } + // Lop off any we don't need any more: + while (table.children.length > things.length) { + table.removeChild(table.children[table.children.length - 1]) } } // syncTableToArrayReOrdered diff --git a/src/widgets/buttons.js b/src/widgets/buttons.js index 58ca41e12..85fdd7a7d 100644 --- a/src/widgets/buttons.js +++ b/src/widgets/buttons.js @@ -404,7 +404,7 @@ buttons.button = function (dom, iconURI, text, handler) { img.setAttribute('style', 'width: 2em; height: 2em;') // trial and error. 2em disappears img.title = text if (handler) { - button.addEventListener('click', handler) + button.addEventListener('click', handler, false) } return button } @@ -723,12 +723,21 @@ buttons.allClassURIs = function () { return set } -// Figuring which propertites could by be used -// -buttons.propertyTriage = function () { +/** Figuring which propertites we know about +* +* When the user is inputs an RDF property, like foir a form field +* or when specifying the relationshiip bteween two arbitrary things, +* then er can prompt them with poperties the session knows about +* +* TODO: Look again ay caching tis somewhere. (On the kb?) +* TODO: move to diff module? Not really a button. +* @param {Store} kb The quadstore to be searchhed. +*/ + +buttons.propertyTriage = function (kb) { var possibleProperties = {} // if (possibleProperties === undefined) possibleProperties = {} - var kb = UI.store + // var kb = UI.store var dp = {} var op = {} var no = 0 @@ -748,7 +757,7 @@ buttons.propertyTriage = function () { var ps = kb.each(undefined, UI.ns.rdf('type'), UI.ns.rdf('Property')) for (var i = 0; i < ps.length; i++) { p = ps[i].toNT() - UI.log.debug('propertyTriage: unknown: ' + p) + // UI.log.debug('propertyTriage: unknown: ' + p) if (!op[p] && !dp[p]) { dp[p] = true op[p] = true diff --git a/src/widgets/forms.js b/src/widgets/forms.js index d918d88fa..fa39bd927 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -87,7 +87,7 @@ forms.field[UI.ns.ui('Form').uri] = forms.field[ var original = [] for (var i = 0; i < p2.length; i++) { var field = p2[i] - var t = forms.bottomURI(field) // Field type + var t = forms.mostSpecificClassURI(field) // Field type if (t === ui('Options').uri) { var dep = kb.any(field, ui('dependingOn')) if (dep && kb.any(subject, dep)) original[i] = kb.any(subject, dep).toNT() @@ -100,7 +100,7 @@ forms.field[UI.ns.ui('Form').uri] = forms.field[ for (var j = 0; j < p2.length; j++) { // This is really messy. var field = p2[j] - var t = forms.bottomURI(field) // Field type + var t = forms.mostSpecificClassURI(field) // Field type if (t === ui('Options').uri) { var dep = kb.any(field, ui('dependingOn')) var newOne = fn( @@ -234,18 +234,47 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( store, callbackFunction ) { - /** Add an item to the control + /** Diagnostic function + */ + function debugString (values) { + return values.map(x => x.toString().slice(-7)).join(', ') + } + + /** Add an item to the local quadstore not the UI or the web + * + * @param {Node} object The RDF object to be represented by this item. + */ + async function addItem (object) { + if (!object) object = forms.newThing(store) // by default just add new nodes + if (ordered) { + createListIfNecessary() // Sets list and unsavedList + list.elements.push(object) + await saveListThenRefresh() + } else { + const toBeInserted = [$rdf.st(subject, property, object, store)] + try { + await kb.updater.update([], toBeInserted) + } catch (err) { + const msg = 'Error adding to unordered multiple: ' + err + box.appendChild(error.errorMessageBlock(dom, msg)) + console.error(msg) + } + refresh() // 20191213 + } + } + + /** Make a dom representation for an item * @param {Event} anyEvent if used as an event handler * @param {Node} object The RDF object to be represented by this item. */ - function addItem (object) { - // can be used as a event handler - async function deleteItem () { + function renderItem (object) { + async function deleteThisItem () { if (ordered) { + console.log('pre delete: ' + debugString(list.elements)) for (let i = 0; i < list.elements.length; i++) { if (list.elements[i].sameTerm(object)) { list.elements.splice(i, 1) - saveListThenRefresh() + await saveListThenRefresh() return } } @@ -273,8 +302,9 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( * @param {Event} anyEvent if used as an event handler * @param {Boolean} upwards Move this up (true) or down (false). */ - async function move (event, upwards) { + async function moveThisItem (event, upwards) { // @@ possibly, allow shift+click to do move to top or bottom? + console.log('pre move: ' + debugString(list.elements)) for (var i = 0; i < list.elements.length; i++) { // Find object in array if (list.elements[i].sameTerm(object)) { @@ -298,21 +328,32 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( } list.elements.splice(i, 2, list.elements[i + 1], list.elements[i]) } - saveListThenRefresh() + await saveListThenRefresh() } /* A subField has been filled in + * + * One possibility is to not actually make the link to the thing until + * this callback happsn to avoid widow links */ function itemDone (uri, ok, message) { + console.log(`Item ${uri} done callback for item ${object.uri.slice(-7)}`) + if (!ok) { // when does this happen? errrs typically deal with upstream + console.error(' Item done callback: Error: ' + message) + } else { + linkDone(uri, ok, message) + } + /* + var ins, del + // alert('Multiple: item calklback.' + uri) if (ok) { // @@@ Check IT hasnt alreday been written in if (ordered) { list = kb.any(subject, property, null, store) if (!list) { - list = new $rdf.Collecion([object]) - // list.append(object) + list = new $rdf.Collection([object]) ins = [$rdf.st(subject, property, list)] // Will this work? } else { - const oldList = new $rdf.Collecion(list.elments) + const oldList = new $rdf.Collection(list.elments) list.append(object) del = [$rdf.st(subject, property, oldList)] // If this doesn't work, kb.saveBack(store) ins = [$rdf.st(subject, property, list)] @@ -324,21 +365,22 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( kb.updater.update(del, ins, linkDone) } } else { - tr.appendChild( + box.appendChild( error.errorMessageBlock(dom, 'Multiple: item failed: ' + body) ) callbackFunction(ok, message) } + */ } var linkDone = function (uri, ok, message) { return callbackFunction(ok, message) } - if (!object) object = forms.newThing(store) - UI.log.debug('Multiple: add: ' + object) - var tr = box.insertBefore(dom.createElement('tr'), tail) - var ins = [] - var del = [] + // if (!object) object = forms.newThing(store) + UI.log.debug('Multiple: render object: ' + object) + // var tr = box.insertBefore(dom.createElement('tr'), tail) + // var ins = [] + // var del = [] var fn = forms.fieldFunction(dom, element) var subField = fn(dom, null, already, object, element, store, itemDone) // p2 was: body. moving to not passing that @@ -346,29 +388,25 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( // delete button and move buttons if (kb.updater.editable(store.uri)) { - buttons.deleteButtonWithCheck( - dom, - subField, - utils.label(property), - deleteItem - ) + buttons.deleteButtonWithCheck(dom, subField, utils.label(property), + deleteThisItem) if (ordered) { subField.appendChild( - UI.widgets.button(dom, UI.icons.iconBase.noun_1369237.svg, 'Move Up'), - async event => move(event, true) + buttons.button( + dom, UI.icons.iconBase + 'noun_1369237.svg', 'Move Up', + async event => moveThisItem(event, true)) ) subField.appendChild( - UI.widgets.button( - dom, - UI.icons.iconBase.noun_1369241.svg, - 'Move Down' - ), - async event => move(event, false) + buttons.button( + dom, UI.icons.iconBase + 'noun_1369241.svg', 'Move Down', + async event => moveThisItem(event, false)) ) } } return subField // unused - } // addItem + } // renderItem + + /// ///////// Body of form field implementation var plusIconURI = UI.icons.iconBase + 'noun_19460_green.svg' // white plus in green circle @@ -380,8 +418,8 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( var ui = UI.ns.ui if (container) container.appendChild(box) - var ordered = kb.any(form, ui('ordered')) - ordered = ordered ? $rdf.Node.toJS(ordered) : false + const orderedNode = kb.any(form, ui('ordered')) + const ordered = orderedNode ? $rdf.Node.toJS(orderedNode) : false var property = kb.any(form, ui('property')) if (!property) { @@ -404,46 +442,49 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( } var body = box.appendChild(dom.createElement('tr')) // 20191207 - var tail = box.appendChild(dom.createElement('tr')) var list // The RDF collection which keeps the ordered version - var values // Initial values + var values // Initial values - an array. Even when no list yet. - var unsavedList = false // Flag that + // var unsavedList = false // Flag that if (ordered) { list = kb.any(subject, property) - if (!list) { - unsavedList = true - values = [] - // list = new $rdf.Collection() - // @@ save {subject property list} triple @@ - } else { + if (list) { values = list.elements + } else { + // unsavedList = true + values = [] } } else { values = kb.each(subject, property) list = null } + // Add control on the bottom for adding more items if (kb.updater.editable(store.uri)) { + var tail = box.appendChild(dom.createElement('tr')) + tail.style.padding = '0.5em' var img = tail.appendChild(dom.createElement('img')) img.setAttribute('src', plusIconURI) // plus sign - img.setAttribute('style', 'margin: 0.2em; width: 1em; height:1em') + img.setAttribute('style', 'margin: 0.2em; width: 1.5em; height:1.5em') img.title = 'Click to add one or more ' + utils.label(property) var prompt = tail.appendChild(dom.createElement('span')) prompt.textContent = (values.length === 0 ? 'Add one or more ' : 'Add more ') + utils.label(property) - tail.addEventListener('click', async _eventNotUsed => addItem(), true) + tail.addEventListener('click', async _eventNotUsed => { + await addItem() + }, true) } function createListIfNecessary () { - if (unsavedList) { + if (!list) { list = new $rdf.Collection() kb.add(subject, property, list, store) } - unsavedList = false } async function saveListThenRefresh () { + console.log('save list: ' + debugString(list.elements)) // 20191214 + createListIfNecessary() try { await kb.fetcher.putBack(store) @@ -456,35 +497,40 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( refresh() } - // values.forEach(function (obj) { addItem(obj) }) function refresh () { + let vals if (ordered) { - list = kb.any(subject, property) - if (!list) { - unsavedList = true - values = [] - } else { - values = list.elements - } + const li = kb.the(subject, property) + vals = li ? li.elements : [] } else { - values = kb.each(subject, property) - list = null + vals = kb.each(subject, property) + vals.sort() // acheive consistency on each refresh } - UI.utils.syncTableToArrayReOrdered(body, values, addItem) + utils.syncTableToArrayReOrdered(body, vals, renderItem) } body.refresh = refresh // Allow live update refresh() - var extra = min - values.length - if (extra > 0) { - for (var j = 0; j < extra; j++) { - console.log('Adding extra: min ' + min) - addItem() // Add blanks if less than minimum + async function asyncStuff () { + var extra = min - values.length + if (extra > 0) { + for (var j = 0; j < extra; j++) { + console.log('Adding extra: min ' + min) + await addItem() // Add blanks if less than minimum + } + await saveListThenRefresh() } - saveListThenRefresh() // async + // if (unsavedList) { + // await saveListThenRefresh() // async + // } } + asyncStuff().then( + () => { console.log(' Multiple render: async stuff ok') }, + (err) => { console.error(' Multiple render: async stuff fails. #### ', err) } + ) // async + return box -} +} // Multiple /* Text field ** @@ -614,7 +660,7 @@ function basicField ( return box } lhs.appendChild(forms.fieldLabel(dom, property, form)) - var uri = forms.bottomURI(form) + var uri = forms.mostSpecificClassURI(form) var params = forms.fieldParams[uri] if (params === undefined) params = {} // non-bottom field types can do this var style = params.style || 'font-size: 100%; margin: 0.1em; padding: 0.1em;' @@ -923,7 +969,7 @@ forms.field[UI.ns.ui('Classifier').uri] = function ( return box } -/* Choice field +/** Choice field ** ** Not nested. Generates a link to something from a given class. ** Optional subform for the thing selected. @@ -976,19 +1022,19 @@ forms.field[UI.ns.ui('Choice').uri] = function ( } // Use rdfs // UI.log.debug("%%% Choice field: possible.length 1 = "+possible.length) if (from.sameTerm(ns.rdfs('Class'))) { - for (p in forms.allClassURIs()) possible.push(kb.sym(p)) + for (p in buttons.allClassURIs()) possible.push(kb.sym(p)) // UI.log.debug("%%% Choice field: possible.length 2 = "+possible.length) } else if (from.sameTerm(ns.rdf('Property'))) { - possibleProperties = forms.propertyTriage() + possibleProperties = buttons.propertyTriage(kb) for (p in possibleProperties.op) possible.push(kb.fromNT(p)) for (p in possibleProperties.dp) possible.push(kb.fromNT(p)) opts.disambiguate = true // This is a big class, and the labels won't be enough. } else if (from.sameTerm(ns.owl('ObjectProperty'))) { - possibleProperties = forms.propertyTriage() + possibleProperties = buttons.propertyTriage(kb) for (p in possibleProperties.op) possible.push(kb.fromNT(p)) opts.disambiguate = true } else if (from.sameTerm(ns.owl('DatatypeProperty'))) { - possibleProperties = forms.propertyTriage() + possibleProperties = buttons.propertyTriage(kb) for (p in possibleProperties.dp) possible.push(kb.fromNT(p)) opts.disambiguate = true } @@ -1054,7 +1100,7 @@ forms.field[UI.ns.ui('Comment').uri] = forms.field[ var contents = kb.any(form, ui('contents')) if (!contents) contents = 'Error: No contents in comment field.' - var uri = forms.bottomURI(form) + var uri = forms.mostSpecificClassURI(form) var params = forms.fieldParams[uri] if (params === undefined) { params = {} @@ -1076,7 +1122,12 @@ forms.field[UI.ns.ui('Comment').uri] = forms.field[ /// ////////////// Form-related functions -forms.bottomURI = function (x) { +/** Which class of field is this? + * @param x a field + * @returns the URI of the most specific class + */ + +forms.mostSpecificClassURI = function (x) { var kb = UI.store var ft = kb.findTypeURIs(x) var bot = kb.bottomTypeURIs(ft) // most specific @@ -1087,7 +1138,8 @@ forms.bottomURI = function (x) { } forms.fieldFunction = function (dom, field) { - var uri = forms.bottomURI(field) + const uri = forms.mostSpecificClassURI(field) // What type + // const uri = field.uri var fun = forms.field[uri] UI.log.debug( 'paneUtils: Going to implement field ' + field + ' of type ' + uri @@ -1163,13 +1215,13 @@ forms.appendForm = function ( ) } -// Find list of properties for class +/** Find list of properties for class // // Three possible sources: Those mentioned in schemas, which exludes many // those which occur in the data we already have, and those predicates we // have come across anywahere and which are not explicitly excluded from // being used with this class. -// +*/ forms.propertiesForClass = function (kb, c) { var ns = UI.ns @@ -1200,8 +1252,11 @@ forms.propertiesForClass = function (kb, c) { return result } -// @param cla - the URI of the class -// @proap +/** Find the closest class +* @param kb The store +* @param cla - the URI of the class +* @param prop +*/ forms.findClosest = function findClosest (kb, cla, prop) { var agenda = [kb.sym(cla)] // ordered - this is breadth first search while (agenda.length > 0) { @@ -1269,11 +1324,11 @@ forms.sortByLabel = function (list) { }) } -// Button to add a new whatever using a form +/** Button to add a new whatever using a form // // @param form - optional form , else will look for one // @param store - optional store else will prompt for one (unimplemented) - +*/ forms.newButton = function ( dom, kb, @@ -1308,8 +1363,7 @@ forms.newButton = function ( return b } -// Prompt for new object of a given class -// +/** Prompt for new object of a given class // // @param dom - the document DOM for the user interface // @param kb - the graph which is the knowledge base we are working with @@ -1320,7 +1374,7 @@ forms.newButton = function ( // @param store - The web document being edited // @param callbackFunction - takes (boolean ok, string errorBody) // @returns a dom object with the form DOM - +*/ forms.promptForNew = function ( dom, kb, @@ -1390,7 +1444,7 @@ forms.promptForNew = function ( } // tabulator.outline.GotoSubject(object, true, undefined, true, undefined) } - var linkDone = function (uri, ok, body) { + function linkDone (uri, ok, body) { return callbackFunction(ok, body) } UI.log.info('paneUtils Object is ' + object) @@ -1411,7 +1465,7 @@ forms.makeDescription = function ( ) { var group = dom.createElement('div') - var sts = kb.statementsMatching(subject, predicate, undefined) // Only one please + var sts = kb.statementsMatching(subject, predicate, null, store) // Only one please if (sts.length > 1) { return error.errorMessageBlock( dom, @@ -1437,7 +1491,7 @@ forms.makeDescription = function ( } group.refresh = function () { - var v = kb.any(subject, predicate) + var v = kb.any(subject, predicate, null, store) if (v && v.value !== field.value) { field.value = v.value // don't touch widget if no change // @@ this is the place to color the field from the user who chanaged it @@ -1448,7 +1502,7 @@ forms.makeDescription = function ( submit.setAttribute('style', 'visibility: hidden; float: right;') // Keep UI clean field.disabled = true field.setAttribute('style', style + 'color: gray;') // pending - var ds = kb.statementsMatching(subject, predicate) + var ds = kb.statementsMatching(subject, predicate, null, store) var is = $rdf.st(subject, predicate, field.value, store) UI.store.updater.update(ds, is, function (uri, ok, body) { if (ok) { @@ -1500,7 +1554,7 @@ forms.makeDescription = function ( return group } -// Make SELECT element to select options +/** Make SELECT element to select options // // @param subject - a term, the subject of the statement(s) being edited. // @param predicate - a term, the predicate of the statement(s) being edited @@ -1512,7 +1566,7 @@ forms.makeDescription = function ( // @param options.subForm - If mint, then the form to be used for minting the new thing // @param store - The web document being edited // @param callbackFunction - takes (boolean ok, string errorBody) - +*/ forms.makeSelectForOptions = function ( dom, kb, @@ -1529,9 +1583,9 @@ forms.makeSelectForOptions = function ( var editable = UI.store.updater.editable(store.uri) for (var i = 0; i < possible.length; i++) { - var sub = possible[i] - // UI.log.debug('Select element: '+ sub) - if (sub.uri in uris) continue + var sub = possible[i] // @@ Maybe; make this so it works with blank nodes too + if (!sub.uri) console.warn(`makeSelectForOptions: option does not have an uri: ${sub}, with predicate: ${predicate}`) + if (!sub.uri || sub.uri in uris) continue uris[sub.uri] = true n++ } // uris is now the set of possible options @@ -1552,7 +1606,7 @@ forms.makeSelectForOptions = function ( if (predicate.sameTerm(UI.ns.rdf('type'))) { actual = kb.findTypeURIs(subject) } else { - kb.each(subject, predicate).map(function (x) { + kb.each(subject, predicate, null, store).map(function (x) { actual[x.uri] = true }) } @@ -1628,7 +1682,7 @@ forms.makeSelectForOptions = function ( UI.log.info('selectForOptions: stote = ' + store) UI.store.updater.update(ds, is, function (uri, ok, body) { actual = getActual() // refresh - // kb.each(subject, predicate).map(function(x){actual[x.uri] = true}) + // kb.each(subject, predicate, null, store).map(function(x){actual[x.uri] = true}) if (ok) { select.disabled = false // data written back if (newObject) { @@ -1765,12 +1819,13 @@ forms.makeSelectForCategory = function ( ) } -// Make SELECT element to select subclasses recurively +/** Make SELECT element to select subclasses recurively // // It will so a mutually exclusive dropdown, with another if there are nested // disjoint unions. -// Callback takes (boolean ok, string errorBody) - +// +// @param callbackFunction takes (boolean ok, string errorBody) +*/ forms.makeSelectForNestedCategory = function ( dom, kb, From 91d943e8eee030d8fd33223f74899ed96ac86f58 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sun, 15 Dec 2019 22:21:31 -0500 Subject: [PATCH 04/32] forms basically working. Making style a bit more consistent. --- src/style.js | 22 ++++-- src/widgets/forms.js | 160 ++++++++++++++++++++++--------------------- 2 files changed, 99 insertions(+), 83 deletions(-) diff --git a/src/style.js b/src/style.js index 31746e7c1..95094ce30 100644 --- a/src/style.js +++ b/src/style.js @@ -6,12 +6,26 @@ module.exports = { textInputStyle: - 'background-color: #eef; padding: 0.5em; border: .5em solid white; font-size: 100%;', + 'background-color: #eef; padding: 0.5em; border: .05em solid #88c; border-radius:0.2em; font-size: 100%; margin:0.2em; ', buttonStyle: - 'background-color: #fff; padding: 0.5em; border: .01em solid white; font-size: 100%;', // 'background-color: #eef; + 'background-color: #fff; padding: 0.7em; border: .01em solid white; border-radius:0.2em; font-size: 100%;', // 'background-color: #eef; + textButtonStyle: + 'background-color: #fff; padding: 0.7em; border: .01em solid grey; border-radius:0.2em; font-size: 100%;', // 'background-color: #eef; // The width of the text field must bot be 100% or it switches to overlapping messageBodyStyle: - 'white-space: pre-wrap; width: 99%; font-size:100%; border: 0.07em solid #eee; padding: .3em 0.5em; margin: 0.1em;', + 'white-space: pre-wrap; width: 99%; font-size:100%; border: 0.07em solid #eee; border-radius:0.2em; padding: .3em 0.5em; margin: 0.1em;', pendingeditModifier: 'color: #bbb;', - highlightColor: '#7C4DFF' // Solid lavendar https://design.inrupt.com/atomic-core/?cat=Core + highlightColor: '#7C4DFF', // Solid lavendar https://design.inrupt.com/atomic-core/?cat=Core + + // Login buttons + + signInButtonStyle: 'padding: 1em; border-radius:0.2em; margin: 2em; font-size: 100%;', // was 0.5em radius + // Forms + + formBorderColor: '#888888', // originall was brown now grey + formHeadingColor: '#888888', // originall was brown now grey + formTextInput: 'font-size: 100%; margin: 0.1em; padding: 0.1em;', // originally used this + + multilineTextInputStyle: 'font-size:100%; white-space: pre-wrap; background-color: #eef;' + + ' border: 0.07em solid gray; padding: 1em 0.5em; margin: 1em 1em;' } diff --git a/src/widgets/forms.js b/src/widgets/forms.js index fa39bd927..da0aa726e 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -22,6 +22,7 @@ var UI = { const $rdf = require('rdflib') const error = require('./error') const buttons = require('./buttons') +const ns = require('../ns') const utils = require('../utils') const checkMarkCharacter = '\u2713' @@ -48,13 +49,13 @@ const dashCharacter = '-' ** ** @returns {Element} The HTML widget created */ -forms.field[UI.ns.ui('Form').uri] = forms.field[ - UI.ns.ui('Group').uri +forms.field[ns.ui('Form').uri] = forms.field[ + ns.ui('Group').uri ] = function (dom, container, already, subject, form, store, callbackFunction) { var kb = UI.store var box = dom.createElement('div') - box.setAttribute('style', 'padding-left: 2em; border: 0.05em solid brown;') // Indent a group - var ui = UI.ns.ui + box.setAttribute('style', `padding-left: 2em; border: 0.05em solid ${UI.style.formBorderColor};`) // Indent a group + var ui = ns.ui if (container) container.appendChild(box) // Prevent loops @@ -62,7 +63,7 @@ forms.field[UI.ns.ui('Form').uri] = forms.field[ if (already[key]) { // been there done that box.appendChild(dom.createTextNode('Group: see above ' + key)) - var plist = [$rdf.st(subject, UI.ns.owl('sameAs'), subject)] // @@ need prev subject + var plist = [$rdf.st(subject, ns.owl('sameAs'), subject)] // @@ need prev subject dom.outlineManager.appendPropertyTRs(box, plist) return box } @@ -140,7 +141,7 @@ forms.field[UI.ns.ui('Form').uri] = forms.field[ ** @returns {Element} The HTML widget created */ -forms.field[UI.ns.ui('Options').uri] = function ( +forms.field[ns.ui('Options').uri] = function ( dom, container, already, @@ -152,19 +153,19 @@ forms.field[UI.ns.ui('Options').uri] = function ( var kb = UI.store var box = dom.createElement('div') // box.setAttribute('style', 'padding-left: 2em; border: 0.05em dotted purple;') // Indent Options - var ui = UI.ns.ui + var ui = ns.ui if (container) container.appendChild(box) var dependingOn = kb.any(form, ui('dependingOn')) if (!dependingOn) { - dependingOn = UI.ns.rdf('type') + dependingOn = ns.rdf('type') } // @@ default to type (do we want defaults?) var cases = kb.each(form, ui('case')) if (!cases) { box.appendChild(error.errorMessageBlock(dom, 'No cases to Options form. ')) } var values - if (dependingOn.sameTerm(UI.ns.rdf('type'))) { + if (dependingOn.sameTerm(ns.rdf('type'))) { values = kb.findTypeURIs(subject) } else { var value = kb.any(subject, dependingOn) @@ -225,7 +226,7 @@ forms.field[UI.ns.ui('Options').uri] = function ( ** ** @returns {Element} The HTML widget created */ -forms.field[UI.ns.ui('Multiple').uri] = function ( +forms.field[ns.ui('Multiple').uri] = function ( dom, container, already, @@ -415,7 +416,7 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( var box = dom.createElement('table') // We don't indent multiple as it is a sort of a prefix of the next field and has contents of one. // box.setAttribute('style', 'padding-left: 2em; border: 0.05em solid green;') // Indent a multiple - var ui = UI.ns.ui + var ui = ns.ui if (container) container.appendChild(box) const orderedNode = kb.any(form, ui('ordered')) @@ -541,79 +542,79 @@ forms.field[UI.ns.ui('Multiple').uri] = function ( forms.fieldParams = {} -forms.fieldParams[UI.ns.ui('ColorField').uri] = { +forms.fieldParams[ns.ui('ColorField').uri] = { size: 9, type: 'color', dt: 'color' } // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/color forms.fieldParams[ - UI.ns.ui('ColorField').uri + ns.ui('ColorField').uri ].pattern = /^\s*#[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]([0-9a-f][0-9a-f])?\s*$/ -forms.fieldParams[UI.ns.ui('DateField').uri] = { +forms.fieldParams[ns.ui('DateField').uri] = { size: 20, type: 'date', dt: 'date' } forms.fieldParams[ - UI.ns.ui('DateField').uri + ns.ui('DateField').uri ].pattern = /^\s*[0-9][0-9][0-9][0-9](-[0-1]?[0-9]-[0-3]?[0-9])?Z?\s*$/ -forms.fieldParams[UI.ns.ui('DateTimeField').uri] = { +forms.fieldParams[ns.ui('DateTimeField').uri] = { size: 20, type: 'date', dt: 'dateTime' } forms.fieldParams[ - UI.ns.ui('DateTimeField').uri + ns.ui('DateTimeField').uri ].pattern = /^\s*[0-9][0-9][0-9][0-9](-[0-1]?[0-9]-[0-3]?[0-9])?(T[0-2][0-9]:[0-5][0-9](:[0-5][0-9])?)?Z?\s*$/ -forms.fieldParams[UI.ns.ui('TimeField').uri] = { +forms.fieldParams[ns.ui('TimeField').uri] = { size: 10, type: 'time', dt: 'time' } forms.fieldParams[ - UI.ns.ui('TimeField').uri + ns.ui('TimeField').uri ].pattern = /^\s*([0-2]?[0-9]:[0-5][0-9](:[0-5][0-9])?)\s*$/ -forms.fieldParams[UI.ns.ui('IntegerField').uri] = { +forms.fieldParams[ns.ui('IntegerField').uri] = { size: 12, style: 'text-align: right', dt: 'integer' } -forms.fieldParams[UI.ns.ui('IntegerField').uri].pattern = /^\s*-?[0-9]+\s*$/ +forms.fieldParams[ns.ui('IntegerField').uri].pattern = /^\s*-?[0-9]+\s*$/ -forms.fieldParams[UI.ns.ui('DecimalField').uri] = { +forms.fieldParams[ns.ui('DecimalField').uri] = { size: 12, style: 'text-align: right', dt: 'decimal' } forms.fieldParams[ - UI.ns.ui('DecimalField').uri + ns.ui('DecimalField').uri ].pattern = /^\s*-?[0-9]*(\.[0-9]*)?\s*$/ -forms.fieldParams[UI.ns.ui('FloatField').uri] = { +forms.fieldParams[ns.ui('FloatField').uri] = { size: 12, style: 'text-align: right', dt: 'float' } forms.fieldParams[ - UI.ns.ui('FloatField').uri + ns.ui('FloatField').uri ].pattern = /^\s*-?[0-9]*(\.[0-9]*)?((e|E)-?[0-9]*)?\s*$/ -forms.fieldParams[UI.ns.ui('SingleLineTextField').uri] = {} -forms.fieldParams[UI.ns.ui('NamedNodeURIField').uri] = { namedNode: true } -forms.fieldParams[UI.ns.ui('TextField').uri] = {} +forms.fieldParams[ns.ui('SingleLineTextField').uri] = {} +forms.fieldParams[ns.ui('NamedNodeURIField').uri] = { namedNode: true } +forms.fieldParams[ns.ui('TextField').uri] = {} -forms.fieldParams[UI.ns.ui('PhoneField').uri] = { size: 20, uriPrefix: 'tel:' } -forms.fieldParams[UI.ns.ui('PhoneField').uri].pattern = /^\+?[\d-]+[\d]*$/ +forms.fieldParams[ns.ui('PhoneField').uri] = { size: 20, uriPrefix: 'tel:' } +forms.fieldParams[ns.ui('PhoneField').uri].pattern = /^\+?[\d-]+[\d]*$/ -forms.fieldParams[UI.ns.ui('EmailField').uri] = { +forms.fieldParams[ns.ui('EmailField').uri] = { size: 30, uriPrefix: 'mailto:' } -forms.fieldParams[UI.ns.ui('EmailField').uri].pattern = /^\s*.*@.*\..*\s*$/ // @@ Get the right regexp here +forms.fieldParams[ns.ui('EmailField').uri].pattern = /^\s*.*@.*\..*\s*$/ // @@ Get the right regexp here /** Render a basic form field * @@ -639,7 +640,7 @@ function basicField ( store, callbackFunction ) { - var ui = UI.ns.ui + var ui = ns.ui var kb = UI.store var box = dom.createElement('tr') @@ -663,9 +664,10 @@ function basicField ( var uri = forms.mostSpecificClassURI(form) var params = forms.fieldParams[uri] if (params === undefined) params = {} // non-bottom field types can do this - var style = params.style || 'font-size: 100%; margin: 0.1em; padding: 0.1em;' + var style = params.style || UI.style.textInputStyle || 'font-size: 100%; margin: 0.1em; padding: 0.1em;' // box.appendChild(dom.createTextNode(' uri='+uri+', pattern='+ params.pattern)) var field = dom.createElement('input') + field.style = UI.style.textInputStyle // Do we have to override length etc? rhs.appendChild(field) field.setAttribute('type', params.type ? params.type : 'text') @@ -732,7 +734,7 @@ function basicField ( result = new $rdf.Literal( field.value.trim(), undefined, - UI.ns.xsd(params.dt) + ns.xsd(params.dt) ) } else { result = new $rdf.Literal(field.value) @@ -791,25 +793,25 @@ function basicField ( return box } -forms.field[UI.ns.ui('PhoneField').uri] = basicField -forms.field[UI.ns.ui('EmailField').uri] = basicField -forms.field[UI.ns.ui('ColorField').uri] = basicField -forms.field[UI.ns.ui('DateField').uri] = basicField -forms.field[UI.ns.ui('DateTimeField').uri] = basicField -forms.field[UI.ns.ui('TimeField').uri] = basicField -forms.field[UI.ns.ui('NumericField').uri] = basicField -forms.field[UI.ns.ui('IntegerField').uri] = basicField -forms.field[UI.ns.ui('DecimalField').uri] = basicField -forms.field[UI.ns.ui('FloatField').uri] = basicField -forms.field[UI.ns.ui('TextField').uri] = basicField -forms.field[UI.ns.ui('SingleLineTextField').uri] = basicField -forms.field[UI.ns.ui('NamedNodeURIField').uri] = basicField +forms.field[ns.ui('PhoneField').uri] = basicField +forms.field[ns.ui('EmailField').uri] = basicField +forms.field[ns.ui('ColorField').uri] = basicField +forms.field[ns.ui('DateField').uri] = basicField +forms.field[ns.ui('DateTimeField').uri] = basicField +forms.field[ns.ui('TimeField').uri] = basicField +forms.field[ns.ui('NumericField').uri] = basicField +forms.field[ns.ui('IntegerField').uri] = basicField +forms.field[ns.ui('DecimalField').uri] = basicField +forms.field[ns.ui('FloatField').uri] = basicField +forms.field[ns.ui('TextField').uri] = basicField +forms.field[ns.ui('SingleLineTextField').uri] = basicField +forms.field[ns.ui('NamedNodeURIField').uri] = basicField /* Multiline Text field ** */ -forms.field[UI.ns.ui('MultiLineTextField').uri] = function ( +forms.field[ns.ui('MultiLineTextField').uri] = function ( dom, container, already, @@ -818,7 +820,7 @@ forms.field[UI.ns.ui('MultiLineTextField').uri] = function ( store, callbackFunction ) { - var ui = UI.ns.ui + var ui = ns.ui var kb = UI.store var property = kb.any(form, ui('property')) if (!property) { @@ -855,7 +857,7 @@ function booleanField ( callbackFunction, tristate ) { - var ui = UI.ns.ui + var ui = ns.ui var kb = UI.store var property = kb.any(form, ui('property')) if (!property) { @@ -880,7 +882,7 @@ function booleanField ( if (container) container.appendChild(box) return box } -forms.field[UI.ns.ui('BooleanField').uri] = function ( +forms.field[ns.ui('BooleanField').uri] = function ( dom, container, already, @@ -901,7 +903,7 @@ forms.field[UI.ns.ui('BooleanField').uri] = function ( ) } -forms.field[UI.ns.ui('TristateField').uri] = function ( +forms.field[ns.ui('TristateField').uri] = function ( dom, container, already, @@ -929,7 +931,7 @@ forms.field[UI.ns.ui('TristateField').uri] = function ( ** @@ To do: If a classification changes, then change any dependent Options fields. */ -forms.field[UI.ns.ui('Classifier').uri] = function ( +forms.field[ns.ui('Classifier').uri] = function ( dom, container, already, @@ -939,7 +941,7 @@ forms.field[UI.ns.ui('Classifier').uri] = function ( callbackFunction ) { var kb = UI.store - var ui = UI.ns.ui + var ui = ns.ui var category = kb.any(form, ui('category')) if (!category) { return error.errorMessageBlock(dom, 'No category for classifier: ' + form) @@ -981,7 +983,7 @@ forms.field[UI.ns.ui('Classifier').uri] = function ( ** Todo: Deal with multiple. Maybe merge with multiple code. */ -forms.field[UI.ns.ui('Choice').uri] = function ( +forms.field[ns.ui('Choice').uri] = function ( dom, container, already, @@ -990,8 +992,8 @@ forms.field[UI.ns.ui('Choice').uri] = function ( store, callbackFunction ) { - var ui = UI.ns.ui var ns = UI.ns + var ui = ns.ui var kb = UI.store var multiple = false var p @@ -1075,17 +1077,17 @@ forms.field[UI.ns.ui('Choice').uri] = function ( // Documentation - non-interactive fields // -forms.fieldParams[UI.ns.ui('Comment').uri] = { +forms.fieldParams[ns.ui('Comment').uri] = { element: 'p', - style: 'padding: 0.1em 1.5em; color: brown; white-space: pre-wrap;' + style: `padding: 0.1em 1.5em; color: ${UI.style.formHeadingColor}; white-space: pre-wrap;` } -forms.fieldParams[UI.ns.ui('Heading').uri] = { +forms.fieldParams[ns.ui('Heading').uri] = { element: 'h3', - style: 'font-size: 110%; color: brown;' + style: `font-size: 110%; color: ${UI.style.formHeadingColor};` } -forms.field[UI.ns.ui('Comment').uri] = forms.field[ - UI.ns.ui('Heading').uri +forms.field[ns.ui('Comment').uri] = forms.field[ + ns.ui('Heading').uri ] = function ( dom, container, @@ -1095,7 +1097,7 @@ forms.field[UI.ns.ui('Comment').uri] = forms.field[ _store, _callbackFunction ) { - var ui = UI.ns.ui + var ui = ns.ui var kb = UI.store var contents = kb.any(form, ui('contents')) if (!contents) contents = 'Error: No contents in comment field.' @@ -1169,7 +1171,7 @@ forms.editFormButton = function ( ) { var b = dom.createElement('button') b.setAttribute('type', 'button') - b.innerHTML = 'Edit ' + utils.label(UI.ns.ui('Form')) + b.innerHTML = 'Edit ' + utils.label(ns.ui('Form')) b.addEventListener( 'click', function (_e) { @@ -1178,13 +1180,13 @@ forms.editFormButton = function ( container, {}, form, - UI.ns.ui('FormForm'), + ns.ui('FormForm'), store, callbackFunction ) ff.setAttribute( 'style', - UI.ns.ui('FormForm').sameTerm(form) + ns.ui('FormForm').sameTerm(form) ? 'background-color: #fee;' : 'background-color: #ffffe7;' ) @@ -1265,7 +1267,7 @@ forms.findClosest = function findClosest (kb, cla, prop) { var lists = kb.each(c, prop) UI.log.debug('Lists for ' + c + ', ' + prop + ': ' + lists.length) if (lists.length !== 0) return lists - var supers = kb.each(c, UI.ns.rdfs('subClassOf')) + var supers = kb.each(c, ns.rdfs('subClassOf')) for (var i = 0; i < supers.length; i++) { agenda.push(supers[i]) UI.log.debug('findClosest: add super: ' + supers[i]) @@ -1303,7 +1305,7 @@ forms.formsFor = function (subject) { forms.sortBySequence = function (list) { var p2 = list.map(function (p) { - var k = UI.store.any(p, UI.ns.ui('sequence')) + var k = UI.store.any(p, ns.ui('sequence')) return [k || 9999, p] }) p2.sort(function (a, b) { @@ -1389,7 +1391,7 @@ forms.promptForNew = function ( var box = dom.createElement('form') if (!form) { - var lists = forms.findClosest(kb, theClass.uri, ns.ui('creationForm')) + var lists = forms.findClosest(kb, theClass.uri, UI.ns.ui('creationForm')) if (lists.length === 0) { var p = box.appendChild(dom.createElement('p')) p.textContent = @@ -1419,7 +1421,7 @@ forms.promptForNew = function ( form = lists[0] // Pick any one } UI.log.debug('form is ' + form) - box.setAttribute('style', 'border: 0.05em solid brown; color: brown') + box.setAttribute('style', `border: 0.05em solid ${UI.style.formBorderColor}; color: ${UI.style.formBorderColor}`) // @@color? box.innerHTML = '

New ' + utils.label(theClass) + '

' var formFunction = forms.fieldFunction(dom, form) @@ -1478,7 +1480,7 @@ forms.makeDescription = function ( group.appendChild(field) field.rows = desc ? desc.split('\n').length + 2 : 2 field.cols = 80 - var style = + var style = UI.style.multilineTextInputStyle || 'font-size:100%; white-space: pre-wrap; background-color: white;' + ' border: 0.07em solid gray; padding: 1em 0.5em; margin: 1em 1em;' field.setAttribute('style', style) @@ -1603,7 +1605,7 @@ forms.makeSelectForOptions = function ( var getActual = function () { actual = {} - if (predicate.sameTerm(UI.ns.rdf('type'))) { + if (predicate.sameTerm(ns.rdf('type'))) { actual = kb.findTypeURIs(subject) } else { kb.each(subject, predicate, null, store).map(function (x) { @@ -1777,11 +1779,11 @@ forms.makeSelectForCategory = function ( callbackFunction ) { var log = UI.log - var du = kb.any(category, UI.ns.owl('disjointUnionOf')) + var du = kb.any(category, ns.owl('disjointUnionOf')) var subs var multiple = false if (!du) { - subs = kb.each(undefined, UI.ns.rdfs('subClassOf'), category) + subs = kb.each(undefined, ns.rdfs('subClassOf'), category) multiple = true } else { subs = du.elements @@ -1811,7 +1813,7 @@ forms.makeSelectForCategory = function ( dom, kb, subject, - UI.ns.rdf('type'), + ns.rdf('type'), subs, { multiple: multiple, nullPrompt: '--classify--' }, store, @@ -1858,7 +1860,7 @@ forms.makeSelectForNestedCategory = function ( } if ( select.currentURI && - kb.any(kb.sym(select.currentURI), UI.ns.owl('disjointUnionOf')) + kb.any(kb.sym(select.currentURI), ns.owl('disjointUnionOf')) ) { child = forms.makeSelectForNestedCategory( dom, @@ -1938,7 +1940,7 @@ function buildCheckboxForm (dom, kb, lab, del, ins, form, store, tristate) { } if (!state && !negation) { state = null - const defa = kb.any(form, UI.ns.ui('default')) + const defa = kb.any(form, ns.ui('default')) displayState = defa ? defa.value === '1' : tristate ? null : false } } @@ -2012,7 +2014,7 @@ function buildCheckboxForm (dom, kb, lab, del, ins, form, store, tristate) { forms.buildCheckboxForm = buildCheckboxForm forms.fieldLabel = function (dom, property, form) { - var lab = UI.store.any(form, UI.ns.ui('label')) + var lab = UI.store.any(form, ns.ui('label')) if (!lab) lab = utils.label(property, true) // Init capital if (property === undefined) { return dom.createTextNode('@@Internal error: undefined property') From c3b2f39751f5b5d814b610310e971a066d5b528d Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Sun, 15 Dec 2019 22:29:31 -0500 Subject: [PATCH 05/32] secondary bug fixes --- src/widgets/forms.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/widgets/forms.js b/src/widgets/forms.js index da0aa726e..1a9a38210 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -52,10 +52,10 @@ const dashCharacter = '-' forms.field[ns.ui('Form').uri] = forms.field[ ns.ui('Group').uri ] = function (dom, container, already, subject, form, store, callbackFunction) { - var kb = UI.store + const kb = UI.store var box = dom.createElement('div') box.setAttribute('style', `padding-left: 2em; border: 0.05em solid ${UI.style.formBorderColor};`) // Indent a group - var ui = ns.ui + const ui = UI.ns.ui if (container) container.appendChild(box) // Prevent loops @@ -150,10 +150,10 @@ forms.field[ns.ui('Options').uri] = function ( store, callbackFunction ) { - var kb = UI.store + const kb = UI.store var box = dom.createElement('div') // box.setAttribute('style', 'padding-left: 2em; border: 0.05em dotted purple;') // Indent Options - var ui = ns.ui + const ui = UI.ns.ui if (container) container.appendChild(box) var dependingOn = kb.any(form, ui('dependingOn')) @@ -411,12 +411,12 @@ forms.field[ns.ui('Multiple').uri] = function ( var plusIconURI = UI.icons.iconBase + 'noun_19460_green.svg' // white plus in green circle - var kb = UI.store + const kb = UI.store kb.updater = kb.updater || new $rdf.UpdateManager(kb) var box = dom.createElement('table') // We don't indent multiple as it is a sort of a prefix of the next field and has contents of one. // box.setAttribute('style', 'padding-left: 2em; border: 0.05em solid green;') // Indent a multiple - var ui = ns.ui + const ui = UI.ns.ui if (container) container.appendChild(box) const orderedNode = kb.any(form, ui('ordered')) @@ -640,8 +640,8 @@ function basicField ( store, callbackFunction ) { - var ui = ns.ui - var kb = UI.store + const ui = UI.ns.ui + const kb = UI.store var box = dom.createElement('tr') if (container) container.appendChild(box) @@ -820,8 +820,8 @@ forms.field[ns.ui('MultiLineTextField').uri] = function ( store, callbackFunction ) { - var ui = ns.ui - var kb = UI.store + const ui = UI.ns.ui + const kb = UI.store var property = kb.any(form, ui('property')) if (!property) { return error.errorMessageBlock(dom, 'No property to text field: ' + form) @@ -857,8 +857,8 @@ function booleanField ( callbackFunction, tristate ) { - var ui = ns.ui - var kb = UI.store + const ui = UI.ns.ui + const kb = UI.store var property = kb.any(form, ui('property')) if (!property) { const errorBlock = error.errorMessageBlock( @@ -940,8 +940,8 @@ forms.field[ns.ui('Classifier').uri] = function ( store, callbackFunction ) { - var kb = UI.store - var ui = ns.ui + const kb = UI.store + const ui = UI.ns.ui var category = kb.any(form, ui('category')) if (!category) { return error.errorMessageBlock(dom, 'No category for classifier: ' + form) @@ -993,8 +993,8 @@ forms.field[ns.ui('Choice').uri] = function ( callbackFunction ) { var ns = UI.ns - var ui = ns.ui - var kb = UI.store + const ui = UI.ns.ui + const kb = UI.store var multiple = false var p var box = dom.createElement('tr') @@ -1097,8 +1097,8 @@ forms.field[ns.ui('Comment').uri] = forms.field[ _store, _callbackFunction ) { - var ui = ns.ui - var kb = UI.store + const ui = UI.ns.ui + const kb = UI.store var contents = kb.any(form, ui('contents')) if (!contents) contents = 'Error: No contents in comment field.' @@ -1130,7 +1130,7 @@ forms.field[ns.ui('Comment').uri] = forms.field[ */ forms.mostSpecificClassURI = function (x) { - var kb = UI.store + const kb = UI.store var ft = kb.findTypeURIs(x) var bot = kb.bottomTypeURIs(ft) // most specific var bots = [] @@ -1280,7 +1280,7 @@ forms.findClosest = function findClosest (kb, cla, prop) { forms.formsFor = function (subject) { var ns = UI.ns - var kb = UI.store + const kb = UI.store UI.log.debug('formsFor: subject=' + subject) var t = kb.findTypeURIs(subject) From 94dcfe8487d0a0958cab01d9ba5315f0c93fa541 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Tue, 17 Dec 2019 22:23:28 -0500 Subject: [PATCH 06/32] Add a doc on ecosystem --- Documentation/form-ecosystem.html | 268 ++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 Documentation/form-ecosystem.html diff --git a/Documentation/form-ecosystem.html b/Documentation/form-ecosystem.html new file mode 100644 index 000000000..e47e93907 --- /dev/null +++ b/Documentation/form-ecosystem.html @@ -0,0 +1,268 @@ + + + + + + + An Ecosystem of forms + + + + +

Forms in the ecosystem

+ +

+ The User Interface ontology at http://www.w3.org/ns/ui defines + RDF terms for describing forms. The + solid-ui project + provides functions to use these forms within your web application to + create a quick user interface solution. An introduction + basically describes how forms work. This document describes how forms fit into the ecosystem of apps + and views and panes databrowsers and stuff. +

+ +

How to forms end up getting used? How do form end up getting created, cpied, and modified?

+ +

The simnplest way is for a developer of a web app to inclued the mashlib, and then insert a form + into the web page as a control, specfifying the form and the subject, and the place he data is to be + stored. This is a good way of building workflow quickly. It also separtes out the + logic of the form from the prsentation details of style. + A solid-ui form is not just a set of components, it is a binding to the underlying linked data. + So no more code is needed by the developper.

+

Now look at other ways in which forms can be used within connected daat system like the mashlib and the databrowser. +

s +

Forms and classes

+ +

Forms create graphs of informatioons starting off a root node, a seed node, if you like. + They are made for redording information about things of particular types, + in particular RFDS Classes. Theer are two relationships: +

+ + + + + + + + + + + + + + + +
ui:creationFormCreation form + A form which may be used to collect information about a + hitherto locally undocumented instance instance of this class. +
ui:annotationFormAnnotation form + A form which may be used to add more infromation to an existing + instance of this class which we know something about. Anything from + adding just add one more fact, to adding a whole lot of information about a specific + facet of the thing. +
+

A creation form must have enough in it to make a record which makes sense. + Just having height and weight, for example, produces a node wiht nothing to identify + what the thing is. a creation form may have say name, address, phone number + so that the record is useful in apps. Once you have made something using a creation form, then you can + later add more data with an annotation form. You can also use any creation form + as an anootation form later to edit the data.

+

So in the form ecosystem, when a user wants to create + something new, it is reasonable to give them a list of creation forms, + for whatever classes they may be interested in. But then when gthey are looking in the UI at + an object of a particular class, then it is reasable to offer them those forms (of either type) relate to thet Class of te object, if they + want to record more things. +

+

Using RDFS inferece is useful here. If there are forms declared as being for Person class, + and the user is looking at something which is of class Student, then it follows that + the subject is also of type Person, and can so those forms may be used. + One trick is to present first the fomrs of the most speific type (Student) before the ones for the more generic type (Person) + as they may at a guess be more relevant. + +

+

How to find out forms for a given class? A simple way is to include the link to the + form in the ontology. This obviously gives it a pretty definitive status. + Many existing ontologues +

+

Communities, projects, organizations and groups may want to share sets of fomrs they use, and + so community-based indexes of these are interesting. +

+ +

Creating forms

+

Filling in forms should be something which is easy for any user to do. + It will be conceptually complicated, should be as easy as possible also for them to make a new form. + How does the work flow work? This one is easy to implement but not very easy. +

    +
  • + A user navigates from an object to its class. + Then the "builder" view of the class prompts them to make a new form, + a creation form or an annotation form. + They have to chose where to put the form. +
  • +
+ +

Maybe they want to edit an existing form.

+
    +
  • + A user is looking at a firm someone else has made. + They find a way in the UI to look at the form itself. + (An "See this form" button etc, or a peeling-back-the corner icon, say). + This takes them to the form istelf. +
  • +
  • + They then edit it if they have permission +
  • +
  • + They clone it into their own project to make their own version +
  • +
+ +

There are many ways of creating and editing forms, but because there is a form for editing forms, + one way is with that. +

+ +

Forms and Ontologies

+

If you are using the form form to create a form, then for each field you will have to supply a + predicate. The solid-ui system will give you a selector to + chose from all the predicates it knows about. + For thios reason it is good to load in ontologies you want to use + before you start editing. + The for playground has tools to do that. +

+

Some days, the user finds that the good old vocabularies the solid projet has been using + provide all the concepts they need. Some days, they will have to look further affield and find + an existing ontology just new to them. Some days they will find one almost perfect and want to imagine + of suggest + friendly ammendment to it. Some days they will decide to just create a new ontology, + for speed, for control, or for fun. + Some days they will find the ad hoc onology they created at one point is now one they want to + get people to adopt or link to from their own ontologies.

+

+ So one way or another users will become ontology users, + and then will become ontology editors and creators. We need tools to enable these workflows, + particularly the social collaborative parts. These tools are not covered here. + It is worth saying that the power of systems in a big world depends on wide interop, and you get + interop from shared vocabularies, and you get shared vocabularies from hard work, where there is much glory + but no free lunch. +

+

So if ontology editing becomes a practical part of these workflows, + can we use forms as one way of editing an ontology? + It seems reasonable, though a cutom engineered UI may be much nicer. +

+

+ + + + + + +
To we have a ...ontologyshapeform
for a ...
ontologyRDFS, OWL????
shapeSHACLshape shape
formUI??FormForm
+

+

Filling in a bit of this table seems like it may by a good move. + +

+ +

Conclusion

+

A worthy goal is to build a platform where collaborative systems can be build + by their users without having to write code. Forms can contribute to that quest.

+
+ + From fd677448dfc3c3576ab5f33def61e95d6eedd54d Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:49:28 +0100 Subject: [PATCH 07/32] Update src/widgets/buttons.js Co-Authored-By: Ted Thibodeau Jr --- src/widgets/buttons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/buttons.js b/src/widgets/buttons.js index 85fdd7a7d..8afc2b685 100644 --- a/src/widgets/buttons.js +++ b/src/widgets/buttons.js @@ -729,7 +729,7 @@ buttons.allClassURIs = function () { * or when specifying the relationshiip bteween two arbitrary things, * then er can prompt them with poperties the session knows about * -* TODO: Look again ay caching tis somewhere. (On the kb?) +* TODO: Look again by catching this somewhere. (On the kb?) * TODO: move to diff module? Not really a button. * @param {Store} kb The quadstore to be searchhed. */ From 80b8c0c1d1be6218dd28b40b0167229d5e40190b Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:49:45 +0100 Subject: [PATCH 08/32] Update src/widgets/buttons.js Co-Authored-By: Ted Thibodeau Jr --- src/widgets/buttons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/buttons.js b/src/widgets/buttons.js index 8afc2b685..c800f5107 100644 --- a/src/widgets/buttons.js +++ b/src/widgets/buttons.js @@ -727,7 +727,7 @@ buttons.allClassURIs = function () { * * When the user is inputs an RDF property, like foir a form field * or when specifying the relationshiip bteween two arbitrary things, -* then er can prompt them with poperties the session knows about +* then er can prompt them with properties the session knows about * * TODO: Look again by catching this somewhere. (On the kb?) * TODO: move to diff module? Not really a button. From 529193e2e24f5a973180208ccbe70d37d002c2a5 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:50:31 +0100 Subject: [PATCH 09/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 97073a731..b5bfd505e 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -112,7 +112,7 @@

Using Forms in the UI ontology

The User Interface ontology at http://www.w3.org/ns/ui defines RDF terms for describing forms. The - solid-ui projet + solid-ui project provides functions to use these forms within your web application to create a quick user interface solution. This document describes how.

From 420d9ae5a8ab67febfa8e4fac02d066b8d05cc50 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:50:44 +0100 Subject: [PATCH 10/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index b5bfd505e..ba28a846f 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -274,7 +274,9 @@

Form field types

- Other properties are given for each field type. +

+ Other properties are given for each field type. +

Form

From 2bf0cee0247bac9fcf005db56381dcffad84bbe8 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:50:56 +0100 Subject: [PATCH 11/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index ba28a846f..4896cea91 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -361,7 +361,7 @@

Single Value fields: Special Types

ColorField

- A color picker is used, and genertes a string which is a CSS_compatible + A color picker is used, and generates a string which is a CSS_compatible color in a string like #ffeebb

From 4029fd24ec7e7e3f48e9ae1642acd959f6846cf8 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:51:31 +0100 Subject: [PATCH 12/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 4896cea91..a53990d75 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -368,7 +368,7 @@

ColorField

DateField

- Uses a date picker on a good browser. Leaves an RDF date literal as is + Uses a date picker on a good browser. Leaves an RDF date literal as its value.

From a2955312d3d427194647440cc1ca3fcb79f7ce43 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:51:42 +0100 Subject: [PATCH 13/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index a53990d75..6178c76d3 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -374,7 +374,7 @@

DateField

DateTimeField

-

Leaves an RDF dateTime literal as is value.

+

Leaves an RDF dateTime literal as its value.

PhoneField

From e7f91801a23cd8c5121145268ede5f94f0728f90 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:51:53 +0100 Subject: [PATCH 14/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 6178c76d3..3dd246bb5 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -378,7 +378,7 @@

DateTimeField

PhoneField

-

Leaves as its value a named node with a uri which starts 'tel:'

+

Leaves as its value a named node with a tel: scheme URI

EmailField

From 921fdc9ac84cb2ef1aa8eed05ebe0c96c247cd08 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:52:24 +0100 Subject: [PATCH 15/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 3dd246bb5..8e55e3e5c 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -382,7 +382,7 @@

PhoneField

EmailField

-

Leaves as its value a named now with a uri which starts 'mailto:'

+

Leaves as its value a named node with a 'mailto:' scheme URI

Single Value fields - Text

From 79aacc69b3ba86f48d7022164af8405ebb64b3e0 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:52:38 +0100 Subject: [PATCH 16/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 8e55e3e5c..8564523cc 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -409,7 +409,7 @@

Choice

rdfs:Class - The selected thing must be a member of this class. E.g. Person. + The selected thing must be a member of this class, e.g., Person. From 0ce646f4c13d3a093f6f0b606b4a80408515ab46 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:52:49 +0100 Subject: [PATCH 17/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 8564523cc..4abd07ecb 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -419,7 +419,7 @@

Choice

When the item is found, the new data links it from the subject with - tis property. E.g. friend. + this property, e.g., friend. From 67ab4a919b5b1ab8e23b22cb6cb452a35fe0c750 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:53:06 +0100 Subject: [PATCH 18/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 4abd07ecb..b497b32da 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -429,7 +429,7 @@

Choice

xsd:Boolean - If the user doesn't find the thing they want, can they introduce a + If the user doesn't find the thing they want, can they introduce an item of that class by filling in a form about it? [Boolean] From eb6b5ff4c56a73f8083a02b6de7fce2259e4ed62 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:53:19 +0100 Subject: [PATCH 19/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index b497b32da..fdc92e65a 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -441,7 +441,7 @@

Choice

Multiple

- When the subject can have several of the same thing, like friends, ro + When the subject can have several of the same thing, like friends or phone numbers, then the Multiple field allows this. The user clicks on the green plus icon, and is prompted for a subform for the related thing. The user can also delete existing ones. From a0493d95826ba0875cd649f15481e779607b85d7 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:53:41 +0100 Subject: [PATCH 20/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index fdc92e65a..22c58c444 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -450,7 +450,7 @@

Multiple

For each new thing, the system generates an arbitrary (timestamp) URI within the file where the data is being stored. The subform is then about - that thing: the subject of the subform is not the subject of the original + that thing; the subject of the subform is not the subject of the original form. It is the field, or the address, and so on.

From 50d952baebaf1edf3b448a3a1a12d3bfc6bd6c60 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:53:52 +0100 Subject: [PATCH 21/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 22c58c444..68f6f7c0e 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -479,7 +479,7 @@

Classifier

selection. And so on.

- The classifier pops a menu to allow the user to select a set of valued to + The classifier pops a menu to allow the user to select a set of values to classify the subject.

From 5f8b13096db870c863a29c649e9a340216bdbab3 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:54:10 +0100 Subject: [PATCH 22/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 68f6f7c0e..c9e05ddfb 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -486,7 +486,7 @@

Classifier

Options

- And Options field is the 'case statement' of the form system. It will + An Options field is the 'case statement' of the form system. It will chose at runtime a subfield depending on a property, often the type, of the subject. Often used after a classifier.

From db58259c343c4552e5bc1f087dbe5254f2a38a19 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:54:22 +0100 Subject: [PATCH 23/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index c9e05ddfb..0d37211dd 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -487,7 +487,7 @@

Options

An Options field is the 'case statement' of the form system. It will - chose at runtime a subfield depending on a property, often the type, of + choose at runtime a subfield depending on a property, often the type, of the subject. Often used after a classifier.

From 74437736aef25709faf00fb7716a7d14c6c92766 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:54:35 +0100 Subject: [PATCH 24/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index 0d37211dd..f0e47542a 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -579,7 +579,7 @@

Comment

The text content of the comment. (This should be displayed by form - systems as pre-wrap mode) + systems as pre-wrap mode.) From 78157960aedc05748d61f80ea72913091bd97b62 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:54:44 +0100 Subject: [PATCH 25/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index f0e47542a..a71ab49ea 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -594,7 +594,7 @@

Conclusion

Future directions include separate implementations of the form UI code in for various platforms, and using various UI frameworks. There may also be - extension of the system with new field types, more options for setting style + extensions of the system with new field types, more options for setting style from various sources, From 2323e4cfcc734d9c4c1e947bce2d1e44a1a8875c Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:54:53 +0100 Subject: [PATCH 26/32] Update Documentation/forms-intro.html Co-Authored-By: Ted Thibodeau Jr --- Documentation/forms-intro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/forms-intro.html b/Documentation/forms-intro.html index a71ab49ea..8faf1b4d4 100644 --- a/Documentation/forms-intro.html +++ b/Documentation/forms-intro.html @@ -595,6 +595,6 @@

Conclusion

Future directions include separate implementations of the form UI code in for various platforms, and using various UI frameworks. There may also be extensions of the system with new field types, more options for setting style - from various sources, + from various sources, etc. From 0dfaa0c7d56c8eef74db48f53314291b67675791 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:55:04 +0100 Subject: [PATCH 27/32] Update src/utils.js Co-Authored-By: Ted Thibodeau Jr --- src/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.js b/src/utils.js index 4694fd453..93ed489b4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -121,7 +121,7 @@ function genUuid () { * @param {function({NamedNode})} createNewRow(thing) returns a TR table row for a new thing * * Tolerates out of order elements but puts new ones in order. - * Can be used in fact for any element type - does not have to be a table and tr. + * Can be used for any element type; does not have to be a table and tr. */ function syncTableToArray (table, things, createNewRow) { let foundOne From b11565b277eb61d73b1b21e587d509842818e7a1 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:56:21 +0100 Subject: [PATCH 28/32] Apply suggestions from code review Thank you for all your suggestions @TallTed ^_^ Co-Authored-By: Ted Thibodeau Jr --- src/utils.js | 6 +++--- src/widgets/buttons.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils.js b/src/utils.js index 93ed489b4..4a2115cf5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -173,7 +173,7 @@ function syncTableToArray (table, things, createNewRow) { * @param {function({NamedNode})} createNewRow(thing) returns a rendering of a new thing * * Ensures order matches exacly. We will re-rder existing elements if necessary - * Can be used in fact for any element type - does not have to be a table and tr. + * Can be used for any element type; does not have to be a table and tr. * Any RDF node value can only appear ONCE in the array */ function syncTableToArrayReOrdered (table, things, createNewRow) { @@ -197,10 +197,10 @@ function syncTableToArrayReOrdered (table, things, createNewRow) { const existingRow = elementMap[thing.toNT()] if (existingRow) { table.removeChild(existingRow) - table.insertBefore(existingRow, row) // Insert existing ro in place of this one + table.insertBefore(existingRow, row) // Insert existing row in place of this one } else { const newRow = createNewRow(thing) - row.before(newRow) // Insert existing ro in place of this one + row.before(newRow) // Insert existing row in place of this one newRow.subject = thing } } diff --git a/src/widgets/buttons.js b/src/widgets/buttons.js index c800f5107..95c319192 100644 --- a/src/widgets/buttons.js +++ b/src/widgets/buttons.js @@ -723,15 +723,15 @@ buttons.allClassURIs = function () { return set } -/** Figuring which propertites we know about +/** Figuring which properties we know about * -* When the user is inputs an RDF property, like foir a form field -* or when specifying the relationshiip bteween two arbitrary things, +* When the user inputs an RDF property, like for a form field +* or when specifying the relationship between two arbitrary things, * then er can prompt them with properties the session knows about * * TODO: Look again by catching this somewhere. (On the kb?) * TODO: move to diff module? Not really a button. -* @param {Store} kb The quadstore to be searchhed. +* @param {Store} kb The quadstore to be searched. */ buttons.propertyTriage = function (kb) { From 8c3751b66b8510bb75a05aab448de878d4e58253 Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Tue, 17 Dec 2019 12:58:23 +0100 Subject: [PATCH 29/32] Apply suggestions from code review Co-Authored-By: Ted Thibodeau Jr --- src/widgets/forms.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/widgets/forms.js b/src/widgets/forms.js index 1a9a38210..d0ce154a7 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -41,7 +41,7 @@ const dashCharacter = '-' ** ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this - ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} store The web document in which the data is @@ -132,7 +132,7 @@ forms.field[ns.ui('Form').uri] = forms.field[ ** ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this - ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} store The web document in which the data is @@ -218,7 +218,7 @@ forms.field[ns.ui('Options').uri] = function ( ** ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this - ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} store The web document in which the data is @@ -334,11 +334,11 @@ forms.field[ns.ui('Multiple').uri] = function ( /* A subField has been filled in * * One possibility is to not actually make the link to the thing until - * this callback happsn to avoid widow links + * this callback happens to avoid widow links */ function itemDone (uri, ok, message) { console.log(`Item ${uri} done callback for item ${object.uri.slice(-7)}`) - if (!ok) { // when does this happen? errrs typically deal with upstream + if (!ok) { // when does this happen? errors typically deal with upstream console.error(' Item done callback: Error: ' + message) } else { linkDone(uri, ok, message) @@ -505,7 +505,7 @@ forms.field[ns.ui('Multiple').uri] = function ( vals = li ? li.elements : [] } else { vals = kb.each(subject, property) - vals.sort() // acheive consistency on each refresh + vals.sort() // achieve consistency on each refresh } utils.syncTableToArrayReOrdered(body, vals, renderItem) } @@ -620,7 +620,7 @@ forms.fieldParams[ns.ui('EmailField').uri].pattern = /^\s*.*@.*\..*\s*$/ // @@ G * ** @param {Document} dom The HTML Document object aka Document Object Model ** @param {Element?} container If present, the created widget will be appended to this - ** @param {Map} already A hash table of (form, suject) kept to prevent recusive forms looping + ** @param {Map} already A hash table of (form, subject) kept to prevent recursive forms looping ** @param {Node} subject The thing about which the form displays/edits data ** @param {Node} form The form or field to be rendered ** @param {Node} store The web document in which the data is @@ -1221,7 +1221,7 @@ forms.appendForm = function ( // // Three possible sources: Those mentioned in schemas, which exludes many // those which occur in the data we already have, and those predicates we -// have come across anywahere and which are not explicitly excluded from +// have come across anywhere and which are not explicitly excluded from // being used with this class. */ From 067ec4feb5fb9d76d29b771261c093b3338523b0 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Tue, 17 Dec 2019 22:50:35 -0500 Subject: [PATCH 30/32] spelling --- Documentation/form-ecosystem.html | 57 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/Documentation/form-ecosystem.html b/Documentation/form-ecosystem.html index e47e93907..e6c2d857c 100644 --- a/Documentation/form-ecosystem.html +++ b/Documentation/form-ecosystem.html @@ -119,21 +119,21 @@

Forms in the ecosystem

and views and panes databrowsers and stuff.

-

How to forms end up getting used? How do form end up getting created, cpied, and modified?

+

How to forms end up getting used? How do form end up getting created, copied, and modified?

-

The simnplest way is for a developer of a web app to inclued the mashlib, and then insert a form - into the web page as a control, specfifying the form and the subject, and the place he data is to be - stored. This is a good way of building workflow quickly. It also separtes out the - logic of the form from the prsentation details of style. +

The simplest way is for a developer of a web app to included the mashlib, and then insert a form + into the web page as a control, specifying the form and the subject, and the place he data is to be + stored. This is a good way of building workflow quickly. It also separates out the + logic of the form from the presentation details of style. A solid-ui form is not just a set of components, it is a binding to the underlying linked data. - So no more code is needed by the developper.

-

Now look at other ways in which forms can be used within connected daat system like the mashlib and the databrowser. + So no more code is needed by the developer.

+

Now look at other ways in which forms can be used within connected data system like the mashlib and the databrowser.

s

Forms and classes

-

Forms create graphs of informatioons starting off a root node, a seed node, if you like. - They are made for redording information about things of particular types, - in particular RFDS Classes. Theer are two relationships: +

Forms create graphs of information starting off a root node, a seed node, if you like. + They are made for recording information about things of particular types, + in particular RFDS Classes. There are two relationships:

@@ -152,7 +152,7 @@

Forms and classes

Annotation form - A form which may be used to add more infromation to an existing + A form which may be used to add more information to an existing instance of this class which we know something about. Anything from adding just add one more fact, to adding a whole lot of information about a specific facet of the thing. @@ -160,32 +160,37 @@

Forms and classes

A creation form must have enough in it to make a record which makes sense. - Just having height and weight, for example, produces a node wiht nothing to identify + Just having height and weight, for example, produces a node with nothing to identify what the thing is. a creation form may have say name, address, phone number so that the record is useful in apps. Once you have made something using a creation form, then you can later add more data with an annotation form. You can also use any creation form - as an anootation form later to edit the data.

+ as an annotation form later to edit the data.

So in the form ecosystem, when a user wants to create something new, it is reasonable to give them a list of creation forms, - for whatever classes they may be interested in. But then when gthey are looking in the UI at - an object of a particular class, then it is reasable to offer them those forms (of either type) relate to thet Class of te object, if they + for whatever classes they may be interested in. But then when they are looking in the UI at + an object of a particular class, then it is reasonable to offer them those forms (of either type) relate to that Class of the object, if they want to record more things.

-

Using RDFS inferece is useful here. If there are forms declared as being for Person class, +

Using RDFS inference is useful here. If there are forms declared as being for Person class, and the user is looking at something which is of class Student, then it follows that the subject is also of type Person, and can so those forms may be used. - One trick is to present first the fomrs of the most speific type (Student) before the ones for the more generic type (Person) + One trick is to present first the forms of the most specific type (Student) before the ones for the more generic type (Person) as they may at a guess be more relevant.

How to find out forms for a given class? A simple way is to include the link to the form in the ontology. This obviously gives it a pretty definitive status. - Many existing ontologues + Many existing ontologies

-

Communities, projects, organizations and groups may want to share sets of fomrs they use, and +

Communities, projects, organizations and groups may want to share sets of forms they use, and so community-based indexes of these are interesting.

+

Forms from shapes

+

+ Forms are very simpler to shapes, as noted elsewhere. Forms can be generated from shapes. +

+

Creating forms

Filling in forms should be something which is easy for any user to do. It will be conceptually complicated, should be as easy as possible also for them to make a new form. @@ -205,7 +210,7 @@

Creating forms

A user is looking at a firm someone else has made. They find a way in the UI to look at the form itself. (An "See this form" button etc, or a peeling-back-the corner icon, say). - This takes them to the form istelf. + This takes them to the form itself.
  • They then edit it if they have permission @@ -223,17 +228,17 @@

    Forms and Ontologies

    If you are using the form form to create a form, then for each field you will have to supply a predicate. The solid-ui system will give you a selector to chose from all the predicates it knows about. - For thios reason it is good to load in ontologies you want to use + For this reason it is good to load in ontologies you want to use before you start editing. The for playground has tools to do that.

    -

    Some days, the user finds that the good old vocabularies the solid projet has been using - provide all the concepts they need. Some days, they will have to look further affield and find +

    Some days, the user finds that the good old vocabularies the solid project has been using + provide all the concepts they need. Some days, they will have to look further afield and find an existing ontology just new to them. Some days they will find one almost perfect and want to imagine of suggest - friendly ammendment to it. Some days they will decide to just create a new ontology, + friendly amendment to it. Some days they will decide to just create a new ontology, for speed, for control, or for fun. - Some days they will find the ad hoc onology they created at one point is now one they want to + Some days they will find the ad hoc ontology they created at one point is now one they want to get people to adopt or link to from their own ontologies.

    So one way or another users will become ontology users, @@ -245,7 +250,7 @@

    Forms and Ontologies

    So if ontology editing becomes a practical part of these workflows, can we use forms as one way of editing an ontology? - It seems reasonable, though a cutom engineered UI may be much nicer. + It seems reasonable, though a custom engineered UI may be much nicer.

    From a2e5d995709e90cdcfbc739a7eb70593bb56a43a Mon Sep 17 00:00:00 2001 From: Arne Hassel Date: Wed, 18 Dec 2019 09:32:54 +0100 Subject: [PATCH 31/32] Fixed some typos and sentence structure faults --- Documentation/form-ecosystem.html | 47 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/Documentation/form-ecosystem.html b/Documentation/form-ecosystem.html index e6c2d857c..c5408c689 100644 --- a/Documentation/form-ecosystem.html +++ b/Documentation/form-ecosystem.html @@ -115,20 +115,24 @@

    Forms in the ecosystem

    solid-ui project provides functions to use these forms within your web application to create a quick user interface solution. An introduction - basically describes how forms work. This document describes how forms fit into the ecosystem of apps - and views and panes databrowsers and stuff. + basically describes how forms work. This document describes how forms fit into the ecosystem of apps, + views/panes, and data browsers and stuff.

    How to forms end up getting used? How do form end up getting created, copied, and modified?

    -

    The simplest way is for a developer of a web app to included the mashlib, and then insert a form +

    + The simplest way is for a developer of a web app to included the mashlib, and then insert a form into the web page as a control, specifying the form and the subject, and the place he data is to be stored. This is a good way of building workflow quickly. It also separates out the - logic of the form from the presentation details of style. - A solid-ui form is not just a set of components, it is a binding to the underlying linked data. - So no more code is needed by the developer.

    -

    Now look at other ways in which forms can be used within connected data system like the mashlib and the databrowser. -

    s + logic of the form from the presentation details of style. + A solid-ui form is not just a set of components, it is a binding to the underlying linked data. + So no more code is needed by the developer. +

    +

    + Now look at other ways in which forms can be used within connected data system like the mashlib and + the databrowser. +

    Forms and classes

    Forms create graphs of information starting off a root node, a seed node, if you like. @@ -161,17 +165,17 @@

    Forms and classes

    A creation form must have enough in it to make a record which makes sense. Just having height and weight, for example, produces a node with nothing to identify - what the thing is. a creation form may have say name, address, phone number + what the thing is. A creation form may have say name, address, phone number so that the record is useful in apps. Once you have made something using a creation form, then you can - later add more data with an annotation form. You can also use any creation form - as an annotation form later to edit the data.

    + later add more data with an annotation form. You can also use any creation form + as an annotation form later to edit the data.

    So in the form ecosystem, when a user wants to create something new, it is reasonable to give them a list of creation forms, for whatever classes they may be interested in. But then when they are looking in the UI at an object of a particular class, then it is reasonable to offer them those forms (of either type) relate to that Class of the object, if they want to record more things.

    -

    Using RDFS inference is useful here. If there are forms declared as being for Person class, +

    Using RDFS inference is useful here. If there are forms declared as being for Person class, and the user is looking at something which is of class Student, then it follows that the subject is also of type Person, and can so those forms may be used. One trick is to present first the forms of the most specific type (Student) before the ones for the more generic type (Person) @@ -179,16 +183,15 @@

    Forms and classes

    How to find out forms for a given class? A simple way is to include the link to the - form in the ontology. This obviously gives it a pretty definitive status. - Many existing ontologies -

    + form in the ontology. This obviously gives it a pretty definitive status.

    +

    Many existing ontologies

    Communities, projects, organizations and groups may want to share sets of forms they use, and so community-based indexes of these are interesting.

    Forms from shapes

    - Forms are very simpler to shapes, as noted elsewhere. Forms can be generated from shapes. + Forms are very simpler to shapes, as noted elsewhere. Forms can be generated from shapes.

    Creating forms

    @@ -207,9 +210,9 @@

    Creating forms

    Maybe they want to edit an existing form.

    • - A user is looking at a firm someone else has made. + A user is looking at a form someone else has made. They find a way in the UI to look at the form itself. - (An "See this form" button etc, or a peeling-back-the corner icon, say). + (A "See this form" button etc, or a peeling-back-the corner icon, say). This takes them to the form itself.
    • @@ -226,13 +229,13 @@

      Creating forms

      Forms and Ontologies

      If you are using the form form to create a form, then for each field you will have to supply a - predicate. The solid-ui system will give you a selector to + predicate. The solid-ui system will give you a selector to chose from all the predicates it knows about. For this reason it is good to load in ontologies you want to use before you start editing. - The for playground has tools to do that. + The form playground has tools to do that.

      -

      Some days, the user finds that the good old vocabularies the solid project has been using +

      Some days, the user finds that the good old vocabularies the Solid project has been using provide all the concepts they need. Some days, they will have to look further afield and find an existing ontology just new to them. Some days they will find one almost perfect and want to imagine of suggest @@ -242,7 +245,7 @@

      Forms and Ontologies

      get people to adopt or link to from their own ontologies.

      So one way or another users will become ontology users, - and then will become ontology editors and creators. We need tools to enable these workflows, + and then will become ontology editors and creators. We need tools to enable these workflows, particularly the social collaborative parts. These tools are not covered here. It is worth saying that the power of systems in a big world depends on wide interop, and you get interop from shared vocabularies, and you get shared vocabularies from hard work, where there is much glory From 49165b49fce094cd66ec9535fd3e3fce96ae80d8 Mon Sep 17 00:00:00 2001 From: Tim Berners-Lee Date: Thu, 19 Dec 2019 13:13:42 -0500 Subject: [PATCH 32/32] tweaks with Arne --- .eslintignore | 1 + package.json | 4 ---- src/widgets/forms.js | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.eslintignore b/.eslintignore index 671f770cf..f3ebf520e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,4 @@ lib *.json Documentation Documentation/forms-intro.html +package.json diff --git a/package.json b/package.json index 16ae9ed90..51298f3cf 100644 --- a/package.json +++ b/package.json @@ -78,10 +78,6 @@ "pre-push": "npm test" } }, - "eslintignore": [ - "package.json", - "*.html" - ], "lint-staged": { "src/**/*.(js|ts)": [ "eslint" diff --git a/src/widgets/forms.js b/src/widgets/forms.js index d0ce154a7..9093382a0 100644 --- a/src/widgets/forms.js +++ b/src/widgets/forms.js @@ -343,7 +343,7 @@ forms.field[ns.ui('Multiple').uri] = function ( } else { linkDone(uri, ok, message) } - /* + /* Put this as a function and call it from only one place var ins, del // alert('Multiple: item calklback.' + uri) if (ok) {