Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New plugin: splitbutton #1766

Closed
wants to merge 71 commits into from
Closed

New plugin: splitbutton #1766

wants to merge 71 commits into from

Conversation

engineering-this
Copy link
Contributor

What is the purpose of this pull request?

New feature

Does your PR contain necessary tests?

All patches which change the editor code must include tests. You can always read more
on PR testing,
how to set the testing environment and
how to create tests
in the official CKEditor documentation.

This PR contains

  • Unit tests
  • Manual tests

What changes did you make?

I've added the splitbutton plugin which is extending the menubutton class. Also I've slightly updated the button

Closes #1679.

Copy link
Member

@Comandeer Comandeer left a comment

Choose a reason for hiding this comment

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

There is issue with plugins with options that are exclusive (e.g. justify) – menu does not correctly update states.

I'm also wondering about making default button not focusable. It makes this button inaccessible not only via screen readers, but also via keyboard. What's more, in screen reader this button does not have any role at all (it is not even used as a state indicator for the splitbutton, to announce which option is active at the moment). Nearly all other implementations in the wild make both of buttons focusable (e.g. Bootstrap).

The other concern I have is the fact how menu works: it focuses the first element upon opening. However our Styles or Format combos focuses the first selected option. I'm wondering what would be more user-friendly. Speaking in terms of ARIA: menu or listbox?

I also don't like the default styles. For now it's pretty obvious that splitbutton is, in fact, two buttons.

basicStyles: evt.editor.ui.create( 'teststylesplit' )
} );

panel.attach( this.editable() );
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a toolbar is broken. Maybe implement following the text?

} );

// Just hiding buttons that are irrelevant to test.
CKEDITOR.instances.editor.on( 'contentDom', function() {
Copy link
Member

Choose a reason for hiding this comment

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

This code breaks ability to focus toolbar via Alt + F10 keystroke, disabling sensible means to test screen reader.

It also uncovers very important issue with the whole implementation: as #678 is not resolved, splitbutton needs other buttons to be added to be able to use their commands. That makes the whole feature de facto not usable at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hej, @mlewand what is your view on this?

Copy link
Contributor

Choose a reason for hiding this comment

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

For sure we should not workaround this like that. We need a proper fix mentioned by @Comandeer.

I mean, workaround can be placed for the time being, but the issue can not be merged to stable release with such workaround in place.

@engineering-this please, put a reference to #678 anywhere where such workaround is used.

* @param {Object} definition The splitButton definition.
*/
$: function( definition ) {
var groups = {},
Copy link
Member

Choose a reason for hiding this comment

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

Whole logic of adding items to the menu could be extracted to separate function(s) to make constructor more readable.

@@ -302,7 +304,7 @@
keydownFn: keydownFn,
focusFn: focusFn,
clickFn: clickFn,
style: CKEDITOR.skin.getIconStyle( iconPath, ( editor.lang.dir == 'rtl' ), overridePath, this.iconOffset ),
icon: ( this.noicon ? '' : '<span class="cke_button_icon cke_button__' + iconName + '_icon" style="' + style + '">&nbsp;</span>' ),
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we can't simplify that and instead of new option (noicon) just use iconName.

What's more, if we stick with noicon option, let's be coherent with the rest of the plugin and rename it to noIcon.

There are also no API docs for this new option.

};

bender.test( {
'test splitbutton in toolbar': function() {
Copy link
Member

Choose a reason for hiding this comment

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

This test name does not describe exactly what the test does.

Copy link
Member

Choose a reason for hiding this comment

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

There are also missing tests for changing the state of menu items.

@engineering-this
Copy link
Contributor Author

engineering-this commented Mar 15, 2018

@Comandeer

There is issue with plugins with options that are exclusive (e.g. justify) – menu does not correctly update states.

It looks fine with justify on sample
https://ckeditor4-preview.ckeditor.com/t/1679/plugins/splitbutton/samples/splitbutton.html

The other concern I have is the fact how menu works: it focuses the first element upon opening. However our Styles or Format combos focuses the first selected option. I'm wondering what would be more user-friendly. Speaking in terms of ARIA: menu or listbox?

I think we need a bit of discussion here if first item should be initially focused or not?

@mlewand What is your tough on that?

It also uncovers very important issue with the whole implementation: as #678 is not resolved, splitbutton needs other buttons to be added to be able to use their commands. That makes the whole feature de facto not usable at all.

Actually it is caused by ACF. I had to set config.allowedContent = true; and everything works. My question here is how should we deal with that?

Just added feature for each button.

@engineering-this
Copy link
Contributor Author

After some talking first item will be focused on menu open, just like other menu buttons.

Copy link
Member

@Comandeer Comandeer left a comment

Choose a reason for hiding this comment

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

Several issues:

1. TypeError

Procedure

  1. Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
  2. Open split button by clicking arrow and select any option. Do not focus editor before.

Expected

Style is applied to the editor.

Actual

Error is thrown:

plugin.js:60 Uncaught TypeError: Cannot read property 'setStyle' of null
    at CKEDITOR.command.<anonymous> (plugin.js:60)
    at CKEDITOR.command.listenerFirer (event.js:144)
    at CKEDITOR.command.CKEDITOR.event.fire (event.js:290)
    at CKEDITOR.command.setState (command.js:231)
    at Editor.<anonymous> (plugin.js:29)
    at Editor.<anonymous> (style.js:1970)
    at Editor.listenerFirer (event.js:144)
    at Editor.CKEDITOR.event.fire (event.js:290)
    at Editor.CKEDITOR.editor.fire (editor_basic.js:24)
    at Editor.checkSelectionChange (selection.js:270)

2. Focus issues

Procedure

  1. Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
  2. Focus editor.
  3. Select any option from the split button (except the first one).
  4. Press Alt + F10.

Expected

The first button in the toolbar is focused.

Actual

Focus remains inside the editor.

3. Changing buttons

Procedure

  1. Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
  2. Select some text.
  3. Apply several styles from split button options.
  4. Keep clicking split button face.

Expected

Actually we haven't defined any behavior for that, but changing icons in such way seems to be confusing. Microsoft Word has always the same icon in split button face and it changes only its state, e.g. "Numbering" has always the same icon despite which numbering option is selected. The face is used for switching feature on/off, not for representing currently selected option.

Actual

Disabling style hides it icon, showing the icon of previously applied style. It also changes the labelling of the whole button, increasing confusion for screen reader users.

4. Styling of focused option

Procedure

  1. Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
  2. Select some text.
  3. Apply several styles from split button options.
  4. Click arrow to open a list of options.

Open list of options, in which focused option has the same styles as selected one (darker background)

Expected

Focused option should be distinctive from selected option.

Actual

Focused and selected options have the same styles.

@@ -286,14 +287,15 @@
} else {
iconPath = iconName;
}

var style = CKEDITOR.skin.getIconStyle( iconPath, ( editor.lang.dir == 'rtl' ), overridePath, this.iconOffset );
Copy link
Member

Choose a reason for hiding this comment

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

Now we have two style variables (this style and… this.style), which do totally different things. We should probably rename this variable to iconStyle or something like that.

@@ -406,8 +408,9 @@
* @param {String} definition.command The command to be executed once the button is activated.
* @param {String} definition.toolbar The {@link CKEDITOR.config#toolbarGroups toolbar group} into which
* the button will be added. An optional index value (separated by a comma) determines the button position within the group.
* @param {String} definition.icon The path to a custom icon or icon name registered by another plugin. Custom icon paths
* are supported since the **4.9.0** version.
* @param {String} definition.style The optional inline style that will be applied on button.
Copy link
Member

Choose a reason for hiding this comment

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

Please add that this parameter was added in 4.10.

* @param {String} definition.icon The path to a custom icon or icon name registered by another plugin. Custom icon paths
* are supported since the **4.9.0** version.
* @param {String} definition.style The optional inline style that will be applied on button.
* @param {String/Boolean} definition.icon The path to a custom icon or icon name registered by another plugin. Custom icon paths
Copy link
Member

Choose a reason for hiding this comment

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

Please add that false value was added in 4.10.

properties.buttons[ activeButton.id ].hidden = false;
previousButton = activeButton;
} else {
CKEDITOR.document.getById( properties.buttons[ defaultButton.id ]._.id ).removeStyle( 'display' );
Copy link
Member

Choose a reason for hiding this comment

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

This code is the same as code above and similar to code, in which display is set to none. It'd move it to separate, dedicated function.

defaultButton = properties.buttons[ item.id ];
} else {
properties.buttons[ item.id ].style = 'display: none;';
properties.buttons[ item.id ].hidden = true;
Copy link
Member

Choose a reason for hiding this comment

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

I understand that it's a workaround for #678 to add features by adding buttons and hiding them?

Copy link
Contributor Author

@engineering-this engineering-this May 8, 2018

Choose a reason for hiding this comment

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

You understand right :)

Copy link
Member

Choose a reason for hiding this comment

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

Then I'd think about making hide method on button or any other toolbar element.

@engineering-this
Copy link
Contributor Author

engineering-this commented May 8, 2018

Actually we haven't defined any behavior for that, but changing icons in such way seems to be confusing. Microsoft Word has always the same icon in split button face and it changes only its state, e.g. "Numbering" has always the same icon despite which numbering option is selected. The face is used for switching feature on/off, not for representing currently selected option.

@Comandeer this was the our idea of split button, to create 'splitbutton' with static face we could just add one button for default behaviour and next to it menu button for options.

@Comandeer
Copy link
Member

But now the face is not static, it's dynamically changing and I think it's quite confusing to the end user.

@engineering-this
Copy link
Contributor Author

Well, I think we could add option to lock face on default button.

Copy link
Member

@Comandeer Comandeer left a comment

Choose a reason for hiding this comment

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

There are no tests for buttons' hiding logic or focus order in toolbar.

I also have very serious doubts around accessibility of the current splitbutton behaviour.

CHANGES.md Outdated
@@ -6,6 +6,7 @@
New Features:

* [#1761](https://github.com/ckeditor/ckeditor-dev/issues/1761): [Autolink](https://ckeditor.com/cke4/addon/autolink) plugin supports email links.
* [#1679](https://github.com/ckeditor/ckeditor-dev/issues/1679): Introduced the Split Button plugin. Also added option to set initial style for button when creating it, and create button without icon.
Copy link
Member

Choose a reason for hiding this comment

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

Please add link to the plugin.

defaultButton = properties.buttons[ item.id ];
} else {
properties.buttons[ item.id ].style = 'display: none;';
properties.buttons[ item.id ].hidden = true;
Copy link
Member

Choose a reason for hiding this comment

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

Then I'd think about making hide method on button or any other toolbar element.

Copy link
Member

@Comandeer Comandeer left a comment

Choose a reason for hiding this comment

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

I don't see any tests for static face. There are also no tests for every skin (moono, moono-lisa, kama).

It is also not possible/very convoluted to create split-button in "Word style", e.g. "Underline" in MS Word 365:
screen shot 2018-05-15 at 15 06 14

  1. Static face is used to apply last selected underline style.
  2. Attached menu is used to select underline style and apply it to selection.

With current solution every choice in menu must be a separate command.


item.role = 'menuitemcheckbox';

item.onClick = function() {
Copy link
Member

Choose a reason for hiding this comment

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

What if I want to declare custom onClick and omit command altogether? This code won't allow me to do this. What's more: if we're forcing working only with commands, there should be a way to pass data to the command.

Copy link
Member

@Comandeer Comandeer left a comment

Choose a reason for hiding this comment

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

I'm not happy with newest API changes. It seems that there are too many new properties and it becomes too complex.

However splitbuttonunderline test is awesome – it works just like I imagined splitbutton! I'd move it also to samples.

*
* @since 4.10.0
*/
hideFromToolbar: function() {
Copy link
Member

Choose a reason for hiding this comment

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

Is it from UI or from toolbar? If from whole UI (e.g. also in balloon toolbar), then simple hide and show seems more appropriate.

*
* @since 4.10.0
*/
showInToolbar: function() {
Copy link
Member

Choose a reason for hiding this comment

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

If button is not yet rendered, shouldn't this function remove initial style if it's set to display: none;?

};
item.click = item.onClick;

editor.getCommand( item.command ).on( 'state', function() {
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 still not possible to bind only click to button, without any command.

Copy link
Member

Choose a reason for hiding this comment

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

There aren't also tests for such case.

@@ -188,7 +188,7 @@ CKEDITOR.plugins.add( 'menu', {
var item = this.editor.getMenuItem( itemName );

if ( item && ( !item.command || this.editor.getCommand( item.command ).state ) ) {
item.state = listenerItems[ itemName ];
item.state = ( item.stateFn && item.stateFn() ) || listenerItems[ itemName ];
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if a better solution won't be to just substitute listener, not add another property directly to menu.

Copy link
Member

@Comandeer Comandeer left a comment

Choose a reason for hiding this comment

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

I'm wondering also about one thing: why do we need to render and hide all buttons? Can't we have one button that would act as face and its icon and click actions would be just replaced ad hoc? Is the current solution connected with our convoluted way of registering commands alongside the buttons? If yes, then I really think if #678 isn't blocking.

CHANGES.md Outdated
@@ -6,6 +6,7 @@
New Features:

* [#1761](https://github.com/ckeditor/ckeditor-dev/issues/1761): [Autolink](https://ckeditor.com/cke4/addon/autolink) plugin supports email links.
* [#1679](https://github.com/ckeditor/ckeditor-dev/issues/1679): Introduced the [Split Button](https://ckeditor.com/cke4/addon/splitbutton plugin. Also added option to set initial style for button when creating it, and create button without icon.
Copy link
Member

Choose a reason for hiding this comment

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

Link is not closed properly.

@@ -302,7 +304,7 @@
keydownFn: keydownFn,
focusFn: focusFn,
clickFn: clickFn,
style: CKEDITOR.skin.getIconStyle( iconPath, ( editor.lang.dir == 'rtl' ), overridePath, this.iconOffset ),
icon: ( this.icon === false ? '' : '<span class="cke_button_icon cke_button__' + iconName + '_icon" style="' + iconStyle + '">&nbsp;</span>' ),
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking about less strict condition here: just this.icon to accept all falsy values. Passing '' or null as icon to tell that we don't want icon seems more intuitive than passing false.

@@ -159,6 +141,29 @@
return false;
}
return true;
function getNextItem( item, nextOrPrevious ) {
var next;
Copy link
Member

Choose a reason for hiding this comment

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

next is a little unfortunate name for variable that could also hold previous item in toolbar ;)

btnEl = CKEDITOR.document.getById( btn._.id );
assert.isNull( btnEl.findOne( '.cke_button_icon' ) );
},
'test hiding button from toolbar': function() {
Copy link
Member

Choose a reason for hiding this comment

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

This test is failing as names of methods were changed.

@engineering-this
Copy link
Contributor Author

I'm wondering also about one thing: why do we need to render and hide all buttons? Can't we have one button that would act as face and its icon and click actions would be just replaced ad hoc? Is the current solution connected with our convoluted way of registering commands alongside the buttons? If yes, then I really think if #678 isn't blocking.

@Comandeer: We need to render buttons, otherwise we won't register commands. And if thats necessary then I'm reusing all rendered buttons. It might be possible to remove them after rendering and just switch face onClick, icon and label, but that would just make all the logic even more complex. Imho this would be too haxy.

@engineering-this
Copy link
Contributor Author

Rebased onto latest major.

@f1ames f1ames mentioned this pull request Jul 17, 2019
@f1ames f1ames closed this Mar 20, 2020
@f1ames f1ames added the pr:frozen ❄ The pull request on which work was suspended due to various reasons. label Mar 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr:frozen ❄ The pull request on which work was suspended due to various reasons.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Split button UI component
4 participants