Skip to content

Fix subform multiple tinymce elements#39449

Closed
wilsonge wants to merge 1 commit intojoomla:4.3-devfrom
wilsonge:fix-subform-multiple-elements
Closed

Fix subform multiple tinymce elements#39449
wilsonge wants to merge 1 commit intojoomla:4.3-devfrom
wilsonge:fix-subform-multiple-elements

Conversation

@wilsonge
Copy link
Copy Markdown
Contributor

Pull Request for Issue #39416 .

There is a minor B/C break here in that the options are injectable by other components and this does change that ID - however there isn't another way to make this work for subform fields. The only other way I can make this work is to code a b/c break that would be more specific to subform fields with tiny (by detecting the property in the url or similar). Either way there's going to be a b/c break. However we don't guarentee b/c breaks in extensions so the impact should be limited and hence I've targeted 4.3 rather than 4.2 even though this is also a bug fix

Summary of Changes

Changes the property used to inject tinymce options from the last element in the name to the id of the element. This is because in subform fields (with a tinymce custom field) the final element is the id of the field (e.g. field5) and the one before that is the row number which differentiates the fields

Testing Instructions

Follow the instructions in the linked issue to test the patch before applying the PR. Apply the PR (remember to run npm run build:js to compile the javascript if using patchtester. Else use the upgrade package from drone. Then retest and find things work as expected

Link to documentations

Please select:

  • Documentation link for docs.joomla.org:

  • No documentation changes for docs.joomla.org needed

  • Pull Request link for manual.joomla.org:

  • No documentation changes for manual.joomla.org needed

@dgrammatiko
Copy link
Copy Markdown
Contributor

dgrammatiko commented Dec 19, 2022

LGTM

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 19, 2022

Sorry, the fix is not a fix. And should not be acepted.
You break overide posibilities for TinyMCE options, in subform, and some more stuff I not remember.
Also it will not work for newly added row in subform.

Editor-XTD buttons broken because they hardly linked to Editor ID.
There need api changes, to make the EXT button access to Active editor (lastest editor in focus), but that need some more work, and probably some b/c break.

@dgrammatiko
Copy link
Copy Markdown
Contributor

Editor-XTD buttons broken because they hardly linked to Editor ID.

True, they only get the editor name as their input, probably we could pass the id as well:

public function onDisplay($name)

There need api changes, to make the EXT button access to Active editor (lastest editor in focus), but that need some more work, and probably some b/c break.

So basically a store that each editor needs to subscribe and update the state each time it takes focus but I don't really follow you on the API part for the buttons, how would that work? Just ask the latest in focus and then apply the command on that editor?
I really would like someone with strong PHP skills to scrap the complete Editor stack and come up with something that truly works with multiple instances, we keep patching this ugly thing since 3.6 and each time someone comes back with more failing cases...

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 19, 2022

Just ask the latest in focus and then apply the command on that editor?

Yeap, kind of that.

The idea like this:
At any point of time on the page can be only One active editor. So we can use it.
When User interact with edittor container the editor script should mark this editor instance as active, (I mean interaction with container with editor and all ext-button, basycaly parent wrapper.)
Kind of: Joomla.editor.setActive(editor). As part our Editor API.
Then button just do: Joomla.Editor.getAtive().doSomething()

@dgrammatiko
Copy link
Copy Markdown
Contributor

Basically (pseudo):

// Only define editors if not defined
window.Joomla.editors = window.Joomla.editors || {};

// Dumb last focused editor state
window.Joomla.editors._active = null;
window.Joomla.editors.setActive = (val) => window.Joomla.editors._active = val;
window.Joomla.editors.getActive = () => window.Joomla.editors._active;

// An object to hold each editor instance on page, only define if not defined.
window.Joomla.editors.instances = window.Joomla.editors.instances || { };

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 19, 2022

Yeap kind of that.

Then

Joomla.editors.instances['jform_articletext'].replaceSelection('Joomla! rocks')

will become

Joomla.editors.getActive().replaceSelection('Joomla! rocks')

Also in setActive() can store only instance key, and then use this key in getActive():

getActive = () => Joomla.editors.instances[Joomla.editors._active];

But that a details, can be anything.

@dgrammatiko
Copy link
Copy Markdown
Contributor

Joomla.editors.getActive().replaceSelection('Joomla! rocks')

Can we use proxy for the state (eg https://github.com/windmaomao/proxy-state) and make it promise based? Or am I complicating it too much?

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 19, 2022

I do not have much expiriance with Proxy. Sounds complicated to me :)
Can be Promise, well, anything that work

@dgrammatiko
Copy link
Copy Markdown
Contributor

I do not have much expiriance with Proxy. Sounds complicated to me :)

It's not ultra weird, this example from MDN is probably closer to what we need: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#validation

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 19, 2022

Hmhm, I would do without, and look how it will go.

@dgrammatiko
Copy link
Copy Markdown
Contributor

We already have similar (synchronous) code:

window.Joomla.Modal = window.Joomla.Modal || {
/**
* *****************************************************************
* Modals should implement
* *****************************************************************
*
* getCurrent Type Function Should return the modal element
* setCurrent Type Function Should set the modal element
* current Type {node} The modal element
*
* USAGE (assuming that exampleId is the modal id)
* To get the current modal element:
* Joomla.Modal.current; // Returns node element, eg: document.getElementById('exampleId')
* To set the current modal element:
* Joomla.Modal.setCurrent(document.getElementById('exampleId'));
*
* *************************************************************
* Joomla's UI modal uses `element.close();` to close the modal
* and `element.open();` to open the modal
* If you are using another modal make sure the same
* functionality is bound to the modal element
* @see media/legacy/bootstrap.init.js
* *************************************************************
*/
current: '',
setCurrent: (element) => {
window.Joomla.current = element;
},
getCurrent: () => window.Joomla.current,
};

@dgrammatiko
Copy link
Copy Markdown
Contributor

We already have similar (synchronous) code:

FFS that code is wrong 😂

  setCurrent: (element) => { 
     window.Joomla.Modal.current = element; 
   }, 
   getCurrent: () => window.Joomla.Modal.current, 

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 19, 2022

If it work, then nothing wrong hehe

@dgrammatiko
Copy link
Copy Markdown
Contributor

@Fedik nevertheless here's a PR #39450

@dgrammatiko
Copy link
Copy Markdown
Contributor

@Fedik out of curiosity #39344 shouldn't have the same problem, the buttons are not bound to the editor id iirc

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 20, 2022

shouldn't have the same problem, the buttons are not bound to the editor id iirc

That modal will not have the problem, it just an modal, it does nothing with editor.
You registre "click", then onClick create modal on fly, and use it. So you always have a reference to it.

The media/user fields, also should work in theory. But I still would change them to use "events" instead of callbacks. But that another topic.

@dgrammatiko
Copy link
Copy Markdown
Contributor

That modal will not have the problem, it just an modal, it does nothing with editor.

I realised that the problem in in the url of the iframe the &editor='. $$name . '

You registre "click", then onClick create modal on fly, and use it. So you always have a reference to it.

I think we should just create a simple API that collects the data for the button (text, icon, etc) pass them to joomlaOptions and have the editor create them and bound them per instance. Should be less problematic going forward

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 20, 2022

I realised that the problem in in the url of the iframe the &editor='. $$name . '

Yeah. As long as the Input id is correct, this should continue to work.
This is a problem of "select" field, not modal itself. This a reference to Input for callback inside iframe.

I think we should just create a simple API that collects the data for the button (text, icon, etc) pass them to joomlaOptions and have the editor create them and bound them per instance.

That could work. But that should be compatible with any editor, not only tinymce.

@dgrammatiko
Copy link
Copy Markdown
Contributor

But that should be compatible with any editor, not only tinymce.

We will implement it for tinyMCE, Codemirror and None, so plenty examples for anyone to pickup the code for their own implementation. I think we should evaluate which option will be also easier for maintenance but also for 3rd PD. I don't have all the answers, I'm just shouting ideas here

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 20, 2022

Yeah, need to make some concept first.
Could be that each button should own script, wich on click just do
Joomla.editors.getActive().replaceSelection('Joomla! rocks').

Probably some simple button can have a callback, and more complex a script, or callback that run code from script :)
Need to think.

I don't have all the answers

Me either :)

@wilsonge
Copy link
Copy Markdown
Contributor Author

I realised that the problem in in the url of the iframe the &editor='. $$name . '

Yes glad you got there in the end. But the reason for that is that the buttons currently reference the last part of the name so jform[com_fields][subform][row0][field1] and jform[com_fields][subform][row1][field1] both have a name in the javascript options of field1 and only have modals loaded for the first row because afterwards https://github.com/joomla/joomla-cms/blob/4.2-dev/plugins/editors/tinymce/src/PluginTraits/DisplayTrait.php#L120 returns false and therefore doesn't load the modals.

Also it's not just modals for example readmore which doesn't have a modal but instead has a click event uses "click":"insertReadmore('jform_com_fields__subform__row0__field1');return false;", As it uses the ID rather than the editor name...

I mean I put in some code that although it is a b/c break works ;) Admittedly I've broken the initial load of tinymce even more - but pretty sure i can fix that in another 10 minutes - it's just the fact the default field misses xtdButtons altogether. But if you guys have a counter proposal happy to figure that out. But right now I'm not sure you do?

You break overide posibilities for TinyMCE options, in subform, and some more stuff I not remember.

Also going to need some extra explanations here....

@wilsonge
Copy link
Copy Markdown
Contributor Author

Also needs fixing that all tiny xtd buttons for a subform field editor have the same id due to https://github.com/joomla/joomla-cms/blob/4.2-dev/plugins/editors/tinymce/src/PluginTraits/XTDButtons.php#L61

@dgrammatiko
Copy link
Copy Markdown
Contributor

dgrammatiko commented Dec 21, 2022

Yes glad you got there in the end. But the reason for that is that the buttons currently reference the last part of the name so jform[com_fields][subform][row0][field1] and jform[com_fields][subform][row1][field1] both have a name in the javascript options of field1 and only have modals loaded for the first row because afterwards

Theoretically speaking #39351 should be orders of magnitude easier to patch for subforms. A possible way out of this rabbit hole might be a twisted code of the xtd-buttons:

  • have a template element with the basic structure of a modal
  • Each editor, on initialisation (here's were you cater for copies on a subform) should create the buttons and the modals client side (this should be the same as the SSR)
  • The advantage here is that you can bound the url/modal/whatevr to the actual (from the DOM) id of the editor which should solve the issue
  • Problem: the CMS doesn't have any method to enqueue some HTML fraction, so either use a unique class and ignore the multiple instances of the template (the template would be created by the HTMLHelper::_('bootstrap.modal') using an extra param) or figure out a enqueue method like [4.2] (re) Introduce a body end renderer #37330)

But all these are hacks around an API that was never designed to support multiple instances of any editor per page...

@wilsonge
Copy link
Copy Markdown
Contributor Author

I mean the specific code dates back to this #14520

But I don't understand why we've done this per part of field name and not just used the full name or id? I assume I'm missing something obvious?

@wilsonge
Copy link
Copy Markdown
Contributor Author

But all these are hacks around an API that was never designed to support multiple instances of any editor per page...

Depends who you want responsible for what :D PHP these days just assembles all the form field params together. The JS is doing all the rendering these days as you know. So honestly I'm not totally sure where the "root problem" is other than it's how the options are being passed through.

I agree about templating and thought about bodging it. But even that involves tiny being aware it's being used in a subfield which isn't how it should be in theory. It should somehow be hooking into https://github.com/joomla/joomla-cms/blob/4.2-dev/build/media_source/system/js/fields/joomla-field-subform.w-c.es6.js#L253 to do the id replacement (a new event??) although subform-row-add might be good enough. But making that templatable is also a huge b/c break in it's own right and would likely be tiny specific anyhow :(

@dgrammatiko
Copy link
Copy Markdown
Contributor

It should somehow be hooking into

Ok, some insight in the way this whole think works maybe will show why I'm leaning into a client side rendering solution. So when php sends the html (assuming that the subform is null at this point) the tinymce js gets all the .js-editor-tinymce elements and apply the init method. At that point all the modals are pre-rendered from the php and the Bootstrap js just binds them to the js and the whole thing works as expected. When a user presses the + button to add a new editor field the subform js clones the .js-editor-tinymce element (that's without the modals as they are one node above, first problem) and on the fly adjusts the ids, etc and THEN the tinymce init retriggers on that element.
You might recall that I was calling that all the interactive elements should be done as custom-elements (they have a proper lifecycle which is extremely helpful for subforms) but the main problem here is that the modals (or any std-button functionality) basically is disconnected from the instance of the cloned editor (same thing applies for the other editors).
Either these should communicate with some bus-sub event system as @Fedik is proposing or another system that allows the buttons to be bound to a particular instance. BTW when was the nested subform introduced? It seem that it was rushed (I remember @Fedik writing code that wasn't exposed because there were cases like this that the current status didn't cope with)

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Dec 22, 2022

But I don't understand why we've done this per part of field name and not just used the full name or id? I assume I'm missing something obvious?

Look: you have ID jform_com_fields__subform__row0__field1, you open the page, all works.
Then you add a new row in subform, now the editor have an id jform_com_fields__subform__row1__field1, therefore there no options for the editor with this ID in the options sorage. Your next move?

I mean I put in some code that although it is a b/c break works ;)

It looks like works, but it is not.
When you try multiple editors, when each have a diferent options, you will see that nothing works as expected.

@dgrammatiko
Copy link
Copy Markdown
Contributor

@Fedik @wilsonge will this code fixes (the modal, for now, for the inline code we need to do a similar search replace) the problem?

      arr.forEach((xtdButton) => {
        const tmp = {};
        tmp.text = xtdButton.name;
        tmp.icon = xtdButton.icon;
        tmp.type = 'menuitem';

        if (xtdButton.iconSVG) {
          icons[tmp.icon] = xtdButton.iconSVG;
        }

        if (xtdButton.href) {
          const modal = element.parentNode.parentNode.querySelector(`#${xtdButton.id}_modal`);

          // Update the editor url parameter of the modal
          if (modal && modal.dataset.url) {
            const url = new URL(modal.dataset.url);
            url.searchParams.delete('editor');
            url.searchParams.append('editor', element.id);
            modal.dataset.url = url.toString();

            tmp.onAction = () => {
              modal.open();
            };
          }
        } else {
          tmp.onAction = () => {
            // eslint-disable-next-line no-new-func
            new Function(xtdButton.click)();
          };
        }

        buttonValues.push(tmp);
      });

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Jan 11, 2023

Looks haky, but if it works then I am fine 😄

@dgrammatiko
Copy link
Copy Markdown
Contributor

Of course it's hacky. We seriously need to re architecture the Editor and buttons to fit the current multi editor status (since the introduction of Custom Fields) and also eliminate all the new Function...

@Fedik
Copy link
Copy Markdown
Member

Fedik commented Mar 26, 2023

I am closing it in favor of #40202. Please review and test ;)

@Fedik Fedik closed this Mar 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation Required NPM Resource Changed This Pull Request can't be tested by Patchtester Small A PR which only has a small change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants