Paris
Account created on 13 April 2012, almost 13 years ago
  • Engagement Manager at Smile 
  • Business Unit Manager at Linagora 
#

Merge Requests

More

Recent comments

🇫🇷France pdureau Paris

@effulgentsia

I don't have strong opinion about the naming of the properties we may add. Let's focus on mechanisms first.

I still believe the logic based on HTML elements in #28 is the best for now. Not because it is dealing with HTML elements, but because we are not adding information about what we expect in slots or as parent, but information about what we are as component or slot wrapper. It delegates the logic to an external, alterable, service and keep component definitions "clean" and furire proof while doing the expected job.

In #31, you propose to use both tags and HTML elements, but your example is dealing with the expectations (cell slot is expecting td components):

slots:
  cells:
    intendedFor: [table_cell, td]
 

How can we achieve that with your new proposals?

Anyway, all those proposals from everybody are useful. Step by step, we are approaching a solution.

🇫🇷France pdureau Paris

Because with the current solution, if you already had a prop named variant because of our lack of support until now, we will be BC compatible ensuring not breaking your component (even if that's pretty unlike to break tbh). So you could have both "modern" components with variants defined and "oldest" components were you had a variant prop. Both will work, and you would have _some way_ to discern between each case.

Also I kinda like having the variant explicitly in the generated template if someone ever gets to debug this deep.

I am not sure we will keep ComponentElement::generateComponentTemplate() forever.

This method is doing complex logic, and dynamically generating a confusing "proxy" template, where loading directly the component template in '#type' => 'inline_template' would be enough.

Of course, it was not done without a reason. It was added to make slots usable as Twig blocks, because:

  • template to template usage was heavily promoted at the beginning of SDC, as a strategy to "seduce" the current Drupal themers, working with node.html.twig, field.html.twig, block.html.twig...
  • there was this idea that Twig blocks would be more convenient for them than just printing the slots

But we are moving away from those Drupal templates, the new display builders (like Experience Builder or the UI Suite's Display Builder ...) are skipping those templates and the service managing those templates, the ThemeManager, may be deprecated.

So let's not add more stuff in this method.

🇫🇷France pdureau Paris

which could be avoided if the component itself is just able to get the right credentials, at least as a default behavior.

Is it possible to express this validation logic only with JSON schema and (native, no custom extension) Twig templating?

🇫🇷France pdureau Paris

Also the settings to provide are defined site-wide, not per plugin configuration.

So, this is the place were the validation logic is expected, isn't it?

🇫🇷France pdureau Paris

I created an SDC that renders a third party provider JS widget. In order to connect to the third party I had to provide some configurable global credential settings. Either I wanted to hide that from the component itself to reduce its complexit so it would have loaded them by itself from a global config object, or when I provide it as SDC prop for credentials I wish I could have a way to validate that these were valid credentials.

Could the credentials you want to validate be expressed using JSON schema? JSON schema is a powerful validation tool, and it may be enough for string validation.

If JSON schema is not enough because you need logic, it will be business logic, not UI logic, so it doesn't belong to the component template.

So, where are you calling the component renderable? From a block plugin ? A field formatter plugin ? A layout plugin ? This is where you need to put the business logic, because in Drupal business implementation emerges from plugins attachments and configurations in config entities.

🇫🇷France pdureau Paris
  • "What contrib modules are compatible with SDC?" removed because obsolete
  • "Does SDC handle attributes, title_suffix, is_admin, etc?" expanded
  • "Why some PHP namespaces are used in props schema?" added
  • "How can I pass arbitrary HTML into a component?"  clarify to avoid confusion between slots & blocks
  • Empty chapters were removed
  • Add "UI Patterns" link alongside "SDC Display" link
  • Add syntax color in YAML

See  https://www.drupal.org/project/drupal/issues/3484727 🌱 Clarify SDC documentation by toning down Twig blocks promotion Active

🇫🇷France pdureau Paris

Also, it may be the opportunity to challenge the various additions Drupal made to Twig last 10 years.

Today, in my very personal and humble opinion, I see only 5 additions which will stay relevant if we move to SDC, decoupling between UI logic and business logic, and design system philosophy:

  • the PrintNode visitor to send all printed variable to the renderer service. That's the core of Drupal Twig rendering, it works very well and must be kept.
  • Attribute object and the related create_attribute() function, but I hope this issue will allow us to get rid of it one of those days: HTML attributes as Twig mappings instead of PHP obejcts Active
  • add_class() & set_attribute() filters, they make sense used with the SDC slots.
  • the new icon() function for the Icon API. It follows Twig philosophy of using functions to generate printable data
  • the translations helpers (t function, trans tag). Important feature for a CMS. Why not proposing this to upstream Twig?

So, other Drupal additions (clean_id(), attach_library(), link(), |add_suggestion()...) seems OK to be deprecated to me, once we have "cleaned" the Render API. I would happy to discuss this.

🇫🇷France pdureau Paris

It makes sense to use the attributes variable, I was wondering about this when implementing it, also I like that the objective is to keep components free of Drupal specific functions, it should make them more compatible with external systems.

I am totally aware of that. In a perfect world, it would be better to not have such Attribute PHP object in our template, because all PHP objects must be avoided in template, and because it is better to stick as much as possible with vanilla Twig. But we don't have a replacement for this yet.

Today, among all the stuff added by Drupal to Twig, I see only 5 additions we can use in component templates:

  • the PrintNode visitor to send all printed variable to the renderer service. That's the core of Drupal Twig rendering and must be kept.
  • this Attribute object and the related create_attribute() function, but I hope this issue will allow us to get rid of it one of those days: HTML attributes as Twig mappings instead of PHP obejcts Active
  • add_class() & set_attribute() filters, they make sense with the SDC slots and they follow Twig philosophy of altering existing data.
  • the new icon() function for the Icon API. It follows Twig philosophy of using function to generate printable data
  • the translations helpers (t function, trans tag). Important feature for a CMS. Why not proposing this to upstream Twig?

Everything else (clean_id(), attach_library(), link(), |add_suggestion()...) is avoidable.

Agree, it's very likely that this text comes from CKE in the future, I changed it. I switched t a block for consistency, but I can use a variable instead if prefered.

I prefer to simply print my slots, but blocks are OK too. It is a matter of personal taste (except a few situations 🌱 Clarify SDC documentation by toning down Twig blocks promotion Active where blocks may be problematic).

Let me know if you have more feedback,

I will have a look this week.

🇫🇷France pdureau Paris

Great, so we can come back to the initial subject: Do we need preprocesses for SDC?

2 years and an half later, I have still not found a good use cases for such preprocesses, and I believe Mateu's initial intuition was right.

Preprocesses have many issues, including:
t

  • hey are triggered very late, just before the template is used and they are often used instead of an other earlier more suitable API (field formatter plugin, text filter plugin, view plugin...)
  • doing so, they can be in conflict with the current config state. A View or an Entity View Display, for example, can configured in a way, but displayed in a different way. This is confusing.
  • when used in a Drupal theme instead of a Drupal module, they often break the expected decoupling between business logic / UI logic

Moreover, if we modernize the Render API by deprecating ThemeManager::render() 🌱 Unify & simplify render & theme system: component-based rendering (enables pattern library, style guides, interface previews, client-side re-rendering) Active , preprocesses will be deprecated everywhere. So it may be silly to add a new one now.

🇫🇷France pdureau Paris

Fix embed example (unexpected parenthesis)

🇫🇷France pdureau Paris

More information about render element

🇫🇷France pdureau Paris

Let's have a look on all 37 Render Elements from Core:

$ grep -r  "\[FormElement(" . | sort | grep -v test
./lib/Drupal/Core/Datetime/Element/Datelist.php:#[FormElement('datelist')]
./lib/Drupal/Core/Datetime/Element/Datetime.php:#[FormElement('datetime')]
./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php:#[FormElement('entity_autocomplete')]
./lib/Drupal/Core/Render/Element/Button.php:#[FormElement('button')]
./lib/Drupal/Core/Render/Element/Checkboxes.php:#[FormElement('checkboxes')]
./lib/Drupal/Core/Render/Element/Checkbox.php:#[FormElement('checkbox')]
./lib/Drupal/Core/Render/Element/Color.php:#[FormElement('color')]
./lib/Drupal/Core/Render/Element/Date.php:#[FormElement('date')]
./lib/Drupal/Core/Render/Element/Email.php:#[FormElement('email')]
./lib/Drupal/Core/Render/Element/File.php:#[FormElement('file')]
./lib/Drupal/Core/Render/Element/Hidden.php:#[FormElement('hidden')]
./lib/Drupal/Core/Render/Element/ImageButton.php:#[FormElement('image_button')]
./lib/Drupal/Core/Render/Element/Item.php:#[FormElement('item')]
./lib/Drupal/Core/Render/Element/LanguageSelect.php:#[FormElement('language_select')]
./lib/Drupal/Core/Render/Element/MachineName.php:#[FormElement('machine_name')]
./lib/Drupal/Core/Render/Element/Number.php:#[FormElement('number')]
./lib/Drupal/Core/Render/Element/PasswordConfirm.php:#[FormElement('password_confirm')]
./lib/Drupal/Core/Render/Element/Password.php:#[FormElement('password')]
./lib/Drupal/Core/Render/Element/PathElement.php:#[FormElement('path')]
./lib/Drupal/Core/Render/Element/Radio.php:#[FormElement('radio')]
./lib/Drupal/Core/Render/Element/Radios.php:#[FormElement('radios')]
./lib/Drupal/Core/Render/Element/Range.php:#[FormElement('range')]
./lib/Drupal/Core/Render/Element/Search.php:#[FormElement('search')]
./lib/Drupal/Core/Render/Element/Select.php:#[FormElement('select')]
./lib/Drupal/Core/Render/Element/Submit.php:#[FormElement('submit')]
./lib/Drupal/Core/Render/Element/Table.php:#[FormElement('table')]
./lib/Drupal/Core/Render/Element/Tableselect.php:#[FormElement('tableselect')]
./lib/Drupal/Core/Render/Element/Tel.php:#[FormElement('tel')]
./lib/Drupal/Core/Render/Element/Textarea.php:#[FormElement('textarea')]
./lib/Drupal/Core/Render/Element/Textfield.php:#[FormElement('textfield')]
./lib/Drupal/Core/Render/Element/Token.php:#[FormElement('token')]
./lib/Drupal/Core/Render/Element/Url.php:#[FormElement('url')]
./lib/Drupal/Core/Render/Element/Value.php:#[FormElement('value')]
./lib/Drupal/Core/Render/Element/VerticalTabs.php:#[FormElement('vertical_tabs')]
./lib/Drupal/Core/Render/Element/Weight.php:#[FormElement('weight')]
./modules/file/src/Element/ManagedFile.php:#[FormElement('managed_file')]
./modules/language/src/Element/LanguageConfiguration.php:#[FormElement('language_configuration')]

Form elements with #theme

23 of them are wrapper around theme hooks:

$ grep -r -A 1000  "\[FormElement(" . | grep '#theme..=' | sort | grep -v test
./lib/Drupal/Core/Datetime/Element/Datelist.php-      '#theme' => 'datetime_form',
./lib/Drupal/Core/Datetime/Element/Datetime.php-      '#theme' => 'datetime_form',
./lib/Drupal/Core/Render/Element/Checkbox.php-      '#theme' => 'input__checkbox',
./lib/Drupal/Core/Render/Element/Color.php-      '#theme' => 'input__color',
./lib/Drupal/Core/Render/Element/Date.php-      '#theme' => 'input__date',
./lib/Drupal/Core/Render/Element/Email.php-      '#theme' => 'input__email',
./lib/Drupal/Core/Render/Element/File.php-      '#theme' => 'input__file',
./lib/Drupal/Core/Render/Element/Hidden.php-      '#theme' => 'input__hidden',
./lib/Drupal/Core/Render/Element/MachineName.php-      '#theme' => 'input__textfield',
./lib/Drupal/Core/Render/Element/Number.php-      '#theme' => 'input__number',
./lib/Drupal/Core/Render/Element/Password.php-      '#theme' => 'input__password',
./lib/Drupal/Core/Render/Element/Radio.php-      '#theme' => 'input__radio',
./lib/Drupal/Core/Render/Element/Range.php-      '#theme' => 'input__range',
./lib/Drupal/Core/Render/Element/Search.php-      '#theme' => 'input__search',
./lib/Drupal/Core/Render/Element/Select.php-      '#theme' => 'select',
./lib/Drupal/Core/Render/Element/Table.php-      '#theme' => 'table',
./lib/Drupal/Core/Render/Element/Tableselect.php-      '#theme' => 'table__tableselect',
./lib/Drupal/Core/Render/Element/Tel.php-      '#theme' => 'input__tel',
./lib/Drupal/Core/Render/Element/Textarea.php-      '#theme' => 'textarea',
./lib/Drupal/Core/Render/Element/Textfield.php-      '#theme' => 'input__textfield',
./lib/Drupal/Core/Render/Element/Token.php-      '#theme' => 'input__hidden',
./lib/Drupal/Core/Render/Element/Url.php-      '#theme' => 'input__url',
./modules/file/src/Element/ManagedFile.php-          '#theme' => 'file_link',
./modules/file/src/Element/ManagedFile.php-      '#theme' => 'file_managed_file',

file_managed_file:

{{ element }}

input.html.twig:

<input{{ attributes }} />{{ children }}

textarea.html.twig:

{{ value }}

select.html.twig:

<select{{ attributes }}>
  {% for option in options %}
    ...
  {% endfor %}
</select>

datetime-form.html.twig:

<div{{ attributes }}>
  {{ content }}
</div>

Other form elements

The other 14 (15?) are:

./lib/Drupal/Core/Entity/Element/EntityAutocomplete.php:#[FormElement('entity_autocomplete')]
./lib/Drupal/Core/Render/Element/Button.php:#[FormElement('button')]
./lib/Drupal/Core/Render/Element/Checkboxes.php:#[FormElement('checkboxes')]
./lib/Drupal/Core/Render/Element/ImageButton.php:#[FormElement('image_button')]
./lib/Drupal/Core/Render/Element/Item.php:#[FormElement('item')]
./lib/Drupal/Core/Render/Element/LanguageSelect.php:#[FormElement('language_select')]
./lib/Drupal/Core/Render/Element/MachineName.php:#[FormElement('machine_name')]
./lib/Drupal/Core/Render/Element/PathElement.php:#[FormElement('path')]
./lib/Drupal/Core/Render/Element/PasswordConfirm.php:#[FormElement('password_confirm')]
./lib/Drupal/Core/Render/Element/Radios.php:#[FormElement('radios')]
./lib/Drupal/Core/Render/Element/Submit.php:#[FormElement('submit')]
./lib/Drupal/Core/Render/Element/Value.php:#[FormElement('value')]
./lib/Drupal/Core/Render/Element/VerticalTabs.php:#[FormElement('vertical_tabs')]
./lib/Drupal/Core/Render/Element/Weight.php:#[FormElement('weight')]
./modules/language/src/Element/LanguageConfiguration.php:#[FormElement('language_configuration')]
🇫🇷France pdureau Paris

Hi Javier,

It would have been better to use https://www.drupal.org/project/sdc_devel because it would have caught half of my feedbacks.

statistics-group component: Twig template

You didn't use the attributes variable. A default attributes object is always injected in template and expected by many Drupal API (for example: adding a10n attributes, i18n attributes, compatibility with |set_attribute() and |add_class() filters...)

It will also allow you do manage aria-labelledby in a more readable way:

{% set attributes = statistics_group_id ? attributes.setAttribute("aria-labelledby", statistics_group_id) : attributes %}

Avoid clean_unique_id because this function call the application state through Html::getUniqueId(). A component must stay stateless.

So, instead of:

{% set statistics_group_id = title ? ('statistics-group-title'|clean_unique_id) : null %}

You can do something like that:

{% set statistics_group_id = statistics_group_id|default('statistics-group-title--' ~ random()) %}

statistics-group component: YAML definition

You used a reference to a definitions provided by Experience Builder module, so this may break if experience_builder (which is still in an early phase and is expected to change a lot before 1.0.0) is not activated:

      $ref: json-schema-definitions://experience_builder.module/textarea

It is better to directly write the expected schema:

      type: "string",
      pattern: "(.|\\r?\\n)*"

cta_url must be an URL instead of a just string. Proposal:

    cta_url:
      type: string
     format: iri-reference

description may be better as a slot, to be able to inject any renderable in the DIV:

    {% if description %}
      <div class="statistics-group__description">{{ description }}</div>
    {% endif %}

(It is not mandatory to use Twig blocks for slots, so you can keep the template like that)

statistics-item component: Twig template

Same feedback for the attributes variable.

statistics-item component: YAML definition

Same feedback for $ref: json-schema-definitions://experience_builder.module/textarea. Also, this reference is particularly problematic because named as a form widget instead of a data structure (as shared with XB team last week). It is better to avoid it.

Same feedback for description which may be better as a slot.

Other drupal_cms_olivero components

Because the development is happening outside of Drupal Core, those Olivero component went a bit under the radar and could be fixed and improved:

  • Same issues about $refs to XB
  • Same overuses of props when slots will be more suitable (examples: content in Paragraph)
  • Testimonial use the internal componentMetadata.path instead of the new icon() function.
  • Testimonial use an undefined source variable
  • Search Narrow and Search Wide use undefined title_attributes, label, content_attributes and content variables
  • Search Wide embed a SVG with the include() function instead of the expected source()Twig function (or, better, the icon() fucntion from the new Icon API)
  • ...

I will create a dedicated ticket for them.

🇫🇷France pdureau Paris

Thanks you @goz. Updating issue status.

🇫🇷France pdureau Paris

Discussed with Laurii which is agreeing with keeping this simple, with ideally a single keyword, but not using such logic based on HTML elements.

So, other proposal...

1. A single keyword: model (or something else, let's discuss) which can be optionally used both in a component definition and a slot definition:

  • At the component definition level, we put any keyword (ex: "section", "flow", "content"...)
  • At the slot definition level, we put a component id (ex: my_theme:accordion_item) OR any keyword (ex: "section", "flow", "content"... anything)

Example with component ID

my_theme:accordion parent component:

name: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    model: ["my_theme:accordion_item"]

Matching my_theme:accordion_item child component:

name: Accordion item
slots: {...}
props: {}

Example with a keyword

my_theme:accordion parent component:

name: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    model: ["accordion_item"]

Matching my_theme:accordion_item child component:

name: Accordion item
model: ["accordion_item"]
slots: {...}
props: {}

Pierre's personal note

I like the keyword-based proposal as much as my previous HTML-based proposal, but I am afraid using component ID will be considered as bad practices (because too constraining) after a while.

🇫🇷France pdureau Paris

The complexity of #19 proposal (6 new keywords! a full rule system! 1 config entity!) show how risky the subject and how careful we need to be.

Let's go back to the start of this issue, when we were still proposing straightforward solutions. Maybe using component IDs in our new keyword is too limiting:

name: Accordion
description: "Render content in a box that expands and collapses vertically."
group: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    suggested: ["my_theme:accordion_item"]

The suggestion of "tags/categories" system, or a DTD like system, as proposed by Wim in #16 & #17 is interesting.

Just brainstorming with you here...

Considering:

  • A component has a single HTML element as a root in the template. Yes, always, never seen a counter example in 8 years of component-based development. But this HTML element can be dynamically printed.
  • A slot has always a parent/wrapper element in the template. Yes, always, at least it is the component root.
  • In HTML5 specification, each element have a "Content model" which describe which children they accept, with 2 kinds of values:

Let's leverage this information, and only this information, coming from an industry standard source, without the temptation of adding drupalisms.

Proposal

By adding only 3 simple things:

1. A single keyword: model (or something else, let's discuss) which can be optionally used both in a component definition and a slot definition:

  • At the component definition level, we put the HTML element(s) of the component root in the template
  • At the slot definition level, we put the HTML element(s) of the slot wrapper in the template

Front devs own both the definition and the templates, and they all know what an HTML is. It will be easy for them.

2. A simple service with a registry of the content model all HTML elements (no need to put much data inside thanks to the HTML content model information) and:

  • a method to check if a HTML element can be the parent of the others ::checkElementModel(string $parent_element_tag, string $child_element_tag): bool;
  • a method to check if a component can be the included in the slot of an others ::checkComponentModel(string $parent_component_id, string $parent_component_slot, string $child_component_id): bool;

A display building tool will be able to use this service for altering its UI and mechanisms.

With this simple system:

  • we will propose the user to put LI outside UL, or TR outside TABLE, or P inside another P
  • this is not covering specifically the initial Accordion Items example from Mike, but it will filter the component available in an Accordion slots to a manageable list, with a low risk of doing something wrong

Example

I took the most radical example i can find in the wild 😉

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.1.x/compo...

name: Table
model: [table]
slots:
  rows:
    model: [tbody]
  header:
    model: [thead]
  footer:
    model: [tfoot]
  caption:
  model: [caption]

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.1.x/compo...

name: "(Table Row)"
model: [tr]
slots:
  cells:
    title: "Row cells"
    description: "A sequence of cell components."
    model: [tr]

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.1.x/compo...

name: "(Table Cell)"
slots:
  content:
    model: [td, th]

⚠️ To be clear, we are not putting here what we expect in slots or as parent, but what we have as component or slot wrapper. So no unexpected behaviours.

🇫🇷France pdureau Paris

A lot of nice changes from DrupalCon

🇫🇷France pdureau Paris

Revert  https://www.drupal.org/node/3352953/revisions/view/13937992/13938010 change

"Statement" is a loosely defined word in Twig documentation, used in the documentation pages of 3 tags (if, include and use) and 1 filter (number_format).  "Tags" is the term used everywhere else in documentation, including for includehttps://twig.symfony.com/doc/3.x/tags/index.html & https://twig.symfony.com/doc/3.x/tags/include.html

🇫🇷France pdureau Paris

Component ID is not only module:component but also theme:component

🇫🇷France pdureau Paris

Clearer explanation. Simple Twig examples. See:  https://www.drupal.org/comment/16045496

🇫🇷France pdureau Paris

Replace the example with block and embed by one with include.

🇫🇷France pdureau Paris

Update Quickstart

Add an example without block and with include() as the primary example.

Waiting 📌 Review and improve SDC documentation Active to be done to not override their current work.

Keep the example with block and embed, but add the print node:

{% block chip_content %}
  {{ chip_content }}
{% endblock %}

Waiting 📌 Review and improve SDC documentation Active to be done to not override their current work.

Update Using your new single-directory component

Keep the mentions of block and embed but move it to the page bottom and update any parts which can make readers believing that "slots == Twig blocks".

DONE. IN REVIEW.

Also, don't promote the use of include tag at the same level of include function:

DONE. IN REVIEW.

Render element is already explained in this page, but it can be promoted a bit.

DONE. IN REVIEW.

It is a good opportunity to explain the difference between presenter templates:

  • unavoidable because of the lack of display building: breadcrumbs.html.twig, status-message.html.twig, menu.html.twig, pager.html.twig..
  • the ones conflicting with the display building: node.html.twig, field.html.twig, views.html.twig, block.html.twig...

NOT DONE YET

Also, "Component data validation" section can be moved in "Creating a single-directory component" page.

DONE. IN REVIEW.

Update Creating a single-directory component

The replacement component is still a full component with its Twig template and its YAML definition.

TODO

Data primitives mapping between the 3 formats we are manipulating:

TODO

Tell something about custom & contrib Twig extensions.

TOOD

Update What are Props and Slots in Drupal SDC Theming?

Remove any mention of block and embed. The goal of this page is to teach about slots and props, not to list all ways of calling components from templates.

TODO

A bit of rewording and an annotated component screenshot like this can also help the reader:

TODO

Update API for Single-Directory Components

Replace the example with block and embed by one with include. Or simply remove the example.

TODO

Also, a schema can be a good way to show how the different parts of the API are interacting.

TODO

Update Frequently Asked Questions

says you can't override a component in a module, but I was able to do it,

TODO

🇫🇷France pdureau Paris

Add recommendation about  with_context and only.

🇫🇷France pdureau Paris

"Component data validation" moved from "Using your new single-directory component" to here

🇫🇷France pdureau Paris
  • Add & clarify information about render element & Drupal Twig templates
  • Move "Component data validation" to "Creating a single-directory component" page

See:  https://www.drupal.org/project/drupal/issues/3484727 🌱 Clarify SDC documentation by toning down Twig blocks promotion Active

🇫🇷France pdureau Paris

So we are good. I am currently editing the documentation.

🇫🇷France pdureau Paris

Currently with Javier. I will review this before any merge.

🇫🇷France pdureau Paris

Nice to see so much great people here (David, Jean, Mike, Wim).

It would be nice to add a Icon API Form Element into Drupal Core. We didn't do it last December, in order to iterate in contrib space first. But it may be relevant now.

🇫🇷France pdureau Paris

Interesting. Also, I am proposing in this XB issue https://www.drupal.org/project/experience_builder/issues/3515074#comment... 🐛 References must not be required to guess props' JSON schema Active to move 2 UIP2 service to Core: they may be useful for XB too, they are inspired from a Python library, they don't depend on anything related to UI Patterns, and they implement pure JSON schema logic.

🇫🇷France pdureau Paris

I think the concerns would be reduced if [PP-1] Allow schema references in Single Directory Component prop schemas Postponed is done and core provided a handful of common definitions — such as for images.

Indeed, i have added a extensive comment [PP-1] Allow schema references in Single Directory Component prop schemas Postponed with the hope we will move fast on this subject. However, it may be a complicated task if we want to do things right.

Slightly tricky: if the ordering of the properties is different, it should also match. For example: not src/alt/width/height, but width/height/alt/src. I assume you agree those should all be treated as equivalent?

Of course, order of properties in a JSON object doesn't matter as far as I know. You can have a look on UI Patterns 2's Canonicalizer service: we are ksorting the properties before working on them.

The compatible subset example … is an interesting observation 😅 I hadn't thought of that one for sure! I want to think that through some more, but purely logically speaking, it's hard to refute.

You can have a look on UI Patterns 2's CompatibilityChecker service. We have happily used this for one year for similar needs.

🇫🇷France pdureau Paris

Just a little up-to-date summary of the 2 existing JSON Schema stream wrapper in contrib.

Experience Builder

  Drupal\experience_builder\JsonSchemaDefinitionsStreamwrapper:
    public: true
    tags:
      - { name: stream_wrapper, scheme: json-schema-definitions }

Example: json-schema-definitions://foo.module/bar

JsonSchemaDefinitionsStreamwrapper service is:

  1. Extracting the extension and the JSON schema $def ID from the URI. Example: foo module as extension and bar $def
  2. Load the schema.json file from the extension path. Example: /modules/contrib/foo/schema.json
  3. Return the extracted $def

Pro: is generic, any module can provide JSON schema definition without any dependency to Experience builder outside this stream wrapper.
Con: only static declaration

UI Patterns 2

  ui_patterns.schema_stream_wrapper:
    class: Drupal\ui_patterns\SchemaManager\StreamWrapper
    arguments:
      - "@plugin.manager.ui_patterns_prop_type"
    tags:
      - { name: stream_wrapper, scheme: ui-patterns }

Example: ui-patterns://bar

Streamwrapper service is:

  1. Extracting the . Example: foo module as extension and bar $def
  2. Load theschema.json file from the extension path
  3. Return the extracted $def

Pro: a dependency to plugin.manager.ui_patterns_prop_type service
Con: $defs are shipped alongside some niceties (type converter, data normalizer...) as a cohesive

Proposal

We can go the Experience Builder way because it is easier to ask UI Patterns 2 maintainers to add and maintain a schema.json file in their module instead of introducing a new plugin type to Core.

Also, this may be the opportunity to:

  • simplify a bit the URI scheme (previous proposal was $ref: module://experience_builder/image but we can propose another schema if "module" or "theme" are too generic) because this kind of URL will be typed a lot by component authors, with a lot of chance of mistakes
  • considerate Mateu's advice to use URI anchors to target the $defs ($ref: #) to leverage existing JSON schema mechanisms instead of extracting manually like JsonSchemaDefinitionsStreamwrapper is actually doing (however, I would advise to explore $anchor keyword instead of the propsoed JSON pointer

So, if we mix both, we can have something like that: $ref: module://experience_builder#image

This may not be enough

However, this proposal is not solving another issue reported by @finnsky : [2.0.0-beta5] Use HTTP URL isntead of a stream wrapper in JSON reference Closed: duplicate

We are becoming more and more generic, but it is still a Drupal only proposal (module and theme scheme, schema.json convention...).

What can we do to make those references working outside of Drupal (for example: in storybook)?

🇫🇷France pdureau Paris

Thanks you so much Kensae, your solution is very pragmatic, targeting exactly where the issue here in SDC prop conversion.

However, we are currently facing a similar issue in UI Example module: Support Single directory components Active and I would address to address both:

  • in a more generic way, because the root cause is the wrong sue of render children by SDC
  • in a similar way in both module

So, I let this in review state for now.

🇫🇷France pdureau Paris

Hi @nicxvan, you have open a MR, can you assign the issue to you so people will know you are working on it?

🇫🇷France pdureau Paris

From Enrique Lacoma:

Overriding components in a module, in the doc https://www.drupal.org/docs/develop/theming-drupal/using-single-director... says you can't override a component in a module, but I was able to do it, is the documentation correct or doing the override inside a module can create any issue? thanks

🇫🇷France pdureau Paris

So, we have agreed on using meta:enum and leveraging this information and the related translations (from locale module API) is up to the display building tools like UI Patterns 2 and Experience Builder.

Also, adding meta:enum to the documentation will be done in this issue 🌱 Clarify SDC documentation by toning down Twig blocks promotion Active .

So, what can we do in Core?

  • Add a mention of meta:enum in ComponentMetadata? Ans some light logic?
  • Add meta:enum to some of our test components
  • Add a specific test about meta:enum? which one?
🇫🇷France pdureau Paris

Review OK

But PHPCS fix to do.

I will create the drupal_cms_olivero issue, because the real problem is on their side.

🇫🇷France pdureau Paris

Suggestion from @mlncn about replaces property:

looking to improve the documentation slightly, the general advice should be to copy the entire component subdirectory into your themes components directory and add the replaces line. It's not like it can inherit anything it's a true replacement, yes?

Indeed, the replacement component is still a full component with its Twig template and its YAML definition.

🇫🇷France pdureau Paris

The projects might not be using them directly, but there are dozens/hundreds of preprocess hooks running on those projects via core and contrib modules.

Of course.

🇫🇷France pdureau Paris

but everything else in #99 I'm 100% agreed

@catch in #106 ? ;)

I don't fully understand #type => component or how to entirely get rid of preprocess via SDCs yet

In UI Suite community (not only the contributors in drupal.org, but also the agencies doing projects I am interacting with), we are not using preprocess hooks anymore, for many years, except for forms. I don't really know why it is a less an issue for us. Maybe because we fully switched to a design system + display builder paradigm. It is worth investigating and writing that down.

Adding three concrete active issues that could use reviews/help.

Thanks you. I will have a look after Atlanta.

I'm more likely to implement a twig extension instead of a preprocess function nowadays.

Twig extensions have a high risk of being harmful:

  • Written in PHP, they may hide some business logic specific to a specific project, or calls to CMS API. A SDC component is just a "dumb" piece of UI logic receiving already resolved data.
  • Written in PHP, they may introduce PHP in a Drupal theme (which we cant to avoid, a theme must be a front dev friendly place) or a dependency to module.
  • Instead of fixing the root cause (why this data is not OK to be injected in my template? We are moving the problematic mechanism elsewhere
🇫🇷France pdureau Paris

I wonder where this fits now that we have SDC, and Experience Builder is in early development?

We still need to work on this because:

  • SDC is addressing only UI components, an important part of a modern Render API but not everything
  • Experience Builder is a display builder, so an higher level tool. We need to address the lower levels

Nearly 5 years after my previous comment, here we are.

Current status

Let's not forget the Render API is great:

  • Declarative: Easy to type. (de)Serializable if clean.
  • Easy nesting: The Virtual DOM of Drupal. We are building a tree.
  • Data bubbling: Declare locally, impact globally
  • Asset libraries management: Our beloved libraries.yml
  • Clever caching: Context, tags, keys…

❤️ Building display by assembling configurable plugins returning renderables.

However, the renderables themselves are an issue.

They are too many of them and they are of 3 kinds:

  • 35 render elements in Core:
    $ grep -hoEr "#type' => '(\S+)'" core/ --exclude-dir tests | sort | uniq -c | sort -nr
    138 #type' => 'details'
      94 #type' => 'container'
      93 #type' => 'link'
      68 #type' => 'table'
      47 #type' => 'actions'
      36 #type' => 'inline_template'
      27 #type' => 'fieldset'
      23 #type' => 'html_tag'
      21 #type' => 'status_messages'
      14 #type' => 'pager'
  • 99 theme hooks in Core:
    $ grep -hoEr "#theme' => '(\S+)'" core/ --exclude-dir tests | grep -v "__" | sort | uniq -c | sort -nr
    84 #theme' => 'item_list'
    21 #theme' => 'username'
    16 #theme' => 'image'
      7 #theme' => 'status_messages'
      7 #theme' => 'image_style'
      6 #theme' => 'table'
      6 #theme' => 'links'
      5 #theme' => 'update_version'
      5 #theme' => 'indentation'
      5 #theme' => 'file_upload_help'
      …
    
  • 2 special ones:
       1016 '#markup'
           85 '#plain_text'
    

😱 135 renderables to learn/use? This is the issue.

What can we remove?

We have SDC for UI components, so:

  • #type => component can replace a lot of hook_theme, the one describing UI components: breadcrumb, progress_bar, links...
  • We can also replace the 34 render elements which are wrapper around hook_theme (because what they do, adding asset libraries, variable typing... is already done by SDC) we removed : table, status_messages, pager...

The hook_theme not suitable for for conversion to SDC are often annoying wrappers we can get rid of anyway : block, node, view, field

The few remaining render elements can't be expressed as UI components:

  • some must be kept because they represent other design systems artefacts (icons...) or low level bricks (HTML elements, placeholders...)
  • some may (not found in my today look) be shortcut to Drupal callables returning renderables, it is better to explicitly call the callable (service, function, method..) instead

Deprecation of ThemeManager::render()

If all template file based renderable are managed by SDC and because SDC is not passing through the ThemeManager, we dont need the template loading feature of the ThemeManager and we get remove it to get roi of the old confusing stuff:

  • Theme wrappers: Confusing and useless. You can always use an upper level instead.
  • Templates suggestions: Not discoverable. Messy. Blur the business / view separation.
  • Preprocess hooks: Risky. Unfriendly. Blur the business / view separation. Unpredictable order of execution.

Conclusion

We can have an equally useful Render API with less than 10 renderables (not counting Form API):

  • Design systems artefacts:
    • #type => component: for UI components
    • #type => icon: for UI components
  • Lower level bricks:
    • #type => inline_template
    • #type => html_tag : for HTML elements
    • #type => link
    • #markup
    • #plain_text
    • #lazy_builder

    Keeping the beloved universal render properties: #attached, #cache... and adding some related to design systems: #styles, #tokens, #mode...

    And now we have an purely design/UI based Render API!

    If we do that, step by step, deprecating slowly, not breaking anything, the display building tools from Core and Contrib will have a simpler implementation and will have an easier time introducing new features.

    To play safe, we can also introduce a new Render API alongside the existing, using @ instead of # for example:

    • #type => component, #component= "foo:bar" @component => "foo:bar"
    • #type => icon@icon => []
    • #type => inline_template, #template => "foo"@template => "foo"
    • #type => html_tag, #tag => "foo"
      @element => "foo"</li>
          <li><code>#type =>link

      @link

    • #markup@markup</li>
    • #plain_text@plain_text
    • #lazy_builder@lazy_builder
🇫🇷France pdureau Paris

Hi Christian,

Thanks for this issue. I hope I am understand what you are trying to do achieve here. I not, I will move this to its own ticket.

They are 3 levels in Field API and 3 kinds of prop types:

Indeed, we use typed_data property only with scalar prop types nowadays:

 $ grep -r typed_data src/Plugin/UiPatterns/PropType
src/Plugin/UiPatterns/PropType/UrlPropType.php:  typed_data: ['uri']
src/Plugin/UiPatterns/PropType/StringPropType.php:  typed_data: ['string']
src/Plugin/UiPatterns/PropType/NumberPropType.php:  typed_data: ['decimal', 'float', 'integer']
src/Plugin/UiPatterns/PropType/EnumPropType.php:  typed_data: ['float', 'integer', 'string'],
src/Plugin/UiPatterns/PropType/BooleanPropType.php:  typed_data: ['boolean']

Let's focus on the 3 red crosses (❌) instead

So we have much to do to cover the full field API.

Every component prop type has a (JSON) schema, every prop type has a (Typed data) schema. They may be matchable.

Example: Icon field with an Icon prop

Icon field:

  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition): array {
    $properties = [];
    $properties['target_id'] = DataDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Icon ID'))
      ->setRequired(TRUE);
    return $properties;
  }

is converted to JSON schema:

type: object
properties:
  target_id:
    type: string

Icon prop type:

type: object
properties:
  pack_id:
    "$ref": ui-patterns://identifier
  icon_id:
    type: string
  settings:
    type: object
required:
- pack_id
- icon_id

Not easy to map because it seems icon field has a single property (target_id) for 2 prop types properties (pack_id and icon_id). Maybe with a separator. Let's check that with UI Icon team.

Example: LinkItem field to Links prop

LinkItem field type :

   public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
        $properties['uri'] = DataDefinition::create('uri')->setLabel(new TranslatableMarkup('URI'));
        $properties['title'] = DataDefinition::create('string')->setLabel(new TranslatableMarkup('Link text'));
        $properties['options'] = MapDataDefinition::create()->setLabel(new TranslatableMarkup('Options'));
        return $properties;
    }

is converted to JSON schema:

type: object
properties:
  title:
    type: string
  uri:
    "$ref": ui-patterns://url
  options:
    type: object

Links prop type:

type: array
items:
  type: object
  properties:
    title:
      type: string
    url:
      "$ref": ui-patterns://url
    attributes:
      "$ref": ui-patterns://attributes
    link_attributes:
      "$ref": ui-patterns://attributes
    below:
      type: array
      items:
        type: object

Not bad :) But url and uri property names don't match, so we can have 2 strategies:

  • We don't care about the property names ("title", "uri", "options"), we care only about the type and we let the builder do the mapping.
  • Can we use a PropTypeAdapter plugin transforming uri and url for that? They were not made for this kind of flow, but it may work.

Example: Multivalued StringItem field to List prop

StringItem field:

  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Text value'))
      ->setSetting('case_sensitive', $field_definition->getSetting('case_sensitive'))
      ->setRequired(TRUE);
    return $properties;
  }

Is converted to this JSON schema:

type: object
properties:
  value:
    type: string

So this when multivalued:

type: array
items:
  type: object
  properties:
    value:
      type: string

List prop type:

type: array
items:
    type: ['string', 'number', 'integer']

It doesn't seem obvious at first, but it may be good match if we flat the single "value" field property.

Conclusion

None of my examples were a perfect match. But each of them is showing mechanism worth to study. There is something smart to do here.

🇫🇷France pdureau Paris

One year later, I am not that sure this must be it's own component instead of just an addition to the footer component.

🇫🇷France pdureau Paris

Great. Can you also change the plugin IDs accordingly?

🇫🇷France pdureau Paris

The 2 field widgets have similar and confusing labels:

  • "Component slot source" ('A field widget for a "component" source.'): the exact same label as the prop type?
  • "UI Patterns slot source" ('A field widget for an UI Patterns source.'): what is the difference with previous?
🇫🇷France pdureau Paris

we could also renovates our conversion mechanism which is now very simple and not easily alterable...

Wow! That sounds very interesting.

- a new plugin type to apply an operation from one prop type to the same prop type. those plugins may be used in components prop forms and component slot forms. One would select a source first, and one would optionally add a sequence of those plugins.
- a new plugin type to declare a conversion from one prop type to another. the introduction of those plugins would be done with a renovation of the prop type conversion system (declaration and conversion paths computation)

I agree only the first kind must be available in the admin UI, but I guess it can be a single plugin type because:

  • they share the same interface
  • we will also need the first kind in the revamped prop type conversion system
🇫🇷France pdureau Paris

but i will give a try to map, every data type needs to have a chance 🤣

Thanks you so much. Can you do some performance tests? If they are more or less the same, let's avoid the introduction of a custom data type.

🇫🇷France pdureau Paris

1

I am happily surprised to see you have added $element['#variant']. I believe our 2 "special" component props (attributes and variant) needs their own (optional) render properties and I need to create the related ticket for #attributes.

2

$component_attributes['data-component-variant'] = $context['variant'];

That's surprising too but why not.

3

  variantDefinitions:
    default:
      label: Default
      description: My default variant

Why a complicated lower camel case property: variantDefinitions. I am already quite a few people struggling with libraryOverrides instead of the more straightforward and expected library. Can we have this instead:

  variants:
    default:
      label: Default
      description: My default variant

4

In my-cta.component.yml, variantDefinitions is inside the prop property:

props:
  type: object
  required: []
  properties: {}
  variantDefinitions: {}

It is not compliant with JSON schema standard. Can we move it to the component definition root?

5

We use label but:

  • component is using name
  • slots are using title
  • props are using title

Instead of introducing a third way of declaring the same information, can we copy the current slot structure?

6

Is it not too late?

    if (empty($variant) || isset($context['variant'])) {
      $template .= sprintf('{%% embed \'%s\' %%}', $id);
    }
    else {
      $template .= sprintf('{%% embed \'%s\' with { variant: \'%s\'} %%}', $id, $variant);
    }

Why tweaking the dynamically generate template instead of moving the variant to the props a bit earlier?

Non tested naive code snippet:

    if (isset($element["#variant"] && other_condition_we_may_add)) {
      $element["#props"]['variant'] = $element["#variant"];
    }
🇫🇷France pdureau Paris

Sorry to disturb that late, but do we really need a custom data type (Source) storing the data in JSON?

There are already data type in Drupal Core for such data structure which are (theoretically, we need to check) more performant for decoding/encoding nested PHP arrays, like https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData...

XB is also using a custom data type based on JSON, but I guess they send the exact data as HTTP Response Body, without the need of unserialize it to PHP arrays

What do you think about that?

🇫🇷France pdureau Paris

There are multiple nice usecases.
for example, to invert a boolean value, to filter an output, to trim a string... etc etc

Indeed, and we need to scope this. I would like to introduce a restriction to avoid rewriting a full data processing framework inside UI Patterns 2 and maange unexpected side effects: we transform from the same prop type to the same prop type.

So:

  • from attributes to attributes: any attribute source can be a filter, the attribuets are merged at each steps with something like AttributeHelper::AmergeCollections
  • from string to string: trim, upper, replace...
  • from boolean to boolean: invert...

Is it a new plugin type? I was initially kind of investigate TypedDataFilter plugins from https://www.drupal.org/project/typed_data
What else can we study from Drupal contrib?

🇫🇷France pdureau Paris

It really depends on how XB and equivalents implement the UI.

Just a side-note: Let's not implement this fetaure only to please some specific display builders. Let's do it the better way in a SDC point of view, and the display building tools will be able to add features upon it.

The key suggested seems somewhat weak for this action.

Maybe we need different keys:

  • suggested: for the DaisyUI card example.
  • enforced: for the Carousel example

So, let's see how a display building tool can handle those:

It also might be worth exploring whether we can limit an SDC to only appear in a specific slot of a specific SDC. A use case of this is that I don't want my end user to be able to place an accordion_item in the main content area. It only works when placed inside of accordion_group

Interesting path to explore indeed:

  • Is it always the reverse of enforced? So no need to add a new keyword?
  • If we need a new keyword, will it be also in the parent's slot definition ? Or in the child definition?

Let's be careful with any restriction we would add. For example, if the design system is expanded by a distinct team with new components, and one of the also want to use accordion_item in of those slots, we need to allow that.

🇫🇷France pdureau Paris

Thanks a lot Mike for this issue which will make a lot of people happy.

Instead of " are allowed" i would say "are suggested" because it would be a mistake for display building tools to strictly enforce this:

  • sometimes business needs are surprising from a design system point of view and business always win. So, if a card component suggests
  • sometimes a display builder doesn't know which component is a renderable. For example, placing a view inside a component slot. The view is build with another component, but the component is not know yet.

So, it is up to the display building too to decide how strict it will be considering this suggestion, but from a SDC point of view it is only a suggestion.

So, an early proposal with an example similar to yours, Bootstrap's accordion:

name: Accordion
description: "Render content in a box that expands and collapses vertically."
group: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    suggested: [accordion_item]

Example with DaisyUI's Card:

name: Card
description: "Cards are used to group and display content in a way that is easily readable."
group: "Data display"
slots:
  image:
    title: Image
  title:
    title: Title
  text:
    title: Text
  actions:
    title: Actions
   suggested: [badge, buttons]

What do you think?

🇫🇷France pdureau Paris

The point of all this: thinking through what it'd mean to go much further in allowing SDC developers to specify the authoring experience for Content Creators.

Hi Wim, it has been a long time you haven't heard of my warnings so here I am again 😉

The mission of SDC developers (as component authors) is to provide the best implementation for UI components, they don't have to think about the business/applicative/CMS side, so they don't have to specify the content editor experience. Once a component author starts to think about the CMS, he is in risk of lowering the quality of its UI Component.

It is not only a theoretical stance, we are doing this with UI Patterns since 2017 and it works well. UI Patterns 2.x is the proof it still work (and work even better IMHO) with SDC: we are able to inject data to slots & props from many sources.

Content editors are not the only users of a component. An UI component has to be as usable in other contexts (non editorial, unexpected constraints, data retrieval from API...).

🇫🇷France pdureau Paris

Thanks you.

2 possible fix:

🇫🇷France pdureau Paris
  • getChildPluginDefinitions >> getItems
  • getSettingsSourceWithChildPlugin >> getMinimalSetting
  • getChildPluginId >>getSelectedItem
interface SourceWithItemsInterface {

  /**
   * Get items.
   *
   * @return array
   *   Associative array of items definitions with ID as the key.
   */
  public function getItems(): array;

  /**
   * Get the minimal settings for an item.
   *
   * @param string $item_id
   *   An item ID.
   *
   * @return array
   *   A source setting.
   */
  public function getMinimalSetting(string $item_id): array;

  /**
   * Get the currently select item.
   *
   * @return string|null
   *   The item ID.
   */
  public function getSelectedItem(): ?string;

Is it better?

Also:

  • Do we also need a getItemLabel(string $item_id): string ?
  • it may be good to move ::getMinimalSetting() to SourceInterface and to implemtn it in SourcePluginBase as a proxy to PluginSettingsInterface::defaultSettings(). What do you think?
🇫🇷France pdureau Paris

getMinimalConfiguration does not need any parameter, unless it is static, which could be a nice idea after all.

Oops, a copy paste mistake, I fix it in my comment

🇫🇷France pdureau Paris

Hi @freelock,

But now trying to make an SDC, we've created a menu_menu SDC and a menu_item SDC, and placed the menu_menu SDC using UI Patterns, as a block. So far so good, we have our menu.

Nice, an user of UI Patterns 👍 I hope you enjoy using it.

but it feels a bit nasty and introduces other dependencies (Twig Tweak).

Twig Tweak is nasty :) Its philosophy is the reverse of SDC one.

I would much rather simply call the child SDC (menu_item) from within the parent, but I don't have any access to the menu_item_content entity!

Are you using a slot in menu_menu to inject menu_item components and a slot in menu_item to inject menu_menu components?

Maybe not because it seems you are manipulating a prop structure here:

The items do have a "below" array of child items...

Can you share with us the definition and the template of the 2 components?

🇫🇷France pdureau Paris

I am not sure about the naming...

Proposal inspired from [ObjectWithPluginCollectionInterface](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Plugin%21...) but not identical because we have a single collection and not many collections:

interface SourceWithCollectionInterface {

  /**
   * Get collection of items.
   *
   * @return array
   *   Array of items definitions.
   */
  public function getCollection(): array;

  /**
   * Get the minimal source configuration for an item.
   *
   * @param string $item_id
   *   An item ID.
   *
   * @return array
   *   A source configuration.
   */
  public function getSourceConfiguration(string $item_id): array;

  /**
   * Get the currently select item from the collection.
   *
   * @return string|null
   *   The item ID.
   */
  public function getSelectedItem(): ?string;

Do we also need a getItemLabel(string $item_id): string ?

🇫🇷France pdureau Paris

why is it called "Media: background image", there is nothing specific to background here, it depends of the usage in the component template, no?

🇫🇷France pdureau Paris

Hi Goz,

When we will move to entities, views & formatters for the component library, we will be able to do a formatter for that. See 📌 [2.0.0-alpha3] Use Drupal entities & plugins for the Library pages Active (expected late summer 2025)

Until then, is it possible to do it in the template? Something with json_encode() with PRE and/or CODE HTML elements?

I am not pushing this solution, I am just asking.

🇫🇷France pdureau Paris

Do I need to somehow define what the keys are in the associative array?

I guess you have to. You need to describe the data you expect, so someone (or something) using your component knows what to inject here.

So, you had this:

    options:
      type: array
      items:
        type: object

Processed like that in your template:

{% for option in options %}
      {% if option.type == 'optgroup' %}
        <optgroup label="{{ option.label }}">
          {% for sub_option in option.options %}
            <option value="{{ sub_option.value }}">{{ sub_option.label }}</option>
          {% endfor %}
        </optgroup>
      {% elseif option.type == 'option' %}
        <option value="{{ option.value }}">{{ option.label }}</option>
      {% endif %}
    {% endfor %}

So, your schema may be (non tested proposal):

    options:
      type: array
      items:
        type: object
       properties:
         label: {type: string}
         value: {type: string}
        options: {type: object}

I know there is still an undefined options object but it is already usable like that.

🇫🇷France pdureau Paris

Thanks so much. I will have a look.

🇫🇷France pdureau Paris

Data primitives mapping between the 3 formats we are manipulating:

Maybe adding such a table will be useful in the SDC documentation.

🇫🇷France pdureau Paris

Hi Liam,

I am a little worried about this sentence:

I think that I should replace type: object with type: array (because) the PHP type is an associative array.

AFAIK, here is the data primitives mapping between the 3 formats we are manipulating:

So, an associative array seems to be type: object.

🇫🇷France pdureau Paris

We may do a documentation session with @mherchel during DrupalCon Atlanta

I am very excited about that. There are many sides of SDC documentation which need to be clarified or completed after 2 years of practice.

🇫🇷France pdureau Paris

I beleive we need to get rid of those PHP Namespace properties instead of extending their usage.

So, in my humble opinion, no need to add 100 lines of code for this, because:

  • They were a mistake from the early days of SDC.
  • They are a "drupalism", non-compliant with JSON Schema standard.
  • SDC is doing weird stiff to skip JSON schema validation for them.
  • They create a coupling between UI components and CMS application.

So, it may be better to create an issue to raise a warning each time someone is using those.

🇫🇷France pdureau Paris

I believe we don't need to specify both prop_types: ['string', 'url'].

We target the more specific, so URL, and it will also be available for the more generic, string

Can you try?

🇫🇷France pdureau Paris

Which PHP object are you sending as prop?

🇫🇷France pdureau Paris

Hi Kristen,

Since October:

  • I am getting in touch with sdc_styleguide maintainer from time to time, adopting the shared story format seems still planned but no date shared yet.
  • UI Patterns 2.0.0 was released in February with great traction, being the first stable module using the story format
  • steepbase module has also adopted this format in January, and I hope more will join this year.
🇫🇷France pdureau Paris

this is not JSON schema compliant:

      allowed_bundles:
        image: 'field_media_image'
      image_style: 'large'

We need to find a way of expressing this data in a JSON schema way.

🇫🇷France pdureau Paris

Let's try to move fast on this issue.

So, we agreed each field item must be a single "object" and if we want a collection of them we configure the field as multivalued.

What would be this object ? I am proposing it to have at least 2 field properties:

public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['source_id'] = DataDefinition::create('string')
    ->setLabel(new TranslatableMarkup('ID'));
  $properties['source'] = MapDataDefinition::create()
    ->setLabel(new TranslatableMarkup('Data'));
  return $properties;
}

Mapping exactly the current UI Patterns 2 form data structure.

For example, a component source:

source_id: component
source:
  component:
    component_id: 'ui_suite_bootstrap:accordion_item'
    variant_id: null
    slots: []
    props: {  }

A block:

source_id: block
source:
  plugin_id: announce_block

A block with configuration:

source_id: block
source:
  plugin_id: 'views_block:content_recent-block_1'
  'views_block:content_recent-block_1':
    id: 'views_block:content_recent-block_1'
    label: 'Recent content'
    label_display: ''
    provider: views
    views_label: 'Recent'
    items_per_page: '5'

An entity field:

source_id: entity_field
source:
  derivable_context: 'field:node:article:status'
  'field:node:article:vid':
    value:
      add_more_button: ''
  'field:node:article:status':
    value:
      add_more_button: ''

Fabien, Christian, Mikael, are you OK with those 2 first field properties? Which other properties do you need?

These processors can be configured during the mapping process. This can also help to reduce the mapping if a config field exists.

Interesting but it looks like a distinct issue, no?

Production build 0.71.5 2024