Feature request: custom dialog construction via config #152

Open
opened 2025-07-21 12:12:16 +00:00 by jsheunis · 5 comments
jsheunis commented 2025-07-21 12:12:16 +00:00 (Migrated from github.com)

From @mih

There should be a collection of UI primitives (text field, int, float, drop-down). A simple language should allow for composing a dialog from a list of such primitive types and an associated label for each).
Each UI primitive would return one or more properties. An int field obvly an int. But the file uploader would return pid, bytesize, hash and name.
and now the trick:
any and all of this information is available to fill in placeholders in a user provided TTL document template (also declared in the config for the dialog)
from my POV this would be a simplistic approach to a "wizard" that could hide arbitrarily complex constructs behind a simple UI
clever combination with PID generation config is likely required, but pretty much nothing that doesn't already exist.
importantly, any relation to a particular scheme is limited to the config and is user provided
the instantiated TTL is then included in the sessions store, and could be post-edited by a user prior to submission
i think this could be powerful
(to clarify: the TTL doc template is a string, nothing more complicated)

Some initial thoughts:

  • Speaking in general terms, shacl-vue's mode of operation is similar in terms of a UI component being constructed from UI primitive as composed by a language (vuejs). The difference is that in your feature the language would allow composing via config as opposed to having to include the code in the application sources, which is powerful.
  • the resulting TTL (outcome of templated TTL being filled with values coming from the primitive UI components) would need integrating into shacl-vue's process for tracking records to be submitted, and this needs to be done in a streamlined way so as to avoid duplicating similar approaches in shacl-vue.
  • The new feature is essentially also providing a schema of sorts that helps to render a dialog with UI components, so it does the same as shacl-vue's main functionality. The difference is that the shacl that drives shacl-vue is an actual shacl schema, whereas templated TTL is just RDF data. But ideally they'd both feed into and drive the common standard functionality of shacl-vue
From @mih > There should be a collection of UI primitives (text field, int, float, drop-down). A simple language should allow for composing a dialog from a list of such primitive types and an associated label for each). > Each UI primitive would return one or more properties. An int field obvly an int. But the file uploader would return pid, bytesize, hash and name. > and now the trick: > any and all of this information is available to fill in placeholders in a user provided TTL document template (also declared in the config for the dialog) > from my POV this would be a simplistic approach to a "wizard" that could hide arbitrarily complex constructs behind a simple UI > clever combination with PID generation config is likely required, but pretty much nothing that doesn't already exist. > importantly, any relation to a particular scheme is limited to the config and is user provided > the instantiated TTL is then included in the sessions store, and could be post-edited by a user prior to submission > i think this could be powerful\ > (to clarify: the TTL doc template is a string, nothing more complicated) Some initial thoughts: - Speaking in general terms, shacl-vue's mode of operation is similar in terms of a UI component being constructed from UI primitive as composed by a language (vuejs). The difference is that in your feature the language would allow composing via config as opposed to having to include the code in the application sources, which is powerful. - the resulting TTL (outcome of templated TTL being filled with values coming from the primitive UI components) would need integrating into shacl-vue's process for tracking records to be submitted, and this needs to be done in a streamlined way so as to avoid duplicating similar approaches in shacl-vue. - The new feature is essentially also providing a schema of sorts that helps to render a dialog with UI components, so it does the same as shacl-vue's main functionality. The difference is that the shacl that drives shacl-vue is an actual shacl schema, whereas templated TTL is just RDF data. But ideally they'd both feed into and drive the common standard functionality of shacl-vue
jsheunis commented 2025-11-17 14:39:08 +00:00 (Migrated from github.com)

https://github.com/psychoinformatics-de/shacl-vue/pull/253 is an appropriate use case for developing the functionality proposed in this issue further.

One drawback of the current state of that PR is:

The other main component is the DistributionUploadEditor, which is responsible for instantiating the GitAnnexUploader based on matching criteria. The current matching criteria is that the schema slot should have an annotation shaclvue:gitAnnexUpload: true, with shaclvue being a new namespace: https://concepts.datalad.org/ns/shaclvue/. The component will then assume that the range is a Distribution, more specifically trr379ra:TRR379Distribution, and will also instantiate an InstancesSelectEditor as it usually would for a slot without the annotation. This is of course not generalized and will only work for that specific schema version and shacl-vue deployment. TODO: this aspect will be changed in future, to allow higher level generalization that assumes no specific schema or UI deployment version.

The idea is to create a TTL templating approach. The UI config will contain a TTL template that should be populated for a record of the specified class, taking the properties returned by the uploading process as inputs, and creating the necessary graph store quads from that. To feed this design process, I'm taking as an example the likelihood that the act of uploading a file won't necessarily be 1-to-1 with creating a Distribution. There might be any number of convoluted (or flat) relations with any number of other classes and slots involved. And the eventual approach should allow that.

The core inputs are:

  • the fact that a slot is annotated with shaclvue:GitAnnexUpload: true
  • There is a template TTL string, with variables embedded in curly brackets
  • The uploader component in shacl-vue returns file information using a defined API

And then once the shacl-vue form, of which one of the fields will be a new generic FileUploadEditor component, is saved:

  • save all form fields to the browser graph store, as per usual
  • also run the templating process to generate a valid TTL string
  • parse the string and add all those quads to the browser graph store as well.

For the last step, there should be a means to prevent duplicate quads.

Also, remember:

the resulting TTL (outcome of templated TTL being filled with values coming from the primitive UI components) would need integrating into shacl-vue's process for tracking records to be submitted, and this needs to be done in a streamlined way so as to avoid duplicating similar approaches in shacl-vue.

https://github.com/psychoinformatics-de/shacl-vue/pull/253 is an appropriate use case for developing the functionality proposed in this issue further. One drawback of the current state of that PR is: > The other main component is the `DistributionUploadEditor`, which is responsible for instantiating the `GitAnnexUploader` based on matching criteria. The current matching criteria is that the schema slot should have an annotation `shaclvue:gitAnnexUpload: true`, with `shaclvue` being a new namespace: `https://concepts.datalad.org/ns/shaclvue/`. The component will then assume that the range is a `Distribution`, more specifically `trr379ra:TRR379Distribution`, and will also instantiate an `InstancesSelectEditor` as it usually would for a slot without the annotation. This is of course not generalized and will only work for that specific schema version and `shacl-vue` deployment. TODO: this aspect will be changed in future, to allow higher level generalization that assumes no specific schema or UI deployment version. The idea is to create a TTL templating approach. The UI config will contain a TTL template that should be populated for a record of the specified class, taking the properties returned by the uploading process as inputs, and creating the necessary graph store quads from that. To feed this design process, I'm taking as an example the likelihood that the act of uploading a file won't necessarily be 1-to-1 with creating a `Distribution`. There might be any number of convoluted (or flat) relations with any number of other classes and slots involved. And the eventual approach should allow that. The core inputs are: - the fact that a slot is annotated with `shaclvue:GitAnnexUpload: true` - There is a template TTL string, with variables embedded in curly brackets - The uploader component in `shacl-vue` returns file information using a defined API And then once the `shacl-vue` form, of which one of the fields will be a new generic `FileUploadEditor` component, is saved: - save all form fields to the browser graph store, as per usual - also run the templating process to generate a valid TTL string - parse the string and add all those quads to the browser graph store as well. For the last step, there should be a means to prevent duplicate quads. Also, remember: > the resulting TTL (outcome of templated TTL being filled with values coming from the primitive UI components) would need integrating into shacl-vue's process for tracking records to be submitted, and this needs to be done in a streamlined way so as to avoid duplicating similar approaches in shacl-vue.
jsheunis commented 2025-11-17 15:21:10 +00:00 (Migrated from github.com)

The use case:

  • A Grant has a proposal_document slot with range Document
  • A Distribution can be a distribution_of a Document
  • The upload feature is rendered with the proposal_document slot, for ease of use

An example template:


@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix trr379ra: <https://concepts.trr379.de/s/research-assets/unreleased/>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix skos: <http://www.w3.org/2004/02/skos/core#>.
@prefix dlthings: <https://concepts.datalad.org/s/things/v1/>.
@prefix dcat: <http://www.w3.org/ns/dcat#>.
@prefix dlflatfiles: <https://concepts.datalad.org/s/flat-files/unreleased/>.
@prefix dlidentifiers: <https://concepts.datalad.org/s/identifiers/unreleased/>.
@prefix dldi: <https://pid.datalad.org/distributions/>.
@prefix spdx: <http://spdx.org/rdf/terms#>.
@prefix dlfilesmx: <https://concepts.datalad.org/s/files-mixin/unreleased/>.
@prefix dlcommonmx: <https://concepts.datalad.org/s/common-mixin/unreleased/>.

{this.pid} a trr379ra:TRR379Document;
    dlcommonmx:title {file.name}.
dldi:{file.annexKey} a trr379ra:TRR379Distribution;
    dlfilesmx:distribution_of {this.subject};
    dlfilesmx:byte_size "{file.size}"^^xsd:nonNegativeInteger;
    dlthings:characterized_by _:n0-1.
_:n0-1 a dlthings:Statement;
    rdf:object <{file.downloadUrl}>;
    rdf:predicate dcat:downloadUrl.
dldi:{file.annexKey} dlflatfiles:checksums _:n0-2.
_:n0-2 a dlidentifiers:Checksum;
    dlidentifiers:notation "{file.hash}";
    dlidentifiers:creator "http://spdx.org/rdf/terms#checksumAlgorithm_sha256"^^xsd:anyURI.

The pipeline would be:

  1. A general InstanceUploadEditor component would be rendered for any slot that has a class as range AND the shaclvue:gitAnnexUpload: true annotation; it instantiates an InstanceSelectEditor as well as the GitAnnexUploader.
  2. User uploads a file -> GitAnnexUploader emits an event with file data including name, size, hash, annexKey
  3. InstanceUploadEditor responds to the event by:
    • Running the template processing with the file data as input
    • Using existing shacl-vue-internal functionality to parse the output TTL and add new quads to the graph store (accounting for deduplicating blank nodes)
    • Sets the selected item of the InstanceSelectEditor inside the InstanceUploadEditor

The last step of setting the selected item needs additional specification, because the "item" could be of any sort (here TRR379Document). Note that the template above has {this.pid}, which is meant to receive the PID of that to-be-created and afterwards to-be-selected item. How this PID is constructed should therefore be known inside and outside of the template, which makes me think configuration is a good way to approach this. Therefore, apart from providing the TTL template itself via config, we could also provide the record PID construction template. Since the InstanceUploadEditor will render for any class, we can use config to distinguish templates per class. E.g.:

{
    "editor_config": {
        "InstanceUploadEditor": {
            "trr379ra:TRR379Document": {
                "pid_template": "",
                "ttl_template": ""
            }
        }
    }
}
The use case: - A `Grant` has a `proposal_document` slot with range `Document` - A `Distribution` can be a `distribution_of` a `Document` - The upload feature is rendered with the `proposal_document` slot, for ease of use An example template: ```ttl @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>. @prefix trr379ra: <https://concepts.trr379.de/s/research-assets/unreleased/>. @prefix xsd: <http://www.w3.org/2001/XMLSchema#>. @prefix skos: <http://www.w3.org/2004/02/skos/core#>. @prefix dlthings: <https://concepts.datalad.org/s/things/v1/>. @prefix dcat: <http://www.w3.org/ns/dcat#>. @prefix dlflatfiles: <https://concepts.datalad.org/s/flat-files/unreleased/>. @prefix dlidentifiers: <https://concepts.datalad.org/s/identifiers/unreleased/>. @prefix dldi: <https://pid.datalad.org/distributions/>. @prefix spdx: <http://spdx.org/rdf/terms#>. @prefix dlfilesmx: <https://concepts.datalad.org/s/files-mixin/unreleased/>. @prefix dlcommonmx: <https://concepts.datalad.org/s/common-mixin/unreleased/>. {this.pid} a trr379ra:TRR379Document; dlcommonmx:title {file.name}. dldi:{file.annexKey} a trr379ra:TRR379Distribution; dlfilesmx:distribution_of {this.subject}; dlfilesmx:byte_size "{file.size}"^^xsd:nonNegativeInteger; dlthings:characterized_by _:n0-1. _:n0-1 a dlthings:Statement; rdf:object <{file.downloadUrl}>; rdf:predicate dcat:downloadUrl. dldi:{file.annexKey} dlflatfiles:checksums _:n0-2. _:n0-2 a dlidentifiers:Checksum; dlidentifiers:notation "{file.hash}"; dlidentifiers:creator "http://spdx.org/rdf/terms#checksumAlgorithm_sha256"^^xsd:anyURI. ``` The pipeline would be: 1. A general `InstanceUploadEditor` component would be rendered for any slot that has a class as `range` AND the `shaclvue:gitAnnexUpload: true` annotation; it instantiates an `InstanceSelectEditor` as well as the `GitAnnexUploader`. 2. User uploads a file -> `GitAnnexUploader` emits an event with `file` data including `name`, `size`, `hash`, `annexKey` 3. `InstanceUploadEditor` responds to the event by: - Running the template processing with the `file` data as input - Using existing `shacl-vue`-internal functionality to parse the output TTL and add new quads to the graph store (accounting for deduplicating blank nodes) - Sets the selected item of the `InstanceSelectEditor` inside the `InstanceUploadEditor` The last step of setting the selected item needs additional specification, because the "item" could be of any sort (here `TRR379Document`). Note that the template above has `{this.pid}`, which is meant to receive the PID of that to-be-created and afterwards to-be-selected item. How this PID is constructed should therefore be known inside and outside of the template, which makes me think configuration is a good way to approach this. Therefore, apart from providing the TTL template itself via config, we could also provide the record PID construction template. Since the `InstanceUploadEditor` will render for any class, we can use config to distinguish templates per class. E.g.: ```json { "editor_config": { "InstanceUploadEditor": { "trr379ra:TRR379Document": { "pid_template": "", "ttl_template": "" } } } } ```
Owner

There is currently some renewed interest in this feature. First some background and exchange of ideas:

@jsheunis:

I am working through the design of the shacl-vue "assistant component" feature and would appreciate any comments.

The idea is that via config one is able to construct an input component, possibly consisting of multiple fields, the values of which upon form save will be fed into a TTL template (also provided via config) and the result will be a set of RDF quads added to the store. This feature was first requested here: datalink/shacl-vue#152.

An existing use case is the DOI. A class might have a simple string slot doi, while we prefer that the Identifier with slot notation is used for capturing such identifier information, where the DOI is just one of many possible identifiers. One could keep the doi string slot, and then:

  • construct a simple "assistant" component (here just a text field)
  • accompanied by a TTL template that defines the Identifier and notation and creator statements
  • instruct shacl-vue (via editor_selection config) to render the custom "assistant" component for this doi slot

And then the assistant feature would create the complete DOI-specific Identifier record and do the necessary linkage behind the scenes, using only the single text field input.

At this point it is important to note that we already have a component, the AssociationClassEditor, which achieves exactly the same for the given example, although currently only for blank nodes and not (yet) for named nodes. There is an issue to extend it to named node records: datalink/shacl-vue#284, essentially turning it into the KeyInfoRoleEditor. One could use this component for the DOI use case as well, by annotating the notation slot of the Identifier class with KeyInfoRole, while the range of the doi slot remains Identifier. The KeyInfoRole annotation would cause the KeyInfoRoleEditor to be rendered, which would display only the text field that populates notation, after the KeyInfoRoleEditor internally creates the Identifier record. The KeyInfoRoleEditor can also receive (via config, per class) some default statements to populate when creating the interim record, which for the current use case would be the creator statement. This would achieve the same as what the template does for the "assistant" component.

This shows that, for the DOI use case, both approaches can achieve the user-facing simplicity of presenting one field while creating a more complex structure of linked objects in the background. Main differences:

  • KeyInfoRoleEditor uses a KeyInfoRole annotation on a single "weight-carrying" field, while assistant component would allow many fields
  • KeyInfoRoleEditor uses config for simple templating of single statements, assistant component allows complex statements via TTL template
  • doi range is Identifierfor KeyInfoRoleEditor versus string for assistant component

Note that this "parity" is for the one-directional flow of: create a new record, enter a text value, record is saved. However, a shacl-vue deployment deals with two-directional information rendering, i.e. both components also need to be able to deal with: open an existing record for editing, figure out from the config where the value to render should come from (this would need to traverse a graph of RDF statements), render that value so that the correct statement is edited if the user changes the field(s). The KeyInfoRoleEditor deals with this already. The assistant component would need dedicated fuctionality to either:

  • slot in with shacl-vue's existing approach to editing form-values using its internal javascript-object-based representation of graph-data; this is the ideal and also what KeyInfoRoleEditor does, the but I don't see a solution for this for the assistant component (yet)
  • work around shacl-vue's existing approach (feels more practically achievable, but would break the existing formData pattern;)

Either way, this will need more thought and smart design, somehow using the template to establish the data structure and then reconstruct those structured routes to know which statement should populate the input field. It is not clear to me yet how to achieve this.

My feeling is that I should first follow the KeyInfoRoleEditor route, i.e. complete that feature and then see if/how it falls short for a given use case. What would help here is that we define specific and real use cases for which this functionality is necessary.

@mih:

In my mind that assistant need not be capable of a two-way transfer of information -- that is essentially a metadata transformation pipeline, which is a hard problem in general. It can perfectly be a "wizard", and still be very useful (for the same reasons you listed).

A key strength would be that such extra dialogs/forms would not need to have any counterpart in the underlying schema. They could be pure convenience, created "to taste".

@jsheunis:

I agree with those strength. I don't have a comfortable feeling about not having two-way transfer of info. What do we do if a user wants to update the DOI, for example? The text field would retain its initially entered value, so if we ignore two-way we could let the wizard just recreate the templated info upon each save. This would result in redundant/outdated Identifier records. They could of course be curated, but there wouldn't be a clear way of knowing they are outdated.

@mih:

Worth a discussion. In my mind there is no "text field would retain its initially entered value"

@jsheunis:

Ah, so it would not be a slot in the data model, it would be a completely separate field (or fields) existing only by virtue of config specification. So every time for example a Publication record is opened, this new doi field would be rendered and would be empty, because it's not tied to a slot that comes from the data model?

@mih:

Right now, I think it would be best to develop this fully detached from a particular "main" linkml schema. One way to frame this is a UI-targeted utility schema. It would have its own shapes. It may be implemented as an add-on to the main schema, but it should not impact the main schema in any way.

A class in this utility schema is basically a blueprint for a dialog (as per usual). This means everything else applies, all the shapes of the main schema are there (etc). The only difference is that the objects that come out of this form to not go into the graph store, and are not to-be-submitted anywhere. Instead, they are the inputs into the/a templating mechanism we already have. The outcome of an instantiated template goes into the graph store and can be submitted.

This makes everything one-way. It would fit into the UI as an alternative "+" button for adding objects. Instead of the primary class interface, users see a different interface (that of the wizard dialog).

This approach should also allow for adding other conveniences to such dialogs without become special cases (think editors for particular types), as they would use the same mechanisms for configuration, and would also be generally available for regular forms.

@jsheunis:

Currently working on this and wondering about:

This makes everything one-way. It would fit into the UI as an alternative "+" button for adding objects. Instead of the primary class interface, users see a different interface (that of the wizard dialog).

If we use the DOI use case, where in the process will the user add this? There will need to be a button that says "Add X (wizard)", but this is not necessarily specific to any slot (actually, by design it won't be). So it is specific to the whole class, implying that we could have such a button at the top of each form, if the relevant class has any wizard functions declared.

But then, how are "wizard functions declared"? For the DOI use case, we would want to allow this wizard functionality for any classes that would sensibly allow a DOI to be added. But internally it would require the class to be compatible with all statements made in the template associated with the wizard function.

So in terms of config, my current thought is to define such wizard functions separately, and then in addition one would declare which classes have access to which wizard functions.

Does this make sense?

i.e.:

  • user opens a form (new record or edit)
  • user sees the normal fields that they see currently
  • user also sees a section at the top with wizard functions that they can select from (assuming a class can have multiple wizard functions), e.g. Add DOI, Add crazy cupcake status, whatever
There is currently some renewed interest in this feature. First some background and exchange of ideas: @jsheunis: > I am working through the design of the `shacl-vue` "assistant component" feature and would appreciate any comments. > > The idea is that via config one is able to construct an input component, possibly consisting of multiple fields, the values of which upon form save will be fed into a TTL template (also provided via config) and the result will be a set of RDF quads added to the store. This feature was first requested here: https://hub.psychoinformatics.de/datalink/shacl-vue/issues/152. > > An existing use case is the DOI. A class might have a simple string slot `doi`, while we prefer that the `Identifier` with slot `notation` is used for capturing such identifier information, where the DOI is just one of many possible identifiers. One could keep the `doi` string slot, and then: > - construct a simple "assistant" component (here just a text field) > - accompanied by a TTL template that defines the `Identifier` and `notation` and `creator` statements > - instruct `shacl-vue` (via `editor_selection` config) to render the custom "assistant" component for this `doi` slot > > And then the assistant feature would create the complete DOI-specific `Identifier` record and do the necessary linkage behind the scenes, using only the single text field input. > > At this point it is important to note that we already have a component, the `AssociationClassEditor`, which achieves exactly the same *for the given example*, although currently only for blank nodes and not (yet) for named nodes. There is an issue to extend it to named node records: https://hub.psychoinformatics.de/datalink/shacl-vue/issues/284, essentially turning it into the `KeyInfoRoleEditor`. One could use this component for the DOI use case as well, by annotating the `notation` slot of the `Identifier` class with `KeyInfoRole`, while the range of the `doi` slot remains `Identifier`. The `KeyInfoRole` annotation would cause the `KeyInfoRoleEditor` to be rendered, which would display only the text field that populates `notation`, after the `KeyInfoRoleEditor` internally creates the `Identifier` record. The `KeyInfoRoleEditor` can also receive (via config, per class) some default statements to populate when creating the interim record, which for the current use case would be the `creator` statement. This would achieve the same as what the template does for the "assistant" component. > > This shows that, for the DOI use case, both approaches can achieve the user-facing simplicity of presenting one field while creating a more complex structure of linked objects in the background. Main differences: > - `KeyInfoRoleEditor` uses a `KeyInfoRole` annotation on a single "weight-carrying" field, while assistant component would allow many fields > - `KeyInfoRoleEditor` uses config for simple templating of single statements, assistant component allows complex statements via TTL template > - `doi` range is `Identifier`for `KeyInfoRoleEditor` versus `string` for assistant component > > Note that this "parity" is for the one-directional flow of: create a new record, enter a text value, record is saved. However, a `shacl-vue` deployment deals with two-directional information rendering, i.e. both components also need to be able to deal with: open an existing record for editing, figure out from the config where the value to render should come from (this would need to traverse a graph of RDF statements), render that value so that the correct statement is edited if the user changes the field(s). The `KeyInfoRoleEditor` deals with this already. The assistant component would need dedicated fuctionality to either: > - slot in with `shacl-vue`'s existing approach to editing form-values using its internal javascript-object-based representation of graph-data; this is the ideal and also what `KeyInfoRoleEditor` does, the but I don't see a solution for this for the assistant component (yet) > - work around `shacl-vue`'s existing approach (feels more practically achievable, but would break the existing `formData` pattern;) > > Either way, this will need more thought and smart design, somehow using the template to establish the data structure and then reconstruct those structured routes to know which statement should populate the input field. It is not clear to me yet how to achieve this. > > My feeling is that I should first follow the `KeyInfoRoleEditor` route, i.e. complete that feature and then see if/how it falls short for a given use case. What would help here is that we define specific and real use cases for which this functionality is necessary. @mih: > In my mind that assistant need not be capable of a two-way transfer of information -- that is essentially a metadata transformation pipeline, which is a hard problem in general. It can perfectly be a "wizard", and still be very useful (for the same reasons you listed). > > A key strength would be that such extra dialogs/forms would not need to have any counterpart in the underlying schema. They could be pure convenience, created "to taste". @jsheunis: > I agree with those strength. I don't have a comfortable feeling about not having two-way transfer of info. What do we do if a user wants to update the DOI, for example? The text field would retain its initially entered value, so if we ignore two-way we could let the wizard just recreate the templated info upon each save. This would result in redundant/outdated Identifier records. They could of course be curated, but there wouldn't be a clear way of knowing they are outdated. @mih: > Worth a discussion. In my mind there is no "text field would retain its initially entered value" @jsheunis: > Ah, so it would not be a slot in the data model, it would be a completely separate field (or fields) existing only by virtue of config specification. So every time for example a Publication record is opened, this new doi field would be rendered and would be empty, because it's not tied to a slot that comes from the data model? @mih: > Right now, I think it would be best to develop this fully detached from a particular "main" linkml schema. One way to frame this is a UI-targeted utility schema. It would have its own shapes. It may be implemented as an add-on to the main schema, but it should not impact the main schema in any way. > > A class in this utility schema is basically a blueprint for a dialog (as per usual). This means everything else applies, all the shapes of the main schema are there (etc). The only difference is that the objects that come out of this form to not go into the graph store, and are not to-be-submitted anywhere. Instead, they are the inputs into the/a templating mechanism we already have. The outcome of an instantiated template goes into the graph store and can be submitted. > > This makes everything one-way. It would fit into the UI as an alternative "+" button for adding objects. Instead of the primary class interface, users see a different interface (that of the wizard dialog). > > This approach should also allow for adding other conveniences to such dialogs without become special cases (think editors for particular types), as they would use the same mechanisms for configuration, and would also be generally available for regular forms. @jsheunis: > Currently working on this and wondering about: > > > This makes everything one-way. It would fit into the UI as an alternative "+" button for adding objects. Instead of the primary class interface, users see a different interface (that of the wizard dialog). > > If we use the DOI use case, where in the process will the user add this? There will need to be a button that says "Add X (wizard)", but this is not necessarily specific to any slot (actually, by design it won't be). So it is specific to the whole class, implying that we could have such a button at the top of each form, if the relevant class has any wizard functions declared. > > But then, how are "wizard functions declared"? For the DOI use case, we would want to allow this wizard functionality for any classes that would sensibly allow a DOI to be added. But internally it would require the class to be compatible with all statements made in the template associated with the wizard function. > > So in terms of config, my current thought is to define such wizard functions separately, and then in addition one would declare which classes have access to which wizard functions. > > Does this make sense? > > i.e.: > > - user opens a form (new record or edit) > - user sees the normal fields that they see currently > - user also sees a section at the top with wizard functions that they can select from (assuming a class can have multiple wizard functions), e.g. Add DOI, Add crazy cupcake status, whatever
Owner

Ok. I have working local prototype of this feature (wip commit: datalink/shacl-vue@bf8754feeb)

Config

It starts with configuration:


wizard_editors:
  DOIWizard:
    name: DOI Wizard
    tooltip: Add a DOI
    icon: mdi-identifier
    description: Enter the 'Notation' field and hit *Save* in order to create a DOI record
    inputs:
      - prop: notation
        name: Notation
        description: This will show in the tooltip over the field name
        type: text
        placeholder: 'example: 10.21105/joss.03262'
        required: true
        # pattern: 
        # default:
    template: content:DOIWizardTemplate

wizard_editor_selection:
  xyzri:XYZPublication:
    - DOIWizard

content:
  DOIWizardTemplate:
    url: DOIWizardTemplate.ttl

Looking at the new option wizard_editors, a wizard editor is declared by its name DOIWizard. A wizard editor can have a few general properties to make it intuitive for the user:

  • tooltip and icon will render for the button that initiates the specific wizard
  • description will render inside the initiated wizard form

Then one has to specify inputs. An input can have several properties (not all properties are 100% supported yet):

  • prop is the field name through which this input is referenced, both in shacl-vue internals and inside the TTL template
  • name will render as the input label
  • description is a further description of the input that will render when hovering over the input label
  • type tells shacl-vue which type of input component to render (these are the primitives that need to be supported; currently only text, text-paragraph and boolean are supported; more will be added...)
  • placeholder will render if the field has no entered value yet
  • required will make the field required (or not) for validation
  • pattern will apply a regex check (for text-based inputs) for validation
  • default will prepulate the value of the input

Finally, one has to specify a TTL template string, which will be populated using the values entered for the wizard inputs. The TTL string format is the same as is already supported by shacl-vue via the InstancesUploadEditor functionality. For example:

@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix dlthings: <https://concepts.datalad.org/s/things/unreleased/> .
@prefix ror: <https://ror.org/> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<{pid}> dcterms:identifier _:n0-1.
_:n0-1 a dlthings:DOI;
    skos:notation "{notation}";
    dcterms:creator ror:01fyxcz70.

The TTL can also be specified via file, which is done by pointing to the associated content: key, as is already supported and also shown in the config above.

Finally, another new config option wizard_editor_selection can be used to specify which wizards should be made available to which classes. The config above says: show the DOI Wizard for xyzri:XYZPublication records (during creation/editing).

Demo

The following video shows what the prototype looks like locally:

How does it work?

  1. The NodeShapeEditor, which renders for a given record when it is being created or edited inside the FormEditor, will check the wizard_editor_selection to see if any wizards needs to be made available for the current record type (a.k.a. class). If yes, it will render those as buttons right at the top of the form fields, below the All fields button. The wizards render as clickable buttons, with customizable tooltips and icons, which will open a dialog when clicked, passing along all config options related to the selected wizard.
  2. The new WizardEditor will render inside the dialog. It will display all general configured options of the wizard, and will then render a row per input. Inputs have their own descriptions and validation rules embedded (taken from config). Reset and Cancel buttons are self-explanatory. The save button will run validation on all inputs, and on successful validation will emit a save event to the parent component (NodeShapeEditor), also passing along all entered input values.
  3. On detecting a save event from a wizard, the NodeShapeEditor will:
    • receive the input values and declare them as wizardData
    • add the node ID of the current record (e.g. xyzrins:myFirstPub) to the wizardData under the pid field (this, {pid}, is how the record can be referenced inside the TTL template, which is why it is necessary as part of wizardData that is used in the next string serialization step)
    • pass wizardData to the existing fillStringTemplate utility functionality in order to get the valid TTL output
    • pass the TTL to the function that parses TTL into quads and adds them to the graph store: rdfDS.parseTTLandDedup(newTTL)

At this point we need to highlight an important caveat: the node ID of a record is how it is indexed inside formData, which is shacl-vue's internal javascript-object-based representation of the graph data; and the node ID of the current record (e.g. xyzrins:myFirstPub) is not necessarily the same as the actual PID of the current record. This could be due to multiple reasons:

  • when a new record is created, shacl-vue assigns a random UUID as the node ID for the relevant formData index
  • it is true that the PID for a record could be autogenerated (i.e. we might already know the PID when the user accesses the wizard functionality), but the PID is just another field in the form; the PID only gets special meaning when the form is saved and all values are converted to quads and added to the graph store
  • when a record is edited, the associated formData node ID migh under some circumstances be the actual PID of the record, but this is also not necessarily persistent throughout the user's interaction with the form, because they could also change the PID.

So, taking the nature of the "node ID" into account, a likely scenario here is that the following TTL could be generated:

@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix dlthings: <https://concepts.datalad.org/s/things/unreleased/> .
@prefix ror: <https://ror.org/> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<9a686266-7edc-42c2-ab75-04513831d25c> dlthings:identifier _:n0-1.
_:n0-1 a dlthings:DOI;
    skos:notation "sadf";
    dcterms:creator ror:01fyxcz70.

where <9a686266-7edc-42c2-ab75-04513831d25c> is the named node referring to the current record (e.g. the publication). Remember, now, that rdfDS.parseTTLandDedup(newTTL) parses this TTL and adds these quads to the graph store, which means that the useless <9a686266-7edc-42c2-ab75-04513831d25c> node is already in the graph store. We don't want this, so we deal with it in the next step.

  1. rdfDS.parseTTLandDedup(newTTL) returns all the quads that were parsed and added to the graph store. These are now process individually. Importantly, if an added quad has a subject value that is identical to the current node ID, we delete it from the graph store, because it uses a nonsense named node. However, we definitely still need that information linked to the current record, and that is why we also add it to the formData under the index of the current node and the quad's predicate. To demonstrate with the above example, we will:

    • remove the <9a686266-7edc-42c2-ab75-04513831d25c> dlthings:identifier _:n0-1. quad from the graph store
    • add the information contained in dlthings:identifier _:n0-1. to formData such that it is linked to the current node ID.

    This is what, by design, will make the parsed-and-saved quads actually show up in the form under the Identifiers field. Importantly, the existing standard functionality that runs when saving a form (for record creation or editing) will then take care of adding the correct quads to the graph store to replace those that were deleted.

  2. Continuing with the individual processing of quads that were parsed from TTL and added to the graph store, if an added quad has a subject value that is NOT identical to the current node ID, AND it is a named node, this means that a new record has been added to the graph store and it is therefore eligible for submission. This has to be tracked (same as what happens when a record being edited is saved, for example). This step adds those nodes to the tracking reference.

NOTE 1: there is no garbage cleanup yet, specifically with regards to quads that were added to the graph store from the parsed TTL, but were then unlinked from the main record. E.g. user creates publication, user adds DOI via wizard, this potentially adds more submittable quads to the graph store (other than the publication record being created), user afterwards decides to remove the DOI, user saves publication. This means unlinked, possibly submittable, quads are now in the graph store. There will show up in the list for submission later, and might confuse the user because they never saw them and they don't know anything about the hidden TTL template functionality.

Ok. I have working local prototype of this feature (wip commit: https://hub.psychoinformatics.de/datalink/shacl-vue/commit/bf8754feeb3388cc050a8eab14667040d71661cc) ### Config It starts with configuration: ```yaml wizard_editors: DOIWizard: name: DOI Wizard tooltip: Add a DOI icon: mdi-identifier description: Enter the 'Notation' field and hit *Save* in order to create a DOI record inputs: - prop: notation name: Notation description: This will show in the tooltip over the field name type: text placeholder: 'example: 10.21105/joss.03262' required: true # pattern: # default: template: content:DOIWizardTemplate wizard_editor_selection: xyzri:XYZPublication: - DOIWizard content: DOIWizardTemplate: url: DOIWizardTemplate.ttl ``` Looking at the new option `wizard_editors`, a wizard editor is declared by its name `DOIWizard`. A wizard editor can have a few general properties to make it intuitive for the user: - `tooltip` and `icon` will render for the button that initiates the specific wizard - `description` will render inside the initiated wizard form Then one has to specify `input`s. An `input` can have several properties (not all properties are 100% supported yet): - `prop` is the field name through which this input is referenced, both in `shacl-vue` internals and inside the TTL template - `name` will render as the input label - `description` is a further description of the input that will render when hovering over the input label - `type` tells `shacl-vue` which type of input component to render (these are the primitives that need to be supported; currently only `text`, `text-paragraph` and `boolean` are supported; more will be added...) - `placeholder` will render if the field has no entered value yet - `required` will make the field required (or not) for validation - `pattern` will apply a regex check (for text-based inputs) for validation - `default` will prepulate the value of the input Finally, one has to specify a TTL template string, which will be populated using the values entered for the wizard inputs. The TTL string format is the same as is already supported by `shacl-vue` via the `InstancesUploadEditor` functionality. For example: ```ttl @prefix dcterms: <http://purl.org/dc/terms/> . @prefix dlthings: <https://concepts.datalad.org/s/things/unreleased/> . @prefix ror: <https://ror.org/> . @prefix skos: <http://www.w3.org/2004/02/skos/core#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <{pid}> dcterms:identifier _:n0-1. _:n0-1 a dlthings:DOI; skos:notation "{notation}"; dcterms:creator ror:01fyxcz70. ``` The TTL can also be specified via file, which is done by pointing to the associated `content:` key, as is already supported and also shown in the config above. Finally, another new config option `wizard_editor_selection` can be used to specify which wizards should be made available to which classes. The config above says: show the `DOI Wizard` for `xyzri:XYZPublication` records (during creation/editing). ### Demo The following video shows what the prototype looks like locally: <video src="/attachments/95d66b67-31b5-473a-bcbe-18282bbd800f" title="wizarddemo1" controls></video> ### How does it work? 1. The `NodeShapeEditor`, which renders for a given record when it is being created or edited inside the `FormEditor`, will check the `wizard_editor_selection` to see if any wizards needs to be made available for the current record type (a.k.a. class). If yes, it will render those as buttons right at the top of the form fields, below the `All fields` button. The wizards render as clickable buttons, with customizable tooltips and icons, which will open a dialog when clicked, passing along all config options related to the selected wizard. 2. The new `WizardEditor` will render inside the dialog. It will display all general configured options of the wizard, and will then render a row per input. Inputs have their own descriptions and validation rules embedded (taken from config). Reset and Cancel buttons are self-explanatory. The save button will run validation on all inputs, and on successful validation will emit a `save` event to the parent component (`NodeShapeEditor`), also passing along all entered input values. 3. On detecting a `save` event from a wizard, the `NodeShapeEditor` will: - receive the input values and declare them as `wizardData` - add the node ID of the current record (e.g. `xyzrins:myFirstPub`) to the `wizardData` under the `pid` field (this, `{pid}`, is how the record can be referenced inside the TTL template, which is why it is necessary as part of `wizardData` that is used in the next string serialization step) - pass `wizardData` to the existing `fillStringTemplate` utility functionality in order to get the valid TTL output - pass the TTL to the function that parses TTL into quads and adds them to the graph store: `rdfDS.parseTTLandDedup(newTTL)` At this point we need to highlight an important caveat: the node ID of a record is how it is indexed inside `formData`, which is `shacl-vue`'s internal javascript-object-based representation of the graph data; and the node ID of the current record (e.g. `xyzrins:myFirstPub`) is not necessarily the same as the actual PID of the current record. This could be due to multiple reasons: - when a new record is created, `shacl-vue` assigns a random UUID as the node ID for the relevant `formData` index - it is true that the PID for a record could be autogenerated (i.e. we might already know the PID when the user accesses the wizard functionality), but the PID is just another field in the form; the PID only gets special meaning when the form is saved and all values are converted to quads and added to the graph store - when a record is edited, the associated `formData` node ID migh under some circumstances be the actual PID of the record, but this is also not necessarily persistent throughout the user's interaction with the form, because they could also change the PID. So, taking the nature of the "node ID" into account, a likely scenario here is that the following TTL could be generated: ``` @prefix dcterms: <http://purl.org/dc/terms/> . @prefix dlthings: <https://concepts.datalad.org/s/things/unreleased/> . @prefix ror: <https://ror.org/> . @prefix skos: <http://www.w3.org/2004/02/skos/core#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . <9a686266-7edc-42c2-ab75-04513831d25c> dlthings:identifier _:n0-1. _:n0-1 a dlthings:DOI; skos:notation "sadf"; dcterms:creator ror:01fyxcz70. ``` where `<9a686266-7edc-42c2-ab75-04513831d25c>` is the named node referring to the current record (e.g. the publication). Remember, now, that `rdfDS.parseTTLandDedup(newTTL)` parses this TTL and adds these quads to the graph store, which means that the useless `<9a686266-7edc-42c2-ab75-04513831d25c>` node is already in the graph store. We don't want this, so we deal with it in the next step. 4. `rdfDS.parseTTLandDedup(newTTL)` returns all the quads that were parsed and added to the graph store. These are now process individually. Importantly, if an added quad has a subject value that is identical to the current node ID, we delete it from the graph store, because it uses a nonsense named node. However, we definitely still need that information linked to the current record, and that is why we also add it to the `formData` under the index of the current node and the quad's predicate. To demonstrate with the above example, we will: - remove the `<9a686266-7edc-42c2-ab75-04513831d25c> dlthings:identifier _:n0-1.` quad from the graph store - add the information contained in `dlthings:identifier _:n0-1.` to `formData` such that it is linked to the current node ID. This is what, by design, will make the parsed-and-saved quads actually show up in the form under the `Identifiers` field. Importantly, the existing standard functionality that runs when saving a form (for record creation or editing) will then take care of adding the correct quads to the graph store to replace those that were deleted. 5. Continuing with the individual processing of quads that were parsed from TTL and added to the graph store, if an added quad has a subject value that is NOT identical to the current node ID, AND it is a named node, this means that a new record has been added to the graph store and it is therefore eligible for submission. This has to be tracked (same as what happens when a record being edited is saved, for example). This step adds those nodes to the tracking reference. NOTE 1: there is no garbage cleanup yet, specifically with regards to quads that were added to the graph store from the parsed TTL, but were then unlinked from the main record. E.g. user creates publication, user adds DOI via wizard, this potentially adds more submittable quads to the graph store (other than the publication record being created), user afterwards decides to remove the DOI, user saves publication. This means unlinked, possibly submittable, quads are now in the graph store. There will show up in the list for submission later, and might confuse the user because they never saw them and they don't know anything about the hidden TTL template functionality.
Owner

The looks great!

Loose collection of thoughts:

  • [deleted a comment about separating wizards that capture enough information to satisfy all requirement to create a new records completely -- I now believe this is a needless complication]
  • can icon be something other that an identifier in a precrafted collection? What these wizards do will likely be very specific, and just having one icon can make selecting a wizard a treasure hunt, parsing tooltips. Even now, the DOI button should better say "DOI" (not ID) to help people make the connection.
  • I wonder if it would be possible to reuse slot types from the underlying schema for the wizards (research information has 80+ of them predefined...
The looks great! Loose collection of thoughts: - [deleted a comment about separating wizards that capture enough information to satisfy all requirement to create a new records completely -- I now believe this is a needless complication] - can `icon` be something other that an identifier in a precrafted collection? What these wizards do will likely be very specific, and just having one icon can make selecting a wizard a treasure hunt, parsing tooltips. Even now, the DOI button should better say "DOI" (not ID) to help people make the connection. - I wonder if it would be possible to reuse slot types from the underlying schema for the wizards (research information has 80+ of them predefined...
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
orinoco/shacl-vue#152
No description provided.