Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Create MediaFormView for each dropdown instance separately. #98

Closed
wants to merge 4 commits into from

Conversation

tomalec
Copy link
Contributor

@tomalec tomalec commented Apr 21, 2020

Fixes ckeditor/ckeditor5#6333

Suggested merge commit message (convention)

Fix: Create MediaFormView for each dropdown instance separately. Closes ckeditor/ckeditor5#6333

MINOR BREAKING CHANGE: mediaembedui~MediaEmbedUI#form was removed from the API


Additional information

Previously it was trying to attach the same MediaFormView instance form for all dropdowns instances, then attach actions multiple times.

BTW, I was thinking about refactoring a bit _setUpDropdown and _setUpForm, to have the same order of arguments, but decided to keep the changes to the minimum.

@coveralls
Copy link

coveralls commented Apr 21, 2020

Pull Request Test Coverage Report for Build 431

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage remained the same at 100.0%

Totals Coverage Status
Change from base Build 425: 0.0%
Covered Lines: 309
Relevant Lines: 309

💛 - Coveralls

@@ -53,6 +53,12 @@ describe( 'MediaEmbedUI', () => {
expect( dropdown ).to.be.instanceOf( DropdownView );
} );

it( 'should allow creating two instances', () => {
Copy link
Member

Choose a reason for hiding this comment

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

To make this test a bit more bulletproof, it'd be good to ensure that two instances are actually created. What if someone changes the beforeEach to not create this item (I can imagine this line being moved to particular it()s?

So, I'd simply call this .create() twice in this test.

It'd also be good to verify that it's not the same instance returned twice :D

// Setup `imageUpload` button.
editor.ui.componentFactory.add( 'mediaEmbed', locale => {
const dropdown = createDropdown( locale );

this._setUpDropdown( dropdown, this.form, command, editor );
this._setUpForm( this.form, dropdown, command );
// Prepare custom view for dropdown's panel.
Copy link
Member

Choose a reason for hiding this comment

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

I think that while taken the bug this line may seem in need of being commented, this is actually completely logical code and the test will be enough.

@Reinmar
Copy link
Member

Reinmar commented Apr 21, 2020

BTW, I was thinking about refactoring a bit _setUpDropdown and _setUpForm, to have the same order of arguments, but decided to keep the changes to the minimum.

Haha :D My OCD suffers here as well. I think it's fine if it lands in another commit.

@@ -41,19 +41,15 @@ export default class MediaEmbedUI extends Plugin {
const command = editor.commands.get( 'mediaEmbed' );
const registry = editor.plugins.get( MediaEmbedEditing ).registry;

/**
Copy link
Member

Choose a reason for hiding this comment

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

It's a breaking change. We need to mention this in the merge commit message (can be "MINOR BREAKING CHANGE"). What I'd also check is why the heck was this exposed on the plugin and whether someone doesn't use it somewhere (you can ping @scofalik to check CF's code for this). In general, it's odd that it was exposed here, so it's worth doublechecking.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • Updated the merge commit message.
  • Checked ckeditor5 project for [\."']form\b - nothing found, any ideas where else should I check?

Copy link
Member

@Reinmar Reinmar left a comment

Choose a reason for hiding this comment

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

Some minor issues as commented.

@tomalec
Copy link
Contributor Author

tomalec commented Apr 22, 2020

All comments addressed, except on unclear thing
#98 (review)

@oleq
Copy link
Member

oleq commented Apr 22, 2020

  • but why this.form was exposed? Is it used somewhere else?

Probably because people (including me) pointed out in some issues that there's no way to access the form and its descendants from the editor instance object. The form hosts several components and integrators may want to access them and adjust things (labels, placeholders, etc.).

As a rule of thumb, there should always be some way to access any piece of UI by a chain of public properties starting with an editor instance (or editor.ui). Otherwise, we're creating a monolith.

As for why I didn't document or test that change, well... that's another topic :P Sorry for the confusion. I don't remember why I decided to do that in an unrelated PR.

@tomalec
Copy link
Contributor Author

tomalec commented Apr 22, 2020

If we are about to expose a form as property on MediaEmbedUI instance, we'd rather have a single instance. However, I'd achieve it by changing the behavior of the media embed button, to behave more like a link element, and pop-up a form where the caret is, rather than in the dropdown.

That would be more like UX change, and I'm not sure whether it's in the scope of this issue, and if it requires any approval from somebody? (@Reinmar ?)

@oleq
Copy link
Member

oleq commented Apr 22, 2020

We definitely shouldn't change the UX of the feature in this PR.

Solutions:

  1. Unless CF is using the reference to the media form, I'm OK with making it private again and scoped to the component returned by the factory.
  2. We could also leave the public MediaEmbedUI#form reference. There's a possibility of sharing a single MediaFormView between multiple dropdowns and it's pretty simple. Because it's unlikely both will be open at the same time, we can inject the form to the currently open dropdown panel as it opens:
diff --git a/src/mediaembedui.js b/src/mediaembedui.js
index ec46f40..50ca212 100644
--- a/src/mediaembedui.js
+++ b/src/mediaembedui.js
@@ -48,12 +48,20 @@ export default class MediaEmbedUI extends Plugin {
                    */
                   this.form = new MediaFormView( getFormValidators( editor.t, registry ), editor.locale );

+                  this.form.urlInputView.bind( 'value' ).to( command, 'value' );
+
+                  // Form elements should be read-only when corresponding commands are disabled.
+                  this.form.urlInputView.bind( 'isReadOnly' ).to( command, 'isEnabled', value => !value );
+
+                  this.form.saveButtonView.bind( 'isEnabled' ).to( command );
+
                   // Setup `imageUpload` button.
                   editor.ui.componentFactory.add( 'mediaEmbed', locale => {
                            const dropdown = createDropdown( locale );

                            this._setUpDropdown( dropdown, this.form, command, editor );
-                           this._setUpForm( this.form, dropdown, command );
+
+                           this.form.delegate( 'submit', 'cancel' ).to( dropdown );

                            return dropdown;
                   } );
@@ -65,7 +73,14 @@ export default class MediaEmbedUI extends Plugin {
                   const button = dropdown.buttonView;

                   dropdown.bind( 'isEnabled' ).to( command );
-                  dropdown.panelView.children.add( form );
+
+                  dropdown.on( 'change:isOpen', () => {
+                           if ( dropdown.isOpen ) {
+                                    dropdown.panelView.children.add( form );
+                           } else {
+                                    dropdown.panelView.children.remove( form );
+                           }
+                  } );

                   button.set( {
                            label: t( 'Insert media' ),
@@ -102,15 +117,6 @@ export default class MediaEmbedUI extends Plugin {
                            dropdown.isOpen = false;
                   }
          }
-
-         _setUpForm( form, dropdown, command ) {
-                  form.delegate( 'submit', 'cancel' ).to( dropdown );
-                  form.urlInputView.bind( 'value' ).to( command, 'value' );
-
-                  // Form elements should be read-only when corresponding commands are disabled.
-                  form.urlInputView.bind( 'isReadOnly' ).to( command, 'isEnabled', value => !value );
-                  form.saveButtonView.bind( 'isEnabled' ).to( command );
-         }
 }

2020-04-22 14 57 48

@tomalec
Copy link
Contributor Author

tomalec commented Apr 22, 2020

Because it's unlikely both will be open at the same

That is a thing I wasn't sure about. I could imagine a theme, a11y mode, or just a UX
change where opened toolbars are left open until explicitly closed. If you are fine with 1., personally I'd go that way due to YAGNI.

@Reinmar
Copy link
Member

Reinmar commented Apr 22, 2020

Can't we expose the form property on the button itself? I wouldn't feel safe with the form being reused as this may lead to strange situations. We're talking about corner cases here, but what if someone wants to have one of these forms look a bit different because it's displayed in the block toolbar (so perhaps should be more narrow) and the other form in the normal toolbar? Etc.

If the form is shown by the button, then the button should expose it. I guess that this is what we'd do if we weren't biased by seeing this bug first and were writing this for the first time :D We just started exploring more complex areas because of what we already saw.

@Reinmar
Copy link
Member

Reinmar commented Apr 22, 2020

personally I'd go that way due to YAGNI.

That'd be fine for me too. However, while we don't see an application for accessing the form, it often happens that it appears at the moment when we simply have other things to do. And then someone needs to wait at least a month until we release a new version. Of course, there's always a way to fork this particular module or the entire package, so making decisions about such things always troubles me :D A trivial change can save someone a lot of work. But will anyone actually need it?

@tomalec
Copy link
Contributor Author

tomalec commented Apr 23, 2020

Can't we expose the form property on the button itself? ...

I think we should rather expose it on dropdown, as that's what we add to the toolbar, and that's where we add the form.

Let me know if I get correctly what you mean by exposing, and making future applications easier.

	const mediaForm = new MediaFormView( /*...*/ );
	dropdown.panelView.children.add( mediaForm );

	// DevX sugar to avoid dropdown.panelView.children.get(0).
	dropdown.form = mediaForm;

	dropdown.panelView.children.get(0) === dropdown.form // true

But then how to document it? What is the JSDOC handle for dropdown - element instance returned by editor.ui.componentFactory.add ?

@Reinmar
Copy link
Member

Reinmar commented Apr 23, 2020

I think we should rather expose it on dropdown, as that's what we add to the toolbar, and that's where we add the form.

👍 Sorry, that's what I meant.

But then how to document it?

Oh, right. I didn't think about this. Yup, we'd need to subclass DropdownView just to document this propert 🤦 For me that makes all this not worth the effort (the ROI drops).

So, as you proposed, let's drop this property completely. After all, it can be accessed via dropdown.panelView.children.get(0), that I didn't even know.

@Reinmar
Copy link
Member

Reinmar commented Apr 23, 2020

Waiting for confirmation from @scofalik that we can safely remove this property.

@Reinmar
Copy link
Member

Reinmar commented Apr 29, 2020

Waiting for confirmation from @scofalik that we can safely remove this property.

Szymon said we're fine.

@Reinmar
Copy link
Member

Reinmar commented Apr 30, 2020

We're waiting with the merge for the monorepo migration.

@tomalec
Copy link
Contributor Author

tomalec commented May 5, 2020

Closing in favor of ckeditor/ckeditor5#6736

@tomalec tomalec closed this May 5, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Editor crashes after inserting two mediaEmbed items to the toolbar
4 participants