8000 Make Choices instance accessible to 3rd parties by bennyborn · Pull Request #8115 · contao/contao · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Make Choices instance accessible to 3rd parties #8115

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

Closed
wants to merge 1 commit into from

Conversation

bennyborn
Copy link
Contributor

I'd appreciate a way to access the Choices instance of any Select element that has chosen set.

Currently, there is no way to do this.
In some of our extensions (e.g., numero2/contao-tags), we want to use the Core's "chosen" feature (with all its settings) while also allowing users to add custom values on the fly. Previously, this functionality was missing in "Chosen," but "Choices" includes it by default—it just needs to be enabled 😊

To address this, I've added a custom event that triggers after Choices has been initialized.
This event carries a reference to the Choices instance, making it easy to access and modify—for example, to enable the addition of custom values.

select.addEventListener('choicesInit',(e)=>{

    const c = e.detail.choices;

    c._canAddUserChoices = true;
    c.config.addItems = true;
    c.config.addChoices = true;
});

Copy link
Member
@m-vo m-vo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If at all, this should IMHO be in the callbackOnInit promise.

But I think I would prefer an event using a config object that gets applied once, instead of passing a reference to the instance. This screams memory leak all over the place.

@bennyborn
Copy link
Contributor Author

If at all, this should IMHO be in the callbackOnInit promise.

I haven't seen any way to access the instance from within the callback.

But I think I would prefer an event using a config object that gets applied once [...]

This would mean that you get the config that the core gets via the event, but if you want to make changes you have to initialize Choices yourself anyway? Or am I misunderstanding something?

@m-vo
Copy link
Member
m-vo commented Feb 19, 2025

This would mean that you get the config that the core gets via the event, but if you want to make changes you have to initialize Choices yourself anyway? Or am I misunderstanding something?

No, I think I would build the options in the controller but, instead of passing them directly to the Choices constructor, dispatch an event including them, first.

8000

@bennyborn
Copy link
Contributor Author

Sorry but I don't see how this could work. When would Choices be initialized then?

@m-vo
Copy link
Member
m-vo commented Feb 20, 2025

Basically this:

let config = {/* current config […] */ };

this.element.dispatchEvent(
  new CustomEvent('contao--choices:init', { detail: { config }})
);

this.choices = new Choices(select, config);

@bennyborn
Copy link
Contributor Author

Doing it like this, I (as a 3rd party in this case) would know what Contao's config looks like but I still would've no access to the Choice instance itself to change anything.

@aschempp
Copy link
Member

If it was about config only, I think our stimulus controller should add a data-value option for additional config. But I'm not sure if this is about config or actually accessing the instance? Not sure if the instance is actually useful for anything?

@bennyborn
Copy link
Contributor Author

Being able to modify the config before Choices is initialized or having a way to access the Choices instance - either would be fine.

@m-vo
Copy link
Member
m-vo commented Feb 21, 2025

Being able to modify the config before Choices is initialized or having a way to access the Choices instance - either would be fine.

I have no idea what you mean. JS events are synchronous. You can simply modify the passed config object.

@aschempp
Copy link
Member
aschempp commented Mar 3, 2025

Here's a draft of what I mean in #8115 (comment). @bennyborn would that solve your issue?

@m-vo
Copy link
Member
m-vo commented Mar 3, 2025

What would be the benefit over the event like described in #8115 (comment)?

@aschempp
Copy link
Member
aschempp commented Mar 3, 2025

it would be much easier to implement if you generate your own choices element and just want one config flag to be changed.

@m-vo
Copy link
Member
m-vo commented Apr 3, 2025

See #8261

@m-vo m-vo closed this Apr 3, 2025
leofeyer pushed a commit that referenced this pull request May 13, 2025
Description
-----------

Fixes #8248

### Overview
We had numerous attempts at integrating Choices with our mutation observer based handling of components (#7901, #8075, #8203, …). The main issue is, that Choices wraps/unwraps the underlying select element by removing and readding  it to the DOM, which is again triggering mutations. On top there are some global event-listeners. This all screams "race condition". 

This PR therefore goes down a completely different route. The strategy basically looks like this:
* On `connect`, create a sibling div element (the _host_) and hide the original element (add `.hidden` class)
* Create a separate DOM (shadow root) with a clone of the original select element inside the host and initialize Choices on that element
* When there are changes  (`change` event on Choices-owned select), update the original element.
* On `disconnect`/`beforeCache`, remove the host and show the original element again (remove `.hidden` class)

Now, no unwanted mutations occur anymore. In fact, the potentially hundreds of added Choices DOM nodes are also not part of the original DOM which should also make queries etc. on parent elements cheaper.

### Implementation details
Choices was a contao-component, now we install it as a npm package (88a718c). We therefore do not load the respective css/js files in `be_main` anymore.

Due to isolation, the shadow DOM styles cannot target classes on the parent (custom properties such as our color presets penetrate the boundary, though). There is the `:host` selector, however, that targets the host element. In order to allow styling depending on the current color scheme, I moved the color scheme controller to the `html` element and made a small change, so that you now can have multiple outlet targets where the classes are applied (d7f298e). The host elements are now such targets.

Because global styles do not penetrate the boundary, we are only applying them to the shadow roots themselves. With webpack that can be done quite nicely: We use an annotated import to get the loader output and then create a `CssStyleSheet` object from it, that is shared across all controller instances (b076c53).

Speaking of styles… There were the choices default styles, our adjustments from the contao-component and the base styles (`backend.css`) - all of them overwriting each other and sometimes only working with a very specific specificity (say that 3 times in a row). Now that styles are isolated, I moved them in one file (`assets/component/choices.pcss` importing `choices.js/public/assets/styles/choices.css`) and refactored them. Even with a lot of testing, it is very likely that I broke something while doing so. So please have a close look at this when reviewing.

### `contao--choices:create` event
Because the controller is now basically rewritten, I also added an event that lets you alter the config before the Choices instance is created. If we want to go with this one-line-addition, this PR will also supersede #8115 and #8154.

Usage:
```js
selectElement.addEventLister('contao--choices:init', (event) => {
    const config = event.details.config;
    config.foo = bar; // alter config

    // If you need to target the select node, the Choices
    // instance is created on, use `event.details.select`.
});
```

Commits
-------

88a718c remove Choices contao-component and install it as a npm package
d7f298e move contao--color-scheme controller to the html element and allow ha…
b076c53 rewrite contao--choices controller with shadow isolation and refactor…
efdb4d1 fix filter layout
b564d49 build assets
bd80975 simplify import
798f69a allow defining a config value via a data attribute
f810e21 rename custom properties to a generic name
1f938ea build assets
f907a1d fix border, remove unnecessary properties, reorder
f367ac8 normalize line height
392cded fix class name
9adfc31 fix specificity
087b59b fix .is-open selector + simplify
d0fef2b fix tl_search panel
0a317ca make this friendly for upstream merging by applying FC CS
2b62096 build assets
4df465b Add widths for the `shadow-root` instances for choices
4ff6c23 Adjust the font-size for .choices list items
d166609 Only add margin to initialized choices controllers that are not withi…

Co-authored-by: zoglo <55794780+zoglo@users.noreply.github.com>
@zoglo zoglo reopened this May 13, 2025
@zoglo zoglo closed this May 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0