Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advanced field type schema #306

Open
donquixote opened this issue Sep 10, 2020 · 13 comments
Open

Advanced field type schema #306

donquixote opened this issue Sep 10, 2020 · 13 comments

Comments

@donquixote
Copy link

Quick summary

Define how a structured field type can be defined in a *.ui_patterns.yml file, so that other modules can use this information.

Background

When defining a pattern in a *.ui_patterns.yml file, there is an entry 'fields', where in each field there is an entry 'type'.

In the docs, the following is said about 'type':

Field type, can be text, numeric, etc. at the moment only used for documentation purposes. Optional.

This means currently the value does not have any effect.

There is no clear definition how a field type should be described.

There is no way to describe "structured" types, e.g. "array of items, where each item has the following fields".
Some patterns I have seen use value objects, but I don't think there is an agreed-upon format to describe this in the yml file.

Problems

Developers don't know what to put in the type definition in a *.ui_patterns.yml file.

Consuming modules cannot reliably use the information, because it could be anything.
E.g. a module that renders a list of items (e.g. field items) cannot filter the available patterns to only show those which have a "list" field.

Modules with dynamic data mapping, if a user chose the wrong pattern, send data to a pattern field that expects a different data format.
This can cause errors or misbehavior. E.g. a render element is sent to a pattern that expects a list of items, and then it would attempt to render the properties...

Desired behavior

A well-defined format to describe structured fields in pattern definitions.

Then other modules can pick this up and send make sure that the data sent to the pattern has the correct format.

Examples:

  • A module that wants to show a list of items will look for patterns that have one field that expects an array of render elements.
  • A module that wants to show a list of labeled elements can look for patterns with a field that expects an array of objects, where each object has a (inline html or plain text) title and a (renderable) body. This would allow patterns like "tabs" and "accordion", but it would reject other patterns.

Proposed change

Field type format

A well-defined format to describe structured fields.

Possible formats:

  • Something like json-schema
  • Something like phpdoc or jsdoc. E.g.
    • <field name>: type: "\Acme\Foo\Timestamp" for value objects.
    • <field name>: type: render[] for an array of render arrays.
    • <field name>[<key>]: "\Acme\Foo\Timestamp" to document nested values.

Built-in plugins

Change the DsFieldTemplate plugin to only accept list-like patterns.

Create a views style plugin that uses list-like ui patterns.

@nedjo
Copy link

nedjo commented Oct 5, 2020

The Component Schema project is an option here; see #296. It's based on core's configuration schema API, itself modeled on Kwalify. I've just updated #296 with further details on how Component Schema works.

If there's interest I'd be happy to contribute to integrating Component Schema and UI Patterns.

@donquixote
Copy link
Author

@nedjo Interesting!

I am looking at the schema here:
https://git.drupalcode.org/project/bulma_components/-/blob/1.0.x/components/columns/columns.variables.yml

One thing I notice is that fields/variables that expect html or render element are just marked as "string". I would prefer if there was a dedicated type for html.
Or perhaps even more, to distinguish different types of strings:

  • block-level html.
  • inline html (this is technically the same as block-level, but some tools may benefit from this distinction).
  • rendered attributes, e.g. src=".." alt="..".
  • raw string, which needs to be escaped to be used as attribute value or as text content.

Another question would be if or how patterns and components could be used together, or if one should be seen as a replacement of the other.

@nedjo
Copy link

nedjo commented Oct 5, 2020

Or perhaps even more, to distinguish different types of strings:

Yes. So far, the custom schema types are mainly focused on the task of processing values to reduce the need for logic at the template level. For example, there's a component_string type that supports three keys beyond the standard set for schema variables:

  • provides_class. This allows for example a size variable to provide a class like is-small that will be added to an Attribute object and rendered as an HTML class.
  • provides_attribute. Like provides_class except for HTML attributes. I think this is the same as your "rendered attributes".
  • default_value. This is used for a default value. Needed because the configuration schema API we're building on doesn't support default values defined in the schema. This could be used for example in a tag variable for a button component. It defaults to button, which is what will be used if no value is given, but can be overridden if/as needed.

It's relatively easy to extend the existing schema types as needed, either directly in Component Schema or in another module. They work just like core's configuration API schemas.

Another question would be if or how patterns and components could be used together, or if one should be seen as a replacement of the other.

I think that we could use just the variables schema directly in UI Patterns. That is, define the variables as fields in UI Patterns. This wouldn't create a dependency on the components as defined by Component Schema but would just reuse the schema definition. To get access to the processing done by Component Schema's process_component() Twig extension we'd have to do some integration since currently that method requires a component type. One potential approach would be to alter the component type definitions to define a component type per UI pattern (if one doesn't already exist).

That might open two potential paths:

  1. Define UI patterns. After this proposed refactoring, pattern fields use the component schema definitions.
  2. Define components per Component Schema. Then write an integration module that includes a UiPatterns/Pattern plugin type that uses a deriver to add a UI Pattern per component, parallel to what's done for example with layouts in https://www.drupal.org/project/ui_patterns_from_layouts.

@donquixote
Copy link
Author

Thanks @nedjo !

It's relatively easy to extend the existing schema types as needed, either directly in Component Schema or in another module. They work just like core's configuration API schemas.

Ok, seems like something we could figure out and improve over time.

Another question would be if or how patterns and components could be used together, or if one should be seen as a replacement of the other.

I could be mistaken, but I think one difference is that every ui pattern is registered as a theme hook, whereas components are just templates. So it might be a good idea to only declare ui patterns that actually need to be ui patterns, and leave the rest as pure components, so not to pollute the theme registry. I am not sure about the exact performance or memory impact.

Another difference is that ui_patterns are exposed to the site-building UI, e.g. as ds field templates or as views plugins. (This is not ideal, and I think one limitation is the unspecific field types.)
On the other hand, components are not exposed in the UI anywhere, or are they?

That might open two potential paths:

Going to make sure I understand correctly..

Define UI patterns. After this proposed refactoring, pattern fields use the component schema definitions.

So a front-end developer would create a ui pattern with a *.ui_patterns.yml file, but the 'fields' part would use the schema from components.
No actual "components!" would be defined.
To mitigate backwards compatibility problems, perhaps we could introduce a new key "variables" as an alternative to "fields", which would use the advanced schema.

Currently the "fields" keys also contain "preview" values. I wonder if the component variables schema also supports this, or if we would need to find a different solution for this.
The "preview" feature is limited anyway, because it allows only one set of values per pattern. Perhaps instead we should have separate yml files with different preview scenarios.

Define components per Component Schema. Then write an integration module that includes a UiPatterns/Pattern plugin type that uses a deriver to add a UI Pattern per component, parallel to what's done for example with layouts in https://www.drupal.org/project/ui_patterns_from_layouts.

(I would call this an "adapter layer")
So the front-end developer would create "components!", which would then be implicitly registered as UI patterns?
Does a component have all the info we need for a UI pattern?
Is it wise to register all components as UI patterns, or should we only do it for some of them?

@donquixote
Copy link
Author

I am interested in this for multiple reasons:

  • We are developing a base theme that should declare some ui patterns, but the question of ui patterns vs components does come up e.g. when comparing this to Radix.
  • I would like to improve the plugins that integrate UI patterns into Drupal subsystems, if I get the time, or if someone else is going to do it.
  • I would like to integrate UI patterns with Renderkit, if that is ever going to happen for D8/D9.

@donquixote
Copy link
Author

If there's interest I'd be happy to contribute to integrating Component Schema and UI Patterns.

So I would clearly be interested :)
But the first step should be to define the desired behavior and how this would be used.

@nedjo
Copy link

nedjo commented Oct 6, 2020

it might be a good idea to only declare ui patterns that actually need to be ui patterns, and leave the rest as pure components, so not to pollute the theme registry

I suspect you're right.

components are not exposed in the UI anywhere

Correct. Before starting work I evaluated UI Patterns as a potential base but wasn't able to work with its limited fields definition. I've deliberately left that out so Component Schema could be focused just on schema functionality and be complimentary to UI Patterns (or other similar efforts).

perhaps we could introduce a new key "variables" as an alternative to "fields", which would use the advanced schema.

That sounds doable. It might open the option of doing integration in a separate module project rather than directly in ui_patterns.

Currently the "fields" keys also contain "preview" values. I wonder if the component variables schema also supports this, or if we would need to find a different solution for this.

There isn't currently a preview. There is something partially analogous: the styleguide integration, which allows defining a template to be displayed in a styleguide.

So the front-end developer would create "components!", which would then be implicitly registered as UI patterns?

Yes.

Does a component have all the info we need for a UI pattern?

Probably not, at least not without further development. A main issue is the relative complexity of variables in Component Schema vs. the single-level list of fields currently supported in UI Patterns. Like the configuration schema API they're based on, Component Schema variables can be nested through mappings and sequences. This means:

  1. Converting the schema programmatically into form elements is non-trivial.
  2. It's also non-trivial to decide how a particular (often, flat) element like an entity view mode maps to component variables.

Also, some of the Component Schema variables aren't necessarily intended to map to incoming data like entity fields but instead are more like settings. This gets into territory partially mapped out in the UI Patterns Settings module.

Also, I haven't yet looked at registering libraries per component. One option would be to follow exactly the model used in UI Patterns, so that part at least would be directly parallel in the two data structures (component, UI pattern).

Is it wise to register all components as UI patterns, or should we only do it for some of them?

Probably only some. That might be as simple as adding a boolean key to the component definition such as provide_ui_pattern or similar. There's already integration for the styleguide module so adding integration for ui_patterns would be in keeping.

@nedjo
Copy link

nedjo commented Oct 6, 2020

Converting the schema programmatically into form elements is non-trivial.

These couple of Drupal core issues (both long since stalled out) are related:

@donquixote
Copy link
Author

Converting the schema programmatically into form elements is non-trivial.

I imagine something different: Instead of producing a form for an arbitrary pattern, we would filter patterns that match a given schema requirement. E.g.

  • to display a list of field items, we look for patterns with a "sequence" field, where each sequence item is html / render element. the same patterns could also be used as views style plugins, or for any other place where we display a list of elements.
  • to convert into layouts, we filter those patterns where each field is html/render
  • for tabs / accordion, we look for patterns with a sequence of title (inline html) + body (block-level html) items.
  • for a single expand/collapse thingie, we look for patterns with a single title + body.

The field names / keys can be arbitrary, there would be some adapter logic to map fields. E.g. it could be "title" in the pattern, but "label" in the subsystem that wants to use the pattern.

If I were to plan this from scratch, perhaps I would instead start with these common types, and then the patterns should follow that: E.g. "labeled elements", "labeled element", "list"..

A pattern that goes beyond these basic types should probably be split up..

Also, some of the Component Schema variables aren't necessarily intended to map to incoming data like entity fields but instead are more like settings. This gets into territory partially mapped out in the UI Patterns Settings module.

I think it can be useful to distinguish "data input" vs "settings" when defining the schema.
Although in some cases the ambiguity could in fact be useful, so that the same variable could be fed from data or from settings.

@nedjo
Copy link

nedjo commented Oct 6, 2020

I imagine something different: Instead of producing a form for an arbitrary pattern, we would filter patterns that match a given schema requirement.

Wow, I like this idea.

The most straightforward way to integrate may be to start in Component Schema. I've opened an issue there and tried to sketch in some potential steps: [meta] Integrate with UI Patterns module. I probably haven't quite captured everything you were suggesting. Please wade in and clarify.

I would prefer if there was a dedicated type for html.

At least tangentially related: I've been mulling over how to handle the need that comes up in multiple components for a flexible container for content. I opened a related issue on Component Schema: Provide a standard way of handling arbitrary content.

@donquixote
Copy link
Author

At least tangentially related: I've been mulling over how to handle the need that comes up in multiple components for a flexible container for content.

I already commented in the linked issue.
A "container for content" should be the simplest thing ever: A component that receives a single variable with type = html.
Where this html is coming from should not be the responsibility of the component, but of the calling template or some adapter layer that can handle render elements.

@nedjo
Copy link

nedjo commented Nov 21, 2020

Basic integration of Component Schema and UI Patterns is largely complete, see [meta] Integrate with UI Patterns module. While that doesn't in itself accomplish everything discussed in this issue, it does open up new possibilities.

@OProf77
Copy link

OProf77 commented Dec 10, 2020

I don't know if this is the place to ask. I don't know how to check the contents of a variable in my pattern file.

I think that's the discussion, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants