-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
New plugin: splitbutton #1766
Conversation
There was a problem hiding this 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() ); |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
plugins/splitbutton/plugin.js
Outdated
* @param {Object} definition The splitButton definition. | ||
*/ | ||
$: function( definition ) { | ||
var groups = {}, |
There was a problem hiding this comment.
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.
plugins/button/plugin.js
Outdated
@@ -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 + '"> </span>' ), |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
2304606
to
960b4ac
Compare
It looks fine with justify on sample
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?
Just added feature for each button. |
8a81e7a
to
b3720d1
Compare
After some talking first item will be focused on menu open, just like other menu buttons. |
There was a problem hiding this 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
- Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
- 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
- Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
- Focus editor.
- Select any option from the split button (except the first one).
- Press Alt + F10.
Expected
The first button in the toolbar is focused.
Actual
Focus remains inside the editor.
3. Changing buttons
Procedure
- Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
- Select some text.
- Apply several styles from split button options.
- 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
- Open http://tests.ckeditor.test:1030/tests/plugins/splitbutton/integration/manual/balloontoolbar
- Select some text.
- Apply several styles from split button options.
- Click arrow to open a list of options.
Expected
Focused option should be distinctive from selected option.
Actual
Focused and selected options have the same styles.
plugins/button/plugin.js
Outdated
@@ -286,14 +287,15 @@ | |||
} else { | |||
iconPath = iconName; | |||
} | |||
|
|||
var style = CKEDITOR.skin.getIconStyle( iconPath, ( editor.lang.dir == 'rtl' ), overridePath, this.iconOffset ); |
There was a problem hiding this comment.
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.
plugins/button/plugin.js
Outdated
@@ -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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
plugins/splitbutton/plugin.js
Outdated
properties.buttons[ activeButton.id ].hidden = false; | ||
previousButton = activeButton; | ||
} else { | ||
CKEDITOR.document.getById( properties.buttons[ defaultButton.id ]._.id ).removeStyle( 'display' ); |
There was a problem hiding this comment.
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.
plugins/splitbutton/plugin.js
Outdated
defaultButton = properties.buttons[ item.id ]; | ||
} else { | ||
properties.buttons[ item.id ].style = 'display: none;'; | ||
properties.buttons[ item.id ].hidden = true; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You understand right :)
There was a problem hiding this comment.
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.
@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. |
But now the face is not static, it's dynamically changing and I think it's quite confusing to the end user. |
Well, I think we could add option to lock face on default button. |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
plugins/splitbutton/plugin.js
Outdated
defaultButton = properties.buttons[ item.id ]; | ||
} else { | ||
properties.buttons[ item.id ].style = 'display: none;'; | ||
properties.buttons[ item.id ].hidden = true; |
There was a problem hiding this comment.
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.
There was a problem hiding this 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:
- Static face is used to apply last selected underline style.
- 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.
plugins/splitbutton/plugin.js
Outdated
|
||
item.role = 'menuitemcheckbox'; | ||
|
||
item.onClick = function() { |
There was a problem hiding this comment.
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.
68c596d
to
653118b
Compare
There was a problem hiding this 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.
plugins/button/plugin.js
Outdated
* | ||
* @since 4.10.0 | ||
*/ | ||
hideFromToolbar: function() { |
There was a problem hiding this comment.
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.
plugins/button/plugin.js
Outdated
* | ||
* @since 4.10.0 | ||
*/ | ||
showInToolbar: function() { |
There was a problem hiding this comment.
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;
?
plugins/splitbutton/plugin.js
Outdated
}; | ||
item.click = item.onClick; | ||
|
||
editor.getCommand( item.command ).on( 'state', function() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
plugins/menu/plugin.js
Outdated
@@ -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 ]; |
There was a problem hiding this comment.
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
.
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
plugins/button/plugin.js
Outdated
@@ -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 + '"> </span>' ), |
There was a problem hiding this comment.
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
.
plugins/toolbar/plugin.js
Outdated
@@ -159,6 +141,29 @@ | |||
return false; | |||
} | |||
return true; | |||
function getNextItem( item, nextOrPrevious ) { | |||
var next; |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
ad170e8
to
ff641eb
Compare
@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. |
7a45ee9
to
b90be88
Compare
c97c489
to
f67f714
Compare
Rebased onto latest |
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
What changes did you make?
I've added the
splitbutton
plugin which is extending themenubutton
class. Also I've slightly updated thebutton
Closes #1679.