From cdbc350e1e682e86b188087e7472defb04e219db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 22 Nov 2017 13:02:23 +0100 Subject: [PATCH 01/78] Other: Initial SplitButton implementation. --- src/button/buttonview.js | 5 +- src/button/splitbuttonview.js | 111 ++++++++++++++++++ .../button/createsplitbuttondropdown.js | 70 +++++++++++ theme/components/splitbutton.scss | 22 ++++ 4 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 src/button/splitbuttonview.js create mode 100644 src/dropdown/button/createsplitbuttondropdown.js create mode 100644 theme/components/splitbutton.scss diff --git a/src/button/buttonview.js b/src/button/buttonview.js index cd3291ed..48eb3f55 100644 --- a/src/button/buttonview.js +++ b/src/button/buttonview.js @@ -178,6 +178,9 @@ export default class ButtonView extends View { */ this.labelView = this._createLabelView(); + // TODO: used for Icon style hack in Highlight UI + this.iconView = new IconView(); + /** * Tooltip of the button bound to the template. * @@ -252,7 +255,7 @@ export default class ButtonView extends View { super.render(); if ( this.icon ) { - const iconView = this.iconView = new IconView(); + const iconView = this.iconView; iconView.bind( 'content' ).to( this, 'icon' ); diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js new file mode 100644 index 00000000..4cf9a60f --- /dev/null +++ b/src/button/splitbuttonview.js @@ -0,0 +1,111 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/button/buttonview + */ + +import View from '../view'; +import ButtonView from './buttonview'; + +import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; +import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; + +/** + * TODO + */ +export default class SplitButtonView extends View { + /** + * @inheritDoc + */ + constructor( locale, startButton ) { + super( locale ); + + this.children = this.createCollection(); + + this.buttonView = startButton || this._createButtonView(); + this.arrowView = this._createArrowView(); + + this.keystrokes = new KeystrokeHandler(); + this.focusTracker = new FocusTracker(); + + this.setTemplate( { + tag: 'div', + + attributes: { + class: 'ck-splitbutton' + }, + + children: this.children + } ); + } + + /** + * @inheritDoc + */ + render() { + super.render(); + + this.children.add( this.buttonView ); + this.children.add( this.arrowView ); + + this.focusTracker.add( this.buttonView.element ); + this.focusTracker.add( this.arrowView.element ); + + this.keystrokes.listenTo( this.element ); + + this.keystrokes.set( 'arrowright', ( evt, cb ) => { + if ( this.focusTracker.focusedElement === this.buttonView.element ) { + cb(); + + this.arrowView.focus(); + } + } ); + + this.keystrokes.set( 'arrowleft', ( evt, cb ) => { + if ( this.focusTracker.focusedElement === this.arrowView.element ) { + cb(); + + this.buttonView.focus(); + } + } ); + } + + swapButton( buttonView ) { + // remove from FT + this.focusTracker.remove( this.buttonView.element ); + + this.children.remove( this.buttonView ); + + this.buttonView = buttonView; + + this.children.add( this.buttonView, 0 ); + this.focusTracker.add( this.buttonView.element ); + } + + focus() { + this.buttonView.focus(); + } + + _createButtonView() { + const buttonView = new ButtonView(); + + buttonView.bind( 'icon' ).to( this, 'icon' ); + + return buttonView; + } + + _createArrowView() { + const arrowView = new ButtonView(); + arrowView.icon = 'abc'; + arrowView.extendTemplate( { + attributes: { + class: 'ck-splitbutton-arrow' + } + } ); + + return arrowView; + } +} diff --git a/src/dropdown/button/createsplitbuttondropdown.js b/src/dropdown/button/createsplitbuttondropdown.js new file mode 100644 index 00000000..a923e3b3 --- /dev/null +++ b/src/dropdown/button/createsplitbuttondropdown.js @@ -0,0 +1,70 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/button/createsplitbuttondropdown + */ + +// TODO: import createDropdown from '../createdropdown'; +import SplitButtonView from '../../button/splitbuttonview'; +import DropdownView from '../dropdownview'; +import DropdownPanelView from '../dropdownpanelview'; + +import ButtonGroupView from '../../buttongroup/buttongroupview'; +import { closeDropdownOnBlur, closeDropdownOnExecute, openDropdownOnArrows } from '../utils'; + +/** + * TODO + */ +export default function createSplitButtonDropdown( model, buttonViews, locale, startButton ) { + // // Make disabled when all buttons are disabled + model.bind( 'isEnabled' ).to( + // Bind to #isEnabled of each command... + ...getBindingTargets( buttonViews, 'isEnabled' ), + // ...and set it true if any command #isEnabled is true. + ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) + ); + + const splitButtonView = new SplitButtonView( locale, startButton ); + + splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + + const panelView = new DropdownPanelView( locale ); + + const dropdownView = new DropdownView( locale, splitButtonView, panelView ); + // END of TODO + + const buttonGroupView = dropdownView.buttonGroupView = new ButtonGroupView( { isVertical: model.isVertical } ); + + buttonGroupView.bind( 'isVertical' ).to( model, 'isVertical' ); + + buttonViews.map( view => buttonGroupView.items.add( view ) ); + + dropdownView.extendTemplate( { + attributes: { + class: [ + 'ck-splitbutton-dropdown' + ] + } + } ); + + dropdownView.buttonView.extendTemplate( { + attributes: { + class: [ 'ck-button-dropdown' ] + } + } ); + + dropdownView.panelView.children.add( buttonGroupView ); + + closeDropdownOnBlur( dropdownView ); + closeDropdownOnExecute( dropdownView, buttonGroupView.items ); + openDropdownOnArrows( dropdownView, buttonGroupView ); + + return dropdownView; +} + +function getBindingTargets( buttons, attribute ) { + return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); +} diff --git a/theme/components/splitbutton.scss b/theme/components/splitbutton.scss new file mode 100644 index 00000000..82dc0ca9 --- /dev/null +++ b/theme/components/splitbutton.scss @@ -0,0 +1,22 @@ +// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. +// For licensing, see LICENSE.md or http://ckeditor.com/license + +.ck-splitbutton-arrow { + //padding-right: 2 * ck-spacing(); + svg.icon { + width: 0; + min-width: 0; + } + + &::after { + content: ''; + width: 0; + height: 0; + pointer-events: none; + z-index: ck-z(); + + position: absolute; + top: 50%; + transform: translate3d(-50%, -50%, 0); + } +} From 7de6eb493315a9534e09d0c13f9eb3322ad504ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 22 Nov 2017 14:26:32 +0100 Subject: [PATCH 02/78] Other: Update SplitButtonDropdown keystrokes behavior. --- src/button/splitbuttonview.js | 12 ++++++------ src/dropdown/button/createsplitbuttondropdown.js | 12 +++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 4cf9a60f..9ff52ec3 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -56,19 +56,19 @@ export default class SplitButtonView extends View { this.keystrokes.listenTo( this.element ); - this.keystrokes.set( 'arrowright', ( evt, cb ) => { + this.keystrokes.set( 'arrowright', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.buttonView.element ) { - cb(); - this.arrowView.focus(); + + cancel(); } } ); - this.keystrokes.set( 'arrowleft', ( evt, cb ) => { + this.keystrokes.set( 'arrowleft', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.arrowView.element ) { - cb(); - this.buttonView.focus(); + + cancel(); } } ); } diff --git a/src/dropdown/button/createsplitbuttondropdown.js b/src/dropdown/button/createsplitbuttondropdown.js index a923e3b3..8d3c69cc 100644 --- a/src/dropdown/button/createsplitbuttondropdown.js +++ b/src/dropdown/button/createsplitbuttondropdown.js @@ -13,7 +13,7 @@ import DropdownView from '../dropdownview'; import DropdownPanelView from '../dropdownpanelview'; import ButtonGroupView from '../../buttongroup/buttongroupview'; -import { closeDropdownOnBlur, closeDropdownOnExecute, openDropdownOnArrows } from '../utils'; +import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownItemsOnArrows } from '../utils'; /** * TODO @@ -34,7 +34,6 @@ export default function createSplitButtonDropdown( model, buttonViews, locale, s const panelView = new DropdownPanelView( locale ); const dropdownView = new DropdownView( locale, splitButtonView, panelView ); - // END of TODO const buttonGroupView = dropdownView.buttonGroupView = new ButtonGroupView( { isVertical: model.isVertical } ); @@ -60,7 +59,14 @@ export default function createSplitButtonDropdown( model, buttonViews, locale, s closeDropdownOnBlur( dropdownView ); closeDropdownOnExecute( dropdownView, buttonGroupView.items ); - openDropdownOnArrows( dropdownView, buttonGroupView ); + focusDropdownItemsOnArrows( dropdownView, buttonGroupView ); + + splitButtonView.arrowView.on( 'execute', () => { + if ( splitButtonView.buttonView.isEnabled && !dropdownView.isOpen ) { + dropdownView.isOpen = true; + buttonGroupView.focus(); + } + } ); return dropdownView; } From 3e62922cd50c1bf05aa8317de7d33ecc1eee1ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 22 Nov 2017 17:22:59 +0100 Subject: [PATCH 03/78] Other: Make createSplitButtonDropdown minimal. --- src/button/splitbuttonview.js | 2 + .../button/createsplitbuttondropdown.js | 76 ------------------- src/dropdown/createsplitbuttondropdown.js | 28 +++++++ 3 files changed, 30 insertions(+), 76 deletions(-) delete mode 100644 src/dropdown/button/createsplitbuttondropdown.js create mode 100644 src/dropdown/createsplitbuttondropdown.js diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 9ff52ec3..b43cf0cf 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -56,6 +56,7 @@ export default class SplitButtonView extends View { this.keystrokes.listenTo( this.element ); + // Overrides toolbar focus cycling behavior this.keystrokes.set( 'arrowright', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.buttonView.element ) { this.arrowView.focus(); @@ -64,6 +65,7 @@ export default class SplitButtonView extends View { } } ); + // Overrides toolbar focus cycling behavior this.keystrokes.set( 'arrowleft', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.arrowView.element ) { this.buttonView.focus(); diff --git a/src/dropdown/button/createsplitbuttondropdown.js b/src/dropdown/button/createsplitbuttondropdown.js deleted file mode 100644 index 8d3c69cc..00000000 --- a/src/dropdown/button/createsplitbuttondropdown.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/button/createsplitbuttondropdown - */ - -// TODO: import createDropdown from '../createdropdown'; -import SplitButtonView from '../../button/splitbuttonview'; -import DropdownView from '../dropdownview'; -import DropdownPanelView from '../dropdownpanelview'; - -import ButtonGroupView from '../../buttongroup/buttongroupview'; -import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownItemsOnArrows } from '../utils'; - -/** - * TODO - */ -export default function createSplitButtonDropdown( model, buttonViews, locale, startButton ) { - // // Make disabled when all buttons are disabled - model.bind( 'isEnabled' ).to( - // Bind to #isEnabled of each command... - ...getBindingTargets( buttonViews, 'isEnabled' ), - // ...and set it true if any command #isEnabled is true. - ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) - ); - - const splitButtonView = new SplitButtonView( locale, startButton ); - - splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - - const panelView = new DropdownPanelView( locale ); - - const dropdownView = new DropdownView( locale, splitButtonView, panelView ); - - const buttonGroupView = dropdownView.buttonGroupView = new ButtonGroupView( { isVertical: model.isVertical } ); - - buttonGroupView.bind( 'isVertical' ).to( model, 'isVertical' ); - - buttonViews.map( view => buttonGroupView.items.add( view ) ); - - dropdownView.extendTemplate( { - attributes: { - class: [ - 'ck-splitbutton-dropdown' - ] - } - } ); - - dropdownView.buttonView.extendTemplate( { - attributes: { - class: [ 'ck-button-dropdown' ] - } - } ); - - dropdownView.panelView.children.add( buttonGroupView ); - - closeDropdownOnBlur( dropdownView ); - closeDropdownOnExecute( dropdownView, buttonGroupView.items ); - focusDropdownItemsOnArrows( dropdownView, buttonGroupView ); - - splitButtonView.arrowView.on( 'execute', () => { - if ( splitButtonView.buttonView.isEnabled && !dropdownView.isOpen ) { - dropdownView.isOpen = true; - buttonGroupView.focus(); - } - } ); - - return dropdownView; -} - -function getBindingTargets( buttons, attribute ) { - return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); -} diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js new file mode 100644 index 00000000..2363575d --- /dev/null +++ b/src/dropdown/createsplitbuttondropdown.js @@ -0,0 +1,28 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/createsplitbuttondropdown + */ + +import SplitButtonView from '../button/splitbuttonview'; +import DropdownView from './dropdownview'; +import DropdownPanelView from './dropdownpanelview'; + +/** + * Create a dropdown that have a split button as button. + * + * TODO: docs + */ +export default function createSplitButtonDropdown( model, locale, startButton ) { + const splitButtonView = new SplitButtonView( locale, startButton ); + + // TODO: keystroke? + splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip' ).to( model ); + + const panelView = new DropdownPanelView( locale ); + + return new DropdownView( locale, splitButtonView, panelView ); +} From 537fbe6a8aeac209a0c3faca52e11ff9f23ab8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 23 Nov 2017 18:53:34 +0100 Subject: [PATCH 04/78] Other: Change how split button is bound to model. --- src/button/splitbuttonview.js | 19 +++++-------------- src/dropdown/createsplitbuttondropdown.js | 6 +++--- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index b43cf0cf..83a76b07 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -20,12 +20,12 @@ export default class SplitButtonView extends View { /** * @inheritDoc */ - constructor( locale, startButton ) { + constructor( locale ) { super( locale ); this.children = this.createCollection(); - this.buttonView = startButton || this._createButtonView(); + this.buttonView = this._createButtonView(); this.arrowView = this._createArrowView(); this.keystrokes = new KeystrokeHandler(); @@ -75,18 +75,6 @@ export default class SplitButtonView extends View { } ); } - swapButton( buttonView ) { - // remove from FT - this.focusTracker.remove( this.buttonView.element ); - - this.children.remove( this.buttonView ); - - this.buttonView = buttonView; - - this.children.add( this.buttonView, 0 ); - this.focusTracker.add( this.buttonView.element ); - } - focus() { this.buttonView.focus(); } @@ -101,7 +89,10 @@ export default class SplitButtonView extends View { _createArrowView() { const arrowView = new ButtonView(); + + // TODO: arrowView.icon = 'abc'; + arrowView.extendTemplate( { attributes: { class: 'ck-splitbutton-arrow' diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js index 2363575d..90b04c12 100644 --- a/src/dropdown/createsplitbuttondropdown.js +++ b/src/dropdown/createsplitbuttondropdown.js @@ -16,11 +16,11 @@ import DropdownPanelView from './dropdownpanelview'; * * TODO: docs */ -export default function createSplitButtonDropdown( model, locale, startButton ) { - const splitButtonView = new SplitButtonView( locale, startButton ); +export default function createSplitButtonDropdown( model, locale ) { + const splitButtonView = new SplitButtonView( locale ); // TODO: keystroke? - splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip' ).to( model ); + splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); const panelView = new DropdownPanelView( locale ); From f4fe2e8ca0707d544ad12aa4d913722765dadd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 24 Nov 2017 13:51:53 +0100 Subject: [PATCH 05/78] Other: Move some SplitButtonDropdown functionality to SplitButtonDropdown. --- src/button/splitbuttonview.js | 23 +++++++++-------- src/dropdown/createsplitbuttondropdown.js | 14 +++++++--- src/dropdown/splitbuttondropdownview.js | 31 +++++++++++++++++++++++ 3 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 src/dropdown/splitbuttondropdownview.js diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 83a76b07..46bcd09d 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -26,7 +26,7 @@ export default class SplitButtonView extends View { this.children = this.createCollection(); this.buttonView = this._createButtonView(); - this.arrowView = this._createArrowView(); + this.selectView = this._createArrowView(); this.keystrokes = new KeystrokeHandler(); this.focusTracker = new FocusTracker(); @@ -49,17 +49,17 @@ export default class SplitButtonView extends View { super.render(); this.children.add( this.buttonView ); - this.children.add( this.arrowView ); + this.children.add( this.selectView ); this.focusTracker.add( this.buttonView.element ); - this.focusTracker.add( this.arrowView.element ); + this.focusTracker.add( this.selectView.element ); this.keystrokes.listenTo( this.element ); // Overrides toolbar focus cycling behavior this.keystrokes.set( 'arrowright', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.buttonView.element ) { - this.arrowView.focus(); + this.selectView.focus(); cancel(); } @@ -67,7 +67,7 @@ export default class SplitButtonView extends View { // Overrides toolbar focus cycling behavior this.keystrokes.set( 'arrowleft', ( evt, cancel ) => { - if ( this.focusTracker.focusedElement === this.arrowView.element ) { + if ( this.focusTracker.focusedElement === this.selectView.element ) { this.buttonView.focus(); cancel(); @@ -84,21 +84,24 @@ export default class SplitButtonView extends View { buttonView.bind( 'icon' ).to( this, 'icon' ); + buttonView.delegate( 'execute' ).to( this ); + return buttonView; } _createArrowView() { - const arrowView = new ButtonView(); + const selectView = new ButtonView(); - // TODO: - arrowView.icon = 'abc'; + selectView.icon = 'TODO'; - arrowView.extendTemplate( { + selectView.extendTemplate( { attributes: { class: 'ck-splitbutton-arrow' } } ); - return arrowView; + selectView.delegate( 'execute' ).to( this, 'select' ); + + return selectView; } } diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js index 90b04c12..1b03f9d4 100644 --- a/src/dropdown/createsplitbuttondropdown.js +++ b/src/dropdown/createsplitbuttondropdown.js @@ -8,7 +8,7 @@ */ import SplitButtonView from '../button/splitbuttonview'; -import DropdownView from './dropdownview'; +import SplitButtonDropdownView from './splitbuttondropdownview'; import DropdownPanelView from './dropdownpanelview'; /** @@ -19,10 +19,18 @@ import DropdownPanelView from './dropdownpanelview'; export default function createSplitButtonDropdown( model, locale ) { const splitButtonView = new SplitButtonView( locale ); - // TODO: keystroke? splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + splitButtonView.buttonView.bind( 'isOn' ).to( model ); const panelView = new DropdownPanelView( locale ); + const dropdownView = new SplitButtonDropdownView( locale, splitButtonView, panelView ); - return new DropdownView( locale, splitButtonView, panelView ); + // Extend template to hide arrow from dropdown. + dropdownView.extendTemplate( { + attributes: { + class: 'ck-splitbutton-dropdown' + } + } ); + + return dropdownView; } diff --git a/src/dropdown/splitbuttondropdownview.js b/src/dropdown/splitbuttondropdownview.js new file mode 100644 index 00000000..6daa8884 --- /dev/null +++ b/src/dropdown/splitbuttondropdownview.js @@ -0,0 +1,31 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/splitbuttondropdownview + */ + +import DropdownView from './dropdownview'; + +/** + * The split button dropdown view class. + * + * @extends module:ui/view~View + */ +export default class SplitButtonDropdownView extends DropdownView { + /** + * @inheritDoc + */ + render() { + super.render(); + + // Disable default panel open on "execute" + this.stopListening( this.buttonView, 'execute' ); + + this.listenTo( this.buttonView, 'select', () => { + this.isOpen = !this.isOpen; + } ); + } +} From 7804863a7cbc9efae52319acc38aa2bff430c746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 15 Jan 2018 17:46:26 +0100 Subject: [PATCH 06/78] Other: Update SplitButton CSS. --- src/button/splitbuttonview.js | 5 ++++- .../{splitbutton.scss => button/splitbutton.css} | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) rename theme/components/{splitbutton.scss => button/splitbutton.css} (80%) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 46bcd09d..ed381763 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -13,6 +13,9 @@ import ButtonView from './buttonview'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; +import arrowIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; +import './../../theme/components/button/splitbutton.css'; + /** * TODO */ @@ -92,7 +95,7 @@ export default class SplitButtonView extends View { _createArrowView() { const selectView = new ButtonView(); - selectView.icon = 'TODO'; + selectView.icon = arrowIcon; selectView.extendTemplate( { attributes: { diff --git a/theme/components/splitbutton.scss b/theme/components/button/splitbutton.css similarity index 80% rename from theme/components/splitbutton.scss rename to theme/components/button/splitbutton.css index 82dc0ca9..69aaac35 100644 --- a/theme/components/splitbutton.scss +++ b/theme/components/button/splitbutton.css @@ -2,8 +2,9 @@ // For licensing, see LICENSE.md or http://ckeditor.com/license .ck-splitbutton-arrow { - //padding-right: 2 * ck-spacing(); - svg.icon { + // padding-right: var(--ck-spacing-small); + + & svg { width: 0; min-width: 0; } @@ -13,7 +14,7 @@ width: 0; height: 0; pointer-events: none; - z-index: ck-z(); + z-index: var(--ck-z-default); position: absolute; top: 50%; From 575b2a9b2dc4dc16095d3aae0139c195920234da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 15 Jan 2018 18:32:18 +0100 Subject: [PATCH 07/78] Other: Fix SplitButton CSS. --- src/button/splitbuttonview.js | 1 + theme/components/button/splitbutton.css | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index ed381763..6b144050 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -14,6 +14,7 @@ import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import arrowIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; + import './../../theme/components/button/splitbutton.css'; /** diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index 69aaac35..6b683c42 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -1,10 +1,17 @@ -// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. -// For licensing, see LICENSE.md or http://ckeditor.com/license +/* + * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ +.ck-splitbutton-dropdown { + &::after { + display: none; + } +} .ck-splitbutton-arrow { - // padding-right: var(--ck-spacing-small); + padding-right: var(--ck-spacing-medium); - & svg { + & svg { width: 0; min-width: 0; } From 488d050e79a64b054c69dcd8015307a0c884b759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 15 Jan 2018 18:32:46 +0100 Subject: [PATCH 08/78] Change: Horizontal toolbar should have nowrap set. --- theme/components/toolbar/toolbar.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/components/toolbar/toolbar.css b/theme/components/toolbar/toolbar.css index 4d3bfee2..892758f5 100644 --- a/theme/components/toolbar/toolbar.css +++ b/theme/components/toolbar/toolbar.css @@ -9,7 +9,7 @@ @mixin ck-unselectable; display: flex; - flex-flow: row wrap; + flex-flow: row nowrap; /** TODO: wrap vs nowrap */ align-items: center; &.ck-toolbar_vertical { From 4a291b9b87b4c15fa0970a3c93758f5b160304cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 17 Jan 2018 15:59:53 +0100 Subject: [PATCH 09/78] Changed: Make split button in toolbar look like other buttons. --- theme/components/button/splitbutton.css | 16 ++++++++++++++-- theme/components/toolbar/toolbar.css | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index 6b683c42..da4be79f 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -8,8 +8,18 @@ } } +.ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-arrow) { + border-top-right-radius: unset; + border-bottom-right-radius: unset; +} + +.ck-rounded-corners .ck-splitbutton > .ck-splitbutton-arrow { + border-top-left-radius: unset; + border-bottom-left-radius: unset; +} + .ck-splitbutton-arrow { - padding-right: var(--ck-spacing-medium); + padding-right: var(--ck-spacing-standard); & svg { width: 0; @@ -25,6 +35,8 @@ position: absolute; top: 50%; - transform: translate3d(-50%, -50%, 0); + /* To make the triangle appear in the middle of split button the translation in X-axis + * should be adjusted by the half of its width. */ + transform: translate3d(calc(-50% + 0.2em), -50%, 0); } } diff --git a/theme/components/toolbar/toolbar.css b/theme/components/toolbar/toolbar.css index 892758f5..4d3bfee2 100644 --- a/theme/components/toolbar/toolbar.css +++ b/theme/components/toolbar/toolbar.css @@ -9,7 +9,7 @@ @mixin ck-unselectable; display: flex; - flex-flow: row nowrap; /** TODO: wrap vs nowrap */ + flex-flow: row wrap; align-items: center; &.ck-toolbar_vertical { From 08fa387bd7dee0883fd3f59055a4c1fc1e7facfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 17 Jan 2018 17:09:54 +0100 Subject: [PATCH 10/78] Added: Style toolbar separator in button dropdown. --- theme/components/dropdown/buttondropdown.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/theme/components/dropdown/buttondropdown.css b/theme/components/dropdown/buttondropdown.css index 171cd086..ad32306e 100644 --- a/theme/components/dropdown/buttondropdown.css +++ b/theme/components/dropdown/buttondropdown.css @@ -6,6 +6,12 @@ .ck-buttondropdown { & .ck-toolbar { flex-wrap: nowrap; + + & .ck-toolbar__separator { + height: calc(var(--ck-line-height-base) * var(--ck-font-size-normal)); + margin-top: 0; + margin-left: var(--ck-spacing-small); + } } & .ck-dropdown__panel .ck-button { From 2c96b24f696dd0df2f3941aa897d813c8642a238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 17 Jan 2018 17:15:48 +0100 Subject: [PATCH 11/78] Fix: Split button's main button outline is not displayed correctly. --- theme/components/button/splitbutton.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index da4be79f..52050606 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -2,6 +2,7 @@ * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ + .ck-splitbutton-dropdown { &::after { display: none; @@ -11,6 +12,10 @@ .ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-arrow) { border-top-right-radius: unset; border-bottom-right-radius: unset; + + &:focus { + z-index: calc(var(--ck-z-default) + 1); + } } .ck-rounded-corners .ck-splitbutton > .ck-splitbutton-arrow { From dbc7a708212b28050fa364b7b2df5635a073c037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 18 Jan 2018 12:50:56 +0100 Subject: [PATCH 12/78] Other: SplitButton dropdown improvements. --- src/button/splitbuttonview.js | 5 +++++ src/dropdown/button/createbuttondropdown.js | 3 ++- src/dropdown/createsplitbuttondropdown.js | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 6b144050..1e97eb36 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -90,6 +90,9 @@ export default class SplitButtonView extends View { buttonView.delegate( 'execute' ).to( this ); + buttonView.bind( 'isEnabled' ).to( this ); + buttonView.bind( 'label' ).to( this ); + return buttonView; } @@ -106,6 +109,8 @@ export default class SplitButtonView extends View { selectView.delegate( 'execute' ).to( this, 'select' ); + selectView.bind( 'isEnabled' ).to( this ); + return selectView; } } diff --git a/src/dropdown/button/createbuttondropdown.js b/src/dropdown/button/createbuttondropdown.js index b3232c2f..27159367 100644 --- a/src/dropdown/button/createbuttondropdown.js +++ b/src/dropdown/button/createbuttondropdown.js @@ -56,10 +56,11 @@ export default function createButtonDropdown( model, locale ) { ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) ); - // If defined `staticIcon` use the `defautlIcon` without binding it to active a button. + // If defined `staticIcon` use the `defaultIcon` without binding it to active a button. if ( model.staticIcon ) { model.bind( 'icon' ).to( model, 'defaultIcon' ); } else { + // TODO: move to alignment // Make dropdown icon as any active button. model.bind( 'icon' ).to( // Bind to #isOn of each button... diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js index 1b03f9d4..0a3c2836 100644 --- a/src/dropdown/createsplitbuttondropdown.js +++ b/src/dropdown/createsplitbuttondropdown.js @@ -21,11 +21,13 @@ export default function createSplitButtonDropdown( model, locale ) { splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); splitButtonView.buttonView.bind( 'isOn' ).to( model ); + splitButtonView.buttonView.bind( 'tooltip' ).to( model ); const panelView = new DropdownPanelView( locale ); const dropdownView = new SplitButtonDropdownView( locale, splitButtonView, panelView ); // Extend template to hide arrow from dropdown. + // TODO: enable this on normal button instead of hiding it dropdownView.extendTemplate( { attributes: { class: 'ck-splitbutton-dropdown' From 0394490b6e35a949011a97cf207609a491bdaf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 18 Jan 2018 14:57:01 +0100 Subject: [PATCH 13/78] Remove splitbuttondropdownview.js and use 'select' button event instead. --- src/dropdown/button/createbuttondropdown.js | 3 ++ src/dropdown/createsplitbuttondropdown.js | 4 +-- src/dropdown/dropdownview.js | 2 +- src/dropdown/list/createlistdropdown.js | 3 ++ src/dropdown/splitbuttondropdownview.js | 31 --------------------- tests/dropdown/dropdownview.js | 8 +++--- 6 files changed, 13 insertions(+), 38 deletions(-) delete mode 100644 src/dropdown/splitbuttondropdownview.js diff --git a/src/dropdown/button/createbuttondropdown.js b/src/dropdown/button/createbuttondropdown.js index 27159367..74d22070 100644 --- a/src/dropdown/button/createbuttondropdown.js +++ b/src/dropdown/button/createbuttondropdown.js @@ -86,6 +86,9 @@ export default function createButtonDropdown( model, locale ) { model.buttons.map( view => toolbarView.items.add( view ) ); + // TODO: better: + dropdownView.buttonView.delegate( 'execute' ).to( dropdownView.buttonView, 'select' ); + dropdownView.extendTemplate( { attributes: { class: [ 'ck-buttondropdown' ] diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js index 0a3c2836..41ed72bc 100644 --- a/src/dropdown/createsplitbuttondropdown.js +++ b/src/dropdown/createsplitbuttondropdown.js @@ -8,8 +8,8 @@ */ import SplitButtonView from '../button/splitbuttonview'; -import SplitButtonDropdownView from './splitbuttondropdownview'; import DropdownPanelView from './dropdownpanelview'; +import DropdownView from './dropdownview'; /** * Create a dropdown that have a split button as button. @@ -24,7 +24,7 @@ export default function createSplitButtonDropdown( model, locale ) { splitButtonView.buttonView.bind( 'tooltip' ).to( model ); const panelView = new DropdownPanelView( locale ); - const dropdownView = new SplitButtonDropdownView( locale, splitButtonView, panelView ); + const dropdownView = new DropdownView( locale, splitButtonView, panelView ); // Extend template to hide arrow from dropdown. // TODO: enable this on normal button instead of hiding it diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 1c71cd61..dd37da83 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -158,7 +158,7 @@ export default class DropdownView extends View { super.render(); // Toggle the the dropdown when it's button has been clicked. - this.listenTo( this.buttonView, 'execute', () => { + this.listenTo( this.buttonView, 'select', () => { this.isOpen = !this.isOpen; } ); diff --git a/src/dropdown/list/createlistdropdown.js b/src/dropdown/list/createlistdropdown.js index a1295272..36449896 100644 --- a/src/dropdown/list/createlistdropdown.js +++ b/src/dropdown/list/createlistdropdown.js @@ -65,6 +65,9 @@ export default function createListDropdown( model, locale ) { dropdownView.panelView.children.add( listView ); + // TODO: better: + dropdownView.buttonView.delegate( 'execute' ).to( dropdownView.buttonView, 'select' ); + closeDropdownOnBlur( dropdownView ); closeDropdownOnExecute( dropdownView, listView.items ); focusDropdownContentsOnArrows( dropdownView, listView ); diff --git a/src/dropdown/splitbuttondropdownview.js b/src/dropdown/splitbuttondropdownview.js deleted file mode 100644 index 6daa8884..00000000 --- a/src/dropdown/splitbuttondropdownview.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/splitbuttondropdownview - */ - -import DropdownView from './dropdownview'; - -/** - * The split button dropdown view class. - * - * @extends module:ui/view~View - */ -export default class SplitButtonDropdownView extends DropdownView { - /** - * @inheritDoc - */ - render() { - super.render(); - - // Disable default panel open on "execute" - this.stopListening( this.buttonView, 'execute' ); - - this.listenTo( this.buttonView, 'select', () => { - this.isOpen = !this.isOpen; - } ); - } -} diff --git a/tests/dropdown/dropdownview.js b/tests/dropdown/dropdownview.js index 48624516..19082b7a 100644 --- a/tests/dropdown/dropdownview.js +++ b/tests/dropdown/dropdownview.js @@ -71,7 +71,7 @@ describe( 'DropdownView', () => { } ); describe( 'bindings', () => { - describe( 'view#isOpen to view.buttonView#execute', () => { + describe( 'view#isOpen to view.buttonView#select', () => { it( 'is activated', () => { const values = []; @@ -79,9 +79,9 @@ describe( 'DropdownView', () => { values.push( view.isOpen ); } ); - view.buttonView.fire( 'execute' ); - view.buttonView.fire( 'execute' ); - view.buttonView.fire( 'execute' ); + view.buttonView.fire( 'select' ); + view.buttonView.fire( 'select' ); + view.buttonView.fire( 'select' ); expect( values ).to.have.members( [ true, false, true ] ); } ); From e1cc15c73a70a7721f021a45e7516fe065d7f9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 18 Jan 2018 15:17:25 +0100 Subject: [PATCH 14/78] Changed: Stub new dropdown creator methods. --- src/dropdown/button/createbuttondropdown.js | 13 +++++--- src/dropdown/createdropdown.js | 18 +++------- src/dropdown/createsplitbuttondropdown.js | 10 ++---- src/dropdown/list/createlistdropdown.js | 11 ++++-- src/dropdown/utils.js | 37 +++++++++++++++++++++ 5 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/dropdown/button/createbuttondropdown.js b/src/dropdown/button/createbuttondropdown.js index 74d22070..fcf5cefe 100644 --- a/src/dropdown/button/createbuttondropdown.js +++ b/src/dropdown/button/createbuttondropdown.js @@ -7,10 +7,14 @@ * @module ui/dropdown/button/createbuttondropdown */ -import createDropdown from '../createdropdown'; - import ToolbarView from '../../toolbar/toolbarview'; -import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownContentsOnArrows } from '../utils'; +import { + closeDropdownOnBlur, + closeDropdownOnExecute, + createButtonForDropdown, + createDropdownView, + focusDropdownContentsOnArrows +} from '../utils'; import '../../../theme/components/dropdown/buttondropdown.css'; @@ -78,8 +82,9 @@ export default function createButtonDropdown( model, locale ) { } ); } + const buttonView = createButtonForDropdown( model, locale ); + const dropdownView = createDropdownView( model, buttonView, locale ); - const dropdownView = createDropdown( model, locale ); const toolbarView = dropdownView.toolbarView = new ToolbarView(); toolbarView.bind( 'isVertical', 'className' ).to( model, 'isVertical', 'toolbarClassName' ); diff --git a/src/dropdown/createdropdown.js b/src/dropdown/createdropdown.js index b1eae9e3..e43cbb4f 100644 --- a/src/dropdown/createdropdown.js +++ b/src/dropdown/createdropdown.js @@ -7,9 +7,7 @@ * @module ui/dropdown/createdropdown */ -import ButtonView from '../button/buttonview'; -import DropdownView from './dropdownview'; -import DropdownPanelView from './dropdownpanelview'; +import { createButtonForDropdown, createDropdownView } from './utils'; /** * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using @@ -38,17 +36,9 @@ import DropdownPanelView from './dropdownpanelview'; * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. + * + * TODO: only used in tests. */ export default function createDropdown( model, locale ) { - const buttonView = new ButtonView( locale ); - const panelView = new DropdownPanelView( locale ); - const dropdownView = new DropdownView( locale, buttonView, panelView ); - - dropdownView.bind( 'isEnabled' ).to( model ); - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { - return isOn || isOpen; - } ); - - return dropdownView; + return createDropdownView( model, createButtonForDropdown( model, locale ), locale ); } diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js index 41ed72bc..54b063e7 100644 --- a/src/dropdown/createsplitbuttondropdown.js +++ b/src/dropdown/createsplitbuttondropdown.js @@ -7,9 +7,7 @@ * @module ui/dropdown/createsplitbuttondropdown */ -import SplitButtonView from '../button/splitbuttonview'; -import DropdownPanelView from './dropdownpanelview'; -import DropdownView from './dropdownview'; +import { createDropdownView, createSplitButtonForDropdown } from './utils'; /** * Create a dropdown that have a split button as button. @@ -17,14 +15,12 @@ import DropdownView from './dropdownview'; * TODO: docs */ export default function createSplitButtonDropdown( model, locale ) { - const splitButtonView = new SplitButtonView( locale ); + const splitButtonView = createSplitButtonForDropdown( model, locale ); - splitButtonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); splitButtonView.buttonView.bind( 'isOn' ).to( model ); splitButtonView.buttonView.bind( 'tooltip' ).to( model ); - const panelView = new DropdownPanelView( locale ); - const dropdownView = new DropdownView( locale, splitButtonView, panelView ); + const dropdownView = createDropdownView( model, splitButtonView, locale ); // Extend template to hide arrow from dropdown. // TODO: enable this on normal button instead of hiding it diff --git a/src/dropdown/list/createlistdropdown.js b/src/dropdown/list/createlistdropdown.js index 36449896..20035f8a 100644 --- a/src/dropdown/list/createlistdropdown.js +++ b/src/dropdown/list/createlistdropdown.js @@ -9,8 +9,11 @@ import ListView from '../../list/listview'; import ListItemView from '../../list/listitemview'; -import createDropdown from '../createdropdown'; -import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownContentsOnArrows } from '../utils'; + +import { + closeDropdownOnBlur, closeDropdownOnExecute, createButtonForDropdown, createDropdownView, + focusDropdownContentsOnArrows +} from '../utils'; /** * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using @@ -51,7 +54,9 @@ import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownContentsOnArr * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. */ export default function createListDropdown( model, locale ) { - const dropdownView = createDropdown( model, locale ); + const buttonView = createButtonForDropdown( model, locale ); + const dropdownView = createDropdownView( model, buttonView, locale ); + const listView = dropdownView.listView = new ListView( locale ); listView.items.bindTo( model.items ).using( itemModel => { diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b2b68ba8..f3944c27 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -8,6 +8,10 @@ */ import clickOutsideHandler from '../bindings/clickoutsidehandler'; +import SplitButtonView from '../button/splitbuttonview'; +import ButtonView from '../button/buttonview'; +import DropdownPanelView from './dropdownpanelview'; +import DropdownView from './dropdownview'; /** * Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. @@ -68,3 +72,36 @@ export function closeDropdownOnBlur( dropdownView ) { } ); } ); } + +/** TODO: new methods below - refactor to own files later */ + +export function createButtonForDropdown( model, locale ) { + const buttonView = new ButtonView( locale ); + + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + + return buttonView; +} + +export function createSplitButtonForDropdown( model, locale ) { + const buttonView = new SplitButtonView( locale ); + + // TODO: check 'isOn' binding. + buttonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + + return buttonView; +} + +export function createDropdownView( model, buttonView, locale ) { + const panelView = new DropdownPanelView( locale ); + const dropdownView = new DropdownView( locale, buttonView, panelView ); + + dropdownView.bind( 'isEnabled' ).to( model ); + + // TODO: check 'isOn' binding. + // buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + // return isOn || isOpen; + // } ); + + return dropdownView; +} From 102e5c77754233c73f1c172d25f8f17469c4ac92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 19 Jan 2018 11:17:32 +0100 Subject: [PATCH 15/78] Changed: Extract common dropdown tasks to helper methods. --- src/dropdown/button/buttondropdownmodel.jsdoc | 16 --- src/dropdown/button/createbuttondropdown.js | 76 +----------- src/dropdown/createsplitbuttondropdown.js | 34 ----- src/dropdown/dropdownpanelview.js | 14 +++ src/dropdown/list/createlistdropdown.js | 32 +---- src/dropdown/utils.js | 117 ++++++++++++++++-- tests/dropdown/button/createbuttondropdown.js | 45 ------- 7 files changed, 131 insertions(+), 203 deletions(-) delete mode 100644 src/dropdown/createsplitbuttondropdown.js diff --git a/src/dropdown/button/buttondropdownmodel.jsdoc b/src/dropdown/button/buttondropdownmodel.jsdoc index cabe9edd..405f9283 100644 --- a/src/dropdown/button/buttondropdownmodel.jsdoc +++ b/src/dropdown/button/buttondropdownmodel.jsdoc @@ -35,22 +35,6 @@ * @member {Boolean} #isVertical=false */ -/** - * Disables automatic button icon binding. If set to true dropdown's button {@link #icon} will be set to {@link #defaultIcon}. - * - * @observable - * @member {Boolean} #staticIcon=false - */ - -/** - * Defines default icon which is used when no button is active. - * - * Also see {@link #icon}. - * - * @observable - * @member {String} #defaultIcon - */ - /** * Button dropdown icon is set from inner button views. * diff --git a/src/dropdown/button/createbuttondropdown.js b/src/dropdown/button/createbuttondropdown.js index fcf5cefe..b36dc07e 100644 --- a/src/dropdown/button/createbuttondropdown.js +++ b/src/dropdown/button/createbuttondropdown.js @@ -7,13 +7,10 @@ * @module ui/dropdown/button/createbuttondropdown */ -import ToolbarView from '../../toolbar/toolbarview'; import { - closeDropdownOnBlur, - closeDropdownOnExecute, - createButtonForDropdown, - createDropdownView, - focusDropdownContentsOnArrows + addDefaultBehavior, + addToolbarToDropdown, + createSingleButtonDropdown } from '../utils'; import '../../../theme/components/dropdown/buttondropdown.css'; @@ -52,71 +49,10 @@ import '../../../theme/components/dropdown/buttondropdown.css'; * @returns {module:ui/dropdown/dropdownview~DropdownView} */ export default function createButtonDropdown( model, locale ) { - // Make disabled when all buttons are disabled - model.bind( 'isEnabled' ).to( - // Bind to #isEnabled of each command... - ...getBindingTargets( model.buttons, 'isEnabled' ), - // ...and set it true if any command #isEnabled is true. - ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) - ); + const dropdownView = createSingleButtonDropdown( model, locale ); - // If defined `staticIcon` use the `defaultIcon` without binding it to active a button. - if ( model.staticIcon ) { - model.bind( 'icon' ).to( model, 'defaultIcon' ); - } else { - // TODO: move to alignment - // Make dropdown icon as any active button. - model.bind( 'icon' ).to( - // Bind to #isOn of each button... - ...getBindingTargets( model.buttons, 'isOn' ), - // ...and chose the title of the first one which #isOn is true. - ( ...areActive ) => { - const index = areActive.findIndex( value => value ); - - // If none of the commands is active, display either defaultIcon or first button icon. - if ( index < 0 && model.defaultIcon ) { - return model.defaultIcon; - } - - return model.buttons[ index < 0 ? 0 : index ].icon; - } - ); - } - const buttonView = createButtonForDropdown( model, locale ); - const dropdownView = createDropdownView( model, buttonView, locale ); - - const toolbarView = dropdownView.toolbarView = new ToolbarView(); - - toolbarView.bind( 'isVertical', 'className' ).to( model, 'isVertical', 'toolbarClassName' ); - - model.buttons.map( view => toolbarView.items.add( view ) ); - - // TODO: better: - dropdownView.buttonView.delegate( 'execute' ).to( dropdownView.buttonView, 'select' ); - - dropdownView.extendTemplate( { - attributes: { - class: [ 'ck-buttondropdown' ] - } - } ); - - dropdownView.panelView.children.add( toolbarView ); - - closeDropdownOnBlur( dropdownView ); - closeDropdownOnExecute( dropdownView, toolbarView.items ); - focusDropdownContentsOnArrows( dropdownView, toolbarView ); + addToolbarToDropdown( dropdownView, model ); + addDefaultBehavior( dropdownView ); return dropdownView; } - -// Returns an array of binding components for -// {@link module:utils/observablemixin~Observable#bind} from a set of iterable -// buttons. -// -// @private -// @param {Iterable.} buttons -// @param {String} attribute -// @returns {Array.} -function getBindingTargets( buttons, attribute ) { - return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); -} diff --git a/src/dropdown/createsplitbuttondropdown.js b/src/dropdown/createsplitbuttondropdown.js deleted file mode 100644 index 54b063e7..00000000 --- a/src/dropdown/createsplitbuttondropdown.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/createsplitbuttondropdown - */ - -import { createDropdownView, createSplitButtonForDropdown } from './utils'; - -/** - * Create a dropdown that have a split button as button. - * - * TODO: docs - */ -export default function createSplitButtonDropdown( model, locale ) { - const splitButtonView = createSplitButtonForDropdown( model, locale ); - - splitButtonView.buttonView.bind( 'isOn' ).to( model ); - splitButtonView.buttonView.bind( 'tooltip' ).to( model ); - - const dropdownView = createDropdownView( model, splitButtonView, locale ); - - // Extend template to hide arrow from dropdown. - // TODO: enable this on normal button instead of hiding it - dropdownView.extendTemplate( { - attributes: { - class: 'ck-splitbutton-dropdown' - } - } ); - - return dropdownView; -} diff --git a/src/dropdown/dropdownpanelview.js b/src/dropdown/dropdownpanelview.js index 66385ccd..cb7e74d3 100644 --- a/src/dropdown/dropdownpanelview.js +++ b/src/dropdown/dropdownpanelview.js @@ -65,4 +65,18 @@ export default class DropdownPanelView extends View { } } ); } + + focus() { + if ( this.children.length ) { + this.children.first.focus(); + } + } + + // TODO: child might not have focusLast + // TODO: maybe adding toolbar/list should somewhat intercept those? + focusLast() { + if ( this.children.length ) { + this.children.last.focusLast(); + } + } } diff --git a/src/dropdown/list/createlistdropdown.js b/src/dropdown/list/createlistdropdown.js index 20035f8a..1a3772a1 100644 --- a/src/dropdown/list/createlistdropdown.js +++ b/src/dropdown/list/createlistdropdown.js @@ -7,12 +7,10 @@ * @module ui/dropdown/list/createlistdropdown */ -import ListView from '../../list/listview'; -import ListItemView from '../../list/listitemview'; - import { - closeDropdownOnBlur, closeDropdownOnExecute, createButtonForDropdown, createDropdownView, - focusDropdownContentsOnArrows + addDefaultBehavior, + addListViewToDropdown, + createSingleButtonDropdown } from '../utils'; /** @@ -54,28 +52,10 @@ import { * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. */ export default function createListDropdown( model, locale ) { - const buttonView = createButtonForDropdown( model, locale ); - const dropdownView = createDropdownView( model, buttonView, locale ); - - const listView = dropdownView.listView = new ListView( locale ); - - listView.items.bindTo( model.items ).using( itemModel => { - const item = new ListItemView( locale ); - - // Bind all attributes of the model to the item view. - item.bind( ...Object.keys( itemModel ) ).to( itemModel ); - - return item; - } ); - - dropdownView.panelView.children.add( listView ); - - // TODO: better: - dropdownView.buttonView.delegate( 'execute' ).to( dropdownView.buttonView, 'select' ); + const dropdownView = createSingleButtonDropdown( model, locale ); - closeDropdownOnBlur( dropdownView ); - closeDropdownOnExecute( dropdownView, listView.items ); - focusDropdownContentsOnArrows( dropdownView, listView ); + addListViewToDropdown( dropdownView, model, locale ); + addDefaultBehavior( dropdownView ); return dropdownView; } diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index f3944c27..c5fcb5f5 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -12,19 +12,21 @@ import SplitButtonView from '../button/splitbuttonview'; import ButtonView from '../button/buttonview'; import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; +import ToolbarView from '../toolbar/toolbarview'; +import ListView from '../list/listview'; +import ListItemView from '../list/listitemview'; /** * Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. * * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - * @param {module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable} panelViewContents */ -export function focusDropdownContentsOnArrows( dropdownView, panelViewContents ) { +export function focusDropdownContentsOnArrows( dropdownView ) { // If the dropdown panel is already open, the arrow down key should // focus the first element in list. dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { if ( dropdownView.isOpen ) { - panelViewContents.focus(); + dropdownView.panelView.focus(); cancel(); } } ); @@ -33,7 +35,7 @@ export function focusDropdownContentsOnArrows( dropdownView, panelViewContents ) // focus the last element in the list. dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { if ( dropdownView.isOpen ) { - panelViewContents.focusLast(); + dropdownView.panelView.focusLast(); cancel(); } } ); @@ -43,12 +45,8 @@ export function focusDropdownContentsOnArrows( dropdownView, panelViewContents ) * Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. * * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - * @param {module:ui/viewcollection~ViewCollection} viewCollection */ -export function closeDropdownOnExecute( dropdownView, viewCollection ) { - // TODO: Delegate all events instead of just execute. - viewCollection.delegate( 'execute' ).to( dropdownView ); - +export function closeDropdownOnExecute( dropdownView ) { // Close the dropdown when one of the list items has been executed. dropdownView.on( 'execute', () => { dropdownView.isOpen = false; @@ -80,6 +78,9 @@ export function createButtonForDropdown( model, locale ) { buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + // Dropdown expects "select" event to show contents. + buttonView.delegate( 'execute' ).to( buttonView, 'select' ); + return buttonView; } @@ -89,6 +90,10 @@ export function createSplitButtonForDropdown( model, locale ) { // TODO: check 'isOn' binding. buttonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + // TODO: something wierd with binding + buttonView.buttonView.bind( 'isOn' ).to( model ); + buttonView.buttonView.bind( 'tooltip' ).to( model ); + return buttonView; } @@ -99,9 +104,97 @@ export function createDropdownView( model, buttonView, locale ) { dropdownView.bind( 'isEnabled' ).to( model ); // TODO: check 'isOn' binding. - // buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { - // return isOn || isOpen; - // } ); + buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + return isOn || isOpen; + } ); return dropdownView; } + +export function createSplitButtonDropdown( model, locale ) { + const splitButtonView = createSplitButtonForDropdown( model, locale ); + const dropdownView = createDropdownView( model, splitButtonView, locale ); + + // Extend template to hide arrow from dropdown. + // TODO: enable this on normal button instead of hiding it + dropdownView.extendTemplate( { + attributes: { + class: 'ck-splitbutton-dropdown' + } + } ); + + return dropdownView; +} + +export function createSingleButtonDropdown( model, locale ) { + const buttonView = createButtonForDropdown( model, locale ); + + return createDropdownView( model, buttonView, locale ); +} + +export function enableModelIfOneIsEnabled( model, observables ) { + model.bind( 'isEnabled' ).to( + // Bind to #isEnabled of each observable... + ...getBindingTargets( observables, 'isEnabled' ), + // ...and set it true if any observable #isEnabled is true. + ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) + ); +} + +export function addListViewToDropdown( dropdownView, model, locale ) { + const listView = dropdownView.listView = new ListView( locale ); + + listView.items.bindTo( model.items ).using( itemModel => { + const item = new ListItemView( locale ); + + // Bind all attributes of the model to the item view. + item.bind( ...Object.keys( itemModel ) ).to( itemModel ); + + return item; + } ); + + dropdownView.panelView.children.add( listView ); + + listView.items.delegate( 'execute' ).to( dropdownView ); + + return listView; +} + +export function addToolbarToDropdown( dropdownView, model ) { + const toolbarView = dropdownView.toolbarView = new ToolbarView(); + + // TODO verify className binding + toolbarView.bind( 'isVertical', 'className' ).to( model, 'isVertical', 'toolbarClassName' ); + + // TODO: verify class names + dropdownView.extendTemplate( { + attributes: { + class: [ 'ck-buttondropdown' ] + } + } ); + + dropdownView.panelView.children.add( toolbarView ); + + // TODO: make it as 'items', 'views' ??? + model.buttons.map( view => toolbarView.items.add( view ) ); + + return toolbarView; +} + +export function addDefaultBehavior( dropdownView ) { + closeDropdownOnBlur( dropdownView ); + closeDropdownOnExecute( dropdownView ); + focusDropdownContentsOnArrows( dropdownView ); +} + +// Returns an array of binding components for +// {@link module:utils/observablemixin~Observable#bind} from a set of iterable +// buttons. +// +// @private +// @param {Iterable.} buttons +// @param {String} attribute +// @returns {Array.} +function getBindingTargets( buttons, attribute ) { + return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); +} diff --git a/tests/dropdown/button/createbuttondropdown.js b/tests/dropdown/button/createbuttondropdown.js index 839b721e..76a2a19f 100644 --- a/tests/dropdown/button/createbuttondropdown.js +++ b/tests/dropdown/button/createbuttondropdown.js @@ -170,50 +170,5 @@ describe( 'createButtonDropdown', () => { sinon.assert.calledOnce( spy ); } ); } ); - - describe( 'icon', () => { - it( 'should be set to first button\'s icon if no defaultIcon defined', () => { - expect( view.buttonView.icon ).to.equal( view.toolbarView.items.get( 0 ).icon ); - } ); - - it( 'should be bound to first button that is on', () => { - view.toolbarView.items.get( 1 ).isOn = true; - - expect( view.buttonView.icon ).to.equal( view.toolbarView.items.get( 1 ).icon ); - - view.toolbarView.items.get( 0 ).isOn = true; - view.toolbarView.items.get( 1 ).isOn = false; - - expect( view.buttonView.icon ).to.equal( view.toolbarView.items.get( 0 ).icon ); - } ); - - it( 'should be set to defaultIcon if defined and on button is on', () => { - const model = new Model( { - defaultIcon: 'baz', - buttons - } ); - - view = createButtonDropdown( model, locale ); - view.render(); - - expect( view.buttonView.icon ).to.equal( 'baz' ); - } ); - - it( 'should not bind icons if staticIcon is set', () => { - const model = new Model( { - defaultIcon: 'baz', - staticIcon: true, - buttons - } ); - - view = createButtonDropdown( model, locale ); - view.render(); - - expect( view.buttonView.icon ).to.equal( 'baz' ); - view.toolbarView.items.get( 1 ).isOn = true; - - expect( view.buttonView.icon ).to.equal( 'baz' ); - } ); - } ); } ); } ); From ba2a0ee17fbd218a231d1ebd9824f1f81ad61009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 19 Jan 2018 14:25:45 +0100 Subject: [PATCH 16/78] Changed: Temporary disable 'isOn' binding in button. --- src/dropdown/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index c5fcb5f5..6f6cb034 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -88,7 +88,7 @@ export function createSplitButtonForDropdown( model, locale ) { const buttonView = new SplitButtonView( locale ); // TODO: check 'isOn' binding. - buttonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); // TODO: something wierd with binding buttonView.buttonView.bind( 'isOn' ).to( model ); From 79ec879a9fa30d27773e97b251810cc9bc6690ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 19 Jan 2018 15:23:03 +0100 Subject: [PATCH 17/78] Changed: Rename parts of SplitButton buttons to action/select. --- src/button/splitbuttonview.js | 28 +++++++++++++--------------- src/dropdown/dropdownview.js | 6 +++--- src/dropdown/utils.js | 10 +++++----- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 1e97eb36..0735aaf4 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -13,6 +13,7 @@ import ButtonView from './buttonview'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; +// TODO: temporary hack... import arrowIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; import './../../theme/components/button/splitbutton.css'; @@ -29,8 +30,8 @@ export default class SplitButtonView extends View { this.children = this.createCollection(); - this.buttonView = this._createButtonView(); - this.selectView = this._createArrowView(); + this.actionView = this._createActionView(); + this.selectView = this._createSelectView(); this.keystrokes = new KeystrokeHandler(); this.focusTracker = new FocusTracker(); @@ -52,17 +53,17 @@ export default class SplitButtonView extends View { render() { super.render(); - this.children.add( this.buttonView ); + this.children.add( this.actionView ); this.children.add( this.selectView ); - this.focusTracker.add( this.buttonView.element ); + this.focusTracker.add( this.actionView.element ); this.focusTracker.add( this.selectView.element ); this.keystrokes.listenTo( this.element ); // Overrides toolbar focus cycling behavior this.keystrokes.set( 'arrowright', ( evt, cancel ) => { - if ( this.focusTracker.focusedElement === this.buttonView.element ) { + if ( this.focusTracker.focusedElement === this.actionView.element ) { this.selectView.focus(); cancel(); @@ -72,7 +73,7 @@ export default class SplitButtonView extends View { // Overrides toolbar focus cycling behavior this.keystrokes.set( 'arrowleft', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.selectView.element ) { - this.buttonView.focus(); + this.actionView.focus(); cancel(); } @@ -80,23 +81,20 @@ export default class SplitButtonView extends View { } focus() { - this.buttonView.focus(); + this.actionView.focus(); } - _createButtonView() { + _createActionView() { const buttonView = new ButtonView(); - buttonView.bind( 'icon' ).to( this, 'icon' ); + buttonView.bind( 'icon', 'isEnabled', 'label' ).to( this ); buttonView.delegate( 'execute' ).to( this ); - buttonView.bind( 'isEnabled' ).to( this ); - buttonView.bind( 'label' ).to( this ); - return buttonView; } - _createArrowView() { + _createSelectView() { const selectView = new ButtonView(); selectView.icon = arrowIcon; @@ -107,10 +105,10 @@ export default class SplitButtonView extends View { } } ); - selectView.delegate( 'execute' ).to( this, 'select' ); - selectView.bind( 'isEnabled' ).to( this ); + selectView.delegate( 'execute' ).to( this, 'select' ); + return selectView; } } diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index dd37da83..590d7775 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -57,7 +57,7 @@ export default class DropdownView extends View { this.buttonView = buttonView; /** - * Panel of the dropdown. It opens when the {@link #buttonView} is + * Panel of the dropdown. It opens when the {@link #actionView} is * {@link module:ui/button/buttonview~ButtonView#event:execute executed} (i.e. clicked). * * Child views can be added to the panel's `children` collection: @@ -158,7 +158,7 @@ export default class DropdownView extends View { super.render(); // Toggle the the dropdown when it's button has been clicked. - this.listenTo( this.buttonView, 'select', () => { + this.listenTo( this.butstonView, 'select', () => { this.isOpen = !this.isOpen; } ); @@ -201,7 +201,7 @@ export default class DropdownView extends View { } /** - * Focuses the {@link #buttonView}. + * Focuses the {@link #actionView}. */ focus() { this.buttonView.focus(); diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 6f6cb034..455c9bc9 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -85,16 +85,16 @@ export function createButtonForDropdown( model, locale ) { } export function createSplitButtonForDropdown( model, locale ) { - const buttonView = new SplitButtonView( locale ); + const splitButtonView = new SplitButtonView( locale ); // TODO: check 'isOn' binding. - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + splitButtonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); // TODO: something wierd with binding - buttonView.buttonView.bind( 'isOn' ).to( model ); - buttonView.buttonView.bind( 'tooltip' ).to( model ); + splitButtonView.actionView.bind( 'isOn' ).to( model ); + splitButtonView.actionView.bind( 'tooltip' ).to( model ); - return buttonView; + return splitButtonView; } export function createDropdownView( model, buttonView, locale ) { From 63642cfe1bf99bacfc0931a28808004f6a0f44e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 19 Jan 2018 15:37:23 +0100 Subject: [PATCH 18/78] Fix: typos. --- src/dropdown/dropdownview.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 590d7775..dd37da83 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -57,7 +57,7 @@ export default class DropdownView extends View { this.buttonView = buttonView; /** - * Panel of the dropdown. It opens when the {@link #actionView} is + * Panel of the dropdown. It opens when the {@link #buttonView} is * {@link module:ui/button/buttonview~ButtonView#event:execute executed} (i.e. clicked). * * Child views can be added to the panel's `children` collection: @@ -158,7 +158,7 @@ export default class DropdownView extends View { super.render(); // Toggle the the dropdown when it's button has been clicked. - this.listenTo( this.butstonView, 'select', () => { + this.listenTo( this.buttonView, 'select', () => { this.isOpen = !this.isOpen; } ); @@ -201,7 +201,7 @@ export default class DropdownView extends View { } /** - * Focuses the {@link #actionView}. + * Focuses the {@link #buttonView}. */ focus() { this.buttonView.focus(); From b70ffca1eeb2cb52104e71fe31a5cac0f2205b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 22 Jan 2018 16:39:42 +0100 Subject: [PATCH 19/78] Changed: Remove crateButtonDropdown() and createListDropdown() helpers. --- src/dropdown/button/createbuttondropdown.js | 58 --- src/dropdown/list/createlistdropdown.js | 61 ---- src/dropdown/utils.js | 79 +++- tests/dropdown/button/createbuttondropdown.js | 174 --------- tests/dropdown/list/createlistdropdown.js | 185 ---------- tests/dropdown/manual/dropdown.js | 13 +- tests/dropdown/utils.js | 343 ++++++++++++++++++ 7 files changed, 430 insertions(+), 483 deletions(-) delete mode 100644 src/dropdown/button/createbuttondropdown.js delete mode 100644 src/dropdown/list/createlistdropdown.js delete mode 100644 tests/dropdown/button/createbuttondropdown.js delete mode 100644 tests/dropdown/list/createlistdropdown.js create mode 100644 tests/dropdown/utils.js diff --git a/src/dropdown/button/createbuttondropdown.js b/src/dropdown/button/createbuttondropdown.js deleted file mode 100644 index b36dc07e..00000000 --- a/src/dropdown/button/createbuttondropdown.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/button/createbuttondropdown - */ - -import { - addDefaultBehavior, - addToolbarToDropdown, - createSingleButtonDropdown -} from '../utils'; - -import '../../../theme/components/dropdown/buttondropdown.css'; - -/** - * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using - * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. - * - * const buttons = []; - * - * buttons.push( new ButtonView() ); - * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); - * - * const model = new Model( { - * label: 'A button dropdown', - * isVertical: true, - * buttons - * } ); - * - * const dropdown = createButtonDropdown( model, locale ); - * - * // Will render a vertical button dropdown labeled "A button dropdown" - * // with a button group in the panel containing two buttons. - * dropdown.render() - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown}. - * - * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} The button dropdown view instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} - */ -export default function createButtonDropdown( model, locale ) { - const dropdownView = createSingleButtonDropdown( model, locale ); - - addToolbarToDropdown( dropdownView, model ); - addDefaultBehavior( dropdownView ); - - return dropdownView; -} diff --git a/src/dropdown/list/createlistdropdown.js b/src/dropdown/list/createlistdropdown.js deleted file mode 100644 index 1a3772a1..00000000 --- a/src/dropdown/list/createlistdropdown.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/list/createlistdropdown - */ - -import { - addDefaultBehavior, - addListViewToDropdown, - createSingleButtonDropdown -} from '../utils'; - -/** - * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using - * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. - * - * const items = new Collection(); - * - * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); - * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); - * - * const model = new Model( { - * isEnabled: true, - * items, - * isOn: false, - * label: 'A dropdown' - * } ); - * - * const dropdown = createListDropdown( model, locale ); - * - * // Will render a dropdown labeled "A dropdown" with a list in the panel - * // containing two items. - * dropdown.render() - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * The - * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} - * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the - * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. - * - * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. - */ -export default function createListDropdown( model, locale ) { - const dropdownView = createSingleButtonDropdown( model, locale ); - - addListViewToDropdown( dropdownView, model, locale ); - addDefaultBehavior( dropdownView ); - - return dropdownView; -} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 455c9bc9..b12adef3 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -141,9 +141,48 @@ export function enableModelIfOneIsEnabled( model, observables ) { ); } +/** + * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using + * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. + * + * const items = new Collection(); + * + * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); + * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); + * + * const model = new Model( { + * isEnabled: true, + * items, + * isOn: false, + * label: 'A dropdown' + * } ); + * + * const dropdown = createListDropdown( model, locale ); + * + * // Will render a dropdown labeled "A dropdown" with a list in the panel + * // containing two items. + * dropdown.render() + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * The + * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} + * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the + * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. + * + * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. + * + * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. + */ export function addListViewToDropdown( dropdownView, model, locale ) { const listView = dropdownView.listView = new ListView( locale ); + // TODO: make this param of method instead of model property listView.items.bindTo( model.items ).using( itemModel => { const item = new ListItemView( locale ); @@ -155,11 +194,48 @@ export function addListViewToDropdown( dropdownView, model, locale ) { dropdownView.panelView.children.add( listView ); + // TODO: make this also on toolbar???? listView.items.delegate( 'execute' ).to( dropdownView ); return listView; } +// TODO: where I go??? Make something smart since +import '../../theme/components/dropdown/buttondropdown.css'; + +/** + * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using + * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. + * + * const buttons = []; + * + * buttons.push( new ButtonView() ); + * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); + * + * const model = new Model( { + * label: 'A button dropdown', + * isVertical: true, + * buttons + * } ); + * + * const dropdown = createButtonDropdown( model, locale ); + * + * // Will render a vertical button dropdown labeled "A button dropdown" + * // with a button group in the panel containing two buttons. + * dropdown.render() + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * See {@link module:ui/dropdown/createdropdown~createDropdown}. + * + * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} The button dropdown view instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} + */ export function addToolbarToDropdown( dropdownView, model ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); @@ -195,6 +271,7 @@ export function addDefaultBehavior( dropdownView ) { // @param {Iterable.} buttons // @param {String} attribute // @returns {Array.} -function getBindingTargets( buttons, attribute ) { +export function getBindingTargets( buttons, attribute ) { return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); } + diff --git a/tests/dropdown/button/createbuttondropdown.js b/tests/dropdown/button/createbuttondropdown.js deleted file mode 100644 index 76a2a19f..00000000 --- a/tests/dropdown/button/createbuttondropdown.js +++ /dev/null @@ -1,174 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document, Event */ - -import Model from '../../../src/model'; -import createButtonDropdown from '../../../src/dropdown/button/createbuttondropdown'; - -import ButtonView from '../../../src/button/buttonview'; -import ToolbarView from '../../../src/toolbar/toolbarview'; - -import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; - -describe( 'createButtonDropdown', () => { - let view, model, locale, buttons; - - beforeEach( () => { - locale = { t() {} }; - buttons = [ 'foo', 'bar' ].map( icon => { - const button = new ButtonView(); - button.icon = icon; - - return button; - } ); - - model = new Model( { - isVertical: true, - buttons - } ); - - view = createButtonDropdown( model, locale ); - view.render(); - document.body.appendChild( view.element ); - } ); - - afterEach( () => { - view.element.remove(); - } ); - - describe( 'constructor()', () => { - it( 'sets view#locale', () => { - expect( view.locale ).to.equal( locale ); - } ); - - describe( 'view#toolbarView', () => { - it( 'is created', () => { - const panelChildren = view.panelView.children; - - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( view.toolbarView ); - expect( view.toolbarView ).to.be.instanceof( ToolbarView ); - } ); - - it( 'delegates view.toolbarView#execute to the view', done => { - view.on( 'execute', evt => { - expect( evt.source ).to.equal( view.toolbarView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ view.toolbarView.items.get( 0 ), view ] ); - - done(); - } ); - - view.toolbarView.items.get( 0 ).fire( 'execute' ); - } ); - - it( 'reacts on model#isVertical', () => { - model.isVertical = false; - expect( view.toolbarView.isVertical ).to.be.false; - - model.isVertical = true; - expect( view.toolbarView.isVertical ).to.be.true; - } ); - - it( 'reacts on model#toolbarClassName', () => { - expect( view.toolbarView.className ).to.be.undefined; - - model.set( 'toolbarClassName', 'foo' ); - expect( view.toolbarView.className ).to.equal( 'foo' ); - } ); - } ); - - it( 'changes view#isOpen on view#execute', () => { - view.isOpen = true; - - view.fire( 'execute' ); - expect( view.isOpen ).to.be.false; - - view.fire( 'execute' ); - expect( view.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { - // Open the dropdown. - view.isOpen = true; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Closed the dropdown. - expect( view.isOpen ).to.be.false; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still closed. - expect( view.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { - // Open the dropdown. - view.isOpen = true; - - // Event from view.element should be discarded. - view.element.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( view.isOpen ).to.be.true; - - // Event from within view.element should be discarded. - const child = document.createElement( 'div' ); - view.element.appendChild( child ); - - child.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( view.isOpen ).to.be.true; - } ); - - describe( 'activates keyboard navigation for the dropdown', () => { - it( 'so "arrowdown" focuses the #toolbarView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowdown, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( view.toolbarView, 'focus' ); - - view.isOpen = false; - view.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - view.isOpen = true; - view.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); - - it( 'so "arrowup" focuses the last #item in #toolbarView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowup, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( view.toolbarView, 'focusLast' ); - - view.isOpen = false; - view.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - view.isOpen = true; - view.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); - } ); - } ); -} ); diff --git a/tests/dropdown/list/createlistdropdown.js b/tests/dropdown/list/createlistdropdown.js deleted file mode 100644 index 9b709902..00000000 --- a/tests/dropdown/list/createlistdropdown.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document, Event */ - -import Model from '../../../src/model'; -import createListDropdown from '../../../src/dropdown/list/createlistdropdown'; -import Collection from '@ckeditor/ckeditor5-utils/src/collection'; -import ListView from '../../../src/list/listview'; -import ListItemView from '../../../src/list/listitemview'; -import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; - -describe( 'createListDropdown', () => { - let view, model, locale, items; - - beforeEach( () => { - locale = { t() {} }; - items = new Collection(); - model = new Model( { - isEnabled: true, - items, - isOn: false, - label: 'foo' - } ); - - view = createListDropdown( model, locale ); - view.render(); - document.body.appendChild( view.element ); - } ); - - afterEach( () => { - view.element.remove(); - } ); - - describe( 'constructor()', () => { - it( 'sets view#locale', () => { - expect( view.locale ).to.equal( locale ); - } ); - - describe( 'view#listView', () => { - it( 'is created', () => { - const panelChildren = view.panelView.children; - - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( view.listView ); - expect( view.listView ).to.be.instanceof( ListView ); - } ); - - it( 'is bound to model#items', () => { - items.add( new Model( { label: 'a', style: 'b' } ) ); - items.add( new Model( { label: 'c', style: 'd' } ) ); - - expect( view.listView.items ).to.have.length( 2 ); - expect( view.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView ); - expect( view.listView.items.get( 1 ).label ).to.equal( 'c' ); - expect( view.listView.items.get( 1 ).style ).to.equal( 'd' ); - - items.remove( 1 ); - expect( view.listView.items ).to.have.length( 1 ); - expect( view.listView.items.get( 0 ).label ).to.equal( 'a' ); - expect( view.listView.items.get( 0 ).style ).to.equal( 'b' ); - } ); - - it( 'binds all attributes in model#items', () => { - const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } ); - - items.add( itemModel ); - - const item = view.listView.items.get( 0 ); - - expect( item.foo ).to.equal( 'bar' ); - expect( item.baz ).to.equal( 'qux' ); - - itemModel.baz = 'foo?'; - expect( item.baz ).to.equal( 'foo?' ); - } ); - - it( 'delegates view.listView#execute to the view', done => { - items.add( new Model( { label: 'a', style: 'b' } ) ); - - view.on( 'execute', evt => { - expect( evt.source ).to.equal( view.listView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ view.listView.items.get( 0 ), view ] ); - - done(); - } ); - - view.listView.items.get( 0 ).fire( 'execute' ); - } ); - } ); - - it( 'changes view#isOpen on view#execute', () => { - view.isOpen = true; - - view.fire( 'execute' ); - expect( view.isOpen ).to.be.false; - - view.fire( 'execute' ); - expect( view.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { - // Open the dropdown. - view.isOpen = true; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Closed the dropdown. - expect( view.isOpen ).to.be.false; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still closed. - expect( view.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { - // Open the dropdown. - view.isOpen = true; - - // Event from view.element should be discarded. - view.element.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( view.isOpen ).to.be.true; - - // Event from within view.element should be discarded. - const child = document.createElement( 'div' ); - view.element.appendChild( child ); - - child.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( view.isOpen ).to.be.true; - } ); - - describe( 'activates keyboard navigation for the dropdown', () => { - it( 'so "arrowdown" focuses the #listView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowdown, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( view.listView, 'focus' ); - - view.isOpen = false; - view.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - view.isOpen = true; - view.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); - - it( 'so "arrowup" focuses the last #item in #listView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowup, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( view.listView, 'focusLast' ); - - view.isOpen = false; - view.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - view.isOpen = true; - view.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); - } ); - } ); -} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 4c3b912c..b73459b1 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -9,7 +9,6 @@ import Model from '../../../src/model'; import Collection from '@ckeditor/ckeditor5-utils/src/collection'; import createDropdown from '../../../src/dropdown/createdropdown'; -import createListDropdown from '../../../src/dropdown/list/createlistdropdown'; import testUtils from '../../_utils/utils'; @@ -18,7 +17,7 @@ import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.sv import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; -import createButtonDropdown from '../../../src/dropdown/button/createbuttondropdown'; +import { addDefaultBehavior, addListViewToDropdown, addToolbarToDropdown, createSingleButtonDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', @@ -59,7 +58,10 @@ function testList() { items: collection } ); - const dropdownView = createListDropdown( model ); + const dropdownView = createSingleButtonDropdown( model, {} ); + + addListViewToDropdown( dropdownView, model, {} ); + addDefaultBehavior( dropdownView ); dropdownView.on( 'execute', evt => { /* global console */ @@ -127,7 +129,10 @@ function testButton() { buttons: buttonViews } ); - const buttonDropdown = createButtonDropdown( buttonDropdownModel, {} ); + const buttonDropdown = createSingleButtonDropdown( buttonDropdownModel, {} ); + + addToolbarToDropdown( buttonDropdown, buttonDropdownModel ); + addDefaultBehavior( buttonDropdown ); ui.buttonDropdown.add( buttonDropdown ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js new file mode 100644 index 00000000..43c800d5 --- /dev/null +++ b/tests/dropdown/utils.js @@ -0,0 +1,343 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document, Event */ + +import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; + +import Model from '../../src/model'; + +import View from '../../src/view'; +import ButtonView from '../../src/button/buttonview'; +import ToolbarView from '../../src/toolbar/toolbarview'; + +import { + addListViewToDropdown, + addToolbarToDropdown, + closeDropdownOnBlur, + closeDropdownOnExecute, + createButtonForDropdown, + createDropdownView, + createSingleButtonDropdown, + focusDropdownContentsOnArrows +} from '../../src/dropdown/utils'; +import ListItemView from '../../src/list/listitemview'; + +import ListView from '../../src/list/listview'; +import Collection from '../../../ckeditor5-utils/src/collection'; + +describe( 'utils', () => { + let dropdownView, buttonView, model, locale; + + beforeEach( () => { + locale = { t() {} }; + model = new Model(); + buttonView = createButtonForDropdown( model, locale ); + dropdownView = createDropdownView( model, buttonView, locale ); + } ); + + describe( 'focusDropdownContentsOnArrows', () => { + let panelChildView; + + beforeEach( () => { + panelChildView = new View(); + panelChildView.setTemplate( { tag: 'div' } ); + panelChildView.focus = () => {}; + panelChildView.focusLast = () => {}; + + // TODO: describe this as #contentView instaed of #listView and #toolbarView + dropdownView.panelView.children.add( panelChildView ); + + focusDropdownContentsOnArrows( dropdownView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowdown, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focus' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + + sinon.assert.calledOnce( spy ); + } ); + + it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowup, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focusLast' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.calledOnce( spy ); + } ); + } ); + + describe( 'closeDropdownOnExecute', () => { + beforeEach( () => { + closeDropdownOnExecute( dropdownView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + it( 'changes view#isOpen on view#execute', () => { + dropdownView.isOpen = true; + + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + } ); + } ); + + describe( 'closeDropdownOnBlur', () => { + beforeEach( () => { + closeDropdownOnBlur( dropdownView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Closed the dropdown. + expect( dropdownView.isOpen ).to.be.false; + + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still closed. + expect( dropdownView.isOpen ).to.be.false; + } ); + + it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + + // Event from view.element should be discarded. + dropdownView.element.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + + // Event from within view.element should be discarded. + const child = document.createElement( 'div' ); + dropdownView.element.appendChild( child ); + + child.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + } ); + } ); + + describe( 'createButtonForDropdown', () => {} ); + + describe( 'createSplitButtonForDropdown', () => {} ); + + describe( 'createDropdownView', () => {} ); + + describe( 'createSplitButtonDropdown', () => {} ); + + describe( 'createSingleButtonDropdown', () => {} ); + + describe( 'enableModelIfOneIsEnabled', () => {} ); + + describe( 'addListViewToDropdown', () => { + let items; + + beforeEach( () => { + items = new Collection(); + model = new Model( { + isEnabled: true, + items, + isOn: false, + label: 'foo' + } ); + + dropdownView = createSingleButtonDropdown( model, locale ); + + addListViewToDropdown( dropdownView, model, locale ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( 'sets view#locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + } ); + + describe( 'view#listView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; + + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.listView ); + expect( dropdownView.listView ).to.be.instanceof( ListView ); + } ); + + it( 'is bound to model#items', () => { + items.add( new Model( { label: 'a', style: 'b' } ) ); + items.add( new Model( { label: 'c', style: 'd' } ) ); + + expect( dropdownView.listView.items ).to.have.length( 2 ); + expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView ); + expect( dropdownView.listView.items.get( 1 ).label ).to.equal( 'c' ); + expect( dropdownView.listView.items.get( 1 ).style ).to.equal( 'd' ); + + items.remove( 1 ); + expect( dropdownView.listView.items ).to.have.length( 1 ); + expect( dropdownView.listView.items.get( 0 ).label ).to.equal( 'a' ); + expect( dropdownView.listView.items.get( 0 ).style ).to.equal( 'b' ); + } ); + + it( 'binds all attributes in model#items', () => { + const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } ); + + items.add( itemModel ); + + const item = dropdownView.listView.items.get( 0 ); + + expect( item.foo ).to.equal( 'bar' ); + expect( item.baz ).to.equal( 'qux' ); + + itemModel.baz = 'foo?'; + expect( item.baz ).to.equal( 'foo?' ); + } ); + + it( 'delegates view.listView#execute to the view', done => { + items.add( new Model( { label: 'a', style: 'b' } ) ); + + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.listView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.listView.items.get( 0 ), dropdownView ] ); + + done(); + } ); + + dropdownView.listView.items.get( 0 ).fire( 'execute' ); + } ); + } ); + } ); + + describe( 'addToolbarToDropdown', () => { + let buttons; + + beforeEach( () => { + buttons = [ 'foo', 'bar' ].map( icon => { + const button = new ButtonView(); + + button.icon = icon; + + return button; + } ); + + model = new Model( { + isVertical: true, + buttons + } ); + + dropdownView = createSingleButtonDropdown( model, locale ); + + addToolbarToDropdown( dropdownView, model ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + describe( 'constructor()', () => { + it( 'sets view#locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + } ); + + describe( 'view#toolbarView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; + + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); + expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); + } ); + + it.skip( 'delegates view.toolbarView.items#execute to the view', done => { + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); + + done(); + } ); + + dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); + } ); + + it( 'reacts on model#isVertical', () => { + model.isVertical = false; + expect( dropdownView.toolbarView.isVertical ).to.be.false; + + model.isVertical = true; + expect( dropdownView.toolbarView.isVertical ).to.be.true; + } ); + + // TODO: remove? + it( 'reacts on model#toolbarClassName', () => { + expect( dropdownView.toolbarView.className ).to.be.undefined; + + model.set( 'toolbarClassName', 'foo' ); + expect( dropdownView.toolbarView.className ).to.equal( 'foo' ); + } ); + } ); + + describe( 'buttons', () => { + // TODO: test me! + } ); + } ); + } ); + + describe( 'addDefaultBehavior', () => {} ); + + describe( 'getBindingTargets', () => {} ); + + describe.skip( 'to sort', () => {} ); +} ); From 80c7258a308996469c77b095c4539608d240d12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 22 Jan 2018 16:47:27 +0100 Subject: [PATCH 20/78] Tests: add tests for dropdown util: enableModelIfOneIsEnabled(). --- tests/dropdown/utils.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 43c800d5..a7eb6091 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -21,6 +21,7 @@ import { createButtonForDropdown, createDropdownView, createSingleButtonDropdown, + enableModelIfOneIsEnabled, focusDropdownContentsOnArrows } from '../../src/dropdown/utils'; import ListItemView from '../../src/list/listitemview'; @@ -175,7 +176,30 @@ describe( 'utils', () => { describe( 'createSingleButtonDropdown', () => {} ); - describe( 'enableModelIfOneIsEnabled', () => {} ); + describe( 'enableModelIfOneIsEnabled', () => { + it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { + const observables = [ + new Model( { isEnabled: false } ), + new Model( { isEnabled: false } ), + new Model( { isEnabled: false } ) + ]; + enableModelIfOneIsEnabled( model, observables ); + + expect( model.isEnabled ).to.be.false; + + observables[ 0 ].isEnabled = true; + + expect( model.isEnabled ).to.be.true; + + observables[ 0 ].isEnabled = false; + + expect( model.isEnabled ).to.be.false; + + observables[ 1 ].isEnabled = true; + + expect( model.isEnabled ).to.be.true; + } ); + } ); describe( 'addListViewToDropdown', () => { let items; From 5cf51a5d515450ba8c5fae881761732d789438c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 22 Jan 2018 18:36:54 +0100 Subject: [PATCH 21/78] Changed: Remove createDropdown() method. --- src/dropdown/createdropdown.js | 44 ------ src/dropdown/utils.js | 40 +++++- tests/dropdown/createdropdown.js | 109 --------------- tests/dropdown/manual/dropdown.js | 32 +++-- tests/dropdown/utils.js | 216 +++++++++++++++++++++++------- 5 files changed, 222 insertions(+), 219 deletions(-) delete mode 100644 src/dropdown/createdropdown.js delete mode 100644 tests/dropdown/createdropdown.js diff --git a/src/dropdown/createdropdown.js b/src/dropdown/createdropdown.js deleted file mode 100644 index e43cbb4f..00000000 --- a/src/dropdown/createdropdown.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/createdropdown - */ - -import { createButtonForDropdown, createDropdownView } from './utils'; - -/** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using - * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. - * - * const model = new Model( { - * label: 'A dropdown', - * isEnabled: true, - * isOn: false, - * withText: true - * } ); - * - * const dropdown = createDropdown( model ); - * - * dropdown.render(); - * - * // Will render a dropdown labeled "A dropdown" with an empty panel. - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. - * - * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. - * - * TODO: only used in tests. - */ -export default function createDropdown( model, locale ) { - return createDropdownView( model, createButtonForDropdown( model, locale ), locale ); -} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b12adef3..9ab71509 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -76,8 +76,6 @@ export function closeDropdownOnBlur( dropdownView ) { export function createButtonForDropdown( model, locale ) { const buttonView = new ButtonView( locale ); - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - // Dropdown expects "select" event to show contents. buttonView.delegate( 'execute' ).to( buttonView, 'select' ); @@ -87,12 +85,9 @@ export function createButtonForDropdown( model, locale ) { export function createSplitButtonForDropdown( model, locale ) { const splitButtonView = new SplitButtonView( locale ); - // TODO: check 'isOn' binding. - splitButtonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - // TODO: something wierd with binding - splitButtonView.actionView.bind( 'isOn' ).to( model ); - splitButtonView.actionView.bind( 'tooltip' ).to( model ); + splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); + splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); return splitButtonView; } @@ -104,6 +99,7 @@ export function createDropdownView( model, buttonView, locale ) { dropdownView.bind( 'isEnabled' ).to( model ); // TODO: check 'isOn' binding. + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { return isOn || isOpen; } ); @@ -275,3 +271,33 @@ export function getBindingTargets( buttons, attribute ) { return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); } +/** + * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using + * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. + * + * const model = new Model( { + * label: 'A dropdown', + * isEnabled: true, + * isOn: false, + * withText: true + * } ); + * + * const dropdown = createDropdown( model ); + * + * dropdown.render(); + * + * // Will render a dropdown labeled "A dropdown" with an empty panel. + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. + * + * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. + * + * TODO: only used in tests. + */ diff --git a/tests/dropdown/createdropdown.js b/tests/dropdown/createdropdown.js deleted file mode 100644 index 7cb0f74a..00000000 --- a/tests/dropdown/createdropdown.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; -import createDropdown from '../../src/dropdown/createdropdown'; -import Model from '../../src/model'; -import DropdownView from '../../src/dropdown/dropdownview'; -import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; -import ButtonView from '../../src/button/buttonview'; - -const assertBinding = utilsTestUtils.assertBinding; - -describe( 'createDropdown', () => { - it( 'binds button attributes to the model', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - - const model = new Model( modelDef ); - const view = createDropdown( model ); - - assertBinding( view.buttonView, - modelDef, - [ - [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] - ], - { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } - ); - } ); - - it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - - const model = new Model( modelDef ); - const view = createDropdown( model ); - - view.isOpen = false; - expect( view.buttonView.isOn ).to.be.false; - - model.isOn = true; - expect( view.buttonView.isOn ).to.be.true; - - view.isOpen = true; - expect( view.buttonView.isOn ).to.be.true; - - model.isOn = false; - expect( view.buttonView.isOn ).to.be.true; - } ); - - it( 'binds dropdown#isEnabled to the model', () => { - const modelDef = { - label: 'foo', - isEnabled: true, - withText: false, - tooltip: false - }; - - const model = new Model( modelDef ); - const view = createDropdown( model ); - - assertBinding( view, - { isEnabled: true }, - [ - [ model, { isEnabled: false } ] - ], - { isEnabled: false } - ); - } ); - - it( 'accepts locale', () => { - const locale = {}; - const view = createDropdown( new Model(), locale ); - - expect( view.locale ).to.equal( locale ); - expect( view.buttonView.locale ).to.equal( locale ); - expect( view.panelView.locale ).to.equal( locale ); - } ); - - it( 'returns view', () => { - const view = createDropdown( new Model() ); - - expect( view ).to.be.instanceOf( DropdownView ); - } ); - - it( 'creates dropdown#buttonView out of ButtonView', () => { - const view = createDropdown( new Model() ); - - expect( view.buttonView ).to.be.instanceOf( ButtonView ); - } ); - - it( 'creates dropdown#panelView out of DropdownPanelView', () => { - const view = createDropdown( new Model() ); - - expect( view.panelView ).to.be.instanceOf( DropdownPanelView ); - } ); -} ); - diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index b73459b1..f7c420ff 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -8,8 +8,6 @@ import Model from '../../../src/model'; import Collection from '@ckeditor/ckeditor5-utils/src/collection'; -import createDropdown from '../../../src/dropdown/createdropdown'; - import testUtils from '../../_utils/utils'; import alignLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg'; @@ -17,7 +15,14 @@ import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.sv import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; -import { addDefaultBehavior, addListViewToDropdown, addToolbarToDropdown, createSingleButtonDropdown } from '../../../src/dropdown/utils'; +import { + addDefaultBehavior, + addListViewToDropdown, + addToolbarToDropdown, + createButtonForDropdown, + createDropdownView, + createSingleButtonDropdown +} from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', @@ -28,12 +33,15 @@ const ui = testUtils.createTestUIView( { } ); function testEmpty() { - const dropdownView = createDropdown( new Model( { + const model = new Model( { label: 'Dropdown', isEnabled: true, isOn: false, withText: true - } ) ); + } ); + + const buttonView = createButtonForDropdown( model, {} ); + const dropdownView = createDropdownView( model, buttonView, {} ); ui.dropdown.add( dropdownView ); @@ -83,8 +91,11 @@ function testSharedModel() { withText: true } ); - const dropdownView1 = createDropdown( model ); - const dropdownView2 = createDropdown( model ); + const buttonView1 = createButtonForDropdown( model, {} ); + const buttonView2 = createButtonForDropdown( model, {} ); + + const dropdownView1 = createDropdownView( model, buttonView1, {} ); + const dropdownView2 = createDropdownView( model, buttonView2, {} ); ui.dropdownShared.add( dropdownView1 ); ui.dropdownShared.add( dropdownView2 ); @@ -93,12 +104,15 @@ function testSharedModel() { } function testLongLabel() { - const dropdownView = createDropdown( new Model( { + const model = new Model( { label: 'Dropdown with a very long label', isEnabled: true, isOn: false, withText: true - } ) ); + } ); + + const buttonView = createButtonForDropdown( model, {} ); + const dropdownView = createDropdownView( model, buttonView, {} ); ui.dropdownLabel.add( dropdownView ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index a7eb6091..ea8e243f 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -7,6 +7,9 @@ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; +import Collection from '@ckeditor/ckeditor5-utils/src/collection'; +import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + import Model from '../../src/model'; import View from '../../src/view'; @@ -21,13 +24,17 @@ import { createButtonForDropdown, createDropdownView, createSingleButtonDropdown, + createSplitButtonForDropdown, enableModelIfOneIsEnabled, focusDropdownContentsOnArrows } from '../../src/dropdown/utils'; import ListItemView from '../../src/list/listitemview'; import ListView from '../../src/list/listview'; -import Collection from '../../../ckeditor5-utils/src/collection'; +import DropdownView from '../../src/dropdown/dropdownview'; +import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; + +const assertBinding = utilsTestUtils.assertBinding; describe( 'utils', () => { let dropdownView, buttonView, model, locale; @@ -39,7 +46,7 @@ describe( 'utils', () => { dropdownView = createDropdownView( model, buttonView, locale ); } ); - describe( 'focusDropdownContentsOnArrows', () => { + describe( 'focusDropdownContentsOnArrows()', () => { let panelChildView; beforeEach( () => { @@ -93,7 +100,7 @@ describe( 'utils', () => { } ); } ); - describe( 'closeDropdownOnExecute', () => { + describe( 'closeDropdownOnExecute()', () => { beforeEach( () => { closeDropdownOnExecute( dropdownView ); @@ -112,7 +119,7 @@ describe( 'utils', () => { } ); } ); - describe( 'closeDropdownOnBlur', () => { + describe( 'closeDropdownOnBlur()', () => { beforeEach( () => { closeDropdownOnBlur( dropdownView ); @@ -166,17 +173,130 @@ describe( 'utils', () => { } ); } ); - describe( 'createButtonForDropdown', () => {} ); + describe( 'createButtonForDropdown()', () => { + it( 'accepts locale', () => { + const buttonView = createButtonForDropdown( model, locale ); + + expect( buttonView.locale ).to.equal( locale ); + } ); + } ); + + describe( 'createSplitButtonForDropdown()', () => { + it( 'accepts locale', () => { + const buttonView = createSplitButtonForDropdown( model, locale ); + + expect( buttonView.locale ).to.equal( locale ); + } ); + } ); + + describe( 'createDropdownView()', () => { + it( 'returns view', () => { + model = new Model(); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView ).to.be.instanceOf( DropdownView ); + } ); + + it( 'creates dropdown#panelView out of DropdownPanelView', () => { + model = new Model(); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); + } ); + + it( 'creates dropdown#buttonView out of buttonView', () => { + model = new Model(); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView.buttonView ).to.equal( buttonView ); + } ); + + it( 'accepts locale', () => { + const buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView.locale ).to.equal( locale ); + expect( dropdownView.panelView.locale ).to.equal( locale ); + } ); + + it( 'binds button attributes to the model', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + buttonView = new ButtonView(); + createDropdownView( model, buttonView, locale ); + + assertBinding( buttonView, + modelDef, + [ + [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + ], + { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } + ); + } ); + + it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + dropdownView.isOpen = false; + expect( buttonView.isOn ).to.be.false; + + model.isOn = true; + expect( buttonView.isOn ).to.be.true; + + dropdownView.isOpen = true; + expect( buttonView.isOn ).to.be.true; + + model.isOn = false; + expect( buttonView.isOn ).to.be.true; + } ); - describe( 'createSplitButtonForDropdown', () => {} ); + it( 'binds dropdown#isEnabled to the model', () => { + const modelDef = { + label: 'foo', + isEnabled: true, + withText: false, + tooltip: false + }; - describe( 'createDropdownView', () => {} ); + model = new Model( modelDef ); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + assertBinding( dropdownView, + { isEnabled: true }, + [ + [ model, { isEnabled: false } ] + ], + { isEnabled: false } + ); + } ); + } ); - describe( 'createSplitButtonDropdown', () => {} ); + describe( 'createSplitButtonDropdown()', () => {} ); - describe( 'createSingleButtonDropdown', () => {} ); + describe( 'createSingleButtonDropdown()', () => {} ); - describe( 'enableModelIfOneIsEnabled', () => { + describe( 'enableModelIfOneIsEnabled()', () => { it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { const observables = [ new Model( { isEnabled: false } ), @@ -201,7 +321,7 @@ describe( 'utils', () => { } ); } ); - describe( 'addListViewToDropdown', () => { + describe( 'addListViewToDropdown()', () => { let items; beforeEach( () => { @@ -282,7 +402,7 @@ describe( 'utils', () => { } ); } ); - describe( 'addToolbarToDropdown', () => { + describe( 'addToolbarToDropdown()', () => { let buttons; beforeEach( () => { @@ -311,57 +431,53 @@ describe( 'utils', () => { dropdownView.element.remove(); } ); - describe( 'constructor()', () => { - it( 'sets view#locale', () => { - expect( dropdownView.locale ).to.equal( locale ); - } ); - - describe( 'view#toolbarView', () => { - it( 'is created', () => { - const panelChildren = dropdownView.panelView.children; + it( 'sets view#locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + } ); - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); - expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); - } ); + describe( 'view#toolbarView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; - it.skip( 'delegates view.toolbarView.items#execute to the view', done => { - dropdownView.on( 'execute', evt => { - expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); + expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); + } ); - done(); - } ); + it.skip( 'delegates view.toolbarView.items#execute to the view', done => { + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); - dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); + done(); } ); - it( 'reacts on model#isVertical', () => { - model.isVertical = false; - expect( dropdownView.toolbarView.isVertical ).to.be.false; - - model.isVertical = true; - expect( dropdownView.toolbarView.isVertical ).to.be.true; - } ); + dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); + } ); - // TODO: remove? - it( 'reacts on model#toolbarClassName', () => { - expect( dropdownView.toolbarView.className ).to.be.undefined; + it( 'reacts on model#isVertical', () => { + model.isVertical = false; + expect( dropdownView.toolbarView.isVertical ).to.be.false; - model.set( 'toolbarClassName', 'foo' ); - expect( dropdownView.toolbarView.className ).to.equal( 'foo' ); - } ); + model.isVertical = true; + expect( dropdownView.toolbarView.isVertical ).to.be.true; } ); - describe( 'buttons', () => { - // TODO: test me! + // TODO: remove? + it( 'reacts on model#toolbarClassName', () => { + expect( dropdownView.toolbarView.className ).to.be.undefined; + + model.set( 'toolbarClassName', 'foo' ); + expect( dropdownView.toolbarView.className ).to.equal( 'foo' ); } ); } ); - } ); - describe( 'addDefaultBehavior', () => {} ); + describe( 'buttons', () => { + // TODO: test me! + } ); + } ); - describe( 'getBindingTargets', () => {} ); + describe( 'addDefaultBehavior()', () => {} ); - describe.skip( 'to sort', () => {} ); + describe( 'getBindingTargets()', () => {} ); } ); From 92360880c51a201afcf7ca00f954c07b6ab1234f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 09:56:29 +0100 Subject: [PATCH 22/78] Changed: Remove toolbar class binding from dropdown. --- src/dropdown/button/buttondropdownmodel.jsdoc | 10 ---------- src/dropdown/utils.js | 2 +- tests/dropdown/utils.js | 8 -------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/dropdown/button/buttondropdownmodel.jsdoc b/src/dropdown/button/buttondropdownmodel.jsdoc index 405f9283..c7788edd 100644 --- a/src/dropdown/button/buttondropdownmodel.jsdoc +++ b/src/dropdown/button/buttondropdownmodel.jsdoc @@ -43,13 +43,3 @@ * @observable * @member {String} #icon */ - -/** - * (Optional) A CSS class set to - * {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView#toolbarView}. - * - * Also see {@link module:ui/toolbar/toolbarview~ToolbarView#className `ToolbarView#className`}. - * - * @observable - * @member {String} #toolbarClassName - */ diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 9ab71509..b275c7f8 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -236,7 +236,7 @@ export function addToolbarToDropdown( dropdownView, model ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); // TODO verify className binding - toolbarView.bind( 'isVertical', 'className' ).to( model, 'isVertical', 'toolbarClassName' ); + toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); // TODO: verify class names dropdownView.extendTemplate( { diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index ea8e243f..eea18fa9 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -462,14 +462,6 @@ describe( 'utils', () => { model.isVertical = true; expect( dropdownView.toolbarView.isVertical ).to.be.true; } ); - - // TODO: remove? - it( 'reacts on model#toolbarClassName', () => { - expect( dropdownView.toolbarView.className ).to.be.undefined; - - model.set( 'toolbarClassName', 'foo' ); - expect( dropdownView.toolbarView.className ).to.equal( 'foo' ); - } ); } ); describe( 'buttons', () => { From 16f14edaf2f136e7c097f5496b3d7c968d1a1803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 13:34:03 +0100 Subject: [PATCH 23/78] Changed: Rename button dropdown to toolbar dropdown. --- src/dropdown/utils.js | 14 +++++--------- tests/dropdown/manual/dropdown.html | 2 +- tests/dropdown/manual/dropdown.js | 14 +++++++------- tests/dropdown/manual/dropdown.md | 4 ++-- tests/dropdown/utils.js | 4 ++++ .../{buttondropdown.css => toolbardropdown.css} | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) rename theme/components/dropdown/{buttondropdown.css => toolbardropdown.css} (94%) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b275c7f8..16ef4905 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -16,6 +16,9 @@ import ToolbarView from '../toolbar/toolbarview'; import ListView from '../list/listview'; import ListItemView from '../list/listitemview'; +// TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. +import '../../theme/components/dropdown/toolbardropdown.css'; + /** * Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. * @@ -196,9 +199,6 @@ export function addListViewToDropdown( dropdownView, model, locale ) { return listView; } -// TODO: where I go??? Make something smart since -import '../../theme/components/dropdown/buttondropdown.css'; - /** * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. @@ -235,19 +235,17 @@ import '../../theme/components/dropdown/buttondropdown.css'; export function addToolbarToDropdown( dropdownView, model ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); - // TODO verify className binding toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); - // TODO: verify class names dropdownView.extendTemplate( { attributes: { - class: [ 'ck-buttondropdown' ] + class: [ 'ck-toolbar-dropdown' ] } } ); dropdownView.panelView.children.add( toolbarView ); - // TODO: make it as 'items', 'views' ??? + // TODO: make it as 'items', 'views' or pass them as parameter??? model.buttons.map( view => toolbarView.items.add( view ) ); return toolbarView; @@ -298,6 +296,4 @@ export function getBindingTargets( buttons, attribute ) { * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. - * - * TODO: only used in tests. */ diff --git a/tests/dropdown/manual/dropdown.html b/tests/dropdown/manual/dropdown.html index 7b14edfa..8c234f59 100644 --- a/tests/dropdown/manual/dropdown.html +++ b/tests/dropdown/manual/dropdown.html @@ -16,4 +16,4 @@

Long label (truncated)

ButtonDropdown

-
+ diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index f7c420ff..e3368ae1 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -29,7 +29,7 @@ const ui = testUtils.createTestUIView( { listDropdown: '#list-dropdown', dropdownShared: '#dropdown-shared', dropdownLabel: '#dropdown-label', - buttonDropdown: '#button-dropown' + toolbarDropdown: '#dropdown-toolbar' } ); function testEmpty() { @@ -138,20 +138,20 @@ function testButton() { return buttonView; } ); - const buttonDropdownModel = new Model( { + const toolbarDropdownModel = new Model( { isVertical: true, buttons: buttonViews } ); - const buttonDropdown = createSingleButtonDropdown( buttonDropdownModel, {} ); + const toolbarDropdown = createSingleButtonDropdown( toolbarDropdownModel, {} ); - addToolbarToDropdown( buttonDropdown, buttonDropdownModel ); - addDefaultBehavior( buttonDropdown ); + addToolbarToDropdown( toolbarDropdown, toolbarDropdownModel ); + addDefaultBehavior( toolbarDropdown ); - ui.buttonDropdown.add( buttonDropdown ); + ui.toolbarDropdown.add( toolbarDropdown ); window.buttons = buttons; - window.buttonDropdownModel = buttonDropdownModel; + window.toolbarDropdownModel = toolbarDropdownModel; } testEmpty(); diff --git a/tests/dropdown/manual/dropdown.md b/tests/dropdown/manual/dropdown.md index a17c8cc4..73818722 100644 --- a/tests/dropdown/manual/dropdown.md +++ b/tests/dropdown/manual/dropdown.md @@ -29,7 +29,7 @@ listDropdownCollection.add( ); ``` -## Button Dropdown +## Toolbar Dropdown 1. Play with `buttons[ n ].isOn` to control buttonDropdown active icon. 2. Play with `buttons[ n ].isEnabled` to control buttonDropdown disabled state (all buttons must be set to `false`). -3. Play with `buttonDropdownModel.isVertical` to control buttonDropdown vertical/horizontal alignment. +3. Play with `toolbarDropdownModel.isVertical` to control buttonDropdown vertical/horizontal alignment. diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index eea18fa9..c0b9906e 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -435,6 +435,10 @@ describe( 'utils', () => { expect( dropdownView.locale ).to.equal( locale ); } ); + it( 'sets view class', () => { + expect( dropdownView.element.classList.contains( 'ck-toolbar-dropdown' ) ).to.be.true; + } ); + describe( 'view#toolbarView', () => { it( 'is created', () => { const panelChildren = dropdownView.panelView.children; diff --git a/theme/components/dropdown/buttondropdown.css b/theme/components/dropdown/toolbardropdown.css similarity index 94% rename from theme/components/dropdown/buttondropdown.css rename to theme/components/dropdown/toolbardropdown.css index ad32306e..692210e8 100644 --- a/theme/components/dropdown/buttondropdown.css +++ b/theme/components/dropdown/toolbardropdown.css @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -.ck-buttondropdown { +.ck-toolbar-dropdown { & .ck-toolbar { flex-wrap: nowrap; From 7bf2edb8da4228cb5b2dae44b60449938dda8876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 13:48:59 +0100 Subject: [PATCH 24/78] Tests: Add tests for createSplitButtonForDropdown() and createButtonForDropdown(). --- src/dropdown/utils.js | 2 +- tests/dropdown/utils.js | 64 +++++++++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 16ef4905..b8b733ae 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -88,7 +88,7 @@ export function createButtonForDropdown( model, locale ) { export function createSplitButtonForDropdown( model, locale ) { const splitButtonView = new SplitButtonView( locale ); - // TODO: something wierd with binding + // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index c0b9906e..77903534 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -15,6 +15,11 @@ import Model from '../../src/model'; import View from '../../src/view'; import ButtonView from '../../src/button/buttonview'; import ToolbarView from '../../src/toolbar/toolbarview'; +import ListItemView from '../../src/list/listitemview'; +import ListView from '../../src/list/listview'; +import DropdownView from '../../src/dropdown/dropdownview'; +import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; +import SplitButtonView from '../../src/button/splitbuttonview'; import { addListViewToDropdown, @@ -28,11 +33,6 @@ import { enableModelIfOneIsEnabled, focusDropdownContentsOnArrows } from '../../src/dropdown/utils'; -import ListItemView from '../../src/list/listitemview'; - -import ListView from '../../src/list/listview'; -import DropdownView from '../../src/dropdown/dropdownview'; -import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; const assertBinding = utilsTestUtils.assertBinding; @@ -46,6 +46,10 @@ describe( 'utils', () => { dropdownView = createDropdownView( model, buttonView, locale ); } ); + afterEach( () => { + dropdownView.element.remove(); + } ); + describe( 'focusDropdownContentsOnArrows()', () => { let panelChildView; @@ -174,19 +178,51 @@ describe( 'utils', () => { } ); describe( 'createButtonForDropdown()', () => { - it( 'accepts locale', () => { - const buttonView = createButtonForDropdown( model, locale ); + beforeEach( () => { + buttonView = createButtonForDropdown( new Model(), locale ); + } ); + it( 'accepts locale', () => { expect( buttonView.locale ).to.equal( locale ); } ); + + it( 'returns ButtonView instance', () => { + expect( buttonView ).to.be.instanceof( ButtonView ); + } ); + + it( 'delegates "execute" to "select" event', () => { + const spy = sinon.spy(); + + buttonView.on( 'select', spy ); + + buttonView.fire( 'exec' ); + + sinon.assert.calledOnce( spy ); + } ); } ); describe( 'createSplitButtonForDropdown()', () => { - it( 'accepts locale', () => { - const buttonView = createSplitButtonForDropdown( model, locale ); + beforeEach( () => { + buttonView = createSplitButtonForDropdown( new Model(), locale ); + } ); + it( 'accepts locale', () => { expect( buttonView.locale ).to.equal( locale ); } ); + + it( 'returns SplitButtonView instance', () => { + expect( buttonView ).to.be.instanceof( SplitButtonView ); + } ); + + it( 'binds actionView "execute" to "select" event', () => { + const spy = sinon.spy(); + + buttonView.on( 'select', spy ); + + buttonView.fire( 'exec' ); + + sinon.assert.calledOnce( spy ); + } ); } ); describe( 'createDropdownView()', () => { @@ -292,7 +328,7 @@ describe( 'utils', () => { } ); } ); - describe( 'createSplitButtonDropdown()', () => {} ); + describe( 'createSplitButtonDropdown()', () => { } ); describe( 'createSingleButtonDropdown()', () => {} ); @@ -341,10 +377,6 @@ describe( 'utils', () => { document.body.appendChild( dropdownView.element ); } ); - afterEach( () => { - dropdownView.element.remove(); - } ); - it( 'sets view#locale', () => { expect( dropdownView.locale ).to.equal( locale ); } ); @@ -427,10 +459,6 @@ describe( 'utils', () => { document.body.appendChild( dropdownView.element ); } ); - afterEach( () => { - dropdownView.element.remove(); - } ); - it( 'sets view#locale', () => { expect( dropdownView.locale ).to.equal( locale ); } ); From 53a9fdc8e41a3a6a02453919a377ae370dbcb025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 14:07:25 +0100 Subject: [PATCH 25/78] Changed: Removed `addDefaultBehavior()` from dropdown utils. --- src/dropdown/utils.js | 6 ------ tests/dropdown/manual/dropdown.js | 14 ++++++++++---- tests/dropdown/utils.js | 2 -- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b8b733ae..62cbf994 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -251,12 +251,6 @@ export function addToolbarToDropdown( dropdownView, model ) { return toolbarView; } -export function addDefaultBehavior( dropdownView ) { - closeDropdownOnBlur( dropdownView ); - closeDropdownOnExecute( dropdownView ); - focusDropdownContentsOnArrows( dropdownView ); -} - // Returns an array of binding components for // {@link module:utils/observablemixin~Observable#bind} from a set of iterable // buttons. diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index e3368ae1..07d2661c 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -16,12 +16,14 @@ import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center. import ButtonView from '../../../src/button/buttonview'; import { - addDefaultBehavior, addListViewToDropdown, addToolbarToDropdown, + closeDropdownOnBlur, + closeDropdownOnExecute, createButtonForDropdown, createDropdownView, - createSingleButtonDropdown + createSingleButtonDropdown, + focusDropdownContentsOnArrows, } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { @@ -69,7 +71,9 @@ function testList() { const dropdownView = createSingleButtonDropdown( model, {} ); addListViewToDropdown( dropdownView, model, {} ); - addDefaultBehavior( dropdownView ); + closeDropdownOnBlur( dropdownView ); + closeDropdownOnExecute( dropdownView ); + focusDropdownContentsOnArrows( dropdownView ); dropdownView.on( 'execute', evt => { /* global console */ @@ -146,7 +150,9 @@ function testButton() { const toolbarDropdown = createSingleButtonDropdown( toolbarDropdownModel, {} ); addToolbarToDropdown( toolbarDropdown, toolbarDropdownModel ); - addDefaultBehavior( toolbarDropdown ); + closeDropdownOnBlur( toolbarDropdown ); + closeDropdownOnExecute( toolbarDropdown ); + focusDropdownContentsOnArrows( toolbarDropdown ); ui.toolbarDropdown.add( toolbarDropdown ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 77903534..68ccb067 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -501,7 +501,5 @@ describe( 'utils', () => { } ); } ); - describe( 'addDefaultBehavior()', () => {} ); - describe( 'getBindingTargets()', () => {} ); } ); From a8b8f2f16b602497dc5cf0f123161006e31a6f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 14:18:17 +0100 Subject: [PATCH 26/78] Changed: Removed `createSingleButtonDropdown()` from dropdown utils. --- src/dropdown/utils.js | 6 ------ tests/dropdown/manual/dropdown.js | 7 ++++--- tests/dropdown/utils.js | 9 ++++----- theme/components/button/splitbutton.css | 1 + 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 62cbf994..b107653f 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -125,12 +125,6 @@ export function createSplitButtonDropdown( model, locale ) { return dropdownView; } -export function createSingleButtonDropdown( model, locale ) { - const buttonView = createButtonForDropdown( model, locale ); - - return createDropdownView( model, buttonView, locale ); -} - export function enableModelIfOneIsEnabled( model, observables ) { model.bind( 'isEnabled' ).to( // Bind to #isEnabled of each observable... diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 07d2661c..f377b718 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -22,7 +22,6 @@ import { closeDropdownOnExecute, createButtonForDropdown, createDropdownView, - createSingleButtonDropdown, focusDropdownContentsOnArrows, } from '../../../src/dropdown/utils'; @@ -68,7 +67,8 @@ function testList() { items: collection } ); - const dropdownView = createSingleButtonDropdown( model, {} ); + const buttonView = createButtonForDropdown( model, {} ); + const dropdownView = createDropdownView( model, buttonView, {} ); addListViewToDropdown( dropdownView, model, {} ); closeDropdownOnBlur( dropdownView ); @@ -147,7 +147,8 @@ function testButton() { buttons: buttonViews } ); - const toolbarDropdown = createSingleButtonDropdown( toolbarDropdownModel, {} ); + const buttonView = createButtonForDropdown( toolbarDropdownModel, locale ); + const toolbarDropdown = createDropdownView( toolbarDropdownModel, buttonView, locale ); addToolbarToDropdown( toolbarDropdown, toolbarDropdownModel ); closeDropdownOnBlur( toolbarDropdown ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 68ccb067..1f5552c7 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -28,7 +28,6 @@ import { closeDropdownOnExecute, createButtonForDropdown, createDropdownView, - createSingleButtonDropdown, createSplitButtonForDropdown, enableModelIfOneIsEnabled, focusDropdownContentsOnArrows @@ -330,8 +329,6 @@ describe( 'utils', () => { describe( 'createSplitButtonDropdown()', () => { } ); - describe( 'createSingleButtonDropdown()', () => {} ); - describe( 'enableModelIfOneIsEnabled()', () => { it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { const observables = [ @@ -369,7 +366,8 @@ describe( 'utils', () => { label: 'foo' } ); - dropdownView = createSingleButtonDropdown( model, locale ); + const buttonView = createButtonForDropdown( model, locale ); + const dropdownView = createDropdownView( model, buttonView, locale ); addListViewToDropdown( dropdownView, model, locale ); @@ -451,7 +449,8 @@ describe( 'utils', () => { buttons } ); - dropdownView = createSingleButtonDropdown( model, locale ); + const buttonView = createButtonForDropdown( model, locale ); + const dropdownView = createDropdownView( model, buttonView, locale ); addToolbarToDropdown( dropdownView, model ); diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index 52050606..9fcb18d9 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -3,6 +3,7 @@ * For licensing, see LICENSE.md. */ +// TODO: change me .ck-splitbutton-dropdown { &::after { display: none; From 0d33dc267db5d7005d32527ab2457a976c15d6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 14:20:55 +0100 Subject: [PATCH 27/78] Changed: Removed `createSplitButtonDropdown()` from dropdown utils. --- src/dropdown/utils.js | 15 --------------- tests/dropdown/utils.js | 2 -- 2 files changed, 17 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b107653f..0fdad859 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -110,21 +110,6 @@ export function createDropdownView( model, buttonView, locale ) { return dropdownView; } -export function createSplitButtonDropdown( model, locale ) { - const splitButtonView = createSplitButtonForDropdown( model, locale ); - const dropdownView = createDropdownView( model, splitButtonView, locale ); - - // Extend template to hide arrow from dropdown. - // TODO: enable this on normal button instead of hiding it - dropdownView.extendTemplate( { - attributes: { - class: 'ck-splitbutton-dropdown' - } - } ); - - return dropdownView; -} - export function enableModelIfOneIsEnabled( model, observables ) { model.bind( 'isEnabled' ).to( // Bind to #isEnabled of each observable... diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 1f5552c7..86e630e1 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -327,8 +327,6 @@ describe( 'utils', () => { } ); } ); - describe( 'createSplitButtonDropdown()', () => { } ); - describe( 'enableModelIfOneIsEnabled()', () => { it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { const observables = [ From 481630cacead11e17483ef49413eea393749c123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 23 Jan 2018 14:48:43 +0100 Subject: [PATCH 28/78] Tests: Fix test after recent changes. --- tests/dropdown/utils.js | 28 +++++++------------------ theme/components/button/splitbutton.css | 2 +- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 86e630e1..1c57a828 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -46,7 +46,9 @@ describe( 'utils', () => { } ); afterEach( () => { - dropdownView.element.remove(); + if ( dropdownView.element ) { + dropdownView.element.remove(); + } } ); describe( 'focusDropdownContentsOnArrows()', () => { @@ -194,7 +196,7 @@ describe( 'utils', () => { buttonView.on( 'select', spy ); - buttonView.fire( 'exec' ); + buttonView.fire( 'execute' ); sinon.assert.calledOnce( spy ); } ); @@ -212,16 +214,6 @@ describe( 'utils', () => { it( 'returns SplitButtonView instance', () => { expect( buttonView ).to.be.instanceof( SplitButtonView ); } ); - - it( 'binds actionView "execute" to "select" event', () => { - const spy = sinon.spy(); - - buttonView.on( 'select', spy ); - - buttonView.fire( 'exec' ); - - sinon.assert.calledOnce( spy ); - } ); } ); describe( 'createDropdownView()', () => { @@ -364,8 +356,8 @@ describe( 'utils', () => { label: 'foo' } ); - const buttonView = createButtonForDropdown( model, locale ); - const dropdownView = createDropdownView( model, buttonView, locale ); + buttonView = createButtonForDropdown( model, locale ); + dropdownView = createDropdownView( model, buttonView, locale ); addListViewToDropdown( dropdownView, model, locale ); @@ -373,10 +365,6 @@ describe( 'utils', () => { document.body.appendChild( dropdownView.element ); } ); - it( 'sets view#locale', () => { - expect( dropdownView.locale ).to.equal( locale ); - } ); - describe( 'view#listView', () => { it( 'is created', () => { const panelChildren = dropdownView.panelView.children; @@ -447,8 +435,8 @@ describe( 'utils', () => { buttons } ); - const buttonView = createButtonForDropdown( model, locale ); - const dropdownView = createDropdownView( model, buttonView, locale ); + buttonView = createButtonForDropdown( model, locale ); + dropdownView = createDropdownView( model, buttonView, locale ); addToolbarToDropdown( dropdownView, model ); diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index 9fcb18d9..c5b41054 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -// TODO: change me +/* TODO: change me */ .ck-splitbutton-dropdown { &::after { display: none; From 67251f939efcfbb61a4af8c167d8db207473e22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 11:33:52 +0100 Subject: [PATCH 29/78] Other: Extract `focusDropdownContentsOnArrows()` to own file. --- .../helpers/focusdropdowncontentsonarrows.js | 33 ++++++++ src/dropdown/utils.js | 27 +------ .../helpers/focusdropdowncontentsonarrows.js | 76 +++++++++++++++++++ tests/dropdown/manual/dropdown.js | 4 +- tests/dropdown/utils.js | 60 +-------------- 5 files changed, 113 insertions(+), 87 deletions(-) create mode 100644 src/dropdown/helpers/focusdropdowncontentsonarrows.js create mode 100644 tests/dropdown/helpers/focusdropdowncontentsonarrows.js diff --git a/src/dropdown/helpers/focusdropdowncontentsonarrows.js b/src/dropdown/helpers/focusdropdowncontentsonarrows.js new file mode 100644 index 00000000..5ee8c81d --- /dev/null +++ b/src/dropdown/helpers/focusdropdowncontentsonarrows.js @@ -0,0 +1,33 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers/focusdropdowncontentsonarrows + */ + +/** + * Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. + * + * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView + */ +export default function focusDropdownContentsOnArrows( dropdownView ) { + // If the dropdown panel is already open, the arrow down key should + // focus the first element in list. + dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { + if ( dropdownView.isOpen ) { + dropdownView.panelView.focus(); + cancel(); + } + } ); + + // If the dropdown panel is already open, the arrow up key should + // focus the last element in the list. + dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { + if ( dropdownView.isOpen ) { + dropdownView.panelView.focusLast(); + cancel(); + } + } ); +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 0fdad859..65b6080f 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -4,7 +4,7 @@ */ /** - * @module ui/dropdown/utils + * @module ui/dropdown/helpers */ import clickOutsideHandler from '../bindings/clickoutsidehandler'; @@ -19,31 +19,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -/** - * Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. - * - * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - */ -export function focusDropdownContentsOnArrows( dropdownView ) { - // If the dropdown panel is already open, the arrow down key should - // focus the first element in list. - dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { - if ( dropdownView.isOpen ) { - dropdownView.panelView.focus(); - cancel(); - } - } ); - - // If the dropdown panel is already open, the arrow up key should - // focus the last element in the list. - dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { - if ( dropdownView.isOpen ) { - dropdownView.panelView.focusLast(); - cancel(); - } - } ); -} - /** * Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. * diff --git a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js new file mode 100644 index 00000000..ebe2d028 --- /dev/null +++ b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js @@ -0,0 +1,76 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document */ + +import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; + +import View from '../../../src/view'; +import Model from '../../../src/model'; +import ButtonView from '../../../src/button/buttonview'; + +import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; +import { createDropdownView } from '../../../src/dropdown/utils'; + +describe( 'focusDropdownContentsOnArrows()', () => { + let dropdownView; + let panelChildView; + + beforeEach( () => { + dropdownView = createDropdownView( new Model(), new ButtonView(), {} ); + + panelChildView = new View(); + panelChildView.setTemplate( { tag: 'div' } ); + panelChildView.focus = () => {}; + panelChildView.focusLast = () => {}; + + // TODO: describe this as #contentView instead of #listView and #toolbarView + dropdownView.panelView.children.add( panelChildView ); + + focusDropdownContentsOnArrows( dropdownView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowdown, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focus' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + + sinon.assert.calledOnce( spy ); + } ); + + it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowup, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focusLast' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.calledOnce( spy ); + } ); +} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index f377b718..c13a075a 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -21,9 +21,9 @@ import { closeDropdownOnBlur, closeDropdownOnExecute, createButtonForDropdown, - createDropdownView, - focusDropdownContentsOnArrows, + createDropdownView } from '../../../src/dropdown/utils'; +import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 1c57a828..2f839259 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -5,14 +5,11 @@ /* globals document, Event */ -import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; - import Collection from '@ckeditor/ckeditor5-utils/src/collection'; import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; import Model from '../../src/model'; -import View from '../../src/view'; import ButtonView from '../../src/button/buttonview'; import ToolbarView from '../../src/toolbar/toolbarview'; import ListItemView from '../../src/list/listitemview'; @@ -29,8 +26,7 @@ import { createButtonForDropdown, createDropdownView, createSplitButtonForDropdown, - enableModelIfOneIsEnabled, - focusDropdownContentsOnArrows + enableModelIfOneIsEnabled } from '../../src/dropdown/utils'; const assertBinding = utilsTestUtils.assertBinding; @@ -51,60 +47,6 @@ describe( 'utils', () => { } } ); - describe( 'focusDropdownContentsOnArrows()', () => { - let panelChildView; - - beforeEach( () => { - panelChildView = new View(); - panelChildView.setTemplate( { tag: 'div' } ); - panelChildView.focus = () => {}; - panelChildView.focusLast = () => {}; - - // TODO: describe this as #contentView instaed of #listView and #toolbarView - dropdownView.panelView.children.add( panelChildView ); - - focusDropdownContentsOnArrows( dropdownView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowdown, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( panelChildView, 'focus' ); - - dropdownView.isOpen = false; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - dropdownView.isOpen = true; - dropdownView.keystrokes.press( keyEvtData ); - - sinon.assert.calledOnce( spy ); - } ); - - it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowup, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( panelChildView, 'focusLast' ); - - dropdownView.isOpen = false; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - dropdownView.isOpen = true; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); - } ); - describe( 'closeDropdownOnExecute()', () => { beforeEach( () => { closeDropdownOnExecute( dropdownView ); From ba41ff80410cddaf2cc3a5d283f782f6f5e414e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 12:27:15 +0100 Subject: [PATCH 30/78] Other: Extract `closeDropdownOnExecute()` to own file. --- .../helpers/closedropdownonexecute.js | 20 ++++++++++ src/dropdown/utils.js | 12 ------ .../helpers/closedropdownonexecute.js | 38 +++++++++++++++++++ tests/dropdown/manual/dropdown.js | 3 +- tests/dropdown/utils.js | 20 ---------- 5 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 src/dropdown/helpers/closedropdownonexecute.js create mode 100644 tests/dropdown/helpers/closedropdownonexecute.js diff --git a/src/dropdown/helpers/closedropdownonexecute.js b/src/dropdown/helpers/closedropdownonexecute.js new file mode 100644 index 00000000..d49ef49a --- /dev/null +++ b/src/dropdown/helpers/closedropdownonexecute.js @@ -0,0 +1,20 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers/closedropdownonexecute + */ + +/** + * Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. + * + * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView + */ +export default function closeDropdownOnExecute( dropdownView ) { + // Close the dropdown when one of the list items has been executed. + dropdownView.on( 'execute', () => { + dropdownView.isOpen = false; + } ); +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 65b6080f..ce89af82 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -19,18 +19,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -/** - * Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. - * - * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - */ -export function closeDropdownOnExecute( dropdownView ) { - // Close the dropdown when one of the list items has been executed. - dropdownView.on( 'execute', () => { - dropdownView.isOpen = false; - } ); -} - /** * Adds a behavior to a dropdownView that closes opened dropdown on user click outside the dropdown. * diff --git a/tests/dropdown/helpers/closedropdownonexecute.js b/tests/dropdown/helpers/closedropdownonexecute.js new file mode 100644 index 00000000..3b28cb19 --- /dev/null +++ b/tests/dropdown/helpers/closedropdownonexecute.js @@ -0,0 +1,38 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document */ + +import Model from '../../../src/model'; +import ButtonView from '../../../src/button/buttonview'; +import { createDropdownView } from '../../../src/dropdown/utils'; +import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; + +describe( 'closeDropdownOnExecute()', () => { + let dropdownView; + + beforeEach( () => { + dropdownView = createDropdownView( new Model(), new ButtonView(), {} ); + + closeDropdownOnExecute( dropdownView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( 'changes view#isOpen on view#execute', () => { + dropdownView.isOpen = true; + + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + } ); +} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index c13a075a..d6882ff7 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -19,10 +19,11 @@ import { addListViewToDropdown, addToolbarToDropdown, closeDropdownOnBlur, - closeDropdownOnExecute, createButtonForDropdown, createDropdownView } from '../../../src/dropdown/utils'; + +import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; const ui = testUtils.createTestUIView( { diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 2f839259..464bc921 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -22,7 +22,6 @@ import { addListViewToDropdown, addToolbarToDropdown, closeDropdownOnBlur, - closeDropdownOnExecute, createButtonForDropdown, createDropdownView, createSplitButtonForDropdown, @@ -47,25 +46,6 @@ describe( 'utils', () => { } } ); - describe( 'closeDropdownOnExecute()', () => { - beforeEach( () => { - closeDropdownOnExecute( dropdownView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - it( 'changes view#isOpen on view#execute', () => { - dropdownView.isOpen = true; - - dropdownView.fire( 'execute' ); - expect( dropdownView.isOpen ).to.be.false; - - dropdownView.fire( 'execute' ); - expect( dropdownView.isOpen ).to.be.false; - } ); - } ); - describe( 'closeDropdownOnBlur()', () => { beforeEach( () => { closeDropdownOnBlur( dropdownView ); From 7b109be2fe53e16dda13688d956d459a3a785f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 12:38:17 +0100 Subject: [PATCH 31/78] Other: Extract `closeDropdownOnBlur()` to own file. --- src/dropdown/helpers/closedropdownonblur.js | 28 +++++++ src/dropdown/utils.js | 21 ------ tests/dropdown/helpers/closedropdownonblur.js | 75 +++++++++++++++++++ tests/dropdown/manual/dropdown.js | 2 +- tests/dropdown/utils.js | 57 +------------- 5 files changed, 105 insertions(+), 78 deletions(-) create mode 100644 src/dropdown/helpers/closedropdownonblur.js create mode 100644 tests/dropdown/helpers/closedropdownonblur.js diff --git a/src/dropdown/helpers/closedropdownonblur.js b/src/dropdown/helpers/closedropdownonblur.js new file mode 100644 index 00000000..49bc9767 --- /dev/null +++ b/src/dropdown/helpers/closedropdownonblur.js @@ -0,0 +1,28 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers + */ + +import clickOutsideHandler from '../../bindings/clickoutsidehandler'; + +/** + * Adds a behavior to a dropdownView that closes opened dropdown on user click outside the dropdown. + * + * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView + */ +export default function closeDropdownOnBlur( dropdownView ) { + dropdownView.on( 'render', () => { + clickOutsideHandler( { + emitter: dropdownView, + activator: () => dropdownView.isOpen, + callback: () => { + dropdownView.isOpen = false; + }, + contextElements: [ dropdownView.element ] + } ); + } ); +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index ce89af82..5ee92f85 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -7,7 +7,6 @@ * @module ui/dropdown/helpers */ -import clickOutsideHandler from '../bindings/clickoutsidehandler'; import SplitButtonView from '../button/splitbuttonview'; import ButtonView from '../button/buttonview'; import DropdownPanelView from './dropdownpanelview'; @@ -19,26 +18,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -/** - * Adds a behavior to a dropdownView that closes opened dropdown on user click outside the dropdown. - * - * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - */ -export function closeDropdownOnBlur( dropdownView ) { - dropdownView.on( 'render', () => { - clickOutsideHandler( { - emitter: dropdownView, - activator: () => dropdownView.isOpen, - callback: () => { - dropdownView.isOpen = false; - }, - contextElements: [ dropdownView.element ] - } ); - } ); -} - -/** TODO: new methods below - refactor to own files later */ - export function createButtonForDropdown( model, locale ) { const buttonView = new ButtonView( locale ); diff --git a/tests/dropdown/helpers/closedropdownonblur.js b/tests/dropdown/helpers/closedropdownonblur.js new file mode 100644 index 00000000..b93b4db9 --- /dev/null +++ b/tests/dropdown/helpers/closedropdownonblur.js @@ -0,0 +1,75 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document, Event */ + +import Model from '../../../src/model'; +import ButtonView from '../../../src/button/buttonview'; +import { createDropdownView } from '../../../src/dropdown/utils'; +import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; + +describe( 'closeDropdownOnBlur()', () => { + let dropdownView; + + beforeEach( () => { + dropdownView = createDropdownView( new Model(), new ButtonView(), {} ); + + closeDropdownOnBlur( dropdownView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + if ( dropdownView.element ) { + dropdownView.element.remove(); + } + } ); + + it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Closed the dropdown. + expect( dropdownView.isOpen ).to.be.false; + + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still closed. + expect( dropdownView.isOpen ).to.be.false; + } ); + + it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + + // Event from view.element should be discarded. + dropdownView.element.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + + // Event from within view.element should be discarded. + const child = document.createElement( 'div' ); + dropdownView.element.appendChild( child ); + + child.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + } ); +} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index d6882ff7..cc7659c6 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -18,11 +18,11 @@ import ButtonView from '../../../src/button/buttonview'; import { addListViewToDropdown, addToolbarToDropdown, - closeDropdownOnBlur, createButtonForDropdown, createDropdownView } from '../../../src/dropdown/utils'; +import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 464bc921..9c8e1986 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -/* globals document, Event */ +/* globals document */ import Collection from '@ckeditor/ckeditor5-utils/src/collection'; import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; @@ -21,7 +21,6 @@ import SplitButtonView from '../../src/button/splitbuttonview'; import { addListViewToDropdown, addToolbarToDropdown, - closeDropdownOnBlur, createButtonForDropdown, createDropdownView, createSplitButtonForDropdown, @@ -46,60 +45,6 @@ describe( 'utils', () => { } } ); - describe( 'closeDropdownOnBlur()', () => { - beforeEach( () => { - closeDropdownOnBlur( dropdownView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { - // Open the dropdown. - dropdownView.isOpen = true; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Closed the dropdown. - expect( dropdownView.isOpen ).to.be.false; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still closed. - expect( dropdownView.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { - // Open the dropdown. - dropdownView.isOpen = true; - - // Event from view.element should be discarded. - dropdownView.element.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( dropdownView.isOpen ).to.be.true; - - // Event from within view.element should be discarded. - const child = document.createElement( 'div' ); - dropdownView.element.appendChild( child ); - - child.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( dropdownView.isOpen ).to.be.true; - } ); - } ); - describe( 'createButtonForDropdown()', () => { beforeEach( () => { buttonView = createButtonForDropdown( new Model(), locale ); From aee02ee58a3b19ce56cd02d48710465d1cdfce39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 12:45:33 +0100 Subject: [PATCH 32/78] Other: Extract `createButtonForDropdown()` to own file. --- .../helpers/createbuttonfordropdown.js | 19 ++++++++++ src/dropdown/utils.js | 10 ----- .../helpers/createbuttonfordropdown.js | 37 +++++++++++++++++++ tests/dropdown/manual/dropdown.js | 2 +- tests/dropdown/utils.js | 26 +------------ 5 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 src/dropdown/helpers/createbuttonfordropdown.js create mode 100644 tests/dropdown/helpers/createbuttonfordropdown.js diff --git a/src/dropdown/helpers/createbuttonfordropdown.js b/src/dropdown/helpers/createbuttonfordropdown.js new file mode 100644 index 00000000..d77008e1 --- /dev/null +++ b/src/dropdown/helpers/createbuttonfordropdown.js @@ -0,0 +1,19 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import ButtonView from '../../button/buttonview'; + +/** + * @module ui/dropdown/helpers/createbuttonfordropdown + */ + +export default function createButtonForDropdown( model, locale ) { + const buttonView = new ButtonView( locale ); + + // Dropdown expects "select" event to show contents. + buttonView.delegate( 'execute' ).to( buttonView, 'select' ); + + return buttonView; +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 5ee92f85..3921e551 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -8,7 +8,6 @@ */ import SplitButtonView from '../button/splitbuttonview'; -import ButtonView from '../button/buttonview'; import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; import ToolbarView from '../toolbar/toolbarview'; @@ -18,15 +17,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -export function createButtonForDropdown( model, locale ) { - const buttonView = new ButtonView( locale ); - - // Dropdown expects "select" event to show contents. - buttonView.delegate( 'execute' ).to( buttonView, 'select' ); - - return buttonView; -} - export function createSplitButtonForDropdown( model, locale ) { const splitButtonView = new SplitButtonView( locale ); diff --git a/tests/dropdown/helpers/createbuttonfordropdown.js b/tests/dropdown/helpers/createbuttonfordropdown.js new file mode 100644 index 00000000..ee0ffbc6 --- /dev/null +++ b/tests/dropdown/helpers/createbuttonfordropdown.js @@ -0,0 +1,37 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import Model from '../../../src/model'; + +import ButtonView from '../../../src/button/buttonview'; + +import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; + +describe( 'createButtonForDropdown()', () => { + let buttonView, locale; + + beforeEach( () => { + locale = { t() {} }; + buttonView = createButtonForDropdown( new Model(), locale ); + } ); + + it( 'accepts locale', () => { + expect( buttonView.locale ).to.equal( locale ); + } ); + + it( 'returns ButtonView instance', () => { + expect( buttonView ).to.be.instanceof( ButtonView ); + } ); + + it( 'delegates "execute" to "select" event', () => { + const spy = sinon.spy(); + + buttonView.on( 'select', spy ); + + buttonView.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + } ); +} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index cc7659c6..19537d58 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -18,10 +18,10 @@ import ButtonView from '../../../src/button/buttonview'; import { addListViewToDropdown, addToolbarToDropdown, - createButtonForDropdown, createDropdownView } from '../../../src/dropdown/utils'; +import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 9c8e1986..8e4b2c66 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -17,11 +17,11 @@ import ListView from '../../src/list/listview'; import DropdownView from '../../src/dropdown/dropdownview'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import SplitButtonView from '../../src/button/splitbuttonview'; +import createButtonForDropdown from '../../src/dropdown/helpers/createbuttonfordropdown'; import { addListViewToDropdown, addToolbarToDropdown, - createButtonForDropdown, createDropdownView, createSplitButtonForDropdown, enableModelIfOneIsEnabled @@ -45,30 +45,6 @@ describe( 'utils', () => { } } ); - describe( 'createButtonForDropdown()', () => { - beforeEach( () => { - buttonView = createButtonForDropdown( new Model(), locale ); - } ); - - it( 'accepts locale', () => { - expect( buttonView.locale ).to.equal( locale ); - } ); - - it( 'returns ButtonView instance', () => { - expect( buttonView ).to.be.instanceof( ButtonView ); - } ); - - it( 'delegates "execute" to "select" event', () => { - const spy = sinon.spy(); - - buttonView.on( 'select', spy ); - - buttonView.fire( 'execute' ); - - sinon.assert.calledOnce( spy ); - } ); - } ); - describe( 'createSplitButtonForDropdown()', () => { beforeEach( () => { buttonView = createSplitButtonForDropdown( new Model(), locale ); From 420de053f644a4c2d03cc0db6926476cbf05e501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 12:50:30 +0100 Subject: [PATCH 33/78] Other: Extract `createSplitButtonForDropdown()` to own file. --- .../helpers/createsplitbuttonfordropdown.js | 20 ++++++++++++++ src/dropdown/utils.js | 11 -------- .../helpers/createsplitbuttonfordropdown.js | 26 +++++++++++++++++++ tests/dropdown/utils.js | 16 ------------ 4 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 src/dropdown/helpers/createsplitbuttonfordropdown.js create mode 100644 tests/dropdown/helpers/createsplitbuttonfordropdown.js diff --git a/src/dropdown/helpers/createsplitbuttonfordropdown.js b/src/dropdown/helpers/createsplitbuttonfordropdown.js new file mode 100644 index 00000000..c5e0782d --- /dev/null +++ b/src/dropdown/helpers/createsplitbuttonfordropdown.js @@ -0,0 +1,20 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers/createsplitbuttonfordropdown + */ + +import SplitButtonView from '../../button/splitbuttonview'; + +export default function createSplitButtonForDropdown( model, locale ) { + const splitButtonView = new SplitButtonView( locale ); + + // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. + splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); + splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); + + return splitButtonView; +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 3921e551..ab2dfa29 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -7,7 +7,6 @@ * @module ui/dropdown/helpers */ -import SplitButtonView from '../button/splitbuttonview'; import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; import ToolbarView from '../toolbar/toolbarview'; @@ -17,16 +16,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -export function createSplitButtonForDropdown( model, locale ) { - const splitButtonView = new SplitButtonView( locale ); - - // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. - splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); - splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); - - return splitButtonView; -} - export function createDropdownView( model, buttonView, locale ) { const panelView = new DropdownPanelView( locale ); const dropdownView = new DropdownView( locale, buttonView, panelView ); diff --git a/tests/dropdown/helpers/createsplitbuttonfordropdown.js b/tests/dropdown/helpers/createsplitbuttonfordropdown.js new file mode 100644 index 00000000..c0c1b814 --- /dev/null +++ b/tests/dropdown/helpers/createsplitbuttonfordropdown.js @@ -0,0 +1,26 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import Model from '../../../src/model'; +import SplitButtonView from '../../../src/button/splitbuttonview'; + +import createSplitButtonForDropdown from '../../../src/dropdown/helpers/createsplitbuttonfordropdown'; + +describe( 'createSplitButtonForDropdown()', () => { + let buttonView, locale; + + beforeEach( () => { + locale = { t() {} }; + buttonView = createSplitButtonForDropdown( new Model(), locale ); + } ); + + it( 'accepts locale', () => { + expect( buttonView.locale ).to.equal( locale ); + } ); + + it( 'returns SplitButtonView instance', () => { + expect( buttonView ).to.be.instanceof( SplitButtonView ); + } ); +} ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 8e4b2c66..c9b9c9d2 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -16,14 +16,12 @@ import ListItemView from '../../src/list/listitemview'; import ListView from '../../src/list/listview'; import DropdownView from '../../src/dropdown/dropdownview'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; -import SplitButtonView from '../../src/button/splitbuttonview'; import createButtonForDropdown from '../../src/dropdown/helpers/createbuttonfordropdown'; import { addListViewToDropdown, addToolbarToDropdown, createDropdownView, - createSplitButtonForDropdown, enableModelIfOneIsEnabled } from '../../src/dropdown/utils'; @@ -45,20 +43,6 @@ describe( 'utils', () => { } } ); - describe( 'createSplitButtonForDropdown()', () => { - beforeEach( () => { - buttonView = createSplitButtonForDropdown( new Model(), locale ); - } ); - - it( 'accepts locale', () => { - expect( buttonView.locale ).to.equal( locale ); - } ); - - it( 'returns SplitButtonView instance', () => { - expect( buttonView ).to.be.instanceof( SplitButtonView ); - } ); - } ); - describe( 'createDropdownView()', () => { it( 'returns view', () => { model = new Model(); From b7879812982aaa7b08bada70ae713e9e547c62fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 13:13:15 +0100 Subject: [PATCH 34/78] Other: Extract `createDropdownView()` to own file. --- src/dropdown/helpers/createdropdownview.js | 54 ++++++++ src/dropdown/utils.js | 46 ------- tests/dropdown/helpers/closedropdownonblur.js | 2 +- .../helpers/closedropdownonexecute.js | 2 +- tests/dropdown/helpers/createdropdownview.js | 125 ++++++++++++++++++ .../helpers/focusdropdowncontentsonarrows.js | 2 +- tests/dropdown/manual/dropdown.js | 2 +- tests/dropdown/utils.js | 110 +-------------- 8 files changed, 184 insertions(+), 159 deletions(-) create mode 100644 src/dropdown/helpers/createdropdownview.js create mode 100644 tests/dropdown/helpers/createdropdownview.js diff --git a/src/dropdown/helpers/createdropdownview.js b/src/dropdown/helpers/createdropdownview.js new file mode 100644 index 00000000..5e7b2104 --- /dev/null +++ b/src/dropdown/helpers/createdropdownview.js @@ -0,0 +1,54 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers/createdropdownview + */ + +import DropdownPanelView from './../dropdownpanelview'; +import DropdownView from './../dropdownview'; + +/** + * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using + * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. + * + * const model = new Model( { + * label: 'A dropdown', + * isEnabled: true, + * isOn: false, + * withText: true + * } ); + * + * const dropdown = createDropdown( model ); + * + * dropdown.render(); + * + * // Will render a dropdown labeled "A dropdown" with an empty panel. + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. + * + * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. + */ +export default function createDropdownView( model, buttonView, locale ) { + const panelView = new DropdownPanelView( locale ); + const dropdownView = new DropdownView( locale, buttonView, panelView ); + + dropdownView.bind( 'isEnabled' ).to( model ); + + // TODO: check 'isOn' binding. + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + return isOn || isOpen; + } ); + + return dropdownView; +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index ab2dfa29..ef010632 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -7,8 +7,6 @@ * @module ui/dropdown/helpers */ -import DropdownPanelView from './dropdownpanelview'; -import DropdownView from './dropdownview'; import ToolbarView from '../toolbar/toolbarview'; import ListView from '../list/listview'; import ListItemView from '../list/listitemview'; @@ -16,21 +14,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -export function createDropdownView( model, buttonView, locale ) { - const panelView = new DropdownPanelView( locale ); - const dropdownView = new DropdownView( locale, buttonView, panelView ); - - dropdownView.bind( 'isEnabled' ).to( model ); - - // TODO: check 'isOn' binding. - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { - return isOn || isOpen; - } ); - - return dropdownView; -} - export function enableModelIfOneIsEnabled( model, observables ) { model.bind( 'isEnabled' ).to( // Bind to #isEnabled of each observable... @@ -162,32 +145,3 @@ export function addToolbarToDropdown( dropdownView, model ) { export function getBindingTargets( buttons, attribute ) { return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); } - -/** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using - * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. - * - * const model = new Model( { - * label: 'A dropdown', - * isEnabled: true, - * isOn: false, - * withText: true - * } ); - * - * const dropdown = createDropdown( model ); - * - * dropdown.render(); - * - * // Will render a dropdown labeled "A dropdown" with an empty panel. - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. - * - * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. - */ diff --git a/tests/dropdown/helpers/closedropdownonblur.js b/tests/dropdown/helpers/closedropdownonblur.js index b93b4db9..22bb66f1 100644 --- a/tests/dropdown/helpers/closedropdownonblur.js +++ b/tests/dropdown/helpers/closedropdownonblur.js @@ -7,7 +7,7 @@ import Model from '../../../src/model'; import ButtonView from '../../../src/button/buttonview'; -import { createDropdownView } from '../../../src/dropdown/utils'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; describe( 'closeDropdownOnBlur()', () => { diff --git a/tests/dropdown/helpers/closedropdownonexecute.js b/tests/dropdown/helpers/closedropdownonexecute.js index 3b28cb19..9acd9b9b 100644 --- a/tests/dropdown/helpers/closedropdownonexecute.js +++ b/tests/dropdown/helpers/closedropdownonexecute.js @@ -7,7 +7,7 @@ import Model from '../../../src/model'; import ButtonView from '../../../src/button/buttonview'; -import { createDropdownView } from '../../../src/dropdown/utils'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; describe( 'closeDropdownOnExecute()', () => { diff --git a/tests/dropdown/helpers/createdropdownview.js b/tests/dropdown/helpers/createdropdownview.js new file mode 100644 index 00000000..80d6a85d --- /dev/null +++ b/tests/dropdown/helpers/createdropdownview.js @@ -0,0 +1,125 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +import Model from '../../../src/model'; + +import ButtonView from '../../../src/button/buttonview'; +import DropdownView from '../../../src/dropdown/dropdownview'; +import DropdownPanelView from '../../../src/dropdown/dropdownpanelview'; +import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; + +const assertBinding = utilsTestUtils.assertBinding; + +describe( 'createDropdownView()', () => { + let dropdownView, buttonView, model, locale; + + beforeEach( () => { + locale = { t() {} }; + model = new Model(); + buttonView = createButtonForDropdown( model, locale ); + dropdownView = createDropdownView( model, buttonView, locale ); + } ); + + it( 'accepts locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + expect( dropdownView.panelView.locale ).to.equal( locale ); + } ); + + it( 'returns view', () => { + model = new Model(); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView ).to.be.instanceOf( DropdownView ); + } ); + + it( 'creates dropdown#panelView out of DropdownPanelView', () => { + model = new Model(); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); + } ); + + it( 'creates dropdown#buttonView out of buttonView', () => { + model = new Model(); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + expect( dropdownView.buttonView ).to.equal( buttonView ); + } ); + + it( 'binds button attributes to the model', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + buttonView = new ButtonView(); + createDropdownView( model, buttonView, locale ); + + assertBinding( buttonView, + modelDef, + [ + [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + ], + { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } + ); + } ); + + it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + dropdownView.isOpen = false; + expect( buttonView.isOn ).to.be.false; + + model.isOn = true; + expect( buttonView.isOn ).to.be.true; + + dropdownView.isOpen = true; + expect( buttonView.isOn ).to.be.true; + + model.isOn = false; + expect( buttonView.isOn ).to.be.true; + } ); + + it( 'binds dropdown#isEnabled to the model', () => { + const modelDef = { + label: 'foo', + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + buttonView = new ButtonView(); + dropdownView = createDropdownView( model, buttonView, locale ); + + assertBinding( dropdownView, + { isEnabled: true }, + [ + [ model, { isEnabled: false } ] + ], + { isEnabled: false } + ); + } ); +} ); diff --git a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js index ebe2d028..2fc47fc5 100644 --- a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js +++ b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js @@ -12,7 +12,7 @@ import Model from '../../../src/model'; import ButtonView from '../../../src/button/buttonview'; import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; -import { createDropdownView } from '../../../src/dropdown/utils'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; describe( 'focusDropdownContentsOnArrows()', () => { let dropdownView; diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 19537d58..324b6a52 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -18,9 +18,9 @@ import ButtonView from '../../../src/button/buttonview'; import { addListViewToDropdown, addToolbarToDropdown, - createDropdownView } from '../../../src/dropdown/utils'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index c9b9c9d2..9b87dc95 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -6,7 +6,6 @@ /* globals document */ import Collection from '@ckeditor/ckeditor5-utils/src/collection'; -import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; import Model from '../../src/model'; @@ -14,19 +13,15 @@ import ButtonView from '../../src/button/buttonview'; import ToolbarView from '../../src/toolbar/toolbarview'; import ListItemView from '../../src/list/listitemview'; import ListView from '../../src/list/listview'; -import DropdownView from '../../src/dropdown/dropdownview'; -import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import createButtonForDropdown from '../../src/dropdown/helpers/createbuttonfordropdown'; +import createDropdownView from '../../src/dropdown/helpers/createdropdownview'; import { addListViewToDropdown, addToolbarToDropdown, - createDropdownView, enableModelIfOneIsEnabled } from '../../src/dropdown/utils'; -const assertBinding = utilsTestUtils.assertBinding; - describe( 'utils', () => { let dropdownView, buttonView, model, locale; @@ -43,109 +38,6 @@ describe( 'utils', () => { } } ); - describe( 'createDropdownView()', () => { - it( 'returns view', () => { - model = new Model(); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView ).to.be.instanceOf( DropdownView ); - } ); - - it( 'creates dropdown#panelView out of DropdownPanelView', () => { - model = new Model(); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); - } ); - - it( 'creates dropdown#buttonView out of buttonView', () => { - model = new Model(); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView.buttonView ).to.equal( buttonView ); - } ); - - it( 'accepts locale', () => { - const buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView.locale ).to.equal( locale ); - expect( dropdownView.panelView.locale ).to.equal( locale ); - } ); - - it( 'binds button attributes to the model', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - - model = new Model( modelDef ); - buttonView = new ButtonView(); - createDropdownView( model, buttonView, locale ); - - assertBinding( buttonView, - modelDef, - [ - [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] - ], - { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } - ); - } ); - - it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - - model = new Model( modelDef ); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - dropdownView.isOpen = false; - expect( buttonView.isOn ).to.be.false; - - model.isOn = true; - expect( buttonView.isOn ).to.be.true; - - dropdownView.isOpen = true; - expect( buttonView.isOn ).to.be.true; - - model.isOn = false; - expect( buttonView.isOn ).to.be.true; - } ); - - it( 'binds dropdown#isEnabled to the model', () => { - const modelDef = { - label: 'foo', - isEnabled: true, - withText: false, - tooltip: false - }; - - model = new Model( modelDef ); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - assertBinding( dropdownView, - { isEnabled: true }, - [ - [ model, { isEnabled: false } ] - ], - { isEnabled: false } - ); - } ); - } ); - describe( 'enableModelIfOneIsEnabled()', () => { it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { const observables = [ From d03791a92d489589a2fe3d1551813057379f9908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 13:28:11 +0100 Subject: [PATCH 35/78] Other: Extract `enableModelIfOneIsEnabled()` to own file. --- .../helpers/enablemodelifoneisenabled.js | 19 +++++++++++ src/dropdown/utils.js | 9 ----- .../helpers/enablemodelifoneisenabled.js | 34 +++++++++++++++++++ tests/dropdown/utils.js | 28 +-------------- 4 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 src/dropdown/helpers/enablemodelifoneisenabled.js create mode 100644 tests/dropdown/helpers/enablemodelifoneisenabled.js diff --git a/src/dropdown/helpers/enablemodelifoneisenabled.js b/src/dropdown/helpers/enablemodelifoneisenabled.js new file mode 100644 index 00000000..c8bb8d73 --- /dev/null +++ b/src/dropdown/helpers/enablemodelifoneisenabled.js @@ -0,0 +1,19 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { getBindingTargets } from '../utils'; + +/** + * @module ui/dropdown/helpers/enablemodelifoneisenabled + */ + +export default function enableModelIfOneIsEnabled( model, observables ) { + model.bind( 'isEnabled' ).to( + // Bind to #isEnabled of each observable... + ...getBindingTargets( observables, 'isEnabled' ), + // ...and set it true if any observable #isEnabled is true. + ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) + ); +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index ef010632..a84ccbe6 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -14,15 +14,6 @@ import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -export function enableModelIfOneIsEnabled( model, observables ) { - model.bind( 'isEnabled' ).to( - // Bind to #isEnabled of each observable... - ...getBindingTargets( observables, 'isEnabled' ), - // ...and set it true if any observable #isEnabled is true. - ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) - ); -} - /** * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. diff --git a/tests/dropdown/helpers/enablemodelifoneisenabled.js b/tests/dropdown/helpers/enablemodelifoneisenabled.js new file mode 100644 index 00000000..8b2b28aa --- /dev/null +++ b/tests/dropdown/helpers/enablemodelifoneisenabled.js @@ -0,0 +1,34 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import Model from '../../../src/model'; + +import enableModelIfOneIsEnabled from '../../../src/dropdown/helpers/enablemodelifoneisenabled'; + +describe( 'enableModelIfOneIsEnabled()', () => { + it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { + const model = new Model(); + const observables = [ + new Model( { isEnabled: false } ), + new Model( { isEnabled: false } ), + new Model( { isEnabled: false } ) + ]; + enableModelIfOneIsEnabled( model, observables ); + + expect( model.isEnabled ).to.be.false; + + observables[ 0 ].isEnabled = true; + + expect( model.isEnabled ).to.be.true; + + observables[ 0 ].isEnabled = false; + + expect( model.isEnabled ).to.be.false; + + observables[ 1 ].isEnabled = true; + + expect( model.isEnabled ).to.be.true; + } ); +} ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 9b87dc95..c6023f71 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -18,8 +18,7 @@ import createDropdownView from '../../src/dropdown/helpers/createdropdownview'; import { addListViewToDropdown, - addToolbarToDropdown, - enableModelIfOneIsEnabled + addToolbarToDropdown } from '../../src/dropdown/utils'; describe( 'utils', () => { @@ -38,31 +37,6 @@ describe( 'utils', () => { } } ); - describe( 'enableModelIfOneIsEnabled()', () => { - it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { - const observables = [ - new Model( { isEnabled: false } ), - new Model( { isEnabled: false } ), - new Model( { isEnabled: false } ) - ]; - enableModelIfOneIsEnabled( model, observables ); - - expect( model.isEnabled ).to.be.false; - - observables[ 0 ].isEnabled = true; - - expect( model.isEnabled ).to.be.true; - - observables[ 0 ].isEnabled = false; - - expect( model.isEnabled ).to.be.false; - - observables[ 1 ].isEnabled = true; - - expect( model.isEnabled ).to.be.true; - } ); - } ); - describe( 'addListViewToDropdown()', () => { let items; From 1b93679ad9372310f2a68e646125e61780b3366e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 13:37:04 +0100 Subject: [PATCH 36/78] Other: Extract `addListViewToDropdown()` to own file. --- src/dropdown/helpers/addlistviewtodropdown.js | 70 +++++++++++++ src/dropdown/utils.js | 61 ------------ .../dropdown/helpers/addlistviewtodropdown.js | 97 +++++++++++++++++++ tests/dropdown/manual/dropdown.js | 6 +- tests/dropdown/utils.js | 83 +--------------- 5 files changed, 170 insertions(+), 147 deletions(-) create mode 100644 src/dropdown/helpers/addlistviewtodropdown.js create mode 100644 tests/dropdown/helpers/addlistviewtodropdown.js diff --git a/src/dropdown/helpers/addlistviewtodropdown.js b/src/dropdown/helpers/addlistviewtodropdown.js new file mode 100644 index 00000000..cdbb48e9 --- /dev/null +++ b/src/dropdown/helpers/addlistviewtodropdown.js @@ -0,0 +1,70 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers/addlistviewtodropdown + */ + +import ListView from '../../list/listview'; +import ListItemView from '../../list/listitemview'; + +/** + * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using + * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. + * + * const items = new Collection(); + * + * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); + * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); + * + * const model = new Model( { + * isEnabled: true, + * items, + * isOn: false, + * label: 'A dropdown' + * } ); + * + * const dropdown = createListDropdown( model, locale ); + * + * // Will render a dropdown labeled "A dropdown" with a list in the panel + * // containing two items. + * dropdown.render() + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * The + * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} + * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the + * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. + * + * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. + * + * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. + */ +export default function addListViewToDropdown( dropdownView, model, locale ) { + const listView = dropdownView.listView = new ListView( locale ); + + // TODO: make this param of method instead of model property + listView.items.bindTo( model.items ).using( itemModel => { + const item = new ListItemView( locale ); + + // Bind all attributes of the model to the item view. + item.bind( ...Object.keys( itemModel ) ).to( itemModel ); + + return item; + } ); + + dropdownView.panelView.children.add( listView ); + + // TODO: make this also on toolbar???? + listView.items.delegate( 'execute' ).to( dropdownView ); + + return listView; +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index a84ccbe6..0293daa6 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -8,71 +8,10 @@ */ import ToolbarView from '../toolbar/toolbarview'; -import ListView from '../list/listview'; -import ListItemView from '../list/listitemview'; // TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. import '../../theme/components/dropdown/toolbardropdown.css'; -/** - * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using - * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. - * - * const items = new Collection(); - * - * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); - * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); - * - * const model = new Model( { - * isEnabled: true, - * items, - * isOn: false, - * label: 'A dropdown' - * } ); - * - * const dropdown = createListDropdown( model, locale ); - * - * // Will render a dropdown labeled "A dropdown" with a list in the panel - * // containing two items. - * dropdown.render() - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * The - * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} - * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the - * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. - * - * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. - */ -export function addListViewToDropdown( dropdownView, model, locale ) { - const listView = dropdownView.listView = new ListView( locale ); - - // TODO: make this param of method instead of model property - listView.items.bindTo( model.items ).using( itemModel => { - const item = new ListItemView( locale ); - - // Bind all attributes of the model to the item view. - item.bind( ...Object.keys( itemModel ) ).to( itemModel ); - - return item; - } ); - - dropdownView.panelView.children.add( listView ); - - // TODO: make this also on toolbar???? - listView.items.delegate( 'execute' ).to( dropdownView ); - - return listView; -} - /** * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. diff --git a/tests/dropdown/helpers/addlistviewtodropdown.js b/tests/dropdown/helpers/addlistviewtodropdown.js new file mode 100644 index 00000000..c6b82509 --- /dev/null +++ b/tests/dropdown/helpers/addlistviewtodropdown.js @@ -0,0 +1,97 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document */ + +import Collection from '@ckeditor/ckeditor5-utils/src/collection'; + +import Model from '../../../src/model'; + +import ListItemView from '../../../src/list/listitemview'; +import ListView from '../../../src/list/listview'; + +import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; + +import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; + +describe( 'addListViewToDropdown()', () => { + let dropdownView, buttonView, model, locale, items; + + beforeEach( () => { + locale = { t() {} }; + items = new Collection(); + model = new Model( { + isEnabled: true, + items, + isOn: false, + label: 'foo' + } ); + + buttonView = createButtonForDropdown( model, locale ); + dropdownView = createDropdownView( model, buttonView, locale ); + + addListViewToDropdown( dropdownView, model, locale ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + describe( 'view#listView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; + + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.listView ); + expect( dropdownView.listView ).to.be.instanceof( ListView ); + } ); + + it( 'is bound to model#items', () => { + items.add( new Model( { label: 'a', style: 'b' } ) ); + items.add( new Model( { label: 'c', style: 'd' } ) ); + + expect( dropdownView.listView.items ).to.have.length( 2 ); + expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView ); + expect( dropdownView.listView.items.get( 1 ).label ).to.equal( 'c' ); + expect( dropdownView.listView.items.get( 1 ).style ).to.equal( 'd' ); + + items.remove( 1 ); + expect( dropdownView.listView.items ).to.have.length( 1 ); + expect( dropdownView.listView.items.get( 0 ).label ).to.equal( 'a' ); + expect( dropdownView.listView.items.get( 0 ).style ).to.equal( 'b' ); + } ); + + it( 'binds all attributes in model#items', () => { + const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } ); + + items.add( itemModel ); + + const item = dropdownView.listView.items.get( 0 ); + + expect( item.foo ).to.equal( 'bar' ); + expect( item.baz ).to.equal( 'qux' ); + + itemModel.baz = 'foo?'; + expect( item.baz ).to.equal( 'foo?' ); + } ); + + it( 'delegates view.listView#execute to the view', done => { + items.add( new Model( { label: 'a', style: 'b' } ) ); + + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.listView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.listView.items.get( 0 ), dropdownView ] ); + + done(); + } ); + + dropdownView.listView.items.get( 0 ).fire( 'execute' ); + } ); + } ); +} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 324b6a52..149034c0 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -15,11 +15,9 @@ import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.sv import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; -import { - addListViewToDropdown, - addToolbarToDropdown, -} from '../../../src/dropdown/utils'; +import { addToolbarToDropdown } from '../../../src/dropdown/utils'; +import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index c6023f71..1c721863 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -5,21 +5,14 @@ /* globals document */ -import Collection from '@ckeditor/ckeditor5-utils/src/collection'; - import Model from '../../src/model'; import ButtonView from '../../src/button/buttonview'; import ToolbarView from '../../src/toolbar/toolbarview'; -import ListItemView from '../../src/list/listitemview'; -import ListView from '../../src/list/listview'; import createButtonForDropdown from '../../src/dropdown/helpers/createbuttonfordropdown'; import createDropdownView from '../../src/dropdown/helpers/createdropdownview'; -import { - addListViewToDropdown, - addToolbarToDropdown -} from '../../src/dropdown/utils'; +import { addToolbarToDropdown } from '../../src/dropdown/utils'; describe( 'utils', () => { let dropdownView, buttonView, model, locale; @@ -37,80 +30,6 @@ describe( 'utils', () => { } } ); - describe( 'addListViewToDropdown()', () => { - let items; - - beforeEach( () => { - items = new Collection(); - model = new Model( { - isEnabled: true, - items, - isOn: false, - label: 'foo' - } ); - - buttonView = createButtonForDropdown( model, locale ); - dropdownView = createDropdownView( model, buttonView, locale ); - - addListViewToDropdown( dropdownView, model, locale ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - describe( 'view#listView', () => { - it( 'is created', () => { - const panelChildren = dropdownView.panelView.children; - - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( dropdownView.listView ); - expect( dropdownView.listView ).to.be.instanceof( ListView ); - } ); - - it( 'is bound to model#items', () => { - items.add( new Model( { label: 'a', style: 'b' } ) ); - items.add( new Model( { label: 'c', style: 'd' } ) ); - - expect( dropdownView.listView.items ).to.have.length( 2 ); - expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView ); - expect( dropdownView.listView.items.get( 1 ).label ).to.equal( 'c' ); - expect( dropdownView.listView.items.get( 1 ).style ).to.equal( 'd' ); - - items.remove( 1 ); - expect( dropdownView.listView.items ).to.have.length( 1 ); - expect( dropdownView.listView.items.get( 0 ).label ).to.equal( 'a' ); - expect( dropdownView.listView.items.get( 0 ).style ).to.equal( 'b' ); - } ); - - it( 'binds all attributes in model#items', () => { - const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } ); - - items.add( itemModel ); - - const item = dropdownView.listView.items.get( 0 ); - - expect( item.foo ).to.equal( 'bar' ); - expect( item.baz ).to.equal( 'qux' ); - - itemModel.baz = 'foo?'; - expect( item.baz ).to.equal( 'foo?' ); - } ); - - it( 'delegates view.listView#execute to the view', done => { - items.add( new Model( { label: 'a', style: 'b' } ) ); - - dropdownView.on( 'execute', evt => { - expect( evt.source ).to.equal( dropdownView.listView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ dropdownView.listView.items.get( 0 ), dropdownView ] ); - - done(); - } ); - - dropdownView.listView.items.get( 0 ).fire( 'execute' ); - } ); - } ); - } ); - describe( 'addToolbarToDropdown()', () => { let buttons; From 88217eda0993e61186d1868299fd4507614b3194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 13:42:09 +0100 Subject: [PATCH 37/78] Other: Extract `addToolbarToDropdown()` to own file. --- src/dropdown/helpers/addtoolbartodropdown.js | 64 +++++++++++ src/dropdown/utils.js | 57 ---------- .../dropdown/helpers/addtoolbartodropdown.js | 89 +++++++++++++++ tests/dropdown/manual/dropdown.js | 3 +- tests/dropdown/utils.js | 102 ------------------ 5 files changed, 154 insertions(+), 161 deletions(-) create mode 100644 src/dropdown/helpers/addtoolbartodropdown.js create mode 100644 tests/dropdown/helpers/addtoolbartodropdown.js delete mode 100644 tests/dropdown/utils.js diff --git a/src/dropdown/helpers/addtoolbartodropdown.js b/src/dropdown/helpers/addtoolbartodropdown.js new file mode 100644 index 00000000..ed4cad67 --- /dev/null +++ b/src/dropdown/helpers/addtoolbartodropdown.js @@ -0,0 +1,64 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/helpers/addtoolbartodropdown + */ + +import ToolbarView from '../../toolbar/toolbarview'; + +import '../../theme/components/dropdown/toolbardropdown.css'; + +/** + * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using + * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. + * + * const buttons = []; + * + * buttons.push( new ButtonView() ); + * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); + * + * const model = new Model( { + * label: 'A button dropdown', + * isVertical: true, + * buttons + * } ); + * + * const dropdown = createButtonDropdown( model, locale ); + * + * // Will render a vertical button dropdown labeled "A button dropdown" + * // with a button group in the panel containing two buttons. + * dropdown.render() + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * See {@link module:ui/dropdown/createdropdown~createDropdown}. + * + * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} The button dropdown view instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} + */ +export default function addToolbarToDropdown( dropdownView, model ) { + const toolbarView = dropdownView.toolbarView = new ToolbarView(); + + toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); + + dropdownView.extendTemplate( { + attributes: { + class: [ 'ck-toolbar-dropdown' ] + } + } ); + + dropdownView.panelView.children.add( toolbarView ); + + // TODO: make it as 'items', 'views' or pass them as parameter??? + model.buttons.map( view => toolbarView.items.add( view ) ); + + return toolbarView; +} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 0293daa6..75694ea6 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -7,63 +7,6 @@ * @module ui/dropdown/helpers */ -import ToolbarView from '../toolbar/toolbarview'; - -// TODO: This should be per-component import AFAIK. It will result in smaller builds that don't use dropdown with toolbar. -import '../../theme/components/dropdown/toolbardropdown.css'; - -/** - * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using - * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. - * - * const buttons = []; - * - * buttons.push( new ButtonView() ); - * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); - * - * const model = new Model( { - * label: 'A button dropdown', - * isVertical: true, - * buttons - * } ); - * - * const dropdown = createButtonDropdown( model, locale ); - * - * // Will render a vertical button dropdown labeled "A button dropdown" - * // with a button group in the panel containing two buttons. - * dropdown.render() - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown}. - * - * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} The button dropdown view instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} - */ -export function addToolbarToDropdown( dropdownView, model ) { - const toolbarView = dropdownView.toolbarView = new ToolbarView(); - - toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); - - dropdownView.extendTemplate( { - attributes: { - class: [ 'ck-toolbar-dropdown' ] - } - } ); - - dropdownView.panelView.children.add( toolbarView ); - - // TODO: make it as 'items', 'views' or pass them as parameter??? - model.buttons.map( view => toolbarView.items.add( view ) ); - - return toolbarView; -} - // Returns an array of binding components for // {@link module:utils/observablemixin~Observable#bind} from a set of iterable // buttons. diff --git a/tests/dropdown/helpers/addtoolbartodropdown.js b/tests/dropdown/helpers/addtoolbartodropdown.js new file mode 100644 index 00000000..266074f5 --- /dev/null +++ b/tests/dropdown/helpers/addtoolbartodropdown.js @@ -0,0 +1,89 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* globals document */ + +import Model from '../../../src/model'; + +import ButtonView from '../../../src/button/buttonview'; +import ToolbarView from '../../../src/toolbar/toolbarview'; +import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; +import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; + +import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; + +describe( 'addToolbarToDropdown()', () => { + let dropdownView, buttonView, model, locale, buttons; + + beforeEach( () => { + locale = { t() {} }; + + buttons = [ 'foo', 'bar' ].map( icon => { + const button = new ButtonView(); + + button.icon = icon; + + return button; + } ); + + model = new Model( { + isVertical: true, + buttons + } ); + + buttonView = createButtonForDropdown( model, locale ); + dropdownView = createDropdownView( model, buttonView, locale ); + + addToolbarToDropdown( dropdownView, model ); + + dropdownView.render(); + + document.body.appendChild( dropdownView.element ); + } ); + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( 'sets view#locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + } ); + + it( 'sets view class', () => { + expect( dropdownView.element.classList.contains( 'ck-toolbar-dropdown' ) ).to.be.true; + } ); + + describe( 'view#toolbarView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; + + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); + expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); + } ); + + it.skip( 'delegates view.toolbarView.items#execute to the view', done => { + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); + + done(); + } ); + + dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); + } ); + + it( 'reacts on model#isVertical', () => { + model.isVertical = false; + expect( dropdownView.toolbarView.isVertical ).to.be.false; + + model.isVertical = true; + expect( dropdownView.toolbarView.isVertical ).to.be.true; + } ); + } ); + + describe( 'buttons', () => { + // TODO: test me! + } ); +} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 149034c0..edfda4b6 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -15,9 +15,8 @@ import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.sv import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; -import { addToolbarToDropdown } from '../../../src/dropdown/utils'; - import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; +import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js deleted file mode 100644 index 1c721863..00000000 --- a/tests/dropdown/utils.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ - -import Model from '../../src/model'; - -import ButtonView from '../../src/button/buttonview'; -import ToolbarView from '../../src/toolbar/toolbarview'; -import createButtonForDropdown from '../../src/dropdown/helpers/createbuttonfordropdown'; -import createDropdownView from '../../src/dropdown/helpers/createdropdownview'; - -import { addToolbarToDropdown } from '../../src/dropdown/utils'; - -describe( 'utils', () => { - let dropdownView, buttonView, model, locale; - - beforeEach( () => { - locale = { t() {} }; - model = new Model(); - buttonView = createButtonForDropdown( model, locale ); - dropdownView = createDropdownView( model, buttonView, locale ); - } ); - - afterEach( () => { - if ( dropdownView.element ) { - dropdownView.element.remove(); - } - } ); - - describe( 'addToolbarToDropdown()', () => { - let buttons; - - beforeEach( () => { - buttons = [ 'foo', 'bar' ].map( icon => { - const button = new ButtonView(); - - button.icon = icon; - - return button; - } ); - - model = new Model( { - isVertical: true, - buttons - } ); - - buttonView = createButtonForDropdown( model, locale ); - dropdownView = createDropdownView( model, buttonView, locale ); - - addToolbarToDropdown( dropdownView, model ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - it( 'sets view#locale', () => { - expect( dropdownView.locale ).to.equal( locale ); - } ); - - it( 'sets view class', () => { - expect( dropdownView.element.classList.contains( 'ck-toolbar-dropdown' ) ).to.be.true; - } ); - - describe( 'view#toolbarView', () => { - it( 'is created', () => { - const panelChildren = dropdownView.panelView.children; - - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); - expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); - } ); - - it.skip( 'delegates view.toolbarView.items#execute to the view', done => { - dropdownView.on( 'execute', evt => { - expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); - - done(); - } ); - - dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); - } ); - - it( 'reacts on model#isVertical', () => { - model.isVertical = false; - expect( dropdownView.toolbarView.isVertical ).to.be.false; - - model.isVertical = true; - expect( dropdownView.toolbarView.isVertical ).to.be.true; - } ); - } ); - - describe( 'buttons', () => { - // TODO: test me! - } ); - } ); - - describe( 'getBindingTargets()', () => {} ); -} ); From 4ee81bfe51f15eed0de263972807f2b0b2b157ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 13:45:30 +0100 Subject: [PATCH 38/78] Other: Extract `getBindingTargets()` to own file and move it to ui/bindings. --- src/bindings/getbindingtargets.js | 21 +++++++++++++++++++ .../helpers/enablemodelifoneisenabled.js | 2 +- src/dropdown/utils.js | 20 ------------------ 3 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 src/bindings/getbindingtargets.js delete mode 100644 src/dropdown/utils.js diff --git a/src/bindings/getbindingtargets.js b/src/bindings/getbindingtargets.js new file mode 100644 index 00000000..d61fa35c --- /dev/null +++ b/src/bindings/getbindingtargets.js @@ -0,0 +1,21 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/bindings/getbindingtargets + */ + +/** + * Returns an array of binding components for + * {@link module:utils/observablemixin~Observable#bind} from a set of iterable + * buttons. + * + * @param {Iterable.} buttons + * @param {String} attribute + * @returns {Array.} + */ +export default function getBindingTargets( buttons, attribute ) { + return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); +} diff --git a/src/dropdown/helpers/enablemodelifoneisenabled.js b/src/dropdown/helpers/enablemodelifoneisenabled.js index c8bb8d73..6d394efd 100644 --- a/src/dropdown/helpers/enablemodelifoneisenabled.js +++ b/src/dropdown/helpers/enablemodelifoneisenabled.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -import { getBindingTargets } from '../utils'; +import getBindingTargets from '../../bindings/getbindingtargets'; /** * @module ui/dropdown/helpers/enablemodelifoneisenabled diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js deleted file mode 100644 index 75694ea6..00000000 --- a/src/dropdown/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers - */ - -// Returns an array of binding components for -// {@link module:utils/observablemixin~Observable#bind} from a set of iterable -// buttons. -// -// @private -// @param {Iterable.} buttons -// @param {String} attribute -// @returns {Array.} -export function getBindingTargets( buttons, attribute ) { - return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); -} From b39b5449512005817c2362fdbea8ec6825c67a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 14:32:10 +0100 Subject: [PATCH 39/78] Fix: Updated toolbardropdown.css path. --- src/dropdown/helpers/addtoolbartodropdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dropdown/helpers/addtoolbartodropdown.js b/src/dropdown/helpers/addtoolbartodropdown.js index ed4cad67..3027b12e 100644 --- a/src/dropdown/helpers/addtoolbartodropdown.js +++ b/src/dropdown/helpers/addtoolbartodropdown.js @@ -9,7 +9,7 @@ import ToolbarView from '../../toolbar/toolbarview'; -import '../../theme/components/dropdown/toolbardropdown.css'; +import '../../../theme/components/dropdown/toolbardropdown.css'; /** * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using From 358c02240bf69810c061381f59d285f8bd5b95ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 14:39:58 +0100 Subject: [PATCH 40/78] Other: Revert icon hack from ButtonView. --- src/button/buttonview.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/button/buttonview.js b/src/button/buttonview.js index 48eb3f55..cd3291ed 100644 --- a/src/button/buttonview.js +++ b/src/button/buttonview.js @@ -178,9 +178,6 @@ export default class ButtonView extends View { */ this.labelView = this._createLabelView(); - // TODO: used for Icon style hack in Highlight UI - this.iconView = new IconView(); - /** * Tooltip of the button bound to the template. * @@ -255,7 +252,7 @@ export default class ButtonView extends View { super.render(); if ( this.icon ) { - const iconView = this.iconView; + const iconView = this.iconView = new IconView(); iconView.bind( 'content' ).to( this, 'icon' ); From 335e5cbf543ca5abefe5ce30611c62479eca5f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 15:49:01 +0100 Subject: [PATCH 41/78] Fixed: Toolbar dropdown's items "execute" event is not delegated to dropdown. --- src/dropdown/helpers/addlistviewtodropdown.js | 4 +--- src/dropdown/helpers/addtoolbartodropdown.js | 7 ++++--- tests/dropdown/helpers/addtoolbartodropdown.js | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/dropdown/helpers/addlistviewtodropdown.js b/src/dropdown/helpers/addlistviewtodropdown.js index cdbb48e9..96cfc099 100644 --- a/src/dropdown/helpers/addlistviewtodropdown.js +++ b/src/dropdown/helpers/addlistviewtodropdown.js @@ -51,7 +51,7 @@ import ListItemView from '../../list/listitemview'; export default function addListViewToDropdown( dropdownView, model, locale ) { const listView = dropdownView.listView = new ListView( locale ); - // TODO: make this param of method instead of model property + // TODO: make this param of method instead of model property? listView.items.bindTo( model.items ).using( itemModel => { const item = new ListItemView( locale ); @@ -62,8 +62,6 @@ export default function addListViewToDropdown( dropdownView, model, locale ) { } ); dropdownView.panelView.children.add( listView ); - - // TODO: make this also on toolbar???? listView.items.delegate( 'execute' ).to( dropdownView ); return listView; diff --git a/src/dropdown/helpers/addtoolbartodropdown.js b/src/dropdown/helpers/addtoolbartodropdown.js index 3027b12e..f2be79a0 100644 --- a/src/dropdown/helpers/addtoolbartodropdown.js +++ b/src/dropdown/helpers/addtoolbartodropdown.js @@ -55,10 +55,11 @@ export default function addToolbarToDropdown( dropdownView, model ) { } } ); - dropdownView.panelView.children.add( toolbarView ); - - // TODO: make it as 'items', 'views' or pass them as parameter??? + // TODO: make this param of method instead of model property? model.buttons.map( view => toolbarView.items.add( view ) ); + dropdownView.panelView.children.add( toolbarView ); + toolbarView.items.delegate( 'execute' ).to( dropdownView ); + return toolbarView; } diff --git a/tests/dropdown/helpers/addtoolbartodropdown.js b/tests/dropdown/helpers/addtoolbartodropdown.js index 266074f5..83697aa8 100644 --- a/tests/dropdown/helpers/addtoolbartodropdown.js +++ b/tests/dropdown/helpers/addtoolbartodropdown.js @@ -63,7 +63,7 @@ describe( 'addToolbarToDropdown()', () => { expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); } ); - it.skip( 'delegates view.toolbarView.items#execute to the view', done => { + it( 'delegates view.toolbarView.items#execute to the view', done => { dropdownView.on( 'execute', evt => { expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); From 3d21766f00074c55ea14df247dfa18dce9644a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 15:49:37 +0100 Subject: [PATCH 42/78] Tests: Remove empty describe() from addtoolbartodropdown tests. --- tests/dropdown/helpers/addtoolbartodropdown.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/dropdown/helpers/addtoolbartodropdown.js b/tests/dropdown/helpers/addtoolbartodropdown.js index 83697aa8..551466cc 100644 --- a/tests/dropdown/helpers/addtoolbartodropdown.js +++ b/tests/dropdown/helpers/addtoolbartodropdown.js @@ -82,8 +82,4 @@ describe( 'addToolbarToDropdown()', () => { expect( dropdownView.toolbarView.isVertical ).to.be.true; } ); } ); - - describe( 'buttons', () => { - // TODO: test me! - } ); } ); From 26d94b66c2e3b9aea3f554bb602c99cc5494a1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 18:17:50 +0100 Subject: [PATCH 43/78] Tests: Add SplitButtonView tests. --- src/button/splitbuttonview.js | 28 +++++ tests/button/splitbuttonview.js | 181 ++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 tests/button/splitbuttonview.js diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 0735aaf4..e48bb3ef 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -28,6 +28,34 @@ export default class SplitButtonView extends View { constructor( locale ) { super( locale ); + /** + * Controls whether the button view is enabled, i.e. it can be clicked and execute an action. + * + * To change the "on" state of the button, use {@link #isOn} instead. + * + * @observable + * @member {Boolean} #isEnabled + */ + this.set( 'isEnabled', true ); + + /** + * The label of the button view visible to the user when {@link #withText} is `true`. + * It can also be used to create a {@link #tooltip}. + * + * @observable + * @member {String} #label + */ + this.set( 'label' ); + + /** + * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. + * When defined, an {@link #iconView} will be added to the button. + * + * @observable + * @member {String} #icon + */ + this.set( 'icon' ); + this.children = this.createCollection(); this.actionView = this._createActionView(); diff --git a/tests/button/splitbuttonview.js b/tests/button/splitbuttonview.js new file mode 100644 index 00000000..95d06c2f --- /dev/null +++ b/tests/button/splitbuttonview.js @@ -0,0 +1,181 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; +import ButtonView from '../../src/button/buttonview'; +import SplitButtonView from '../../src/button/splitbuttonview'; + +testUtils.createSinonSandbox(); + +describe( 'SplitButtonView', () => { + let locale, view; + + beforeEach( () => { + locale = { t() {} }; + + view = new SplitButtonView( locale ); + view.render(); + } ); + + describe( 'constructor()', () => { + it( 'sets view#locale', () => { + expect( view.locale ).to.equal( locale ); + } ); + + it( 'creates view#actionView', () => { + expect( view.actionView ).to.be.instanceOf( ButtonView ); + } ); + + it( 'creates view#selectView', () => { + expect( view.selectView ).to.be.instanceOf( ButtonView ); + expect( view.selectView.element.classList.contains( 'ck-splitbutton-arrow' ) ).to.be.true; + expect( view.selectView.icon ).to.be.not.undefined; + } ); + + it( 'creates element from template', () => { + expect( view.element.tagName ).to.equal( 'DIV' ); + expect( view.element.classList.contains( 'ck-splitbutton' ) ).to.be.true; + } ); + + describe( 'activates keyboard navigation for the toolbar', () => { + it( 'so "arrowright" on view#selectView does nothing', () => { + const keyEvtData = { + keyCode: keyCodes.arrowright, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + + view.focusTracker.isFocused = true; + view.focusTracker.focusedElement = view.selectView.element; + + const spy = sinon.spy( view.actionView, 'focus' ); + + view.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + sinon.assert.notCalled( keyEvtData.preventDefault ); + sinon.assert.notCalled( keyEvtData.stopPropagation ); + } ); + + it( 'so "arrowleft" on view#selectView focuses view#actionView', () => { + const keyEvtData = { + keyCode: keyCodes.arrowleft, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + + view.focusTracker.isFocused = true; + view.focusTracker.focusedElement = view.selectView.element; + + const spy = sinon.spy( view.actionView, 'focus' ); + + view.keystrokes.press( keyEvtData ); + sinon.assert.calledOnce( spy ); + sinon.assert.calledOnce( keyEvtData.preventDefault ); + sinon.assert.calledOnce( keyEvtData.stopPropagation ); + } ); + + it( 'so "arrowright" on view#actionView focuses view#selectView', () => { + const keyEvtData = { + keyCode: keyCodes.arrowright, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + + view.focusTracker.isFocused = true; + view.focusTracker.focusedElement = view.actionView.element; + + const spy = sinon.spy( view.selectView, 'focus' ); + + view.keystrokes.press( keyEvtData ); + sinon.assert.calledOnce( spy ); + sinon.assert.calledOnce( keyEvtData.preventDefault ); + sinon.assert.calledOnce( keyEvtData.stopPropagation ); + } ); + + it( 'so "arrowleft" on view#actionsView does nothing', () => { + const keyEvtData = { + keyCode: keyCodes.arrowleft, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + + view.focusTracker.isFocused = true; + view.focusTracker.focusedElement = view.actionView.element; + + const spy = sinon.spy( view.selectView, 'focus' ); + + view.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + sinon.assert.notCalled( keyEvtData.preventDefault ); + sinon.assert.notCalled( keyEvtData.stopPropagation ); + } ); + } ); + } ); + + describe( 'bindings', () => { + it( 'delegates actionView#execute to view#execute', () => { + const spy = sinon.spy(); + + view.on( 'execute', spy ); + + view.actionView.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + } ); + + it( 'binds actionView#icon to view', () => { + expect( view.actionView.icon ).to.be.undefined; + + view.icon = 'foo'; + + expect( view.actionView.icon ).to.equal( 'foo' ); + } ); + + it( 'binds actionView#isEnabled to view', () => { + expect( view.actionView.isEnabled ).to.be.true; + + view.isEnabled = false; + + expect( view.actionView.isEnabled ).to.be.false; + } ); + + it( 'binds actionView#label to view', () => { + expect( view.actionView.label ).to.be.undefined; + + view.label = 'foo'; + + expect( view.actionView.label ).to.equal( 'foo' ); + } ); + + it( 'delegates selectView#execute to view#select', () => { + const spy = sinon.spy(); + + view.on( 'select', spy ); + + view.selectView.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + } ); + + it( 'binds selectView#isEnabled to view', () => { + expect( view.selectView.isEnabled ).to.be.true; + + view.isEnabled = false; + + expect( view.selectView.isEnabled ).to.be.false; + } ); + } ); + + describe( 'focus()', () => { + it( 'focuses the actionButton', () => { + const spy = sinon.spy( view.actionView, 'focus' ); + + view.focus(); + + sinon.assert.calledOnce( spy ); + } ); + } ); +} ); From e9424604e658d32884de6aae751f5b009c29d785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 18:29:49 +0100 Subject: [PATCH 44/78] Tests: Add `DropdownPanelView#focus()` and `DropdownPanelView#focusLast()` tests. --- src/dropdown/dropdownpanelview.js | 10 ++++-- tests/dropdown/dropdownpanelview.js | 53 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/dropdown/dropdownpanelview.js b/src/dropdown/dropdownpanelview.js index cb7e74d3..be213d44 100644 --- a/src/dropdown/dropdownpanelview.js +++ b/src/dropdown/dropdownpanelview.js @@ -72,11 +72,15 @@ export default class DropdownPanelView extends View { } } - // TODO: child might not have focusLast - // TODO: maybe adding toolbar/list should somewhat intercept those? focusLast() { if ( this.children.length ) { - this.children.last.focusLast(); + const lastChild = this.children.last; + + if ( typeof lastChild.focusLast === 'function' ) { + lastChild.focusLast(); + } else { + lastChild.focus(); + } } } } diff --git a/tests/dropdown/dropdownpanelview.js b/tests/dropdown/dropdownpanelview.js index c1ee8551..aee1b4f5 100644 --- a/tests/dropdown/dropdownpanelview.js +++ b/tests/dropdown/dropdownpanelview.js @@ -1,3 +1,4 @@ + /** * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. @@ -7,6 +8,7 @@ import ViewCollection from '../../src/viewcollection'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; +import View from '../../src/view'; describe( 'DropdownPanelView', () => { let view, locale; @@ -63,4 +65,55 @@ describe( 'DropdownPanelView', () => { } ); } ); } ); + + describe( 'focus()', () => { + it( 'does nothing for empty panel', () => { + expect( () => view.focus() ).to.not.throw(); + } ); + + it( 'focuses first child view', () => { + const firstChildView = new View(); + + firstChildView.focus = sinon.spy(); + + view.children.add( firstChildView ); + view.children.add( new View() ); + + view.focus(); + + sinon.assert.calledOnce( firstChildView.focus ); + } ); + } ); + + describe( 'focusLast()', () => { + it( 'does nothing for empty panel', () => { + expect( () => view.focusLast() ).to.not.throw(); + } ); + + it( 'focuses last child view', () => { + const lastChildView = new View(); + + lastChildView.focusLast = sinon.spy(); + + view.children.add( new View() ); + view.children.add( lastChildView ); + + view.focusLast(); + + sinon.assert.calledOnce( lastChildView.focusLast ); + } ); + + it( 'focuses last child view even if it does not have focusLast() method', () => { + const lastChildView = new View(); + + lastChildView.focus = sinon.spy(); + + view.children.add( new View() ); + view.children.add( lastChildView ); + + view.focusLast(); + + sinon.assert.calledOnce( lastChildView.focus ); + } ); + } ); } ); From f446a4abceb62400e268a12145f1ee0120b7fdbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Wed, 24 Jan 2018 18:55:26 +0100 Subject: [PATCH 45/78] Docs: Add dropdowns framework guide stub. --- docs/framework/guides/dropdowns.md | 103 +++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/framework/guides/dropdowns.md diff --git a/docs/framework/guides/dropdowns.md b/docs/framework/guides/dropdowns.md new file mode 100644 index 00000000..83854975 --- /dev/null +++ b/docs/framework/guides/dropdowns.md @@ -0,0 +1,103 @@ +--- +category: framework-ui +order: 30 +--- + +# Dropdowns + +## Creating Toolbar dropdown with SplitButton + +```js +import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; +import Model from '@ckeditor/ckeditor5-ui/src/model'; + +import addToolbarToDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/addtoolbartodropdown'; +import closeDropdownOnBlur from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonblur'; +import closeDropdownOnExecute from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonexecute'; +import createDropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createdropdownview'; +import createSplitButtonForDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createsplitbuttonfordropdown'; +import enableModelIfOneIsEnabled from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/enablemodelifoneisenabled'; +import focusDropdownContentsOnArrows from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/focusdropdowncontentsonarrows'; + +const model = new Model( { + icon: 'some SVG', + tooltip: 'My dropdown' +} ); + +buttons.push( new ButtonView() ); +buttons.push( componentFactory.create( 'someExistingButton' ) ); + +model.set( 'buttons', buttons ); + +const splitButtonView = createSplitButtonForDropdown( model, locale ); +const dropdownView = createDropdownView( model, splitButtonView, locale ); + +// Customize dropdown + +// This will enable toolbar button when any of button in dropdown is enabled. +enableModelIfOneIsEnabled( model, buttons ); + +// Make this a dropdown with toolbar inside dropdown panel. +addToolbarToDropdown( dropdownView, model ); + +// Add default behavior of dropdown +closeDropdownOnBlur( dropdownView ); +closeDropdownOnExecute( dropdownView ); +focusDropdownContentsOnArrows( dropdownView ); + +// Execute current action from dropdown's split button action button. +dropdownView.buttonView.on( 'execute', () => { + editor.execute( 'command', { value: model.commandValue } ); + editor.editing.view.focus(); +} ); +``` + +## Creating ListView dropdown with standard button + +```js +import Model from '@ckeditor/ckeditor5-ui/src/model'; + +import addListViewToDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/addlistviewtodropdown'; +import closeDropdownOnBlur from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonblur'; +import closeDropdownOnExecute from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonexecute'; +import createDropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createdropdownview'; +import createButtonForDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createbuttonfordropdown'; +import focusDropdownContentsOnArrows from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/focusdropdowncontentsonarrows'; + +const items = [ + new Model( { + label: 'Do Foo', + commandName: 'foo' + } ), + new Model( { + label: 'Do Bar', + commandName: 'bar' + } ), +]; + +// Create dropdown model. +const model = new Model( { + icon: 'some icon SVG', + items +} ); + +const buttonView = createButtonForDropdown( model, locale ); +const dropdownView = createDropdownView( model, buttonView, locale ); + +// Customize dropdown + +// This will enable toolbar button when any of button in dropdown is enabled. + +addListViewToDropdown( dropdownView, model, locale ); + +// Add default behavior of dropdown +closeDropdownOnBlur( dropdownView ); +closeDropdownOnExecute( dropdownView ); +focusDropdownContentsOnArrows( dropdownView ); + +// Execute command when an item from the dropdown is selected. +this.listenTo( dropdownView, 'execute', evt => { + editor.execute( evt.source.commandName ); + editor.editing.view.focus(); +} ); +``` From acca5f438209b2425b07c706ccb39cc9d5326432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Sun, 28 Jan 2018 13:01:40 +0100 Subject: [PATCH 46/78] Changed: Change how dropdowns with `ButtonView` and `SplitButtonView` are created. --- .../helpers/createbuttonfordropdown.js | 19 -- src/dropdown/helpers/createdropdownview.js | 54 ---- .../helpers/createsplitbuttonfordropdown.js | 20 -- src/dropdown/utils.js | 123 +++++++++ .../dropdown/helpers/addlistviewtodropdown.js | 8 +- .../dropdown/helpers/addtoolbartodropdown.js | 9 +- tests/dropdown/helpers/closedropdownonblur.js | 6 +- .../helpers/closedropdownonexecute.js | 5 +- .../helpers/createbuttonfordropdown.js | 37 --- tests/dropdown/helpers/createdropdownview.js | 125 --------- .../helpers/createsplitbuttonfordropdown.js | 26 -- .../helpers/focusdropdowncontentsonarrows.js | 5 +- tests/dropdown/manual/dropdown.js | 22 +- tests/dropdown/utils.js | 238 ++++++++++++++++++ 14 files changed, 382 insertions(+), 315 deletions(-) delete mode 100644 src/dropdown/helpers/createbuttonfordropdown.js delete mode 100644 src/dropdown/helpers/createdropdownview.js delete mode 100644 src/dropdown/helpers/createsplitbuttonfordropdown.js create mode 100644 src/dropdown/utils.js delete mode 100644 tests/dropdown/helpers/createbuttonfordropdown.js delete mode 100644 tests/dropdown/helpers/createdropdownview.js delete mode 100644 tests/dropdown/helpers/createsplitbuttonfordropdown.js create mode 100644 tests/dropdown/utils.js diff --git a/src/dropdown/helpers/createbuttonfordropdown.js b/src/dropdown/helpers/createbuttonfordropdown.js deleted file mode 100644 index d77008e1..00000000 --- a/src/dropdown/helpers/createbuttonfordropdown.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import ButtonView from '../../button/buttonview'; - -/** - * @module ui/dropdown/helpers/createbuttonfordropdown - */ - -export default function createButtonForDropdown( model, locale ) { - const buttonView = new ButtonView( locale ); - - // Dropdown expects "select" event to show contents. - buttonView.delegate( 'execute' ).to( buttonView, 'select' ); - - return buttonView; -} diff --git a/src/dropdown/helpers/createdropdownview.js b/src/dropdown/helpers/createdropdownview.js deleted file mode 100644 index 5e7b2104..00000000 --- a/src/dropdown/helpers/createdropdownview.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers/createdropdownview - */ - -import DropdownPanelView from './../dropdownpanelview'; -import DropdownView from './../dropdownview'; - -/** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using - * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. - * - * const model = new Model( { - * label: 'A dropdown', - * isEnabled: true, - * isOn: false, - * withText: true - * } ); - * - * const dropdown = createDropdown( model ); - * - * dropdown.render(); - * - * // Will render a dropdown labeled "A dropdown" with an empty panel. - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. - * - * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. - */ -export default function createDropdownView( model, buttonView, locale ) { - const panelView = new DropdownPanelView( locale ); - const dropdownView = new DropdownView( locale, buttonView, panelView ); - - dropdownView.bind( 'isEnabled' ).to( model ); - - // TODO: check 'isOn' binding. - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { - return isOn || isOpen; - } ); - - return dropdownView; -} diff --git a/src/dropdown/helpers/createsplitbuttonfordropdown.js b/src/dropdown/helpers/createsplitbuttonfordropdown.js deleted file mode 100644 index c5e0782d..00000000 --- a/src/dropdown/helpers/createsplitbuttonfordropdown.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers/createsplitbuttonfordropdown - */ - -import SplitButtonView from '../../button/splitbuttonview'; - -export default function createSplitButtonForDropdown( model, locale ) { - const splitButtonView = new SplitButtonView( locale ); - - // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. - splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); - splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); - - return splitButtonView; -} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js new file mode 100644 index 00000000..312bf526 --- /dev/null +++ b/src/dropdown/utils.js @@ -0,0 +1,123 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/utils + */ + +import DropdownPanelView from './dropdownpanelview'; +import DropdownView from './dropdownview'; +import SplitButtonView from '../button/splitbuttonview'; +import ButtonView from '../button/buttonview'; + +/** + * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using + * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. + * + * const model = new Model( { + * label: 'A dropdown', + * isEnabled: true, + * isOn: false, + * withText: true + * } ); + * + * const dropdown = createDropdown( model ); + * + * dropdown.render(); + * + * // Will render a dropdown labeled "A dropdown" with an empty panel. + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. + * + * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. + */ +export function createDropdown( model, locale ) { + const buttonView = createButtonForDropdown( model, locale ); + + const dropdownView = prepareDropdown( locale, buttonView, model ); + + return dropdownView; +} + +/** + * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using + * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. + * + * const model = new Model( { + * label: 'A dropdown', + * isEnabled: true, + * isOn: false, + * withText: true + * } ); + * + * const dropdown = createDropdown( model ); + * + * dropdown.render(); + * + * // Will render a dropdown labeled "A dropdown" with an empty panel. + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. + * + * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. + */ +export function createSplitButtonDropdown( model, locale ) { + const buttonView = createSplitButtonForDropdown( model, locale ); + + const dropdownView = prepareDropdown( locale, buttonView, model ); + + buttonView.delegate( 'execute' ).to( dropdownView ); + + return dropdownView; +} + +// @private +function prepareDropdown( locale, buttonView, model ) { + const panelView = new DropdownPanelView( locale ); + const dropdownView = new DropdownView( locale, buttonView, panelView ); + + dropdownView.bind( 'isEnabled' ).to( model ); + + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); + buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + return isOn || isOpen; + } ); + + return dropdownView; +} + +// @private +function createSplitButtonForDropdown( model, locale ) { + const splitButtonView = new SplitButtonView( locale ); + + // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. + splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); + splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); + + return splitButtonView; +} + +// @private +function createButtonForDropdown( model, locale ) { + const buttonView = new ButtonView( locale ); + + // Dropdown expects "select" event to show contents. + buttonView.delegate( 'execute' ).to( buttonView, 'select' ); + + return buttonView; +} diff --git a/tests/dropdown/helpers/addlistviewtodropdown.js b/tests/dropdown/helpers/addlistviewtodropdown.js index c6b82509..df59b8a1 100644 --- a/tests/dropdown/helpers/addlistviewtodropdown.js +++ b/tests/dropdown/helpers/addlistviewtodropdown.js @@ -12,13 +12,12 @@ import Model from '../../../src/model'; import ListItemView from '../../../src/list/listitemview'; import ListView from '../../../src/list/listview'; -import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; +import { createDropdown } from './../../../src/dropdown/utils'; import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; describe( 'addListViewToDropdown()', () => { - let dropdownView, buttonView, model, locale, items; + let dropdownView, model, locale, items; beforeEach( () => { locale = { t() {} }; @@ -30,8 +29,7 @@ describe( 'addListViewToDropdown()', () => { label: 'foo' } ); - buttonView = createButtonForDropdown( model, locale ); - dropdownView = createDropdownView( model, buttonView, locale ); + dropdownView = createDropdown( model, locale ); addListViewToDropdown( dropdownView, model, locale ); diff --git a/tests/dropdown/helpers/addtoolbartodropdown.js b/tests/dropdown/helpers/addtoolbartodropdown.js index 551466cc..f2465f62 100644 --- a/tests/dropdown/helpers/addtoolbartodropdown.js +++ b/tests/dropdown/helpers/addtoolbartodropdown.js @@ -9,13 +9,12 @@ import Model from '../../../src/model'; import ButtonView from '../../../src/button/buttonview'; import ToolbarView from '../../../src/toolbar/toolbarview'; -import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; +import { createDropdown } from '../../../src/dropdown/utils'; import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; describe( 'addToolbarToDropdown()', () => { - let dropdownView, buttonView, model, locale, buttons; + let dropdownView, model, locale, buttons; beforeEach( () => { locale = { t() {} }; @@ -33,8 +32,7 @@ describe( 'addToolbarToDropdown()', () => { buttons } ); - buttonView = createButtonForDropdown( model, locale ); - dropdownView = createDropdownView( model, buttonView, locale ); + dropdownView = createDropdown( model, locale ); addToolbarToDropdown( dropdownView, model ); @@ -42,6 +40,7 @@ describe( 'addToolbarToDropdown()', () => { document.body.appendChild( dropdownView.element ); } ); + afterEach( () => { dropdownView.element.remove(); } ); diff --git a/tests/dropdown/helpers/closedropdownonblur.js b/tests/dropdown/helpers/closedropdownonblur.js index 22bb66f1..50a34737 100644 --- a/tests/dropdown/helpers/closedropdownonblur.js +++ b/tests/dropdown/helpers/closedropdownonblur.js @@ -6,15 +6,15 @@ /* globals document, Event */ import Model from '../../../src/model'; -import ButtonView from '../../../src/button/buttonview'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; + +import { createDropdown } from '../../../src/dropdown/utils'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; describe( 'closeDropdownOnBlur()', () => { let dropdownView; beforeEach( () => { - dropdownView = createDropdownView( new Model(), new ButtonView(), {} ); + dropdownView = createDropdown( new Model(), {} ); closeDropdownOnBlur( dropdownView ); diff --git a/tests/dropdown/helpers/closedropdownonexecute.js b/tests/dropdown/helpers/closedropdownonexecute.js index 9acd9b9b..1e2e4a3a 100644 --- a/tests/dropdown/helpers/closedropdownonexecute.js +++ b/tests/dropdown/helpers/closedropdownonexecute.js @@ -6,15 +6,14 @@ /* globals document */ import Model from '../../../src/model'; -import ButtonView from '../../../src/button/buttonview'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; +import { createDropdown } from '../../../src/dropdown/utils'; describe( 'closeDropdownOnExecute()', () => { let dropdownView; beforeEach( () => { - dropdownView = createDropdownView( new Model(), new ButtonView(), {} ); + dropdownView = createDropdown( new Model(), {} ); closeDropdownOnExecute( dropdownView ); diff --git a/tests/dropdown/helpers/createbuttonfordropdown.js b/tests/dropdown/helpers/createbuttonfordropdown.js deleted file mode 100644 index ee0ffbc6..00000000 --- a/tests/dropdown/helpers/createbuttonfordropdown.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import Model from '../../../src/model'; - -import ButtonView from '../../../src/button/buttonview'; - -import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; - -describe( 'createButtonForDropdown()', () => { - let buttonView, locale; - - beforeEach( () => { - locale = { t() {} }; - buttonView = createButtonForDropdown( new Model(), locale ); - } ); - - it( 'accepts locale', () => { - expect( buttonView.locale ).to.equal( locale ); - } ); - - it( 'returns ButtonView instance', () => { - expect( buttonView ).to.be.instanceof( ButtonView ); - } ); - - it( 'delegates "execute" to "select" event', () => { - const spy = sinon.spy(); - - buttonView.on( 'select', spy ); - - buttonView.fire( 'execute' ); - - sinon.assert.calledOnce( spy ); - } ); -} ); diff --git a/tests/dropdown/helpers/createdropdownview.js b/tests/dropdown/helpers/createdropdownview.js deleted file mode 100644 index 80d6a85d..00000000 --- a/tests/dropdown/helpers/createdropdownview.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; - -import Model from '../../../src/model'; - -import ButtonView from '../../../src/button/buttonview'; -import DropdownView from '../../../src/dropdown/dropdownview'; -import DropdownPanelView from '../../../src/dropdown/dropdownpanelview'; -import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; - -const assertBinding = utilsTestUtils.assertBinding; - -describe( 'createDropdownView()', () => { - let dropdownView, buttonView, model, locale; - - beforeEach( () => { - locale = { t() {} }; - model = new Model(); - buttonView = createButtonForDropdown( model, locale ); - dropdownView = createDropdownView( model, buttonView, locale ); - } ); - - it( 'accepts locale', () => { - expect( dropdownView.locale ).to.equal( locale ); - expect( dropdownView.panelView.locale ).to.equal( locale ); - } ); - - it( 'returns view', () => { - model = new Model(); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView ).to.be.instanceOf( DropdownView ); - } ); - - it( 'creates dropdown#panelView out of DropdownPanelView', () => { - model = new Model(); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); - } ); - - it( 'creates dropdown#buttonView out of buttonView', () => { - model = new Model(); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - expect( dropdownView.buttonView ).to.equal( buttonView ); - } ); - - it( 'binds button attributes to the model', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - - model = new Model( modelDef ); - buttonView = new ButtonView(); - createDropdownView( model, buttonView, locale ); - - assertBinding( buttonView, - modelDef, - [ - [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] - ], - { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } - ); - } ); - - it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - - model = new Model( modelDef ); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - dropdownView.isOpen = false; - expect( buttonView.isOn ).to.be.false; - - model.isOn = true; - expect( buttonView.isOn ).to.be.true; - - dropdownView.isOpen = true; - expect( buttonView.isOn ).to.be.true; - - model.isOn = false; - expect( buttonView.isOn ).to.be.true; - } ); - - it( 'binds dropdown#isEnabled to the model', () => { - const modelDef = { - label: 'foo', - isEnabled: true, - withText: false, - tooltip: false - }; - - model = new Model( modelDef ); - buttonView = new ButtonView(); - dropdownView = createDropdownView( model, buttonView, locale ); - - assertBinding( dropdownView, - { isEnabled: true }, - [ - [ model, { isEnabled: false } ] - ], - { isEnabled: false } - ); - } ); -} ); diff --git a/tests/dropdown/helpers/createsplitbuttonfordropdown.js b/tests/dropdown/helpers/createsplitbuttonfordropdown.js deleted file mode 100644 index c0c1b814..00000000 --- a/tests/dropdown/helpers/createsplitbuttonfordropdown.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import Model from '../../../src/model'; -import SplitButtonView from '../../../src/button/splitbuttonview'; - -import createSplitButtonForDropdown from '../../../src/dropdown/helpers/createsplitbuttonfordropdown'; - -describe( 'createSplitButtonForDropdown()', () => { - let buttonView, locale; - - beforeEach( () => { - locale = { t() {} }; - buttonView = createSplitButtonForDropdown( new Model(), locale ); - } ); - - it( 'accepts locale', () => { - expect( buttonView.locale ).to.equal( locale ); - } ); - - it( 'returns SplitButtonView instance', () => { - expect( buttonView ).to.be.instanceof( SplitButtonView ); - } ); -} ); diff --git a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js index 2fc47fc5..806e9c42 100644 --- a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js +++ b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js @@ -9,17 +9,16 @@ import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; import View from '../../../src/view'; import Model from '../../../src/model'; -import ButtonView from '../../../src/button/buttonview'; import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; +import { createDropdown } from './../../../src/dropdown/utils'; describe( 'focusDropdownContentsOnArrows()', () => { let dropdownView; let panelChildView; beforeEach( () => { - dropdownView = createDropdownView( new Model(), new ButtonView(), {} ); + dropdownView = createDropdown( new Model(), {} ); panelChildView = new View(); panelChildView.setTemplate( { tag: 'div' } ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index edfda4b6..06cebd79 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -17,11 +17,10 @@ import ButtonView from '../../../src/button/buttonview'; import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; -import createDropdownView from '../../../src/dropdown/helpers/createdropdownview'; -import createButtonForDropdown from '../../../src/dropdown/helpers/createbuttonfordropdown'; import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; +import { createDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', @@ -39,8 +38,7 @@ function testEmpty() { withText: true } ); - const buttonView = createButtonForDropdown( model, {} ); - const dropdownView = createDropdownView( model, buttonView, {} ); + const dropdownView = createDropdown( model, {} ); ui.dropdown.add( dropdownView ); @@ -65,8 +63,7 @@ function testList() { items: collection } ); - const buttonView = createButtonForDropdown( model, {} ); - const dropdownView = createDropdownView( model, buttonView, {} ); + const dropdownView = createDropdown( model, {} ); addListViewToDropdown( dropdownView, model, {} ); closeDropdownOnBlur( dropdownView ); @@ -93,11 +90,8 @@ function testSharedModel() { withText: true } ); - const buttonView1 = createButtonForDropdown( model, {} ); - const buttonView2 = createButtonForDropdown( model, {} ); - - const dropdownView1 = createDropdownView( model, buttonView1, {} ); - const dropdownView2 = createDropdownView( model, buttonView2, {} ); + const dropdownView1 = createDropdown( model, {} ); + const dropdownView2 = createDropdown( model, {} ); ui.dropdownShared.add( dropdownView1 ); ui.dropdownShared.add( dropdownView2 ); @@ -113,8 +107,7 @@ function testLongLabel() { withText: true } ); - const buttonView = createButtonForDropdown( model, {} ); - const dropdownView = createDropdownView( model, buttonView, {} ); + const dropdownView = createDropdown( model, {} ); ui.dropdownLabel.add( dropdownView ); @@ -145,8 +138,7 @@ function testButton() { buttons: buttonViews } ); - const buttonView = createButtonForDropdown( toolbarDropdownModel, locale ); - const toolbarDropdown = createDropdownView( toolbarDropdownModel, buttonView, locale ); + const toolbarDropdown = createDropdown( toolbarDropdownModel, locale ); addToolbarToDropdown( toolbarDropdown, toolbarDropdownModel ); closeDropdownOnBlur( toolbarDropdown ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js new file mode 100644 index 00000000..fcf1457d --- /dev/null +++ b/tests/dropdown/utils.js @@ -0,0 +1,238 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; + +import Model from '../../src/model'; + +import ButtonView from '../../src/button/buttonview'; +import DropdownView from '../../src/dropdown/dropdownview'; +import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; +import SplitButtonView from '../../src/button/splitbuttonview'; +import { createDropdown, createSplitButtonDropdown } from '../../src/dropdown/utils'; + +const assertBinding = utilsTestUtils.assertBinding; + +describe( 'utils', () => { + let locale; + + beforeEach( () => { + locale = { t() {} }; + } ); + + describe( 'createDropdown()', () => { + let dropdownView, model; + + beforeEach( () => { + model = new Model(); + dropdownView = createDropdown( model, locale ); + } ); + + it( 'accepts locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + expect( dropdownView.panelView.locale ).to.equal( locale ); + } ); + + it( 'returns view', () => { + expect( dropdownView ).to.be.instanceOf( DropdownView ); + } ); + + it( 'creates dropdown#panelView out of DropdownPanelView', () => { + expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); + } ); + + it( 'creates dropdown#buttonView out of ButtonView', () => { + expect( dropdownView.buttonView ).to.be.instanceOf( ButtonView ); + } ); + + it( 'binds button attributes to the model', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + dropdownView = createDropdown( model, locale ); + + assertBinding( dropdownView.buttonView, + modelDef, + [ + [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + ], + { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } + ); + } ); + + it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + dropdownView = createDropdown( model, locale ); + + dropdownView.isOpen = false; + expect( dropdownView.buttonView.isOn ).to.be.false; + + model.isOn = true; + expect( dropdownView.buttonView.isOn ).to.be.true; + + dropdownView.isOpen = true; + expect( dropdownView.buttonView.isOn ).to.be.true; + + model.isOn = false; + expect( dropdownView.buttonView.isOn ).to.be.true; + } ); + + it( 'binds dropdown#isEnabled to the model', () => { + const modelDef = { + label: 'foo', + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + dropdownView = createDropdown( model, locale ); + + assertBinding( dropdownView, + { isEnabled: true }, + [ + [ model, { isEnabled: false } ] + ], + { isEnabled: false } + ); + } ); + + describe( '#buttonView', () => { + it( 'accepts locale', () => { + expect( dropdownView.buttonView.locale ).to.equal( locale ); + } ); + + it( 'is a ButtonView instance', () => { + expect( dropdownView.buttonView ).to.be.instanceof( ButtonView ); + } ); + + it( 'delegates "execute" to "select" event', () => { + const spy = sinon.spy(); + + dropdownView.buttonView.on( 'select', spy ); + + dropdownView.buttonView.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + } ); + } ); + } ); + + describe( 'createSplitButtonDropdown()', () => { + let dropdownView, model; + + beforeEach( () => { + model = new Model(); + dropdownView = createSplitButtonDropdown( model, locale ); + } ); + + it( 'accepts locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + expect( dropdownView.panelView.locale ).to.equal( locale ); + } ); + + it( 'returns view', () => { + expect( dropdownView ).to.be.instanceOf( DropdownView ); + } ); + + it( 'creates dropdown#panelView out of DropdownPanelView', () => { + expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); + } ); + + it( 'creates dropdown#buttonView out of SplitButtonView', () => { + expect( dropdownView.buttonView ).to.be.instanceOf( SplitButtonView ); + } ); + + it( 'binds button attributes to the model', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + dropdownView = createDropdown( model, locale ); + + assertBinding( dropdownView.buttonView, + modelDef, + [ + [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + ], + { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } + ); + } ); + + it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { + const modelDef = { + label: 'foo', + isOn: false, + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + dropdownView = createDropdown( model, locale ); + + dropdownView.isOpen = false; + expect( dropdownView.buttonView.isOn ).to.be.false; + + model.isOn = true; + expect( dropdownView.buttonView.isOn ).to.be.true; + + dropdownView.isOpen = true; + expect( dropdownView.buttonView.isOn ).to.be.true; + + model.isOn = false; + expect( dropdownView.buttonView.isOn ).to.be.true; + } ); + + it( 'binds dropdown#isEnabled to the model', () => { + const modelDef = { + label: 'foo', + isEnabled: true, + withText: false, + tooltip: false + }; + + model = new Model( modelDef ); + dropdownView = createDropdown( model, locale ); + + assertBinding( dropdownView, + { isEnabled: true }, + [ + [ model, { isEnabled: false } ] + ], + { isEnabled: false } + ); + } ); + + describe( '#buttonView', () => { + it( 'accepts locale', () => { + expect( dropdownView.buttonView.locale ).to.equal( locale ); + } ); + + it( 'returns SplitButtonView instance', () => { + expect( dropdownView.buttonView ).to.be.instanceof( SplitButtonView ); + } ); + } ); + } ); +} ); From 2e2815fd0914345a4c0e9b680e2a604a6e914826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Sun, 28 Jan 2018 13:30:57 +0100 Subject: [PATCH 47/78] Changed: Remove dafult dropdown behavior helper methods from API. --- src/dropdown/helpers/closedropdownonblur.js | 28 ---- .../helpers/closedropdownonexecute.js | 20 --- .../helpers/focusdropdowncontentsonarrows.js | 33 ---- src/dropdown/utils.js | 62 ++++++++ tests/dropdown/helpers/closedropdownonblur.js | 75 --------- .../helpers/closedropdownonexecute.js | 37 ----- .../helpers/focusdropdowncontentsonarrows.js | 75 --------- tests/dropdown/manual/dropdown.js | 9 -- tests/dropdown/utils.js | 147 +++++++++++++++++- 9 files changed, 206 insertions(+), 280 deletions(-) delete mode 100644 src/dropdown/helpers/closedropdownonblur.js delete mode 100644 src/dropdown/helpers/closedropdownonexecute.js delete mode 100644 src/dropdown/helpers/focusdropdowncontentsonarrows.js delete mode 100644 tests/dropdown/helpers/closedropdownonblur.js delete mode 100644 tests/dropdown/helpers/closedropdownonexecute.js delete mode 100644 tests/dropdown/helpers/focusdropdowncontentsonarrows.js diff --git a/src/dropdown/helpers/closedropdownonblur.js b/src/dropdown/helpers/closedropdownonblur.js deleted file mode 100644 index 49bc9767..00000000 --- a/src/dropdown/helpers/closedropdownonblur.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers - */ - -import clickOutsideHandler from '../../bindings/clickoutsidehandler'; - -/** - * Adds a behavior to a dropdownView that closes opened dropdown on user click outside the dropdown. - * - * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - */ -export default function closeDropdownOnBlur( dropdownView ) { - dropdownView.on( 'render', () => { - clickOutsideHandler( { - emitter: dropdownView, - activator: () => dropdownView.isOpen, - callback: () => { - dropdownView.isOpen = false; - }, - contextElements: [ dropdownView.element ] - } ); - } ); -} diff --git a/src/dropdown/helpers/closedropdownonexecute.js b/src/dropdown/helpers/closedropdownonexecute.js deleted file mode 100644 index d49ef49a..00000000 --- a/src/dropdown/helpers/closedropdownonexecute.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers/closedropdownonexecute - */ - -/** - * Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. - * - * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - */ -export default function closeDropdownOnExecute( dropdownView ) { - // Close the dropdown when one of the list items has been executed. - dropdownView.on( 'execute', () => { - dropdownView.isOpen = false; - } ); -} diff --git a/src/dropdown/helpers/focusdropdowncontentsonarrows.js b/src/dropdown/helpers/focusdropdowncontentsonarrows.js deleted file mode 100644 index 5ee8c81d..00000000 --- a/src/dropdown/helpers/focusdropdowncontentsonarrows.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers/focusdropdowncontentsonarrows - */ - -/** - * Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. - * - * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView - */ -export default function focusDropdownContentsOnArrows( dropdownView ) { - // If the dropdown panel is already open, the arrow down key should - // focus the first element in list. - dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { - if ( dropdownView.isOpen ) { - dropdownView.panelView.focus(); - cancel(); - } - } ); - - // If the dropdown panel is already open, the arrow up key should - // focus the last element in the list. - dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { - if ( dropdownView.isOpen ) { - dropdownView.panelView.focusLast(); - cancel(); - } - } ); -} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 312bf526..3700418a 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -12,6 +12,8 @@ import DropdownView from './dropdownview'; import SplitButtonView from '../button/splitbuttonview'; import ButtonView from '../button/buttonview'; +import clickOutsideHandler from '../bindings/clickoutsidehandler'; + /** * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. @@ -45,6 +47,8 @@ export function createDropdown( model, locale ) { const dropdownView = prepareDropdown( locale, buttonView, model ); + addDefaultBehavior( dropdownView ); + return dropdownView; } @@ -81,6 +85,8 @@ export function createSplitButtonDropdown( model, locale ) { const dropdownView = prepareDropdown( locale, buttonView, model ); + addDefaultBehavior( dropdownView ); + buttonView.delegate( 'execute' ).to( dropdownView ); return dropdownView; @@ -121,3 +127,59 @@ function createButtonForDropdown( model, locale ) { return buttonView; } + +// @private +function addDefaultBehavior( dropdown ) { + closeDropdownOnBlur( dropdown ); + closeDropdownOnExecute( dropdown ); + focusDropdownContentsOnArrows( dropdown ); +} + +// Adds a behavior to a dropdownView that closes opened dropdown on user click outside the dropdown. +// +// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView +function closeDropdownOnBlur( dropdownView ) { + dropdownView.on( 'render', () => { + clickOutsideHandler( { + emitter: dropdownView, + activator: () => dropdownView.isOpen, + callback: () => { + dropdownView.isOpen = false; + }, + contextElements: [ dropdownView.element ] + } ); + } ); +} + +// Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. +// +// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView +function closeDropdownOnExecute( dropdownView ) { + // Close the dropdown when one of the list items has been executed. + dropdownView.on( 'execute', () => { + dropdownView.isOpen = false; + } ); +} + +// Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. +// +// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView +function focusDropdownContentsOnArrows( dropdownView ) { + // If the dropdown panel is already open, the arrow down key should + // focus the first element in list. + dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { + if ( dropdownView.isOpen ) { + dropdownView.panelView.focus(); + cancel(); + } + } ); + + // If the dropdown panel is already open, the arrow up key should + // focus the last element in the list. + dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { + if ( dropdownView.isOpen ) { + dropdownView.panelView.focusLast(); + cancel(); + } + } ); +} diff --git a/tests/dropdown/helpers/closedropdownonblur.js b/tests/dropdown/helpers/closedropdownonblur.js deleted file mode 100644 index 50a34737..00000000 --- a/tests/dropdown/helpers/closedropdownonblur.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document, Event */ - -import Model from '../../../src/model'; - -import { createDropdown } from '../../../src/dropdown/utils'; -import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; - -describe( 'closeDropdownOnBlur()', () => { - let dropdownView; - - beforeEach( () => { - dropdownView = createDropdown( new Model(), {} ); - - closeDropdownOnBlur( dropdownView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - if ( dropdownView.element ) { - dropdownView.element.remove(); - } - } ); - - it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { - // Open the dropdown. - dropdownView.isOpen = true; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Closed the dropdown. - expect( dropdownView.isOpen ).to.be.false; - - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still closed. - expect( dropdownView.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { - // Open the dropdown. - dropdownView.isOpen = true; - - // Event from view.element should be discarded. - dropdownView.element.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( dropdownView.isOpen ).to.be.true; - - // Event from within view.element should be discarded. - const child = document.createElement( 'div' ); - dropdownView.element.appendChild( child ); - - child.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( dropdownView.isOpen ).to.be.true; - } ); -} ); diff --git a/tests/dropdown/helpers/closedropdownonexecute.js b/tests/dropdown/helpers/closedropdownonexecute.js deleted file mode 100644 index 1e2e4a3a..00000000 --- a/tests/dropdown/helpers/closedropdownonexecute.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ - -import Model from '../../../src/model'; -import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; -import { createDropdown } from '../../../src/dropdown/utils'; - -describe( 'closeDropdownOnExecute()', () => { - let dropdownView; - - beforeEach( () => { - dropdownView = createDropdown( new Model(), {} ); - - closeDropdownOnExecute( dropdownView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - it( 'changes view#isOpen on view#execute', () => { - dropdownView.isOpen = true; - - dropdownView.fire( 'execute' ); - expect( dropdownView.isOpen ).to.be.false; - - dropdownView.fire( 'execute' ); - expect( dropdownView.isOpen ).to.be.false; - } ); -} ); diff --git a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js b/tests/dropdown/helpers/focusdropdowncontentsonarrows.js deleted file mode 100644 index 806e9c42..00000000 --- a/tests/dropdown/helpers/focusdropdowncontentsonarrows.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ - -import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; - -import View from '../../../src/view'; -import Model from '../../../src/model'; - -import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; -import { createDropdown } from './../../../src/dropdown/utils'; - -describe( 'focusDropdownContentsOnArrows()', () => { - let dropdownView; - let panelChildView; - - beforeEach( () => { - dropdownView = createDropdown( new Model(), {} ); - - panelChildView = new View(); - panelChildView.setTemplate( { tag: 'div' } ); - panelChildView.focus = () => {}; - panelChildView.focusLast = () => {}; - - // TODO: describe this as #contentView instead of #listView and #toolbarView - dropdownView.panelView.children.add( panelChildView ); - - focusDropdownContentsOnArrows( dropdownView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowdown, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( panelChildView, 'focus' ); - - dropdownView.isOpen = false; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - dropdownView.isOpen = true; - dropdownView.keystrokes.press( keyEvtData ); - - sinon.assert.calledOnce( spy ); - } ); - - it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowup, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( panelChildView, 'focusLast' ); - - dropdownView.isOpen = false; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - dropdownView.isOpen = true; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); -} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 06cebd79..43935453 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -17,9 +17,6 @@ import ButtonView from '../../../src/button/buttonview'; import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; -import closeDropdownOnBlur from '../../../src/dropdown/helpers/closedropdownonblur'; -import closeDropdownOnExecute from '../../../src/dropdown/helpers/closedropdownonexecute'; -import focusDropdownContentsOnArrows from '../../../src/dropdown/helpers/focusdropdowncontentsonarrows'; import { createDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { @@ -66,9 +63,6 @@ function testList() { const dropdownView = createDropdown( model, {} ); addListViewToDropdown( dropdownView, model, {} ); - closeDropdownOnBlur( dropdownView ); - closeDropdownOnExecute( dropdownView ); - focusDropdownContentsOnArrows( dropdownView ); dropdownView.on( 'execute', evt => { /* global console */ @@ -141,9 +135,6 @@ function testButton() { const toolbarDropdown = createDropdown( toolbarDropdownModel, locale ); addToolbarToDropdown( toolbarDropdown, toolbarDropdownModel ); - closeDropdownOnBlur( toolbarDropdown ); - closeDropdownOnExecute( toolbarDropdown ); - focusDropdownContentsOnArrows( toolbarDropdown ); ui.toolbarDropdown.add( toolbarDropdown ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index fcf1457d..7544a541 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -3,7 +3,10 @@ * For licensing, see LICENSE.md. */ +/* globals document Event */ + import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; +import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; import Model from '../../src/model'; @@ -12,18 +15,19 @@ import DropdownView from '../../src/dropdown/dropdownview'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import SplitButtonView from '../../src/button/splitbuttonview'; import { createDropdown, createSplitButtonDropdown } from '../../src/dropdown/utils'; +import View from '../../src/view'; const assertBinding = utilsTestUtils.assertBinding; describe( 'utils', () => { - let locale; + let locale, dropdownView; beforeEach( () => { locale = { t() {} }; } ); describe( 'createDropdown()', () => { - let dropdownView, model; + let model; beforeEach( () => { model = new Model(); @@ -132,10 +136,12 @@ describe( 'utils', () => { sinon.assert.calledOnce( spy ); } ); } ); + + addDefaultBehaviorTests(); } ); describe( 'createSplitButtonDropdown()', () => { - let dropdownView, model; + let model; beforeEach( () => { model = new Model(); @@ -234,5 +240,140 @@ describe( 'utils', () => { expect( dropdownView.buttonView ).to.be.instanceof( SplitButtonView ); } ); } ); + + addDefaultBehaviorTests(); } ); + + function addDefaultBehaviorTests() { + describe( 'hasDefaultBehavior', () => { + describe( 'closeDropdownOnBlur()', () => { + beforeEach( () => { + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + // Closed the dropdown. + expect( dropdownView.isOpen ).to.be.false; + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + // Dropdown is still closed. + expect( dropdownView.isOpen ).to.be.false; + } ); + + it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + + // Event from view.element should be discarded. + dropdownView.element.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + + // Event from within view.element should be discarded. + const child = document.createElement( 'div' ); + dropdownView.element.appendChild( child ); + + child.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + } ); + } ); + + describe( 'closeDropdownOnExecute()', () => { + beforeEach( () => { + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( 'changes view#isOpen on view#execute', () => { + dropdownView.isOpen = true; + + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + } ); + } ); + + describe( 'focusDropdownContentsOnArrows()', () => { + let panelChildView; + + beforeEach( () => { + panelChildView = new View(); + panelChildView.setTemplate( { tag: 'div' } ); + panelChildView.focus = () => {}; + panelChildView.focusLast = () => {}; + + // TODO: describe this as #contentView instead of #listView and #toolbarView + dropdownView.panelView.children.add( panelChildView ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowdown, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focus' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + + sinon.assert.calledOnce( spy ); + } ); + + it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowup, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focusLast' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); + + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.calledOnce( spy ); + } ); + } ); + } ); + } } ); From 30e1e9b6767bf081483cf406b3e00b22a92d2543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Sun, 28 Jan 2018 13:48:25 +0100 Subject: [PATCH 48/78] Added: Introduce `bindOneToMany()` binding helper. --- src/bindings/bindonetomany.js | 28 ++++++++++++++ src/bindings/getbindingtargets.js | 21 ----------- .../helpers/enablemodelifoneisenabled.js | 19 ---------- tests/bindings/bindonetomany.js | 37 +++++++++++++++++++ .../helpers/enablemodelifoneisenabled.js | 34 ----------------- 5 files changed, 65 insertions(+), 74 deletions(-) create mode 100644 src/bindings/bindonetomany.js delete mode 100644 src/bindings/getbindingtargets.js delete mode 100644 src/dropdown/helpers/enablemodelifoneisenabled.js create mode 100644 tests/bindings/bindonetomany.js delete mode 100644 tests/dropdown/helpers/enablemodelifoneisenabled.js diff --git a/src/bindings/bindonetomany.js b/src/bindings/bindonetomany.js new file mode 100644 index 00000000..9643abe7 --- /dev/null +++ b/src/bindings/bindonetomany.js @@ -0,0 +1,28 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/bindings/getbindingtargets + */ + +export default function bindOneToMany( dropdownModel, boundProperty, collection, collectionProperty, callback ) { + dropdownModel.bind( boundProperty ).to( + // Bind to #isOn of each button... + ...getBindingTargets( collection, collectionProperty ), + // ...and chose the title of the first one which #isOn is true. + callback + ); +} + +// Returns an array of binding components for +// {@link module:utils/observablemixin~Observable#bind} from a set of iterable +// buttons. +// +// @param {Iterable.} buttons +// @param {String} attribute +// @returns {Array.} +function getBindingTargets( buttons, attribute ) { + return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); +} diff --git a/src/bindings/getbindingtargets.js b/src/bindings/getbindingtargets.js deleted file mode 100644 index d61fa35c..00000000 --- a/src/bindings/getbindingtargets.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/bindings/getbindingtargets - */ - -/** - * Returns an array of binding components for - * {@link module:utils/observablemixin~Observable#bind} from a set of iterable - * buttons. - * - * @param {Iterable.} buttons - * @param {String} attribute - * @returns {Array.} - */ -export default function getBindingTargets( buttons, attribute ) { - return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); -} diff --git a/src/dropdown/helpers/enablemodelifoneisenabled.js b/src/dropdown/helpers/enablemodelifoneisenabled.js deleted file mode 100644 index 6d394efd..00000000 --- a/src/dropdown/helpers/enablemodelifoneisenabled.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import getBindingTargets from '../../bindings/getbindingtargets'; - -/** - * @module ui/dropdown/helpers/enablemodelifoneisenabled - */ - -export default function enableModelIfOneIsEnabled( model, observables ) { - model.bind( 'isEnabled' ).to( - // Bind to #isEnabled of each observable... - ...getBindingTargets( observables, 'isEnabled' ), - // ...and set it true if any observable #isEnabled is true. - ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) - ); -} diff --git a/tests/bindings/bindonetomany.js b/tests/bindings/bindonetomany.js new file mode 100644 index 00000000..c8a04f1d --- /dev/null +++ b/tests/bindings/bindonetomany.js @@ -0,0 +1,37 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import Model from '../../src/model'; + +import bindOneToMany from './../../src/bindings/bindonetomany'; + +describe( 'bindOneToMany()', () => { + it( 'binds observable property to collection property using callback', () => { + const model = new Model(); + const observables = [ + new Model( { property: false } ), + new Model( { property: false } ), + new Model( { property: false } ) + ]; + + bindOneToMany( model, 'property', observables, 'property', + ( ...areEnabled ) => areEnabled.some( property => property ) + ); + + expect( model.property ).to.be.false; + + observables[ 0 ].property = true; + + expect( model.property ).to.be.true; + + observables[ 0 ].property = false; + + expect( model.property ).to.be.false; + + observables[ 1 ].property = true; + + expect( model.property ).to.be.true; + } ); +} ); diff --git a/tests/dropdown/helpers/enablemodelifoneisenabled.js b/tests/dropdown/helpers/enablemodelifoneisenabled.js deleted file mode 100644 index 8b2b28aa..00000000 --- a/tests/dropdown/helpers/enablemodelifoneisenabled.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import Model from '../../../src/model'; - -import enableModelIfOneIsEnabled from '../../../src/dropdown/helpers/enablemodelifoneisenabled'; - -describe( 'enableModelIfOneIsEnabled()', () => { - it( 'Bind to #isEnabled of each observable and set it true if any observable #isEnabled is true', () => { - const model = new Model(); - const observables = [ - new Model( { isEnabled: false } ), - new Model( { isEnabled: false } ), - new Model( { isEnabled: false } ) - ]; - enableModelIfOneIsEnabled( model, observables ); - - expect( model.isEnabled ).to.be.false; - - observables[ 0 ].isEnabled = true; - - expect( model.isEnabled ).to.be.true; - - observables[ 0 ].isEnabled = false; - - expect( model.isEnabled ).to.be.false; - - observables[ 1 ].isEnabled = true; - - expect( model.isEnabled ).to.be.true; - } ); -} ); From bcd04c5e2ac37228cc922c323c69b4550ea462eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 10:45:35 +0100 Subject: [PATCH 49/78] Changed: Move `addToolbarToDropdown()` helper to ui/dropdown/utils. --- src/dropdown/helpers/addtoolbartodropdown.js | 65 -------------- src/dropdown/utils.js | 55 ++++++++++++ .../dropdown/helpers/addtoolbartodropdown.js | 84 ------------------- tests/dropdown/manual/dropdown.js | 8 +- tests/dropdown/utils.js | 66 ++++++++++++++- 5 files changed, 123 insertions(+), 155 deletions(-) delete mode 100644 src/dropdown/helpers/addtoolbartodropdown.js delete mode 100644 tests/dropdown/helpers/addtoolbartodropdown.js diff --git a/src/dropdown/helpers/addtoolbartodropdown.js b/src/dropdown/helpers/addtoolbartodropdown.js deleted file mode 100644 index f2be79a0..00000000 --- a/src/dropdown/helpers/addtoolbartodropdown.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers/addtoolbartodropdown - */ - -import ToolbarView from '../../toolbar/toolbarview'; - -import '../../../theme/components/dropdown/toolbardropdown.css'; - -/** - * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using - * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. - * - * const buttons = []; - * - * buttons.push( new ButtonView() ); - * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); - * - * const model = new Model( { - * label: 'A button dropdown', - * isVertical: true, - * buttons - * } ); - * - * const dropdown = createButtonDropdown( model, locale ); - * - * // Will render a vertical button dropdown labeled "A button dropdown" - * // with a button group in the panel containing two buttons. - * dropdown.render() - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown}. - * - * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} The button dropdown view instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} - */ -export default function addToolbarToDropdown( dropdownView, model ) { - const toolbarView = dropdownView.toolbarView = new ToolbarView(); - - toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); - - dropdownView.extendTemplate( { - attributes: { - class: [ 'ck-toolbar-dropdown' ] - } - } ); - - // TODO: make this param of method instead of model property? - model.buttons.map( view => toolbarView.items.add( view ) ); - - dropdownView.panelView.children.add( toolbarView ); - toolbarView.items.delegate( 'execute' ).to( dropdownView ); - - return toolbarView; -} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 3700418a..8111a683 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -11,9 +11,12 @@ import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; import SplitButtonView from '../button/splitbuttonview'; import ButtonView from '../button/buttonview'; +import ToolbarView from '../toolbar/toolbarview'; import clickOutsideHandler from '../bindings/clickoutsidehandler'; +import '../../theme/components/dropdown/toolbardropdown.css'; + /** * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. @@ -92,6 +95,58 @@ export function createSplitButtonDropdown( model, locale ) { return dropdownView; } +/** + * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using + * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. + * + * const buttons = []; + * + * buttons.push( new ButtonView() ); + * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); + * + * const model = new Model( { + * label: 'A button dropdown', + * isVertical: true, + * buttons + * } ); + * + * const dropdown = createButtonDropdown( model, locale ); + * + * // Will render a vertical button dropdown labeled "A button dropdown" + * // with a button group in the panel containing two buttons. + * dropdown.render() + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * See {@link module:ui/dropdown/createdropdown~createDropdown}. + * + * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/dropdownview~DropdownView} + */ +export function addToolbarToDropdown( dropdownView, buttons, model ) { + const toolbarView = dropdownView.toolbarView = new ToolbarView(); + + toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); + + dropdownView.extendTemplate( { + attributes: { + class: [ 'ck-toolbar-dropdown' ] + } + } ); + + // TODO: bind buttons to items in toolbar + buttons.map( view => toolbarView.items.add( view ) ); + + dropdownView.panelView.children.add( toolbarView ); + toolbarView.items.delegate( 'execute' ).to( dropdownView ); + + return toolbarView; +} + // @private function prepareDropdown( locale, buttonView, model ) { const panelView = new DropdownPanelView( locale ); diff --git a/tests/dropdown/helpers/addtoolbartodropdown.js b/tests/dropdown/helpers/addtoolbartodropdown.js deleted file mode 100644 index f2465f62..00000000 --- a/tests/dropdown/helpers/addtoolbartodropdown.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ - -import Model from '../../../src/model'; - -import ButtonView from '../../../src/button/buttonview'; -import ToolbarView from '../../../src/toolbar/toolbarview'; -import { createDropdown } from '../../../src/dropdown/utils'; - -import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; - -describe( 'addToolbarToDropdown()', () => { - let dropdownView, model, locale, buttons; - - beforeEach( () => { - locale = { t() {} }; - - buttons = [ 'foo', 'bar' ].map( icon => { - const button = new ButtonView(); - - button.icon = icon; - - return button; - } ); - - model = new Model( { - isVertical: true, - buttons - } ); - - dropdownView = createDropdown( model, locale ); - - addToolbarToDropdown( dropdownView, model ); - - dropdownView.render(); - - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - it( 'sets view#locale', () => { - expect( dropdownView.locale ).to.equal( locale ); - } ); - - it( 'sets view class', () => { - expect( dropdownView.element.classList.contains( 'ck-toolbar-dropdown' ) ).to.be.true; - } ); - - describe( 'view#toolbarView', () => { - it( 'is created', () => { - const panelChildren = dropdownView.panelView.children; - - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); - expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); - } ); - - it( 'delegates view.toolbarView.items#execute to the view', done => { - dropdownView.on( 'execute', evt => { - expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); - - done(); - } ); - - dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); - } ); - - it( 'reacts on model#isVertical', () => { - model.isVertical = false; - expect( dropdownView.toolbarView.isVertical ).to.be.false; - - model.isVertical = true; - expect( dropdownView.toolbarView.isVertical ).to.be.true; - } ); - } ); -} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 43935453..6c95957b 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -16,8 +16,7 @@ import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center. import ButtonView from '../../../src/button/buttonview'; import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; -import addToolbarToDropdown from '../../../src/dropdown/helpers/addtoolbartodropdown'; -import { createDropdown } from '../../../src/dropdown/utils'; +import { createDropdown, addToolbarToDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', @@ -128,13 +127,12 @@ function testButton() { } ); const toolbarDropdownModel = new Model( { - isVertical: true, - buttons: buttonViews + isVertical: true } ); const toolbarDropdown = createDropdown( toolbarDropdownModel, locale ); - addToolbarToDropdown( toolbarDropdown, toolbarDropdownModel ); + addToolbarToDropdown( toolbarDropdown, buttonViews, toolbarDropdownModel ); ui.toolbarDropdown.add( toolbarDropdown ); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 7544a541..abcc3ecd 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -14,8 +14,9 @@ import ButtonView from '../../src/button/buttonview'; import DropdownView from '../../src/dropdown/dropdownview'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import SplitButtonView from '../../src/button/splitbuttonview'; -import { createDropdown, createSplitButtonDropdown } from '../../src/dropdown/utils'; import View from '../../src/view'; +import ToolbarView from '../../src/toolbar/toolbarview'; +import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown } from '../../src/dropdown/utils'; const assertBinding = utilsTestUtils.assertBinding; @@ -244,6 +245,69 @@ describe( 'utils', () => { addDefaultBehaviorTests(); } ); + describe( 'addToolbarToDropdown()', () => { + let model, buttons; + + beforeEach( () => { + buttons = [ 'foo', 'bar' ].map( icon => { + const button = new ButtonView(); + + button.icon = icon; + + return button; + } ); + + model = new Model( { isVertical: true } ); + + dropdownView = createDropdown( model, locale ); + addToolbarToDropdown( dropdownView, buttons, model ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + it( 'sets view#locale', () => { + expect( dropdownView.locale ).to.equal( locale ); + } ); + + it( 'sets view class', () => { + expect( dropdownView.element.classList.contains( 'ck-toolbar-dropdown' ) ).to.be.true; + } ); + + describe( 'view#toolbarView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; + + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.toolbarView ); + expect( dropdownView.toolbarView ).to.be.instanceof( ToolbarView ); + } ); + + it( 'delegates view.toolbarView.items#execute to the view', done => { + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.toolbarView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.toolbarView.items.get( 0 ), dropdownView ] ); + + done(); + } ); + + dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); + } ); + + it( 'reacts on model#isVertical', () => { + model.isVertical = false; + expect( dropdownView.toolbarView.isVertical ).to.be.false; + + model.isVertical = true; + expect( dropdownView.toolbarView.isVertical ).to.be.true; + } ); + } ); + } ); + function addDefaultBehaviorTests() { describe( 'hasDefaultBehavior', () => { describe( 'closeDropdownOnBlur()', () => { From 2e7ab21c13469ec94f3350cd96f4bb439df1bef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 10:55:31 +0100 Subject: [PATCH 50/78] Changed: Move `addListViewToDropdown()` helper to ui/dropdown/utils. --- src/dropdown/helpers/addlistviewtodropdown.js | 68 ------------- src/dropdown/utils.js | 59 ++++++++++++ .../dropdown/helpers/addlistviewtodropdown.js | 95 ------------------- tests/dropdown/manual/dropdown.js | 8 +- tests/dropdown/utils.js | 82 +++++++++++++++- 5 files changed, 143 insertions(+), 169 deletions(-) delete mode 100644 src/dropdown/helpers/addlistviewtodropdown.js delete mode 100644 tests/dropdown/helpers/addlistviewtodropdown.js diff --git a/src/dropdown/helpers/addlistviewtodropdown.js b/src/dropdown/helpers/addlistviewtodropdown.js deleted file mode 100644 index 96cfc099..00000000 --- a/src/dropdown/helpers/addlistviewtodropdown.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/helpers/addlistviewtodropdown - */ - -import ListView from '../../list/listview'; -import ListItemView from '../../list/listitemview'; - -/** - * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using - * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. - * - * const items = new Collection(); - * - * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); - * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); - * - * const model = new Model( { - * isEnabled: true, - * items, - * isOn: false, - * label: 'A dropdown' - * } ); - * - * const dropdown = createListDropdown( model, locale ); - * - * // Will render a dropdown labeled "A dropdown" with a list in the panel - * // containing two items. - * dropdown.render() - * document.body.appendChild( dropdown.element ); - * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * The - * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} - * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the - * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. - * - * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. - */ -export default function addListViewToDropdown( dropdownView, model, locale ) { - const listView = dropdownView.listView = new ListView( locale ); - - // TODO: make this param of method instead of model property? - listView.items.bindTo( model.items ).using( itemModel => { - const item = new ListItemView( locale ); - - // Bind all attributes of the model to the item view. - item.bind( ...Object.keys( itemModel ) ).to( itemModel ); - - return item; - } ); - - dropdownView.panelView.children.add( listView ); - listView.items.delegate( 'execute' ).to( dropdownView ); - - return listView; -} diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 8111a683..b6348298 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -12,6 +12,8 @@ import DropdownView from './dropdownview'; import SplitButtonView from '../button/splitbuttonview'; import ButtonView from '../button/buttonview'; import ToolbarView from '../toolbar/toolbarview'; +import ListView from '../list/listview'; +import ListItemView from '../list/listitemview'; import clickOutsideHandler from '../bindings/clickoutsidehandler'; @@ -147,6 +149,63 @@ export function addToolbarToDropdown( dropdownView, buttons, model ) { return toolbarView; } +/** + * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using + * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. + * + * const items = new Collection(); + * + * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); + * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); + * + * const model = new Model( { + * isEnabled: true, + * items, + * isOn: false, + * label: 'A dropdown' + * } ); + * + * const dropdown = createListDropdown( model, locale ); + * + * // Will render a dropdown labeled "A dropdown" with a list in the panel + * // containing two items. + * dropdown.render() + * document.body.appendChild( dropdown.element ); + * + * The model instance remains in control of the dropdown after it has been created. E.g. changes to the + * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the + * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * + * The + * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} + * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the + * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. + * + * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. + * + * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. + * @param {module:utils/locale~Locale} locale The locale instance. + * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. + */ +export function addListViewToDropdown( dropdownView, listViewItems, model, locale ) { + const listView = dropdownView.listView = new ListView( locale ); + + // TODO: make this param of method instead of model property? + listView.items.bindTo( listViewItems ).using( itemModel => { + const item = new ListItemView( locale ); + + // Bind all attributes of the model to the item view. + item.bind( ...Object.keys( itemModel ) ).to( itemModel ); + + return item; + } ); + + dropdownView.panelView.children.add( listView ); + listView.items.delegate( 'execute' ).to( dropdownView ); + + return listView; +} + // @private function prepareDropdown( locale, buttonView, model ) { const panelView = new DropdownPanelView( locale ); diff --git a/tests/dropdown/helpers/addlistviewtodropdown.js b/tests/dropdown/helpers/addlistviewtodropdown.js deleted file mode 100644 index df59b8a1..00000000 --- a/tests/dropdown/helpers/addlistviewtodropdown.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ - -import Collection from '@ckeditor/ckeditor5-utils/src/collection'; - -import Model from '../../../src/model'; - -import ListItemView from '../../../src/list/listitemview'; -import ListView from '../../../src/list/listview'; - -import { createDropdown } from './../../../src/dropdown/utils'; - -import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; - -describe( 'addListViewToDropdown()', () => { - let dropdownView, model, locale, items; - - beforeEach( () => { - locale = { t() {} }; - items = new Collection(); - model = new Model( { - isEnabled: true, - items, - isOn: false, - label: 'foo' - } ); - - dropdownView = createDropdown( model, locale ); - - addListViewToDropdown( dropdownView, model, locale ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - describe( 'view#listView', () => { - it( 'is created', () => { - const panelChildren = dropdownView.panelView.children; - - expect( panelChildren ).to.have.length( 1 ); - expect( panelChildren.get( 0 ) ).to.equal( dropdownView.listView ); - expect( dropdownView.listView ).to.be.instanceof( ListView ); - } ); - - it( 'is bound to model#items', () => { - items.add( new Model( { label: 'a', style: 'b' } ) ); - items.add( new Model( { label: 'c', style: 'd' } ) ); - - expect( dropdownView.listView.items ).to.have.length( 2 ); - expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView ); - expect( dropdownView.listView.items.get( 1 ).label ).to.equal( 'c' ); - expect( dropdownView.listView.items.get( 1 ).style ).to.equal( 'd' ); - - items.remove( 1 ); - expect( dropdownView.listView.items ).to.have.length( 1 ); - expect( dropdownView.listView.items.get( 0 ).label ).to.equal( 'a' ); - expect( dropdownView.listView.items.get( 0 ).style ).to.equal( 'b' ); - } ); - - it( 'binds all attributes in model#items', () => { - const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } ); - - items.add( itemModel ); - - const item = dropdownView.listView.items.get( 0 ); - - expect( item.foo ).to.equal( 'bar' ); - expect( item.baz ).to.equal( 'qux' ); - - itemModel.baz = 'foo?'; - expect( item.baz ).to.equal( 'foo?' ); - } ); - - it( 'delegates view.listView#execute to the view', done => { - items.add( new Model( { label: 'a', style: 'b' } ) ); - - dropdownView.on( 'execute', evt => { - expect( evt.source ).to.equal( dropdownView.listView.items.get( 0 ) ); - expect( evt.path ).to.deep.equal( [ dropdownView.listView.items.get( 0 ), dropdownView ] ); - - done(); - } ); - - dropdownView.listView.items.get( 0 ).fire( 'execute' ); - } ); - } ); -} ); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 6c95957b..5712a540 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -15,8 +15,7 @@ import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.sv import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; -import addListViewToDropdown from '../../../src/dropdown/helpers/addlistviewtodropdown'; -import { createDropdown, addToolbarToDropdown } from '../../../src/dropdown/utils'; +import { createDropdown, addToolbarToDropdown, addListViewToDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', @@ -55,13 +54,12 @@ function testList() { label: 'ListDropdown', isEnabled: true, isOn: false, - withText: true, - items: collection + withText: true } ); const dropdownView = createDropdown( model, {} ); - addListViewToDropdown( dropdownView, model, {} ); + addListViewToDropdown( dropdownView, collection, model, {} ); dropdownView.on( 'execute', evt => { /* global console */ diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index abcc3ecd..8e1b1c68 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -16,7 +16,10 @@ import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import SplitButtonView from '../../src/button/splitbuttonview'; import View from '../../src/view'; import ToolbarView from '../../src/toolbar/toolbarview'; -import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown } from '../../src/dropdown/utils'; +import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown, addListViewToDropdown } from '../../src/dropdown/utils'; +import ListItemView from '../../src/list/listitemview'; +import ListView from '../../src/list/listview'; +import Collection from '../../../ckeditor5-utils/src/collection'; const assertBinding = utilsTestUtils.assertBinding; @@ -308,6 +311,83 @@ describe( 'utils', () => { } ); } ); + describe( 'addListViewToDropdown()', () => { + let dropdownView, model, locale, items; + + beforeEach( () => { + locale = { t() {} }; + items = new Collection(); + model = new Model( { + isEnabled: true, + isOn: false, + label: 'foo' + } ); + + dropdownView = createDropdown( model, locale ); + + addListViewToDropdown( dropdownView, items, model, locale ); + + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); + + afterEach( () => { + dropdownView.element.remove(); + } ); + + describe( 'view#listView', () => { + it( 'is created', () => { + const panelChildren = dropdownView.panelView.children; + + expect( panelChildren ).to.have.length( 1 ); + expect( panelChildren.get( 0 ) ).to.equal( dropdownView.listView ); + expect( dropdownView.listView ).to.be.instanceof( ListView ); + } ); + + it( 'is bound to model#items', () => { + items.add( new Model( { label: 'a', style: 'b' } ) ); + items.add( new Model( { label: 'c', style: 'd' } ) ); + + expect( dropdownView.listView.items ).to.have.length( 2 ); + expect( dropdownView.listView.items.get( 0 ) ).to.be.instanceOf( ListItemView ); + expect( dropdownView.listView.items.get( 1 ).label ).to.equal( 'c' ); + expect( dropdownView.listView.items.get( 1 ).style ).to.equal( 'd' ); + + items.remove( 1 ); + expect( dropdownView.listView.items ).to.have.length( 1 ); + expect( dropdownView.listView.items.get( 0 ).label ).to.equal( 'a' ); + expect( dropdownView.listView.items.get( 0 ).style ).to.equal( 'b' ); + } ); + + it( 'binds all attributes in model#items', () => { + const itemModel = new Model( { label: 'a', style: 'b', foo: 'bar', baz: 'qux' } ); + + items.add( itemModel ); + + const item = dropdownView.listView.items.get( 0 ); + + expect( item.foo ).to.equal( 'bar' ); + expect( item.baz ).to.equal( 'qux' ); + + itemModel.baz = 'foo?'; + expect( item.baz ).to.equal( 'foo?' ); + } ); + + it( 'delegates view.listView#execute to the view', done => { + items.add( new Model( { label: 'a', style: 'b' } ) ); + + dropdownView.on( 'execute', evt => { + expect( evt.source ).to.equal( dropdownView.listView.items.get( 0 ) ); + expect( evt.path ).to.deep.equal( [ dropdownView.listView.items.get( 0 ), dropdownView ] ); + + done(); + } ); + + dropdownView.listView.items.get( 0 ).fire( 'execute' ); + } ); + } ); + } ); + function addDefaultBehaviorTests() { describe( 'hasDefaultBehavior', () => { describe( 'closeDropdownOnBlur()', () => { From fec44e76a1ede3f57ec0a90773089794a820b33a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 11:43:06 +0100 Subject: [PATCH 51/78] Changed: Bind dropdown behavior to `DropdownView` instead of `Model`. --- src/dropdown/utils.js | 34 +++++++------- tests/dropdown/manual/dropdown.js | 42 +++++++++--------- tests/dropdown/utils.js | 74 ++++++++++++++----------------- 3 files changed, 73 insertions(+), 77 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b6348298..307562d2 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -47,10 +47,10 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ -export function createDropdown( model, locale ) { - const buttonView = createButtonForDropdown( model, locale ); +export function createDropdown( locale ) { + const buttonView = createButtonForDropdown( locale ); - const dropdownView = prepareDropdown( locale, buttonView, model ); + const dropdownView = prepareDropdown( locale, buttonView ); addDefaultBehavior( dropdownView ); @@ -85,10 +85,10 @@ export function createDropdown( model, locale ) { * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ -export function createSplitButtonDropdown( model, locale ) { - const buttonView = createSplitButtonForDropdown( model, locale ); +export function createSplitButtonDropdown( locale ) { + const buttonView = createSplitButtonForDropdown( locale ); - const dropdownView = prepareDropdown( locale, buttonView, model ); + const dropdownView = prepareDropdown( locale, buttonView ); addDefaultBehavior( dropdownView ); @@ -129,10 +129,11 @@ export function createSplitButtonDropdown( model, locale ) { * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} */ -export function addToolbarToDropdown( dropdownView, buttons, model ) { +export function addToolbarToDropdown( dropdownView, buttons ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); - toolbarView.bind( 'isVertical' ).to( model, 'isVertical' ); + // dropdownView.set( 'isVertical' ); + toolbarView.bind( 'isVertical' ).to( dropdownView, 'isVertical' ); dropdownView.extendTemplate( { attributes: { @@ -187,7 +188,8 @@ export function addToolbarToDropdown( dropdownView, buttons, model ) { * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. */ -export function addListViewToDropdown( dropdownView, listViewItems, model, locale ) { +export function addListViewToDropdown( dropdownView, listViewItems ) { + const locale = dropdownView.locale; const listView = dropdownView.listView = new ListView( locale ); // TODO: make this param of method instead of model property? @@ -207,14 +209,16 @@ export function addListViewToDropdown( dropdownView, listViewItems, model, local } // @private -function prepareDropdown( locale, buttonView, model ) { +function prepareDropdown( locale, buttonView ) { const panelView = new DropdownPanelView( locale ); const dropdownView = new DropdownView( locale, buttonView, panelView ); - dropdownView.bind( 'isEnabled' ).to( model ); + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( dropdownView ); - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model ); - buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + // TODO: buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + dropdownView.set( 'isOn', true ); + + buttonView.bind( 'isOn' ).to( dropdownView, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { return isOn || isOpen; } ); @@ -222,7 +226,7 @@ function prepareDropdown( locale, buttonView, model ) { } // @private -function createSplitButtonForDropdown( model, locale ) { +function createSplitButtonForDropdown( locale ) { const splitButtonView = new SplitButtonView( locale ); // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. @@ -233,7 +237,7 @@ function createSplitButtonForDropdown( model, locale ) { } // @private -function createButtonForDropdown( model, locale ) { +function createButtonForDropdown( locale ) { const buttonView = new ButtonView( locale ); // Dropdown expects "select" event to show contents. diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index 5712a540..caf18ed0 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -26,15 +26,15 @@ const ui = testUtils.createTestUIView( { } ); function testEmpty() { - const model = new Model( { + const dropdownView = createDropdown( {} ); + + dropdownView.set( { label: 'Dropdown', isEnabled: true, isOn: false, withText: true } ); - const dropdownView = createDropdown( model, {} ); - ui.dropdown.add( dropdownView ); dropdownView.panelView.element.innerHTML = 'Empty panel. There is no child view in this DropdownPanelView.'; @@ -50,16 +50,16 @@ function testList() { } ) ); } ); - const model = new Model( { + const dropdownView = createDropdown( {} ); + + dropdownView.set( { label: 'ListDropdown', isEnabled: true, isOn: false, withText: true } ); - const dropdownView = createDropdown( model, {} ); - - addListViewToDropdown( dropdownView, collection, model, {} ); + addListViewToDropdown( dropdownView, collection ); dropdownView.on( 'execute', evt => { /* global console */ @@ -68,21 +68,23 @@ function testList() { ui.listDropdown.add( dropdownView ); - window.listDropdownModel = model; window.listDropdownCollection = collection; window.Model = Model; } function testSharedModel() { - const model = new Model( { + const dropdownSettings = { label: 'Shared Model', isEnabled: true, isOn: false, withText: true - } ); + }; - const dropdownView1 = createDropdown( model, {} ); - const dropdownView2 = createDropdown( model, {} ); + const dropdownView1 = createDropdown( {} ); + const dropdownView2 = createDropdown( {} ); + + dropdownView1.set( dropdownSettings ); + dropdownView2.set( dropdownSettings ); ui.dropdownShared.add( dropdownView1 ); ui.dropdownShared.add( dropdownView2 ); @@ -91,15 +93,15 @@ function testSharedModel() { } function testLongLabel() { - const model = new Model( { + const dropdownView = createDropdown( {} ); + + dropdownView.set( { label: 'Dropdown with a very long label', isEnabled: true, isOn: false, withText: true } ); - const dropdownView = createDropdown( model, {} ); - ui.dropdownLabel.add( dropdownView ); dropdownView.panelView.element.innerHTML = 'Empty panel. There is no child view in this DropdownPanelView.'; @@ -124,18 +126,14 @@ function testButton() { return buttonView; } ); - const toolbarDropdownModel = new Model( { - isVertical: true - } ); - - const toolbarDropdown = createDropdown( toolbarDropdownModel, locale ); + const toolbarDropdown = createDropdown( locale ); + toolbarDropdown.set( 'isVertical', true ); - addToolbarToDropdown( toolbarDropdown, buttonViews, toolbarDropdownModel ); + addToolbarToDropdown( toolbarDropdown, buttonViews ); ui.toolbarDropdown.add( toolbarDropdown ); window.buttons = buttons; - window.toolbarDropdownModel = toolbarDropdownModel; } testEmpty(); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 8e1b1c68..f7028934 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -31,11 +31,8 @@ describe( 'utils', () => { } ); describe( 'createDropdown()', () => { - let model; - beforeEach( () => { - model = new Model(); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); } ); it( 'accepts locale', () => { @@ -64,13 +61,14 @@ describe( 'utils', () => { tooltip: false }; - model = new Model( modelDef ); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); + + dropdownView.set( modelDef ); assertBinding( dropdownView.buttonView, modelDef, [ - [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + [ dropdownView, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] ], { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ); @@ -85,19 +83,19 @@ describe( 'utils', () => { tooltip: false }; - model = new Model( modelDef ); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); + dropdownView.set( modelDef ); dropdownView.isOpen = false; expect( dropdownView.buttonView.isOn ).to.be.false; - model.isOn = true; + dropdownView.isOn = true; expect( dropdownView.buttonView.isOn ).to.be.true; dropdownView.isOpen = true; expect( dropdownView.buttonView.isOn ).to.be.true; - model.isOn = false; + dropdownView.isOn = false; expect( dropdownView.buttonView.isOn ).to.be.true; } ); @@ -109,13 +107,13 @@ describe( 'utils', () => { tooltip: false }; - model = new Model( modelDef ); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); + dropdownView.set( modelDef ); assertBinding( dropdownView, { isEnabled: true }, [ - [ model, { isEnabled: false } ] + [ dropdownView, { isEnabled: false } ] ], { isEnabled: false } ); @@ -145,11 +143,8 @@ describe( 'utils', () => { } ); describe( 'createSplitButtonDropdown()', () => { - let model; - beforeEach( () => { - model = new Model(); - dropdownView = createSplitButtonDropdown( model, locale ); + dropdownView = createSplitButtonDropdown( locale ); } ); it( 'accepts locale', () => { @@ -178,13 +173,13 @@ describe( 'utils', () => { tooltip: false }; - model = new Model( modelDef ); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); + dropdownView.set( modelDef ); assertBinding( dropdownView.buttonView, modelDef, [ - [ model, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + [ dropdownView, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] ], { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ); @@ -199,19 +194,19 @@ describe( 'utils', () => { tooltip: false }; - model = new Model( modelDef ); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); + dropdownView.set( modelDef ); dropdownView.isOpen = false; expect( dropdownView.buttonView.isOn ).to.be.false; - model.isOn = true; + dropdownView.isOn = true; expect( dropdownView.buttonView.isOn ).to.be.true; dropdownView.isOpen = true; expect( dropdownView.buttonView.isOn ).to.be.true; - model.isOn = false; + dropdownView.isOn = false; expect( dropdownView.buttonView.isOn ).to.be.true; } ); @@ -223,13 +218,13 @@ describe( 'utils', () => { tooltip: false }; - model = new Model( modelDef ); - dropdownView = createDropdown( model, locale ); + dropdownView = createDropdown( locale ); + dropdownView.set( modelDef ); assertBinding( dropdownView, { isEnabled: true }, [ - [ model, { isEnabled: false } ] + [ dropdownView, { isEnabled: false } ] ], { isEnabled: false } ); @@ -249,7 +244,7 @@ describe( 'utils', () => { } ); describe( 'addToolbarToDropdown()', () => { - let model, buttons; + let buttons; beforeEach( () => { buttons = [ 'foo', 'bar' ].map( icon => { @@ -260,10 +255,10 @@ describe( 'utils', () => { return button; } ); - model = new Model( { isVertical: true } ); + dropdownView = createDropdown( locale ); + dropdownView.set( 'isVertical', true ); - dropdownView = createDropdown( model, locale ); - addToolbarToDropdown( dropdownView, buttons, model ); + addToolbarToDropdown( dropdownView, buttons ); dropdownView.render(); document.body.appendChild( dropdownView.element ); @@ -302,30 +297,29 @@ describe( 'utils', () => { } ); it( 'reacts on model#isVertical', () => { - model.isVertical = false; + dropdownView.isVertical = false; expect( dropdownView.toolbarView.isVertical ).to.be.false; - model.isVertical = true; + dropdownView.isVertical = true; expect( dropdownView.toolbarView.isVertical ).to.be.true; } ); } ); } ); describe( 'addListViewToDropdown()', () => { - let dropdownView, model, locale, items; + let items; beforeEach( () => { - locale = { t() {} }; items = new Collection(); - model = new Model( { + + dropdownView = createDropdown( locale ); + dropdownView.set( { isEnabled: true, isOn: false, label: 'foo' } ); - dropdownView = createDropdown( model, locale ); - - addListViewToDropdown( dropdownView, items, model, locale ); + addListViewToDropdown( dropdownView, items ); dropdownView.render(); document.body.appendChild( dropdownView.element ); From 82b46f33911e96fe218aa30d69d982df6c55f9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 11:56:11 +0100 Subject: [PATCH 52/78] Docs: Updated dropdown framework guide. --- docs/framework/guides/dropdowns.md | 67 +++++++----------------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/docs/framework/guides/dropdowns.md b/docs/framework/guides/dropdowns.md index 83854975..e2e92936 100644 --- a/docs/framework/guides/dropdowns.md +++ b/docs/framework/guides/dropdowns.md @@ -9,44 +9,30 @@ order: 30 ```js import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; -import Model from '@ckeditor/ckeditor5-ui/src/model'; - -import addToolbarToDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/addtoolbartodropdown'; -import closeDropdownOnBlur from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonblur'; -import closeDropdownOnExecute from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonexecute'; -import createDropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createdropdownview'; -import createSplitButtonForDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createsplitbuttonfordropdown'; -import enableModelIfOneIsEnabled from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/enablemodelifoneisenabled'; -import focusDropdownContentsOnArrows from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/focusdropdowncontentsonarrows'; - -const model = new Model( { - icon: 'some SVG', - tooltip: 'My dropdown' -} ); +import bindOneToMany from '@ckeditor/ckeditor5-ui/src/bindings/bindonetomany'; +import { addToolbarToDropdown, createSplitButtonDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; buttons.push( new ButtonView() ); buttons.push( componentFactory.create( 'someExistingButton' ) ); -model.set( 'buttons', buttons ); +const dropdownView = createSplitButtonDropdown( locale ); -const splitButtonView = createSplitButtonForDropdown( model, locale ); -const dropdownView = createDropdownView( model, splitButtonView, locale ); +dropdownView.set( { + icon: 'some SVG', + tooltip: 'My dropdown' +} ); -// Customize dropdown // This will enable toolbar button when any of button in dropdown is enabled. -enableModelIfOneIsEnabled( model, buttons ); +bindOneToMany( dropdownView, 'isEnabled', buttons, 'isEnabled', + ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) +); // Make this a dropdown with toolbar inside dropdown panel. -addToolbarToDropdown( dropdownView, model ); - -// Add default behavior of dropdown -closeDropdownOnBlur( dropdownView ); -closeDropdownOnExecute( dropdownView ); -focusDropdownContentsOnArrows( dropdownView ); +addToolbarToDropdown( dropdownView, buttons ); // Execute current action from dropdown's split button action button. -dropdownView.buttonView.on( 'execute', () => { +dropdownView.on( 'execute', () => { editor.execute( 'command', { value: model.commandValue } ); editor.editing.view.focus(); } ); @@ -57,12 +43,7 @@ dropdownView.buttonView.on( 'execute', () => { ```js import Model from '@ckeditor/ckeditor5-ui/src/model'; -import addListViewToDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/addlistviewtodropdown'; -import closeDropdownOnBlur from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonblur'; -import closeDropdownOnExecute from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/closedropdownonexecute'; -import createDropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createdropdownview'; -import createButtonForDropdown from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/createbuttonfordropdown'; -import focusDropdownContentsOnArrows from '@ckeditor/ckeditor5-ui/src/dropdown/helpers/focusdropdowncontentsonarrows'; +import { addListViewToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; const items = [ new Model( { @@ -75,28 +56,12 @@ const items = [ } ), ]; -// Create dropdown model. -const model = new Model( { - icon: 'some icon SVG', - items -} ); - -const buttonView = createButtonForDropdown( model, locale ); -const dropdownView = createDropdownView( model, buttonView, locale ); - -// Customize dropdown - -// This will enable toolbar button when any of button in dropdown is enabled. - -addListViewToDropdown( dropdownView, model, locale ); +const dropdownView = createDropdown( locale ); -// Add default behavior of dropdown -closeDropdownOnBlur( dropdownView ); -closeDropdownOnExecute( dropdownView ); -focusDropdownContentsOnArrows( dropdownView ); +addListViewToDropdown( dropdownView, items ); // Execute command when an item from the dropdown is selected. -this.listenTo( dropdownView, 'execute', evt => { +dropdownView.on( 'execute', evt => { editor.execute( evt.source.commandName ); editor.editing.view.focus(); } ); From a0e87436058bf4452c5bd8cc179d2b3f3896b32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 12:16:31 +0100 Subject: [PATCH 53/78] Fix: Wrong path to ckeditor5-utils package in tests. --- tests/dropdown/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index f7028934..eee42518 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -7,6 +7,7 @@ import utilsTestUtils from '@ckeditor/ckeditor5-utils/tests/_utils/utils'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; +import Collection from '@ckeditor/ckeditor5-utils/src/collection'; import Model from '../../src/model'; @@ -19,7 +20,6 @@ import ToolbarView from '../../src/toolbar/toolbarview'; import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown, addListViewToDropdown } from '../../src/dropdown/utils'; import ListItemView from '../../src/list/listitemview'; import ListView from '../../src/list/listview'; -import Collection from '../../../ckeditor5-utils/src/collection'; const assertBinding = utilsTestUtils.assertBinding; From e5251ed29f081a4496a05ddf166166e00e1e4b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 18:41:28 +0100 Subject: [PATCH 54/78] Code style: The dots are required at the end of the comment. --- src/button/splitbuttonview.js | 4 ++-- src/dropdown/utils.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index e48bb3ef..e50a2202 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -89,7 +89,7 @@ export default class SplitButtonView extends View { this.keystrokes.listenTo( this.element ); - // Overrides toolbar focus cycling behavior + // Overrides toolbar focus cycling behavior. this.keystrokes.set( 'arrowright', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.actionView.element ) { this.selectView.focus(); @@ -98,7 +98,7 @@ export default class SplitButtonView extends View { } } ); - // Overrides toolbar focus cycling behavior + // Overrides toolbar focus cycling behavior. this.keystrokes.set( 'arrowleft', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.selectView.element ) { this.actionView.focus(); diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 307562d2..6bd24c52 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -132,7 +132,6 @@ export function createSplitButtonDropdown( locale ) { export function addToolbarToDropdown( dropdownView, buttons ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); - // dropdownView.set( 'isVertical' ); toolbarView.bind( 'isVertical' ).to( dropdownView, 'isVertical' ); dropdownView.extendTemplate( { From e3139aa700f29017ac1f194e6c3568cc69cbe27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jan 2018 19:02:46 +0100 Subject: [PATCH 55/78] Docs: Update `SplitButtonView` documentation. --- src/button/splitbuttonview.js | 60 +++++++++++++++++++++++-- tests/button/splitbuttonview.js | 2 +- theme/components/button/splitbutton.css | 6 +-- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index e50a2202..9f4b52b7 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -19,7 +19,21 @@ import arrowIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; import './../../theme/components/button/splitbutton.css'; /** - * TODO + * The split button view class. + * + * const view = new SplitButtonView(); + * + * view.set( { + * label: 'A button', + * keystroke: 'Ctrl+B', + * tooltip: true + * } ); + * + * view.render(); + * + * document.body.append( view.element ); + * + * @extends module:ui/view~View */ export default class SplitButtonView extends View { /** @@ -49,19 +63,42 @@ export default class SplitButtonView extends View { /** * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. - * When defined, an {@link #iconView} will be added to the button. + * When defined, an {@link #iconView} will be added to the action button. * * @observable * @member {String} #icon */ this.set( 'icon' ); + /** + * Collection of the child views inside of the split button {@link #element}. + * + * @readonly + * @member {module:ui/viewcollection~ViewCollection} + */ this.children = this.createCollection(); this.actionView = this._createActionView(); this.selectView = this._createSelectView(); + /** + * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages + * keystrokes of the split button: + * + * * moves focus to select view when action view is focused, + * * moves focus to action view when select view is focused. + * + * @readonly + * @member {module:utils/keystrokehandler~KeystrokeHandler} + */ this.keystrokes = new KeystrokeHandler(); + + /** + * Tracks information about DOM focus in the dropdown. + * + * @readonly + * @member {module:utils/focustracker~FocusTracker} + */ this.focusTracker = new FocusTracker(); this.setTemplate( { @@ -108,10 +145,20 @@ export default class SplitButtonView extends View { } ); } + /** + * Focuses the {@link #actionView#element} of the action part of split button. + */ focus() { this.actionView.focus(); } + /** + * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #actionView} and binds it with main split button + * attributes. + * + * @private + * @returns {module:ui/button/buttonview~ButtonView} + */ _createActionView() { const buttonView = new ButtonView(); @@ -122,6 +169,13 @@ export default class SplitButtonView extends View { return buttonView; } + /** + * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #selectView} and binds it with main split button + * attributes. + * + * @private + * @returns {module:ui/button/buttonview~ButtonView} + */ _createSelectView() { const selectView = new ButtonView(); @@ -129,7 +183,7 @@ export default class SplitButtonView extends View { selectView.extendTemplate( { attributes: { - class: 'ck-splitbutton-arrow' + class: 'ck-splitbutton-select' } } ); diff --git a/tests/button/splitbuttonview.js b/tests/button/splitbuttonview.js index 95d06c2f..735d74ff 100644 --- a/tests/button/splitbuttonview.js +++ b/tests/button/splitbuttonview.js @@ -31,7 +31,7 @@ describe( 'SplitButtonView', () => { it( 'creates view#selectView', () => { expect( view.selectView ).to.be.instanceOf( ButtonView ); - expect( view.selectView.element.classList.contains( 'ck-splitbutton-arrow' ) ).to.be.true; + expect( view.selectView.element.classList.contains( 'ck-splitbutton-select' ) ).to.be.true; expect( view.selectView.icon ).to.be.not.undefined; } ); diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index c5b41054..6d1bc09c 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -10,7 +10,7 @@ } } -.ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-arrow) { +.ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-select) { border-top-right-radius: unset; border-bottom-right-radius: unset; @@ -19,12 +19,12 @@ } } -.ck-rounded-corners .ck-splitbutton > .ck-splitbutton-arrow { +.ck-rounded-corners .ck-splitbutton > .ck-splitbutton-select { border-top-left-radius: unset; border-bottom-left-radius: unset; } -.ck-splitbutton-arrow { +.ck-splitbutton-select { padding-right: var(--ck-spacing-standard); & svg { From 8161c10212696b20f969778d23a03ae9d0571135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 1 Feb 2018 16:05:40 +0100 Subject: [PATCH 56/78] Changed: Use `bind().toMany()` binding chain from `ObservableMixin`. --- src/bindings/bindonetomany.js | 28 ------------------------- tests/bindings/bindonetomany.js | 37 --------------------------------- 2 files changed, 65 deletions(-) delete mode 100644 src/bindings/bindonetomany.js delete mode 100644 tests/bindings/bindonetomany.js diff --git a/src/bindings/bindonetomany.js b/src/bindings/bindonetomany.js deleted file mode 100644 index 9643abe7..00000000 --- a/src/bindings/bindonetomany.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/bindings/getbindingtargets - */ - -export default function bindOneToMany( dropdownModel, boundProperty, collection, collectionProperty, callback ) { - dropdownModel.bind( boundProperty ).to( - // Bind to #isOn of each button... - ...getBindingTargets( collection, collectionProperty ), - // ...and chose the title of the first one which #isOn is true. - callback - ); -} - -// Returns an array of binding components for -// {@link module:utils/observablemixin~Observable#bind} from a set of iterable -// buttons. -// -// @param {Iterable.} buttons -// @param {String} attribute -// @returns {Array.} -function getBindingTargets( buttons, attribute ) { - return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) ); -} diff --git a/tests/bindings/bindonetomany.js b/tests/bindings/bindonetomany.js deleted file mode 100644 index c8a04f1d..00000000 --- a/tests/bindings/bindonetomany.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import Model from '../../src/model'; - -import bindOneToMany from './../../src/bindings/bindonetomany'; - -describe( 'bindOneToMany()', () => { - it( 'binds observable property to collection property using callback', () => { - const model = new Model(); - const observables = [ - new Model( { property: false } ), - new Model( { property: false } ), - new Model( { property: false } ) - ]; - - bindOneToMany( model, 'property', observables, 'property', - ( ...areEnabled ) => areEnabled.some( property => property ) - ); - - expect( model.property ).to.be.false; - - observables[ 0 ].property = true; - - expect( model.property ).to.be.true; - - observables[ 0 ].property = false; - - expect( model.property ).to.be.false; - - observables[ 1 ].property = true; - - expect( model.property ).to.be.true; - } ); -} ); From 23e0d16441b69fd5419c36ec3c0d883f29731a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 2 Feb 2018 11:23:57 +0100 Subject: [PATCH 57/78] Docs: Brought dropdown/utils methods to a common naming scheme. --- docs/framework/guides/dropdowns.md | 4 +- src/dropdown/list/listdropdownview.jsdoc | 15 +++ src/dropdown/utils.js | 159 ++++++++++------------- tests/dropdown/manual/dropdown.js | 4 +- tests/dropdown/utils.js | 6 +- 5 files changed, 92 insertions(+), 96 deletions(-) diff --git a/docs/framework/guides/dropdowns.md b/docs/framework/guides/dropdowns.md index e2e92936..770ffb92 100644 --- a/docs/framework/guides/dropdowns.md +++ b/docs/framework/guides/dropdowns.md @@ -43,7 +43,7 @@ dropdownView.on( 'execute', () => { ```js import Model from '@ckeditor/ckeditor5-ui/src/model'; -import { addListViewToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; +import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; const items = [ new Model( { @@ -58,7 +58,7 @@ const items = [ const dropdownView = createDropdown( locale ); -addListViewToDropdown( dropdownView, items ); +addListToDropdown( dropdownView, items ); // Execute command when an item from the dropdown is selected. dropdownView.on( 'execute', evt => { diff --git a/src/dropdown/list/listdropdownview.jsdoc b/src/dropdown/list/listdropdownview.jsdoc index cc498dfa..53ba952f 100644 --- a/src/dropdown/list/listdropdownview.jsdoc +++ b/src/dropdown/list/listdropdownview.jsdoc @@ -24,3 +24,18 @@ * @readonly * @member {module:ui/list/listview~ListView} #listView */ + +/** + * + * + * @observable + * @member {module:utils/collection~Collection.} #items + */ + +/** + * Fired when the list dropdown is executed. It fires when one of the list items in + * {@link #items the collection} has been + * {@link module:ui/list/listitemview~ListItemView#event:execute executed}. + * + * @event #execute + */ diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 6bd24c52..fa7bd013 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -20,30 +20,27 @@ import clickOutsideHandler from '../bindings/clickoutsidehandler'; import '../../theme/components/dropdown/toolbardropdown.css'; /** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using - * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. + * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of + * {@link module:ui/button/buttonview~ButtonView} in toolbar. * - * const model = new Model( { + * const dropdown = createDropdown( model ); + * + * // Configure dropdown properties: + * dropdown.set( { * label: 'A dropdown', * isEnabled: true, * isOn: false, * withText: true * } ); * - * const dropdown = createDropdown( model ); - * * dropdown.render(); * * // Will render a dropdown labeled "A dropdown" with an empty panel. * document.body.appendChild( dropdown.element ); * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * Also see {@link module:ui/dropdown/utils~createSplitButtonDropdown}, {@link module:ui/dropdown/utils~addListToDropdown} + * and {@link module:ui/dropdown/utils~addToolbarToDropdown}. * - * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. - * - * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ @@ -58,30 +55,26 @@ export function createDropdown( locale ) { } /** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class using - * a provided {@link module:ui/dropdown/dropdownmodel~DropdownModel}. + * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of + * {@link module:ui/button/splitbuttonview~SplitButtonView} in toolbar. + * + * const dropdown = createSplitButtonDropdown( model ); * - * const model = new Model( { + * // Configure dropdown properties: + * dropdown.set( { * label: 'A dropdown', * isEnabled: true, - * isOn: false, - * withText: true + * isOn: false * } ); * - * const dropdown = createDropdown( model ); - * * dropdown.render(); * * // Will render a dropdown labeled "A dropdown" with an empty panel. * document.body.appendChild( dropdown.element ); * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * Also see {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. + * Also see {@link module:ui/dropdown/utils~createDropdown}, {@link module:ui/dropdown/utils~addListToDropdown} + * and {@link module:ui/dropdown/utils~addToolbarToDropdown}. * - * @param {module:ui/dropdown/dropdownmodel~DropdownModel} model Model of this dropdown. * @param {module:utils/locale~Locale} locale The locale instance. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ @@ -98,36 +91,30 @@ export function createSplitButtonDropdown( locale ) { } /** - * Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using - * a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}. + * Adds an instance of {@link module:ui/toolbar/toolbarview~ToolbarView} to a dropdown. * * const buttons = []; * + * // Either create a new ButtonView instance or create existing. * buttons.push( new ButtonView() ); * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); * - * const model = new Model( { - * label: 'A button dropdown', - * isVertical: true, - * buttons - * } ); + * const dropdown = createDropdown( locale ); + * + * addToolbarToDropdown( dropdown, buttons ); * - * const dropdown = createButtonDropdown( model, locale ); + * dropdown.isVertical = true; * * // Will render a vertical button dropdown labeled "A button dropdown" * // with a button group in the panel containing two buttons. * dropdown.render() * document.body.appendChild( dropdown.element ); * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. - * - * See {@link module:ui/dropdown/createdropdown~createDropdown}. + * See {@link module:ui/dropdown/utils~createDropdown}, {@link module:ui/dropdown/utils~createSplitButtonDropdown} + * and {@link module:ui/toolbar/toolbarview~ToolbarView}. * - * @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} + * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ToolbarView` will be added. + * @param {Iterable.} buttons */ export function addToolbarToDropdown( dropdownView, buttons ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); @@ -140,59 +127,46 @@ export function addToolbarToDropdown( dropdownView, buttons ) { } } ); - // TODO: bind buttons to items in toolbar buttons.map( view => toolbarView.items.add( view ) ); dropdownView.panelView.children.add( toolbarView ); toolbarView.items.delegate( 'execute' ).to( dropdownView ); - - return toolbarView; } /** - * Creates an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} class using - * a provided {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel}. + * Adds an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} to a dropdown. * * const items = new Collection(); * * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); * - * const model = new Model( { - * isEnabled: true, - * items, - * isOn: false, - * label: 'A dropdown' - * } ); + * const dropdown = createDropdown( locale ); * - * const dropdown = createListDropdown( model, locale ); + * addListToDropdown( dropdown, items ); * - * // Will render a dropdown labeled "A dropdown" with a list in the panel - * // containing two items. + * // Will render a dropdown with a list in the panel containing two items. * dropdown.render() * document.body.appendChild( dropdown.element ); * - * The model instance remains in control of the dropdown after it has been created. E.g. changes to the - * {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the - * dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM. + * The `items` collection passed to this methods controls the presence and attributes of respective + * {@link module:ui/list/listitemview~ListItemView list items}. * - * The - * {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel#items items collection} - * of the {@link module:ui/dropdown/list/listdropdownmodel~ListDropdownModel model} also controls the - * presence and attributes of respective {@link module:ui/list/listitemview~ListItemView list items}. * - * See {@link module:ui/dropdown/createdropdown~createDropdown} and {@link module:list/list~List}. + * See {@link module:ui/dropdown/utils~createDropdown}, {@link module:ui/dropdown/utils~createSplitButtonDropdown} + * and {@link module:list/list~List}. * - * @param {module:ui/dropdown/list/listdropdownmodel~ListDropdownModel} model Model of the list dropdown. - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/list/listdropdownview~ListDropdownView} The list dropdown view instance. + * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ListVIew` will be added. + * @param {module:utils/collection~Collection.} items + * that the inner dropdown {@link module:ui/list/listview~ListView} children are created from. + * + * Usually, it is a collection of {@link module:ui/model~Model models}. */ -export function addListViewToDropdown( dropdownView, listViewItems ) { +export function addListToDropdown( dropdownView, items ) { const locale = dropdownView.locale; const listView = dropdownView.listView = new ListView( locale ); - // TODO: make this param of method instead of model property? - listView.items.bindTo( listViewItems ).using( itemModel => { + listView.items.bindTo( items ).using( itemModel => { const item = new ListItemView( locale ); // Bind all attributes of the model to the item view. @@ -202,19 +176,21 @@ export function addListViewToDropdown( dropdownView, listViewItems ) { } ); dropdownView.panelView.children.add( listView ); - listView.items.delegate( 'execute' ).to( dropdownView ); - return listView; + listView.items.delegate( 'execute' ).to( dropdownView ); } -// @private +// Creates a dropdown view instance and binds dropdown view with a button view. +// +// @param {module:utils/locale~Locale} locale The locale instance. +// @param {module:ui/button/buttonview~ButtonView|module:ui/button/splitbuttonview~SplitButtonView} locale The button view instance. +// @returns {module:ui/dropdown/dropdownview~DropdownView} function prepareDropdown( locale, buttonView ) { const panelView = new DropdownPanelView( locale ); const dropdownView = new DropdownView( locale, buttonView, panelView ); buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( dropdownView ); - // TODO: buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { dropdownView.set( 'isOn', true ); buttonView.bind( 'isOn' ).to( dropdownView, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { @@ -224,35 +200,42 @@ function prepareDropdown( locale, buttonView ) { return dropdownView; } -// @private +// Creates a split button view instance to be used as a toolbar button that opens a dropdown. +// +// @param {module:utils/locale~Locale} locale The locale instance. +// @returns {module:ui/button/splitbuttonview~SplitButtonView} function createSplitButtonForDropdown( locale ) { const splitButtonView = new SplitButtonView( locale ); // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. - splitButtonView.actionView.bind( 'isOn' ).to( splitButtonView ); - splitButtonView.actionView.bind( 'tooltip' ).to( splitButtonView ); + splitButtonView.actionView.bind( 'isOn', 'tooltip' ).to( splitButtonView ); return splitButtonView; } -// @private +// Creates a default button view instance to be used as a toolbar button that opens a dropdown. +// +// @param {module:utils/locale~Locale} locale The locale instance. +// @returns {module:ui/button/buttonview~ButtonView} function createButtonForDropdown( locale ) { const buttonView = new ButtonView( locale ); - // Dropdown expects "select" event to show contents. + // Dropdown expects "select" event on button view upon which the dropdown will open. buttonView.delegate( 'execute' ).to( buttonView, 'select' ); return buttonView; } -// @private -function addDefaultBehavior( dropdown ) { - closeDropdownOnBlur( dropdown ); - closeDropdownOnExecute( dropdown ); - focusDropdownContentsOnArrows( dropdown ); +// Add a set of default behaviors to dropdown view. +// +// @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView +function addDefaultBehavior( dropdownView ) { + closeDropdownOnBlur( dropdownView ); + closeDropdownOnExecute( dropdownView ); + focusDropdownContentsOnArrows( dropdownView ); } -// Adds a behavior to a dropdownView that closes opened dropdown on user click outside the dropdown. +// Adds a behavior to a dropdownView that closes opened dropdown when user clicks outside the dropdown. // // @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView function closeDropdownOnBlur( dropdownView ) { @@ -268,7 +251,7 @@ function closeDropdownOnBlur( dropdownView ) { } ); } -// Adds a behavior to a dropdownView that closes dropdown view on any view collection item's "execute" event. +// Adds a behavior to a dropdownView that closes the dropdown view on "execute" event. // // @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView function closeDropdownOnExecute( dropdownView ) { @@ -278,12 +261,11 @@ function closeDropdownOnExecute( dropdownView ) { } ); } -// Adds a behavior to a dropdownView that focuses dropdown panel view contents on keystrokes. +// Adds a behavior to a dropdownView that focuses the dropdown's panel view contents on keystrokes. // // @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView function focusDropdownContentsOnArrows( dropdownView ) { - // If the dropdown panel is already open, the arrow down key should - // focus the first element in list. + // If the dropdown panel is already open, the arrow down key should focus the first child of the #panelView. dropdownView.keystrokes.set( 'arrowdown', ( data, cancel ) => { if ( dropdownView.isOpen ) { dropdownView.panelView.focus(); @@ -291,8 +273,7 @@ function focusDropdownContentsOnArrows( dropdownView ) { } } ); - // If the dropdown panel is already open, the arrow up key should - // focus the last element in the list. + // If the dropdown panel is already open, the arrow up key should focus the last child of the #panelView. dropdownView.keystrokes.set( 'arrowup', ( data, cancel ) => { if ( dropdownView.isOpen ) { dropdownView.panelView.focusLast(); diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index caf18ed0..f8561f54 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -15,7 +15,7 @@ import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.sv import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; -import { createDropdown, addToolbarToDropdown, addListViewToDropdown } from '../../../src/dropdown/utils'; +import { createDropdown, addToolbarToDropdown, addListToDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', @@ -59,7 +59,7 @@ function testList() { withText: true } ); - addListViewToDropdown( dropdownView, collection ); + addListToDropdown( dropdownView, collection ); dropdownView.on( 'execute', evt => { /* global console */ diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index eee42518..a2029fb9 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -17,7 +17,7 @@ import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import SplitButtonView from '../../src/button/splitbuttonview'; import View from '../../src/view'; import ToolbarView from '../../src/toolbar/toolbarview'; -import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown, addListViewToDropdown } from '../../src/dropdown/utils'; +import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown, addListToDropdown } from '../../src/dropdown/utils'; import ListItemView from '../../src/list/listitemview'; import ListView from '../../src/list/listview'; @@ -306,7 +306,7 @@ describe( 'utils', () => { } ); } ); - describe( 'addListViewToDropdown()', () => { + describe( 'addListToDropdown()', () => { let items; beforeEach( () => { @@ -319,7 +319,7 @@ describe( 'utils', () => { label: 'foo' } ); - addListViewToDropdown( dropdownView, items ); + addListToDropdown( dropdownView, items ); dropdownView.render(); document.body.appendChild( dropdownView.element ); From 093e735d20611db4bd1486f24099cd4157a9f420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 2 Feb 2018 12:32:15 +0100 Subject: [PATCH 58/78] Changed: Review `SplitButton#actionView` bindings. --- src/button/splitbuttonview.js | 64 +++++++++++++++++++++++++++++++++-- src/dropdown/utils.js | 16 +-------- 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 9f4b52b7..281c4416 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -4,7 +4,7 @@ */ /** - * @module ui/button/buttonview + * @module ui/button/splitbuttonview */ import View from '../view'; @@ -61,6 +61,15 @@ export default class SplitButtonView extends View { */ this.set( 'label' ); + /** + * (Optional) The keystroke associated with the button, i.e. CTRL+B, + * in the string format compatible with {@link module:utils/keyboard}. + * + * @observable + * @member {Boolean} #keystroke + */ + this.set( 'keystroke' ); + /** * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. * When defined, an {@link #iconView} will be added to the action button. @@ -70,6 +79,44 @@ export default class SplitButtonView extends View { */ this.set( 'icon' ); + /** + * (Optional) Tooltip of the button, i.e. displayed when hovering the button with the mouse cursor. + * + * * If defined as a `Boolean` (e.g. `true`), then combination of `label` and `keystroke` will be set as a tooltip. + * * If defined as a `String`, tooltip will equal the exact text of that `String`. + * * If defined as a `Function`, `label` and `keystroke` will be passed to that function, which is to return + * a string with the tooltip text. + * + * const view = new ButtonView( locale ); + * view.tooltip = ( label, keystroke ) => `A tooltip for ${ label } and ${ keystroke }.` + * + * @observable + * @default false + * @member {Boolean|String|Function} #tooltip + */ + this.set( 'tooltip' ); + + /** + * Controls whether the button view is "on". It makes sense when a feature it represents + * is currently active, e.g. a bold button is "on" when the selection is in the bold text. + * + * To disable the button, use {@link #isEnabled} instead. + * + * @observable + * @member {Boolean} #isOn + */ + this.set( 'isOn', false ); + + /** + * Controls whether the button view is enabled, i.e. it can be clicked and execute an action. + * + * To change the "on" state of the button, use {@link #isOn} instead. + * + * @observable + * @member {Boolean} #isEnabled + */ + this.set( 'isEnabled', true ); + /** * Collection of the child views inside of the split button {@link #element}. * @@ -78,7 +125,20 @@ export default class SplitButtonView extends View { */ this.children = this.createCollection(); + /** + * A main button of split button. + * + * @readonly + * @member {module:ui/button/buttonview~ButtonView} + */ this.actionView = this._createActionView(); + + /** + * A secondary button of split button that opens dropdown. + * + * @readonly + * @member {module:ui/button/buttonview~ButtonView} + */ this.selectView = this._createSelectView(); /** @@ -162,7 +222,7 @@ export default class SplitButtonView extends View { _createActionView() { const buttonView = new ButtonView(); - buttonView.bind( 'icon', 'isEnabled', 'label' ).to( this ); + buttonView.bind( 'icon', 'isEnabled', 'label', 'isOn', 'tooltip', 'keystroke' ).to( this ); buttonView.delegate( 'execute' ).to( this ); diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index fa7bd013..4955e73e 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -79,8 +79,7 @@ export function createDropdown( locale ) { * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ export function createSplitButtonDropdown( locale ) { - const buttonView = createSplitButtonForDropdown( locale ); - + const buttonView = new SplitButtonView( locale ); const dropdownView = prepareDropdown( locale, buttonView ); addDefaultBehavior( dropdownView ); @@ -200,19 +199,6 @@ function prepareDropdown( locale, buttonView ) { return dropdownView; } -// Creates a split button view instance to be used as a toolbar button that opens a dropdown. -// -// @param {module:utils/locale~Locale} locale The locale instance. -// @returns {module:ui/button/splitbuttonview~SplitButtonView} -function createSplitButtonForDropdown( locale ) { - const splitButtonView = new SplitButtonView( locale ); - - // TODO: Check if those binding are in good place (maybe move them to SplitButton) or add tests. - splitButtonView.actionView.bind( 'isOn', 'tooltip' ).to( splitButtonView ); - - return splitButtonView; -} - // Creates a default button view instance to be used as a toolbar button that opens a dropdown. // // @param {module:utils/locale~Locale} locale The locale instance. From 2264fcf9e5c951bb3c4852708294a2cba5c9a344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 2 Feb 2018 12:35:54 +0100 Subject: [PATCH 59/78] Changed: Removed unnecessary private method from dropdown/utils. --- src/dropdown/utils.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 4955e73e..f111c023 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -45,10 +45,12 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ export function createDropdown( locale ) { - const buttonView = createButtonForDropdown( locale ); + const buttonView = new ButtonView( locale ); - const dropdownView = prepareDropdown( locale, buttonView ); + // Dropdown expects "select" event on button view upon which the dropdown will open. + buttonView.delegate( 'execute' ).to( buttonView, 'select' ); + const dropdownView = prepareDropdown( locale, buttonView ); addDefaultBehavior( dropdownView ); return dropdownView; @@ -80,8 +82,8 @@ export function createDropdown( locale ) { */ export function createSplitButtonDropdown( locale ) { const buttonView = new SplitButtonView( locale ); - const dropdownView = prepareDropdown( locale, buttonView ); + const dropdownView = prepareDropdown( locale, buttonView ); addDefaultBehavior( dropdownView ); buttonView.delegate( 'execute' ).to( dropdownView ); @@ -199,19 +201,6 @@ function prepareDropdown( locale, buttonView ) { return dropdownView; } -// Creates a default button view instance to be used as a toolbar button that opens a dropdown. -// -// @param {module:utils/locale~Locale} locale The locale instance. -// @returns {module:ui/button/buttonview~ButtonView} -function createButtonForDropdown( locale ) { - const buttonView = new ButtonView( locale ); - - // Dropdown expects "select" event on button view upon which the dropdown will open. - buttonView.delegate( 'execute' ).to( buttonView, 'select' ); - - return buttonView; -} - // Add a set of default behaviors to dropdown view. // // @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView From caefc2c17d786ed1cc5634cb80f1365d9f97e129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 2 Feb 2018 13:01:51 +0100 Subject: [PATCH 60/78] Docs: Document optional observable properties of the DropdownView. --- src/dropdown/dropdownmodel.jsdoc | 60 ------------------------------- src/dropdown/dropdownview.js | 61 ++++++++++++++++++++++++++++++++ src/dropdown/utils.js | 2 -- 3 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 src/dropdown/dropdownmodel.jsdoc diff --git a/src/dropdown/dropdownmodel.jsdoc b/src/dropdown/dropdownmodel.jsdoc deleted file mode 100644 index 40497977..00000000 --- a/src/dropdown/dropdownmodel.jsdoc +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/dropdownmodel - */ - -/** - * The basic dropdown model interface. - * - * @interface module:ui/dropdown/dropdownmodel~DropdownModel - */ - -/** - * The label of the dropdown. - * - * Also see {@link module:ui/button/buttonview~ButtonView#label}. - * - * @observable - * @member {String} #label - */ - -/** - * Controls whether the dropdown is enabled, i.e. it opens the panel when clicked. - * - * Also see {@link module:ui/button/buttonview~ButtonView#isEnabled}. - * - * @observable - * @member {Boolean} #isEnabled - */ - -/** - * Controls whether the dropdown is "on". It makes sense when a feature it represents - * is currently active. - * - * Also see {@link module:ui/button/buttonview~ButtonView#isOn}. - * - * @observable - * @member {Boolean} #isOn - */ - -/** - * (Optional) Controls whether the label of the dropdown is visible. - * - * Also see {@link module:ui/button/buttonview~ButtonView#withText}. - * - * @observable - * @member {Boolean} #withText - */ - -/** - * (Optional) Controls the icon of the dropdown. - * - * Also see {@link module:ui/button/buttonview~ButtonView#withText}. - * - * @observable - * @member {Boolean} #icon - */ diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index dd37da83..44f9bd60 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -149,6 +149,67 @@ export default class DropdownView extends View { ] } } ); + + /** + * The label of the dropdown. + * + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or + * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * + * Also see {@link module:ui/button/buttonview~ButtonView#label}. + * + * @observable + * @member {String} #label + */ + + /** + * Controls whether the dropdown is enabled, i.e. it opens the panel when clicked. + * + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or + * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * + * Also see {@link module:ui/button/buttonview~ButtonView#isEnabled}. + * + * @observable + * @member {Boolean} #isEnabled + */ + + /** + * Controls whether the dropdown is "on". It makes sense when a feature it represents + * is currently active. + * + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or + * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * + * Also see {@link module:ui/button/buttonview~ButtonView#isOn}. + * + * @observable + * @member {Boolean} #isOn + */ + + /** + * (Optional) Controls whether the label of the dropdown is visible. + * + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or + * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * + * Also see {@link module:ui/button/buttonview~ButtonView#withText}. + * + * @observable + * @member {Boolean} #withText + */ + + /** + * (Optional) Controls the icon of the dropdown. + * + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or + * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * + * Also see {@link module:ui/button/buttonview~ButtonView#withText}. + * + * @observable + * @member {Boolean} #icon + */ } /** diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index f111c023..9ad6d0b2 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -192,8 +192,6 @@ function prepareDropdown( locale, buttonView ) { buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( dropdownView ); - dropdownView.set( 'isOn', true ); - buttonView.bind( 'isOn' ).to( dropdownView, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { return isOn || isOpen; } ); From 284142251c528b6d924146c6005abd8427ae4d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 2 Feb 2018 13:40:13 +0100 Subject: [PATCH 61/78] Docs: Fix links in documentation and update dropdown's properties. --- src/button/splitbuttonview.js | 5 +- src/dropdown/button/buttondropdownmodel.jsdoc | 45 ----------------- src/dropdown/button/buttondropdownview.jsdoc | 26 ---------- src/dropdown/dropdownpanelview.js | 6 +-- src/dropdown/dropdownview.js | 48 +++++++++++++++++-- src/dropdown/list/listdropdownmodel.jsdoc | 33 ------------- src/dropdown/list/listdropdownview.jsdoc | 41 ---------------- src/dropdown/utils.js | 4 +- 8 files changed, 52 insertions(+), 156 deletions(-) delete mode 100644 src/dropdown/button/buttondropdownmodel.jsdoc delete mode 100644 src/dropdown/button/buttondropdownview.jsdoc delete mode 100644 src/dropdown/list/listdropdownmodel.jsdoc delete mode 100644 src/dropdown/list/listdropdownview.jsdoc diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 281c4416..217f736b 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -53,8 +53,7 @@ export default class SplitButtonView extends View { this.set( 'isEnabled', true ); /** - * The label of the button view visible to the user when {@link #withText} is `true`. - * It can also be used to create a {@link #tooltip}. + * Used to create a {@link #tooltip}. * * @observable * @member {String} #label @@ -72,7 +71,7 @@ export default class SplitButtonView extends View { /** * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. - * When defined, an {@link #iconView} will be added to the action button. + * When defined, an {@link module:ui/button/buttonview~ButtonView#iconView} will be added to the {@link #actionView} button. * * @observable * @member {String} #icon diff --git a/src/dropdown/button/buttondropdownmodel.jsdoc b/src/dropdown/button/buttondropdownmodel.jsdoc deleted file mode 100644 index c7788edd..00000000 --- a/src/dropdown/button/buttondropdownmodel.jsdoc +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/button/buttondropdownmodel - */ - -/** - * The button dropdown model interface. - * - * @implements module:ui/dropdown/dropdownmodel~DropdownModel - * @interface module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel - */ - -/** - * List of buttons to be included in dropdown - * - * @observable - * @member {Array.} #buttons - */ - -/** - * Fired when the button dropdown is executed. It fires when one of the buttons - * {@link module:ui/button/buttonview~ButtonView#event:execute executed}. - * - * @event #execute - */ - -/** - * Controls dropdown direction. - * - * @observable - * @member {Boolean} #isVertical=false - */ - -/** - * Button dropdown icon is set from inner button views. - * - * Also see {@link #defaultIcon} and {@link #staticIcon}. - * - * @observable - * @member {String} #icon - */ diff --git a/src/dropdown/button/buttondropdownview.jsdoc b/src/dropdown/button/buttondropdownview.jsdoc deleted file mode 100644 index cd148969..00000000 --- a/src/dropdown/button/buttondropdownview.jsdoc +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/button/createbuttondropdown - */ - -/** - * The button dropdown view. - * - * See {@link module:ui/dropdown/button/createbuttondropdown~createButtonDropdown}. - * - * @abstract - * @class module:ui/dropdown/button/buttondropdownview~ButtonDropdownView - * @extends module:ui/dropdown/dropdownview~DropdownView - */ - -/** - * A child toolbar of the dropdown located in the - * {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}. - * - * @readonly - * @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView - */ diff --git a/src/dropdown/dropdownpanelview.js b/src/dropdown/dropdownpanelview.js index be213d44..11236e37 100644 --- a/src/dropdown/dropdownpanelview.js +++ b/src/dropdown/dropdownpanelview.js @@ -36,9 +36,9 @@ export default class DropdownPanelView extends View { /** * Collection of the child views in this panel. * - * A common child type is the {@link module:list/list~List}. See - * {@link module:ui/dropdown/list/createlistdropdown~createListDropdown} to learn more - * about list dropdowns. + * A common child type is the {@link module:ui/list/listview~ListView} and {@link module:ui/toolbar/toolbarview~ToolbarView}. + * See {@link module:ui/dropdown/utils~addListToDropdown} and + * {@link module:ui/dropdown/utils~addToolbarToDropdown} to learn more about child views of dropdowns. * * @readonly * @member {module:ui/viewcollection~ViewCollection} diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 44f9bd60..9ee75506 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -33,9 +33,8 @@ import '../../theme/components/dropdown/dropdown.css'; * // Will render a dropdown with a panel containing a "Content of the panel" text. * document.body.appendChild( dropdown.element ); * - * Also see {@link module:ui/dropdown/createdropdown~createDropdown} and - * {@link module:ui/dropdown/list/createlistdropdown~createListDropdown} to learn about different - * dropdown creation helpers. + * Also see {@link module:ui/dropdown/utils~createDropdown} and {@link module:ui/dropdown/utils~createSplitButtonDropdown} + * to learn about different dropdown creation helpers. * * @extends module:ui/view~View */ @@ -210,6 +209,49 @@ export default class DropdownView extends View { * @observable * @member {Boolean} #icon */ + + /** + * Controls dropdown's toolbar direction. + * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}. + * + * @observable + * @member {Boolean} #isVertical=false + */ + + /** + * A child {@link module:ui/list/listview~ListView list view} of the dropdown located + * in its {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}. + * + * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown}. + * + * @readonly + * @member {module:ui/list/listview~ListView} #listView + */ + + /** + * A child toolbar of the dropdown located in the + * {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}. + * + * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}. + * + * @readonly + * @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView + */ + + /** + * Fired when the toolbar button or list item is executed. + * + * For {@link #listView} It fires when one of the list items has been + * {@link module:ui/list/listitemview~ListItemView#event:execute executed}. + * + * For {@link #toolbarView} It fires when one of the buttons has been + * {@link module:ui/button/buttonview~ButtonView#event:execute executed}. + * + * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addListToDropdown} + * or {@link module:ui/dropdown/utils~addToolbarToDropdown}. + * + * @event #execute + */ } /** diff --git a/src/dropdown/list/listdropdownmodel.jsdoc b/src/dropdown/list/listdropdownmodel.jsdoc deleted file mode 100644 index d20f72d1..00000000 --- a/src/dropdown/list/listdropdownmodel.jsdoc +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/list/listdropdownmodel - */ - -/** - * The list dropdown model interface. - * - * @implements module:ui/dropdown/dropdownmodel~DropdownModel - * @interface module:ui/dropdown/list/listdropdownmodel~ListDropdownModel - */ - -/** - * A {@link module:utils/collection~Collection} of {@link module:utils/observablemixin~Observable} - * that the inner dropdown {@link module:ui/list/listview~ListView} children are created from. - * - * Usually, it is a collection of {@link module:ui/model~Model models}. - * - * @observable - * @member {module:utils/collection~Collection.} #items - */ - -/** - * Fired when the list dropdown is executed. It fires when one of the list items in - * {@link #items the collection} has been - * {@link module:ui/list/listitemview~ListItemView#event:execute executed}. - * - * @event #execute - */ diff --git a/src/dropdown/list/listdropdownview.jsdoc b/src/dropdown/list/listdropdownview.jsdoc deleted file mode 100644 index 53ba952f..00000000 --- a/src/dropdown/list/listdropdownview.jsdoc +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/** - * @module ui/dropdown/list/createlistdropdown - */ - -/** - * The list dropdown view. - * - * See {@link module:ui/dropdown/list/createlistdropdown~createListDropdown}. - * - * @abstract - * @class module:ui/dropdown/list/listdropdownview~ListDropdownView - * @extends module:ui/dropdown/dropdownview~DropdownView - */ - -/** - * A child {@link module:ui/list/listview~ListView list view} of the dropdown located - * in its {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}. - * - * @readonly - * @member {module:ui/list/listview~ListView} #listView - */ - -/** - * - * - * @observable - * @member {module:utils/collection~Collection.} #items - */ - -/** - * Fired when the list dropdown is executed. It fires when one of the list items in - * {@link #items the collection} has been - * {@link module:ui/list/listitemview~ListItemView#event:execute executed}. - * - * @event #execute - */ diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 9ad6d0b2..1b48fcab 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -135,7 +135,7 @@ export function addToolbarToDropdown( dropdownView, buttons ) { } /** - * Adds an instance of {@link module:ui/dropdown/list/listdropdownview~ListDropdownView} to a dropdown. + * Adds an instance of {@link module:ui/list/listview~ListView} to a dropdown. * * const items = new Collection(); * @@ -158,7 +158,7 @@ export function addToolbarToDropdown( dropdownView, buttons ) { * and {@link module:list/list~List}. * * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ListVIew` will be added. - * @param {module:utils/collection~Collection.} items + * @param {module:utils/collection~Collection} items * that the inner dropdown {@link module:ui/list/listview~ListView} children are created from. * * Usually, it is a collection of {@link module:ui/model~Model models}. From 383524ce4cb9ad75d3dea73258443762b0cea1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Fri, 2 Feb 2018 16:23:23 +0100 Subject: [PATCH 62/78] Docs: Fix code block padding in utils.js. --- src/dropdown/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 1b48fcab..b946e71c 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -142,11 +142,11 @@ export function addToolbarToDropdown( dropdownView, buttons ) { * items.add( new Model( { label: 'First item', style: 'color: red' } ) ); * items.add( new Model( { label: 'Second item', style: 'color: green', class: 'foo' } ) ); * - * const dropdown = createDropdown( locale ); + * const dropdown = createDropdown( locale ); * - * addListToDropdown( dropdown, items ); + * addListToDropdown( dropdown, items ); * - * // Will render a dropdown with a list in the panel containing two items. + * // Will render a dropdown with a list in the panel containing two items. * dropdown.render() * document.body.appendChild( dropdown.element ); * From 39449f7553729907c0b983b4218aa60edefb290a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 13:26:34 +0100 Subject: [PATCH 63/78] Changed: Extract DropdownButtonView from Dropdown. --- src/button/dropdownbuttonview.js | 80 +++++++++++++++++++++++++ src/button/splitbuttonview.js | 5 +- src/dropdown/dropdownview.js | 18 ------ src/dropdown/utils.js | 7 +-- theme/components/button/splitbutton.css | 35 +++-------- 5 files changed, 93 insertions(+), 52 deletions(-) create mode 100644 src/button/dropdownbuttonview.js diff --git a/src/button/dropdownbuttonview.js b/src/button/dropdownbuttonview.js new file mode 100644 index 00000000..796c610f --- /dev/null +++ b/src/button/dropdownbuttonview.js @@ -0,0 +1,80 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/button/splitbuttonview + */ + +import ButtonView from './buttonview'; + +import dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg'; +import IconView from '../icon/iconview'; + +/** + * The default dropdown button view class. + * + * const view = new SplitButtonView(); + * + * view.set( { + * label: 'A button', + * keystroke: 'Ctrl+B', + * tooltip: true + * } ); + * + * view.render(); + * + * document.body.append( view.element ); + * + * @extends module:ui/view~View + */ +export default class DropdownButtonView extends ButtonView { + /** + * @inheritDoc + */ + constructor( locale ) { + super( locale ); + + /** + * A secondary button of split button that opens dropdown. + * + * @readonly + * @member {module:ui/button/buttonview~ButtonView} + */ + this.selectView = this._createSelectView(); + + // Dropdown expects "select" event on button view upon which the dropdown will open. + this.delegate( 'execute' ).to( this, 'select' ); + } + + /** + * @inheritDoc + */ + render() { + super.render(); + + this.children.add( this.selectView ); + } + + /** + * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #selectView} and binds it with main split button + * attributes. + * + * @private + * @returns {module:ui/button/buttonview~ButtonView} + */ + _createSelectView() { + const arrowView = new IconView(); + + arrowView.content = dropdownArrowIcon; + + arrowView.extendTemplate( { + attributes: { + class: 'ck-dropdown__arrow' + } + } ); + + return arrowView; + } +} diff --git a/src/button/splitbuttonview.js b/src/button/splitbuttonview.js index 217f736b..8979a12d 100644 --- a/src/button/splitbuttonview.js +++ b/src/button/splitbuttonview.js @@ -13,8 +13,7 @@ import ButtonView from './buttonview'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; -// TODO: temporary hack... -import arrowIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; +import dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg'; import './../../theme/components/button/splitbutton.css'; @@ -238,7 +237,7 @@ export default class SplitButtonView extends View { _createSelectView() { const selectView = new ButtonView(); - selectView.icon = arrowIcon; + selectView.icon = dropdownArrowIcon; selectView.extendTemplate( { attributes: { diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 9ee75506..1eeba38d 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -8,10 +8,8 @@ */ import View from '../view'; -import IconView from '../icon/iconview'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; -import dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg'; import '../../theme/components/dropdown/dropdown.css'; @@ -109,14 +107,6 @@ export default class DropdownView extends View { */ this.keystrokes = new KeystrokeHandler(); - /** - * The arrow icon of the dropdown. - * - * @readonly - * @member {module:ui/icon/iconview~IconView} #arrowView - */ - const arrowView = this.arrowView = new IconView(); - this.setTemplate( { tag: 'div', @@ -129,18 +119,10 @@ export default class DropdownView extends View { children: [ buttonView, - arrowView, panelView ] } ); - arrowView.content = dropdownArrowIcon; - arrowView.extendTemplate( { - attributes: { - class: 'ck-dropdown__arrow' - } - } ); - buttonView.extendTemplate( { attributes: { class: [ diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b946e71c..ec67cf0e 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -9,8 +9,8 @@ import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; +import DropdownButtonView from '../button/dropdownbuttonview'; import SplitButtonView from '../button/splitbuttonview'; -import ButtonView from '../button/buttonview'; import ToolbarView from '../toolbar/toolbarview'; import ListView from '../list/listview'; import ListItemView from '../list/listitemview'; @@ -45,10 +45,7 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ export function createDropdown( locale ) { - const buttonView = new ButtonView( locale ); - - // Dropdown expects "select" event on button view upon which the dropdown will open. - buttonView.delegate( 'execute' ).to( buttonView, 'select' ); + const buttonView = new DropdownButtonView( locale ); const dropdownView = prepareDropdown( locale, buttonView ); addDefaultBehavior( dropdownView ); diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index 6d1bc09c..3be00c8f 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -3,13 +3,6 @@ * For licensing, see LICENSE.md. */ -/* TODO: change me */ -.ck-splitbutton-dropdown { - &::after { - display: none; - } -} - .ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-select) { border-top-right-radius: unset; border-bottom-right-radius: unset; @@ -24,25 +17,15 @@ border-bottom-left-radius: unset; } -.ck-splitbutton-select { - padding-right: var(--ck-spacing-standard); - - & svg { - width: 0; - min-width: 0; - } - - &::after { - content: ''; - width: 0; - height: 0; - pointer-events: none; - z-index: var(--ck-z-default); +.ck-dropdown { + /* Enable font size inheritance, which allows fluid UI scaling. */ + font-size: inherit; - position: absolute; - top: 50%; - /* To make the triangle appear in the middle of split button the translation in X-axis - * should be adjusted by the half of its width. */ - transform: translate3d(calc(-50% + 0.2em), -50%, 0); + & .ck-splitbutton .ck-splitbutton-select svg { + position: static; + top: initial; + transform: initial; + right: auto; + width: var(--ck-dropdown-icon-size); } } From f1beae82d8828d044b0bd301c702fe90908aa7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 13:37:32 +0100 Subject: [PATCH 64/78] Changed: Move dropdown buttons to ui/dropdown/buttons. --- src/{ => dropdown}/button/dropdownbuttonview.js | 8 ++++---- src/{ => dropdown}/button/splitbuttonview.js | 10 +++++----- src/dropdown/utils.js | 9 +++++---- tests/{ => dropdown}/button/splitbuttonview.js | 4 ++-- tests/dropdown/utils.js | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) rename src/{ => dropdown}/button/dropdownbuttonview.js (87%) rename src/{ => dropdown}/button/splitbuttonview.js (96%) rename tests/{ => dropdown}/button/splitbuttonview.js (97%) diff --git a/src/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js similarity index 87% rename from src/button/dropdownbuttonview.js rename to src/dropdown/button/dropdownbuttonview.js index 796c610f..0aeac5e7 100644 --- a/src/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -4,13 +4,13 @@ */ /** - * @module ui/button/splitbuttonview + * @module ui/dropdown/button/dropdownbuttonview */ -import ButtonView from './buttonview'; +import ButtonView from '../../button/buttonview'; -import dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg'; -import IconView from '../icon/iconview'; +import dropdownArrowIcon from '../../../theme/icons/dropdown-arrow.svg'; +import IconView from '../../icon/iconview'; /** * The default dropdown button view class. diff --git a/src/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js similarity index 96% rename from src/button/splitbuttonview.js rename to src/dropdown/button/splitbuttonview.js index 8979a12d..20fc694f 100644 --- a/src/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -4,18 +4,18 @@ */ /** - * @module ui/button/splitbuttonview + * @module ui/dropdown/button/splitbuttonview */ -import View from '../view'; -import ButtonView from './buttonview'; +import View from '../../view'; +import ButtonView from '../../button/buttonview'; import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; -import dropdownArrowIcon from '../../theme/icons/dropdown-arrow.svg'; +import dropdownArrowIcon from '../../../theme/icons/dropdown-arrow.svg'; -import './../../theme/components/button/splitbutton.css'; +import '../../../theme/components/button/splitbutton.css'; /** * The split button view class. diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index ec67cf0e..38c6fb76 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -9,8 +9,8 @@ import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; -import DropdownButtonView from '../button/dropdownbuttonview'; -import SplitButtonView from '../button/splitbuttonview'; +import DropdownButtonView from './button/dropdownbuttonview'; +import SplitButtonView from './button/splitbuttonview'; import ToolbarView from '../toolbar/toolbarview'; import ListView from '../list/listview'; import ListItemView from '../list/listitemview'; @@ -55,7 +55,7 @@ export function createDropdown( locale ) { /** * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of - * {@link module:ui/button/splitbuttonview~SplitButtonView} in toolbar. + * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} in toolbar. * * const dropdown = createSplitButtonDropdown( model ); * @@ -181,7 +181,8 @@ export function addListToDropdown( dropdownView, items ) { // Creates a dropdown view instance and binds dropdown view with a button view. // // @param {module:utils/locale~Locale} locale The locale instance. -// @param {module:ui/button/buttonview~ButtonView|module:ui/button/splitbuttonview~SplitButtonView} locale The button view instance. +// @param {module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView|module:ui/dropdown/button/splitbuttonview~SplitButtonView} +// buttonView // The button view instance. // @returns {module:ui/dropdown/dropdownview~DropdownView} function prepareDropdown( locale, buttonView ) { const panelView = new DropdownPanelView( locale ); diff --git a/tests/button/splitbuttonview.js b/tests/dropdown/button/splitbuttonview.js similarity index 97% rename from tests/button/splitbuttonview.js rename to tests/dropdown/button/splitbuttonview.js index 735d74ff..2541e741 100644 --- a/tests/button/splitbuttonview.js +++ b/tests/dropdown/button/splitbuttonview.js @@ -5,8 +5,8 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; -import ButtonView from '../../src/button/buttonview'; -import SplitButtonView from '../../src/button/splitbuttonview'; +import ButtonView from '../../../src/button/buttonview'; +import SplitButtonView from '../../../src/dropdown/button/splitbuttonview'; testUtils.createSinonSandbox(); diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index a2029fb9..c3dde30f 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -14,7 +14,7 @@ import Model from '../../src/model'; import ButtonView from '../../src/button/buttonview'; import DropdownView from '../../src/dropdown/dropdownview'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; -import SplitButtonView from '../../src/button/splitbuttonview'; +import SplitButtonView from '../../src/dropdown/button/splitbuttonview'; import View from '../../src/view'; import ToolbarView from '../../src/toolbar/toolbarview'; import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown, addListToDropdown } from '../../src/dropdown/utils'; From b0718365f0fdb867dbd0c301e0ef7a65a4ce3886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 15:41:02 +0100 Subject: [PATCH 65/78] Changed: Make ButtonClass as a parameter of `createDropdown()`. --- src/dropdown/button/dropdownbuttonview.js | 8 +- src/dropdown/button/splitbuttonview.js | 28 +- src/dropdown/dropdownview.js | 18 +- src/dropdown/utils.js | 71 ++--- tests/dropdown/button/dropdownbuttonview.js | 47 +++ tests/dropdown/button/splitbuttonview.js | 32 +- tests/dropdown/dropdownview.js | 11 +- tests/dropdown/utils.js | 334 +++++++------------- theme/components/button/splitbutton.css | 6 +- 9 files changed, 225 insertions(+), 330 deletions(-) create mode 100644 tests/dropdown/button/dropdownbuttonview.js diff --git a/src/dropdown/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js index 0aeac5e7..3a61055f 100644 --- a/src/dropdown/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -42,7 +42,7 @@ export default class DropdownButtonView extends ButtonView { * @readonly * @member {module:ui/button/buttonview~ButtonView} */ - this.selectView = this._createSelectView(); + this.arrowView = this._createArrowView(); // Dropdown expects "select" event on button view upon which the dropdown will open. this.delegate( 'execute' ).to( this, 'select' ); @@ -54,17 +54,17 @@ export default class DropdownButtonView extends ButtonView { render() { super.render(); - this.children.add( this.selectView ); + this.children.add( this.arrowView ); } /** - * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #selectView} and binds it with main split button + * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #arrowView} and binds it with main split button * attributes. * * @private * @returns {module:ui/button/buttonview~ButtonView} */ - _createSelectView() { + _createArrowView() { const arrowView = new IconView(); arrowView.content = dropdownArrowIcon; diff --git a/src/dropdown/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js index 20fc694f..307b5698 100644 --- a/src/dropdown/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -137,7 +137,7 @@ export default class SplitButtonView extends View { * @readonly * @member {module:ui/button/buttonview~ButtonView} */ - this.selectView = this._createSelectView(); + this.arrowView = this._createArrowView(); /** * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages @@ -177,17 +177,17 @@ export default class SplitButtonView extends View { super.render(); this.children.add( this.actionView ); - this.children.add( this.selectView ); + this.children.add( this.arrowView ); this.focusTracker.add( this.actionView.element ); - this.focusTracker.add( this.selectView.element ); + this.focusTracker.add( this.arrowView.element ); this.keystrokes.listenTo( this.element ); // Overrides toolbar focus cycling behavior. this.keystrokes.set( 'arrowright', ( evt, cancel ) => { if ( this.focusTracker.focusedElement === this.actionView.element ) { - this.selectView.focus(); + this.arrowView.focus(); cancel(); } @@ -195,7 +195,7 @@ export default class SplitButtonView extends View { // Overrides toolbar focus cycling behavior. this.keystrokes.set( 'arrowleft', ( evt, cancel ) => { - if ( this.focusTracker.focusedElement === this.selectView.element ) { + if ( this.focusTracker.focusedElement === this.arrowView.element ) { this.actionView.focus(); cancel(); @@ -228,27 +228,27 @@ export default class SplitButtonView extends View { } /** - * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #selectView} and binds it with main split button + * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #arrowView} and binds it with main split button * attributes. * * @private * @returns {module:ui/button/buttonview~ButtonView} */ - _createSelectView() { - const selectView = new ButtonView(); + _createArrowView() { + const arrowView = new ButtonView(); - selectView.icon = dropdownArrowIcon; + arrowView.icon = dropdownArrowIcon; - selectView.extendTemplate( { + arrowView.extendTemplate( { attributes: { - class: 'ck-splitbutton-select' + class: 'ck-splitbutton-arrow' } } ); - selectView.bind( 'isEnabled' ).to( this ); + arrowView.bind( 'isEnabled' ).to( this ); - selectView.delegate( 'execute' ).to( this, 'select' ); + arrowView.delegate( 'execute' ).to( this, 'select' ); - return selectView; + return arrowView; } } diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 1eeba38d..ef718208 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -31,8 +31,7 @@ import '../../theme/components/dropdown/dropdown.css'; * // Will render a dropdown with a panel containing a "Content of the panel" text. * document.body.appendChild( dropdown.element ); * - * Also see {@link module:ui/dropdown/utils~createDropdown} and {@link module:ui/dropdown/utils~createSplitButtonDropdown} - * to learn about different dropdown creation helpers. + * Also see {@link module:ui/dropdown/utils~createDropdown} to learn about dropdown creation helper. * * @extends module:ui/view~View */ @@ -134,8 +133,7 @@ export default class DropdownView extends View { /** * The label of the dropdown. * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or - * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. * * Also see {@link module:ui/button/buttonview~ButtonView#label}. * @@ -146,8 +144,7 @@ export default class DropdownView extends View { /** * Controls whether the dropdown is enabled, i.e. it opens the panel when clicked. * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or - * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. * * Also see {@link module:ui/button/buttonview~ButtonView#isEnabled}. * @@ -159,8 +156,7 @@ export default class DropdownView extends View { * Controls whether the dropdown is "on". It makes sense when a feature it represents * is currently active. * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or - * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. * * Also see {@link module:ui/button/buttonview~ButtonView#isOn}. * @@ -171,8 +167,7 @@ export default class DropdownView extends View { /** * (Optional) Controls whether the label of the dropdown is visible. * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or - * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. * * Also see {@link module:ui/button/buttonview~ButtonView#withText}. * @@ -183,8 +178,7 @@ export default class DropdownView extends View { /** * (Optional) Controls the icon of the dropdown. * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown} or - * {@link module:ui/dropdown/utils~createSplitButtonDropdown}. + * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. * * Also see {@link module:ui/button/buttonview~ButtonView#withText}. * diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 38c6fb76..70ce375f 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -10,7 +10,6 @@ import DropdownPanelView from './dropdownpanelview'; import DropdownView from './dropdownview'; import DropdownButtonView from './button/dropdownbuttonview'; -import SplitButtonView from './button/splitbuttonview'; import ToolbarView from '../toolbar/toolbarview'; import ListView from '../list/listview'; import ListItemView from '../list/listitemview'; @@ -21,7 +20,9 @@ import '../../theme/components/dropdown/toolbardropdown.css'; /** * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of - * {@link module:ui/button/buttonview~ButtonView} in toolbar. + * a button class passed as `ButtonClass` parameter` + * + * The default value of `ButtonClass` is {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} class. * * const dropdown = createDropdown( model ); * @@ -38,26 +39,11 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * // Will render a dropdown labeled "A dropdown" with an empty panel. * document.body.appendChild( dropdown.element ); * - * Also see {@link module:ui/dropdown/utils~createSplitButtonDropdown}, {@link module:ui/dropdown/utils~addListToDropdown} - * and {@link module:ui/dropdown/utils~addToolbarToDropdown}. + * The second supported button class is {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} * - * @param {module:utils/locale~Locale} locale The locale instance. - * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. - */ -export function createDropdown( locale ) { - const buttonView = new DropdownButtonView( locale ); - - const dropdownView = prepareDropdown( locale, buttonView ); - addDefaultBehavior( dropdownView ); - - return dropdownView; -} - -/** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of - * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} in toolbar. + * import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview'; * - * const dropdown = createSplitButtonDropdown( model ); + * const dropdown = createDropdown( model, SplitButtonView ); * * // Configure dropdown properties: * dropdown.set( { @@ -71,19 +57,25 @@ export function createDropdown( locale ) { * // Will render a dropdown labeled "A dropdown" with an empty panel. * document.body.appendChild( dropdown.element ); * - * Also see {@link module:ui/dropdown/utils~createDropdown}, {@link module:ui/dropdown/utils~addListToDropdown} - * and {@link module:ui/dropdown/utils~addToolbarToDropdown}. + * Also see {@link module:ui/dropdown/utils~addListToDropdown} and {@link module:ui/dropdown/utils~addToolbarToDropdown}. * * @param {module:utils/locale~Locale} locale The locale instance. + * @param {Function} ButtonClass The dropdown button view class. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ -export function createSplitButtonDropdown( locale ) { - const buttonView = new SplitButtonView( locale ); +export function createDropdown( locale, ButtonClass = DropdownButtonView ) { + const buttonView = new ButtonClass( locale ); - const dropdownView = prepareDropdown( locale, buttonView ); - addDefaultBehavior( dropdownView ); + const panelView = new DropdownPanelView( locale ); + const dropdownView = new DropdownView( locale, buttonView, panelView ); - buttonView.delegate( 'execute' ).to( dropdownView ); + buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( dropdownView ); + + buttonView.bind( 'isOn' ).to( dropdownView, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { + return isOn || isOpen; + } ); + + addDefaultBehavior( dropdownView ); return dropdownView; } @@ -108,8 +100,7 @@ export function createSplitButtonDropdown( locale ) { * dropdown.render() * document.body.appendChild( dropdown.element ); * - * See {@link module:ui/dropdown/utils~createDropdown}, {@link module:ui/dropdown/utils~createSplitButtonDropdown} - * and {@link module:ui/toolbar/toolbarview~ToolbarView}. + * See {@link module:ui/dropdown/utils~createDropdown} and {@link module:ui/toolbar/toolbarview~ToolbarView}. * * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ToolbarView` will be added. * @param {Iterable.} buttons @@ -151,8 +142,7 @@ export function addToolbarToDropdown( dropdownView, buttons ) { * {@link module:ui/list/listitemview~ListItemView list items}. * * - * See {@link module:ui/dropdown/utils~createDropdown}, {@link module:ui/dropdown/utils~createSplitButtonDropdown} - * and {@link module:list/list~List}. + * See {@link module:ui/dropdown/utils~createDropdown} and {@link module:list/list~List}. * * @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView A dropdown instance to which `ListVIew` will be added. * @param {module:utils/collection~Collection} items @@ -178,25 +168,6 @@ export function addListToDropdown( dropdownView, items ) { listView.items.delegate( 'execute' ).to( dropdownView ); } -// Creates a dropdown view instance and binds dropdown view with a button view. -// -// @param {module:utils/locale~Locale} locale The locale instance. -// @param {module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView|module:ui/dropdown/button/splitbuttonview~SplitButtonView} -// buttonView // The button view instance. -// @returns {module:ui/dropdown/dropdownview~DropdownView} -function prepareDropdown( locale, buttonView ) { - const panelView = new DropdownPanelView( locale ); - const dropdownView = new DropdownView( locale, buttonView, panelView ); - - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( dropdownView ); - - buttonView.bind( 'isOn' ).to( dropdownView, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { - return isOn || isOpen; - } ); - - return dropdownView; -} - // Add a set of default behaviors to dropdown view. // // @param {module:ui/dropdown/dropdownview~DropdownView} dropdownView diff --git a/tests/dropdown/button/dropdownbuttonview.js b/tests/dropdown/button/dropdownbuttonview.js new file mode 100644 index 00000000..9fb6ad93 --- /dev/null +++ b/tests/dropdown/button/dropdownbuttonview.js @@ -0,0 +1,47 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import IconView from '../../../src/icon/iconview'; +import DropdownButtonView from '../../../src/dropdown/button/dropdownbuttonview'; + +testUtils.createSinonSandbox(); + +describe( 'DropdownButtonView', () => { + let locale, view; + + beforeEach( () => { + locale = { t() {} }; + + view = new DropdownButtonView( locale ); + view.render(); + } ); + + describe( 'constructor()', () => { + it( 'sets view#locale', () => { + expect( view.locale ).to.equal( locale ); + } ); + + it( 'creates view#arrowView', () => { + expect( view.arrowView ).to.be.instanceOf( IconView ); + } ); + + it( 'creates element from template', () => { + expect( view.element.tagName ).to.equal( 'BUTTON' ); + } ); + } ); + + describe( 'bindings', () => { + it( 'delegates view#execute to view#select', () => { + const spy = sinon.spy(); + + view.on( 'select', spy ); + + view.fire( 'execute' ); + + sinon.assert.calledOnce( spy ); + } ); + } ); +} ); diff --git a/tests/dropdown/button/splitbuttonview.js b/tests/dropdown/button/splitbuttonview.js index 2541e741..1bad0577 100644 --- a/tests/dropdown/button/splitbuttonview.js +++ b/tests/dropdown/button/splitbuttonview.js @@ -29,10 +29,10 @@ describe( 'SplitButtonView', () => { expect( view.actionView ).to.be.instanceOf( ButtonView ); } ); - it( 'creates view#selectView', () => { - expect( view.selectView ).to.be.instanceOf( ButtonView ); - expect( view.selectView.element.classList.contains( 'ck-splitbutton-select' ) ).to.be.true; - expect( view.selectView.icon ).to.be.not.undefined; + it( 'creates view#arrowView', () => { + expect( view.arrowView ).to.be.instanceOf( ButtonView ); + expect( view.arrowView.element.classList.contains( 'ck-splitbutton-arrow' ) ).to.be.true; + expect( view.arrowView.icon ).to.be.not.undefined; } ); it( 'creates element from template', () => { @@ -41,7 +41,7 @@ describe( 'SplitButtonView', () => { } ); describe( 'activates keyboard navigation for the toolbar', () => { - it( 'so "arrowright" on view#selectView does nothing', () => { + it( 'so "arrowright" on view#arrowView does nothing', () => { const keyEvtData = { keyCode: keyCodes.arrowright, preventDefault: sinon.spy(), @@ -49,7 +49,7 @@ describe( 'SplitButtonView', () => { }; view.focusTracker.isFocused = true; - view.focusTracker.focusedElement = view.selectView.element; + view.focusTracker.focusedElement = view.arrowView.element; const spy = sinon.spy( view.actionView, 'focus' ); @@ -59,7 +59,7 @@ describe( 'SplitButtonView', () => { sinon.assert.notCalled( keyEvtData.stopPropagation ); } ); - it( 'so "arrowleft" on view#selectView focuses view#actionView', () => { + it( 'so "arrowleft" on view#arrowView focuses view#actionView', () => { const keyEvtData = { keyCode: keyCodes.arrowleft, preventDefault: sinon.spy(), @@ -67,7 +67,7 @@ describe( 'SplitButtonView', () => { }; view.focusTracker.isFocused = true; - view.focusTracker.focusedElement = view.selectView.element; + view.focusTracker.focusedElement = view.arrowView.element; const spy = sinon.spy( view.actionView, 'focus' ); @@ -77,7 +77,7 @@ describe( 'SplitButtonView', () => { sinon.assert.calledOnce( keyEvtData.stopPropagation ); } ); - it( 'so "arrowright" on view#actionView focuses view#selectView', () => { + it( 'so "arrowright" on view#actionView focuses view#arrowView', () => { const keyEvtData = { keyCode: keyCodes.arrowright, preventDefault: sinon.spy(), @@ -87,7 +87,7 @@ describe( 'SplitButtonView', () => { view.focusTracker.isFocused = true; view.focusTracker.focusedElement = view.actionView.element; - const spy = sinon.spy( view.selectView, 'focus' ); + const spy = sinon.spy( view.arrowView, 'focus' ); view.keystrokes.press( keyEvtData ); sinon.assert.calledOnce( spy ); @@ -105,7 +105,7 @@ describe( 'SplitButtonView', () => { view.focusTracker.isFocused = true; view.focusTracker.focusedElement = view.actionView.element; - const spy = sinon.spy( view.selectView, 'focus' ); + const spy = sinon.spy( view.arrowView, 'focus' ); view.keystrokes.press( keyEvtData ); sinon.assert.notCalled( spy ); @@ -150,22 +150,22 @@ describe( 'SplitButtonView', () => { expect( view.actionView.label ).to.equal( 'foo' ); } ); - it( 'delegates selectView#execute to view#select', () => { + it( 'delegates arrowView#execute to view#select', () => { const spy = sinon.spy(); view.on( 'select', spy ); - view.selectView.fire( 'execute' ); + view.arrowView.fire( 'execute' ); sinon.assert.calledOnce( spy ); } ); - it( 'binds selectView#isEnabled to view', () => { - expect( view.selectView.isEnabled ).to.be.true; + it( 'binds arrowView#isEnabled to view', () => { + expect( view.arrowView.isEnabled ).to.be.true; view.isEnabled = false; - expect( view.selectView.isEnabled ).to.be.false; + expect( view.arrowView.isEnabled ).to.be.false; } ); } ); diff --git a/tests/dropdown/dropdownview.js b/tests/dropdown/dropdownview.js index 19082b7a..268a5f51 100644 --- a/tests/dropdown/dropdownview.js +++ b/tests/dropdown/dropdownview.js @@ -8,7 +8,6 @@ import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; import ButtonView from '../../src/button/buttonview'; -import IconView from '../../src/icon/iconview'; import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; describe( 'DropdownView', () => { @@ -55,21 +54,15 @@ describe( 'DropdownView', () => { it( 'creates #element from template', () => { expect( view.element.classList.contains( 'ck-dropdown' ) ).to.be.true; - expect( view.element.children ).to.have.length( 3 ); + expect( view.element.children ).to.have.length( 2 ); expect( view.element.children[ 0 ] ).to.equal( buttonView.element ); - expect( view.element.children[ 1 ] ).to.equal( view.arrowView.element ); - expect( view.element.children[ 2 ] ).to.equal( panelView.element ); + expect( view.element.children[ 1 ] ).to.equal( panelView.element ); } ); it( 'sets view#buttonView class', () => { expect( view.buttonView.element.classList.contains( 'ck-dropdown__button' ) ).to.be.true; } ); - it( 'creates #arrowView icon instance', () => { - expect( view.arrowView ).to.be.instanceOf( IconView ); - expect( view.arrowView.element.classList.contains( 'ck-dropdown__arrow' ) ); - } ); - describe( 'bindings', () => { describe( 'view#isOpen to view.buttonView#select', () => { it( 'is activated', () => { diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index c3dde30f..10d37cf0 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -17,7 +17,7 @@ import DropdownPanelView from '../../src/dropdown/dropdownpanelview'; import SplitButtonView from '../../src/dropdown/button/splitbuttonview'; import View from '../../src/view'; import ToolbarView from '../../src/toolbar/toolbarview'; -import { createDropdown, createSplitButtonDropdown, addToolbarToDropdown, addListToDropdown } from '../../src/dropdown/utils'; +import { createDropdown, addToolbarToDropdown, addListToDropdown } from '../../src/dropdown/utils'; import ListItemView from '../../src/list/listitemview'; import ListView from '../../src/list/listview'; @@ -52,6 +52,12 @@ describe( 'utils', () => { expect( dropdownView.buttonView ).to.be.instanceOf( ButtonView ); } ); + it( 'creates dropdown#buttonView out of passed SplitButtonView', () => { + dropdownView = createDropdown( locale, SplitButtonView ); + + expect( dropdownView.buttonView ).to.be.instanceOf( SplitButtonView ); + } ); + it( 'binds button attributes to the model', () => { const modelDef = { label: 'foo', @@ -127,120 +133,137 @@ describe( 'utils', () => { it( 'is a ButtonView instance', () => { expect( dropdownView.buttonView ).to.be.instanceof( ButtonView ); } ); + } ); - it( 'delegates "execute" to "select" event', () => { - const spy = sinon.spy(); + describe( 'hasDefaultBehavior', () => { + describe( 'closeDropdownOnBlur()', () => { + beforeEach( () => { + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); - dropdownView.buttonView.on( 'select', spy ); + afterEach( () => { + dropdownView.element.remove(); + } ); - dropdownView.buttonView.fire( 'execute' ); + it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + // Closed the dropdown. + expect( dropdownView.isOpen ).to.be.false; + // Fire event from outside of the dropdown. + document.body.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); + // Dropdown is still closed. + expect( dropdownView.isOpen ).to.be.false; + } ); - sinon.assert.calledOnce( spy ); - } ); - } ); + it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { + // Open the dropdown. + dropdownView.isOpen = true; - addDefaultBehaviorTests(); - } ); + // Event from view.element should be discarded. + dropdownView.element.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); - describe( 'createSplitButtonDropdown()', () => { - beforeEach( () => { - dropdownView = createSplitButtonDropdown( locale ); - } ); + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; - it( 'accepts locale', () => { - expect( dropdownView.locale ).to.equal( locale ); - expect( dropdownView.panelView.locale ).to.equal( locale ); - } ); + // Event from within view.element should be discarded. + const child = document.createElement( 'div' ); + dropdownView.element.appendChild( child ); - it( 'returns view', () => { - expect( dropdownView ).to.be.instanceOf( DropdownView ); - } ); + child.dispatchEvent( new Event( 'mousedown', { + bubbles: true + } ) ); - it( 'creates dropdown#panelView out of DropdownPanelView', () => { - expect( dropdownView.panelView ).to.be.instanceOf( DropdownPanelView ); - } ); + // Dropdown is still open. + expect( dropdownView.isOpen ).to.be.true; + } ); + } ); - it( 'creates dropdown#buttonView out of SplitButtonView', () => { - expect( dropdownView.buttonView ).to.be.instanceOf( SplitButtonView ); - } ); + describe( 'closeDropdownOnExecute()', () => { + beforeEach( () => { + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); - it( 'binds button attributes to the model', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; + afterEach( () => { + dropdownView.element.remove(); + } ); - dropdownView = createDropdown( locale ); - dropdownView.set( modelDef ); + it( 'changes view#isOpen on view#execute', () => { + dropdownView.isOpen = true; - assertBinding( dropdownView.buttonView, - modelDef, - [ - [ dropdownView, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] - ], - { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } - ); - } ); + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; - it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; + dropdownView.fire( 'execute' ); + expect( dropdownView.isOpen ).to.be.false; + } ); + } ); - dropdownView = createDropdown( locale ); - dropdownView.set( modelDef ); + describe( 'focusDropdownContentsOnArrows()', () => { + let panelChildView; - dropdownView.isOpen = false; - expect( dropdownView.buttonView.isOn ).to.be.false; + beforeEach( () => { + panelChildView = new View(); + panelChildView.setTemplate( { tag: 'div' } ); + panelChildView.focus = () => {}; + panelChildView.focusLast = () => {}; - dropdownView.isOn = true; - expect( dropdownView.buttonView.isOn ).to.be.true; + dropdownView.panelView.children.add( panelChildView ); - dropdownView.isOpen = true; - expect( dropdownView.buttonView.isOn ).to.be.true; + dropdownView.render(); + document.body.appendChild( dropdownView.element ); + } ); - dropdownView.isOn = false; - expect( dropdownView.buttonView.isOn ).to.be.true; - } ); + afterEach( () => { + dropdownView.element.remove(); + } ); - it( 'binds dropdown#isEnabled to the model', () => { - const modelDef = { - label: 'foo', - isEnabled: true, - withText: false, - tooltip: false - }; + it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowdown, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focus' ); - dropdownView = createDropdown( locale ); - dropdownView.set( modelDef ); + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); - assertBinding( dropdownView, - { isEnabled: true }, - [ - [ dropdownView, { isEnabled: false } ] - ], - { isEnabled: false } - ); - } ); + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); - describe( '#buttonView', () => { - it( 'accepts locale', () => { - expect( dropdownView.buttonView.locale ).to.equal( locale ); - } ); + sinon.assert.calledOnce( spy ); + } ); + + it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { + const keyEvtData = { + keyCode: keyCodes.arrowup, + preventDefault: sinon.spy(), + stopPropagation: sinon.spy() + }; + const spy = sinon.spy( panelChildView, 'focusLast' ); + + dropdownView.isOpen = false; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.notCalled( spy ); - it( 'returns SplitButtonView instance', () => { - expect( dropdownView.buttonView ).to.be.instanceof( SplitButtonView ); + dropdownView.isOpen = true; + dropdownView.keystrokes.press( keyEvtData ); + sinon.assert.calledOnce( spy ); + } ); } ); } ); - - addDefaultBehaviorTests(); } ); describe( 'addToolbarToDropdown()', () => { @@ -381,137 +404,4 @@ describe( 'utils', () => { } ); } ); } ); - - function addDefaultBehaviorTests() { - describe( 'hasDefaultBehavior', () => { - describe( 'closeDropdownOnBlur()', () => { - beforeEach( () => { - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - it( 'listens to view#isOpen and reacts to DOM events (valid target)', () => { - // Open the dropdown. - dropdownView.isOpen = true; - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - // Closed the dropdown. - expect( dropdownView.isOpen ).to.be.false; - // Fire event from outside of the dropdown. - document.body.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - // Dropdown is still closed. - expect( dropdownView.isOpen ).to.be.false; - } ); - - it( 'listens to view#isOpen and reacts to DOM events (invalid target)', () => { - // Open the dropdown. - dropdownView.isOpen = true; - - // Event from view.element should be discarded. - dropdownView.element.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( dropdownView.isOpen ).to.be.true; - - // Event from within view.element should be discarded. - const child = document.createElement( 'div' ); - dropdownView.element.appendChild( child ); - - child.dispatchEvent( new Event( 'mousedown', { - bubbles: true - } ) ); - - // Dropdown is still open. - expect( dropdownView.isOpen ).to.be.true; - } ); - } ); - - describe( 'closeDropdownOnExecute()', () => { - beforeEach( () => { - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - it( 'changes view#isOpen on view#execute', () => { - dropdownView.isOpen = true; - - dropdownView.fire( 'execute' ); - expect( dropdownView.isOpen ).to.be.false; - - dropdownView.fire( 'execute' ); - expect( dropdownView.isOpen ).to.be.false; - } ); - } ); - - describe( 'focusDropdownContentsOnArrows()', () => { - let panelChildView; - - beforeEach( () => { - panelChildView = new View(); - panelChildView.setTemplate( { tag: 'div' } ); - panelChildView.focus = () => {}; - panelChildView.focusLast = () => {}; - - // TODO: describe this as #contentView instead of #listView and #toolbarView - dropdownView.panelView.children.add( panelChildView ); - - dropdownView.render(); - document.body.appendChild( dropdownView.element ); - } ); - - afterEach( () => { - dropdownView.element.remove(); - } ); - - it( '"arrowdown" focuses the #innerPanelView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowdown, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( panelChildView, 'focus' ); - - dropdownView.isOpen = false; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - dropdownView.isOpen = true; - dropdownView.keystrokes.press( keyEvtData ); - - sinon.assert.calledOnce( spy ); - } ); - - it( '"arrowup" focuses the last #item in #innerPanelView if dropdown is open', () => { - const keyEvtData = { - keyCode: keyCodes.arrowup, - preventDefault: sinon.spy(), - stopPropagation: sinon.spy() - }; - const spy = sinon.spy( panelChildView, 'focusLast' ); - - dropdownView.isOpen = false; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.notCalled( spy ); - - dropdownView.isOpen = true; - dropdownView.keystrokes.press( keyEvtData ); - sinon.assert.calledOnce( spy ); - } ); - } ); - } ); - } } ); diff --git a/theme/components/button/splitbutton.css b/theme/components/button/splitbutton.css index 3be00c8f..1bcd63c3 100644 --- a/theme/components/button/splitbutton.css +++ b/theme/components/button/splitbutton.css @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md. */ -.ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-select) { +.ck-rounded-corners .ck-splitbutton > .ck-button:not(.ck-splitbutton-arrow) { border-top-right-radius: unset; border-bottom-right-radius: unset; @@ -12,7 +12,7 @@ } } -.ck-rounded-corners .ck-splitbutton > .ck-splitbutton-select { +.ck-rounded-corners .ck-splitbutton > .ck-splitbutton-arrow { border-top-left-radius: unset; border-bottom-left-radius: unset; } @@ -21,7 +21,7 @@ /* Enable font size inheritance, which allows fluid UI scaling. */ font-size: inherit; - & .ck-splitbutton .ck-splitbutton-select svg { + & .ck-splitbutton .ck-splitbutton-arrow svg { position: static; top: initial; transform: initial; From f92a96b7ee90eae4d22e701dce0a760d73702c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 15:43:35 +0100 Subject: [PATCH 66/78] Docs: Updated the dropdown framework guide stub. --- docs/framework/guides/dropdowns.md | 65 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/docs/framework/guides/dropdowns.md b/docs/framework/guides/dropdowns.md index 770ffb92..80b5caae 100644 --- a/docs/framework/guides/dropdowns.md +++ b/docs/framework/guides/dropdowns.md @@ -5,17 +5,47 @@ order: 30 # Dropdowns +## Creating ListView dropdown with standard button + +```js +import Model from '@ckeditor/ckeditor5-ui/src/model'; + +import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; + +const items = [ + new Model( { + label: 'Do Foo', + commandName: 'foo' + } ), + new Model( { + label: 'Do Bar', + commandName: 'bar' + } ), +]; + +const dropdownView = createDropdown( locale ); + +addListToDropdown( dropdownView, items ); + +// Execute command when an item from the dropdown is selected. +dropdownView.on( 'execute', evt => { + editor.execute( evt.source.commandName ); + editor.editing.view.focus(); +} ); +``` + ## Creating Toolbar dropdown with SplitButton ```js import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; import bindOneToMany from '@ckeditor/ckeditor5-ui/src/bindings/bindonetomany'; -import { addToolbarToDropdown, createSplitButtonDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; +import { addToolbarToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; +import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview'; buttons.push( new ButtonView() ); buttons.push( componentFactory.create( 'someExistingButton' ) ); -const dropdownView = createSplitButtonDropdown( locale ); +const dropdownView = createDropdown( locale, SplitButtonView ); dropdownView.set( { icon: 'some SVG', @@ -32,37 +62,8 @@ bindOneToMany( dropdownView, 'isEnabled', buttons, 'isEnabled', addToolbarToDropdown( dropdownView, buttons ); // Execute current action from dropdown's split button action button. -dropdownView.on( 'execute', () => { +dropdownView.buttonView.on( 'execute', () => { editor.execute( 'command', { value: model.commandValue } ); editor.editing.view.focus(); } ); ``` - -## Creating ListView dropdown with standard button - -```js -import Model from '@ckeditor/ckeditor5-ui/src/model'; - -import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; - -const items = [ - new Model( { - label: 'Do Foo', - commandName: 'foo' - } ), - new Model( { - label: 'Do Bar', - commandName: 'bar' - } ), -]; - -const dropdownView = createDropdown( locale ); - -addListToDropdown( dropdownView, items ); - -// Execute command when an item from the dropdown is selected. -dropdownView.on( 'execute', evt => { - editor.execute( evt.source.commandName ); - editor.editing.view.focus(); -} ); -``` From 3e0d7a8d954611cbed0bef890c340ea97140837c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 15:46:06 +0100 Subject: [PATCH 67/78] Code style: Fix code indentation in code examples. --- src/dropdown/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 70ce375f..e850af97 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -41,7 +41,7 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * * The second supported button class is {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} * - * import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview'; + * import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview'; * * const dropdown = createDropdown( model, SplitButtonView ); * @@ -85,7 +85,7 @@ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { * * const buttons = []; * - * // Either create a new ButtonView instance or create existing. + * // Either create a new ButtonView instance or create existing. * buttons.push( new ButtonView() ); * buttons.push( editor.ui.componentFactory.get( 'someButton' ) ); * From 6a08edc45e1bb3b5f107fb2e87bbec71ef7a1bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 18:49:41 +0100 Subject: [PATCH 68/78] Changed: Remove `dropdown.buttonView` bindings to `dropdown`. --- docs/framework/guides/dropdowns.md | 2 +- src/dropdown/dropdownview.js | 64 ---------------------------- src/dropdown/utils.js | 14 +++---- tests/dropdown/manual/dropdown.js | 6 +-- tests/dropdown/manual/dropdown.md | 2 +- tests/dropdown/utils.js | 67 ++++-------------------------- 6 files changed, 19 insertions(+), 136 deletions(-) diff --git a/docs/framework/guides/dropdowns.md b/docs/framework/guides/dropdowns.md index 80b5caae..dab7b045 100644 --- a/docs/framework/guides/dropdowns.md +++ b/docs/framework/guides/dropdowns.md @@ -47,7 +47,7 @@ buttons.push( componentFactory.create( 'someExistingButton' ) ); const dropdownView = createDropdown( locale, SplitButtonView ); -dropdownView.set( { +dropdownView.buttonView.set( { icon: 'some SVG', tooltip: 'My dropdown' } ); diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index ef718208..faabfa12 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -130,70 +130,6 @@ export default class DropdownView extends View { } } ); - /** - * The label of the dropdown. - * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. - * - * Also see {@link module:ui/button/buttonview~ButtonView#label}. - * - * @observable - * @member {String} #label - */ - - /** - * Controls whether the dropdown is enabled, i.e. it opens the panel when clicked. - * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. - * - * Also see {@link module:ui/button/buttonview~ButtonView#isEnabled}. - * - * @observable - * @member {Boolean} #isEnabled - */ - - /** - * Controls whether the dropdown is "on". It makes sense when a feature it represents - * is currently active. - * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. - * - * Also see {@link module:ui/button/buttonview~ButtonView#isOn}. - * - * @observable - * @member {Boolean} #isOn - */ - - /** - * (Optional) Controls whether the label of the dropdown is visible. - * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. - * - * Also see {@link module:ui/button/buttonview~ButtonView#withText}. - * - * @observable - * @member {Boolean} #withText - */ - - /** - * (Optional) Controls the icon of the dropdown. - * - * **Note**: Only supported when dropdown was created using {@link module:ui/dropdown/utils~createDropdown}. - * - * Also see {@link module:ui/button/buttonview~ButtonView#withText}. - * - * @observable - * @member {Boolean} #icon - */ - - /** - * Controls dropdown's toolbar direction. - * **Note**: Only supported when dropdown has list view added using {@link module:ui/dropdown/utils~addToolbarToDropdown}. - * - * @observable - * @member {Boolean} #isVertical=false - */ - /** * A child {@link module:ui/list/listview~ListView list view} of the dropdown located * in its {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}. diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index e850af97..c334f3f1 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -69,11 +69,13 @@ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { const panelView = new DropdownPanelView( locale ); const dropdownView = new DropdownView( locale, buttonView, panelView ); - buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( dropdownView ); + buttonView.bind( 'isEnabled' ).to( dropdownView ); - buttonView.bind( 'isOn' ).to( dropdownView, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => { - return isOn || isOpen; - } ); + if ( buttonView instanceof DropdownButtonView ) { + buttonView.bind( 'isOn' ).to( dropdownView, 'isOpen' ); + } else { + buttonView.arrowView.bind( 'isOn' ).to( dropdownView, 'isOpen' ); + } addDefaultBehavior( dropdownView ); @@ -93,7 +95,7 @@ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { * * addToolbarToDropdown( dropdown, buttons ); * - * dropdown.isVertical = true; + * dropdown.toolbarView.isVertical = true; * * // Will render a vertical button dropdown labeled "A button dropdown" * // with a button group in the panel containing two buttons. @@ -108,8 +110,6 @@ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { export function addToolbarToDropdown( dropdownView, buttons ) { const toolbarView = dropdownView.toolbarView = new ToolbarView(); - toolbarView.bind( 'isVertical' ).to( dropdownView, 'isVertical' ); - dropdownView.extendTemplate( { attributes: { class: [ 'ck-toolbar-dropdown' ] diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index f8561f54..c30bdc21 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -28,7 +28,7 @@ const ui = testUtils.createTestUIView( { function testEmpty() { const dropdownView = createDropdown( {} ); - dropdownView.set( { + dropdownView.buttonView.set( { label: 'Dropdown', isEnabled: true, isOn: false, @@ -52,7 +52,7 @@ function testList() { const dropdownView = createDropdown( {} ); - dropdownView.set( { + dropdownView.buttonView.set( { label: 'ListDropdown', isEnabled: true, isOn: false, @@ -95,7 +95,7 @@ function testSharedModel() { function testLongLabel() { const dropdownView = createDropdown( {} ); - dropdownView.set( { + dropdownView.buttonView.set( { label: 'Dropdown with a very long label', isEnabled: true, isOn: false, diff --git a/tests/dropdown/manual/dropdown.md b/tests/dropdown/manual/dropdown.md index 73818722..27e4a90b 100644 --- a/tests/dropdown/manual/dropdown.md +++ b/tests/dropdown/manual/dropdown.md @@ -32,4 +32,4 @@ listDropdownCollection.add( ## Toolbar Dropdown 1. Play with `buttons[ n ].isOn` to control buttonDropdown active icon. 2. Play with `buttons[ n ].isEnabled` to control buttonDropdown disabled state (all buttons must be set to `false`). -3. Play with `toolbarDropdownModel.isVertical` to control buttonDropdown vertical/horizontal alignment. +3. Play with `toolbarDropdown.toolbarView.isVertical` to control buttonDropdown vertical/horizontal alignment. diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 10d37cf0..6b02b799 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -58,71 +58,27 @@ describe( 'utils', () => { expect( dropdownView.buttonView ).to.be.instanceOf( SplitButtonView ); } ); - it( 'binds button attributes to the model', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - + it( 'binds #isEnabled to the buttonView', () => { dropdownView = createDropdown( locale ); - dropdownView.set( modelDef ); - assertBinding( dropdownView.buttonView, - modelDef, + { isEnabled: true }, [ - [ dropdownView, { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } ] + [ dropdownView, { isEnabled: false } ] ], - { label: 'bar', isEnabled: false, isOn: true, withText: true, tooltip: true } + { isEnabled: false } ); } ); - it( 'binds button#isOn do dropdown #isOpen and model #isOn', () => { - const modelDef = { - label: 'foo', - isOn: false, - isEnabled: true, - withText: false, - tooltip: false - }; - + it( 'binds button#isOn to dropdown #isOpen', () => { dropdownView = createDropdown( locale ); - dropdownView.set( modelDef ); + dropdownView.buttonView.isEnabled = true; dropdownView.isOpen = false; expect( dropdownView.buttonView.isOn ).to.be.false; - dropdownView.isOn = true; - expect( dropdownView.buttonView.isOn ).to.be.true; - dropdownView.isOpen = true; expect( dropdownView.buttonView.isOn ).to.be.true; - - dropdownView.isOn = false; - expect( dropdownView.buttonView.isOn ).to.be.true; - } ); - - it( 'binds dropdown#isEnabled to the model', () => { - const modelDef = { - label: 'foo', - isEnabled: true, - withText: false, - tooltip: false - }; - - dropdownView = createDropdown( locale ); - dropdownView.set( modelDef ); - - assertBinding( dropdownView, - { isEnabled: true }, - [ - [ dropdownView, { isEnabled: false } ] - ], - { isEnabled: false } - ); } ); describe( '#buttonView', () => { @@ -279,7 +235,6 @@ describe( 'utils', () => { } ); dropdownView = createDropdown( locale ); - dropdownView.set( 'isVertical', true ); addToolbarToDropdown( dropdownView, buttons ); @@ -318,14 +273,6 @@ describe( 'utils', () => { dropdownView.toolbarView.items.get( 0 ).fire( 'execute' ); } ); - - it( 'reacts on model#isVertical', () => { - dropdownView.isVertical = false; - expect( dropdownView.toolbarView.isVertical ).to.be.false; - - dropdownView.isVertical = true; - expect( dropdownView.toolbarView.isVertical ).to.be.true; - } ); } ); } ); @@ -336,7 +283,7 @@ describe( 'utils', () => { items = new Collection(); dropdownView = createDropdown( locale ); - dropdownView.set( { + dropdownView.buttonView.set( { isEnabled: true, isOn: false, label: 'foo' From 33783ebd493c0fdc892f5ab26b15b668faeff1ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 18:53:46 +0100 Subject: [PATCH 69/78] Docs: Move dropdowns framework guide to architecture/ui-library guide. --- docs/framework/guides/dropdowns.md | 69 ------------------------------ 1 file changed, 69 deletions(-) delete mode 100644 docs/framework/guides/dropdowns.md diff --git a/docs/framework/guides/dropdowns.md b/docs/framework/guides/dropdowns.md deleted file mode 100644 index dab7b045..00000000 --- a/docs/framework/guides/dropdowns.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -category: framework-ui -order: 30 ---- - -# Dropdowns - -## Creating ListView dropdown with standard button - -```js -import Model from '@ckeditor/ckeditor5-ui/src/model'; - -import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; - -const items = [ - new Model( { - label: 'Do Foo', - commandName: 'foo' - } ), - new Model( { - label: 'Do Bar', - commandName: 'bar' - } ), -]; - -const dropdownView = createDropdown( locale ); - -addListToDropdown( dropdownView, items ); - -// Execute command when an item from the dropdown is selected. -dropdownView.on( 'execute', evt => { - editor.execute( evt.source.commandName ); - editor.editing.view.focus(); -} ); -``` - -## Creating Toolbar dropdown with SplitButton - -```js -import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; -import bindOneToMany from '@ckeditor/ckeditor5-ui/src/bindings/bindonetomany'; -import { addToolbarToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'; -import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview'; - -buttons.push( new ButtonView() ); -buttons.push( componentFactory.create( 'someExistingButton' ) ); - -const dropdownView = createDropdown( locale, SplitButtonView ); - -dropdownView.buttonView.set( { - icon: 'some SVG', - tooltip: 'My dropdown' -} ); - - -// This will enable toolbar button when any of button in dropdown is enabled. -bindOneToMany( dropdownView, 'isEnabled', buttons, 'isEnabled', - ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) -); - -// Make this a dropdown with toolbar inside dropdown panel. -addToolbarToDropdown( dropdownView, buttons ); - -// Execute current action from dropdown's split button action button. -dropdownView.buttonView.on( 'execute', () => { - editor.execute( 'command', { value: model.commandValue } ); - editor.editing.view.focus(); -} ); -``` From a0308c317cab70217d0de12ae5ee3fea39de1b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 5 Feb 2018 19:21:53 +0100 Subject: [PATCH 70/78] Docs: Introduce `ButtonInterface` and `DropdownButtonInterface`. --- src/button/buttoninterface.jsdoc | 140 +++++++++++++++++++++ src/button/buttonview.js | 1 + src/dropdown/button/dropdownbuttonview.js | 18 ++- src/dropdown/button/splitbuttonview.js | 7 +- src/dropdown/dropdownbuttoninterface.jsdoc | 21 ++++ src/dropdown/dropdownpanelfocusable.jsdoc | 2 +- 6 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 src/button/buttoninterface.jsdoc create mode 100644 src/dropdown/dropdownbuttoninterface.jsdoc diff --git a/src/button/buttoninterface.jsdoc b/src/button/buttoninterface.jsdoc new file mode 100644 index 00000000..48f17b95 --- /dev/null +++ b/src/button/buttoninterface.jsdoc @@ -0,0 +1,140 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/button/buttoninterface + */ + +/** + * The button interface. + * + * @interface module:ui/button/buttoninterface~ButtonInterface + */ + +/** + * The button view class. + * + * const view = new ButtonView(); + * + * view.set( { + * label: 'A button', + * keystroke: 'Ctrl+B', + * tooltip: true, + * withText: true + * } ); + * + * view.render(); + * + * document.body.append( view.element ); + * + * @extends module:ui/view~View + */ + +/** + * The label of the button view visible to the user when {@link #withText} is `true`. + * It can also be used to create a {@link #tooltip}. + * + * @observable + * @member {String} #label + */ + +/** + * (Optional) The keystroke associated with the button, i.e. CTRL+B, + * in the string format compatible with {@link module:utils/keyboard}. + * + * @observable + * @member {Boolean} #keystroke + */ + +/** + * (Optional) Tooltip of the button, i.e. displayed when hovering the button with the mouse cursor. + * + * * If defined as a `Boolean` (e.g. `true`), then combination of `label` and `keystroke` will be set as a tooltip. + * * If defined as a `String`, tooltip will equal the exact text of that `String`. + * * If defined as a `Function`, `label` and `keystroke` will be passed to that function, which is to return + * a string with the tooltip text. + * + * const view = new ButtonView( locale ); + * view.tooltip = ( label, keystroke ) => `A tooltip for ${ label } and ${ keystroke }.` + * + * @observable + * @default false + * @member {Boolean|String|Function} #tooltip + */ + +/** + * (Optional) The position of the tooltip. See {@link module:ui/tooltip/tooltipview~TooltipView#position} + * to learn more about the available position values. + * + * **Note:** It makes sense only when the {@link #tooltip `tooltip` attribute} is defined. + * + * @observable + * @default 's' + * @member {'s'|'n'} #position + */ + +/** + * The HTML type of the button. Default `button`. + * + * @observable + * @member {'button'|'submit'|'reset'|'menu'} #type + */ + +/** + * Controls whether the button view is "on". It makes sense when a feature it represents + * is currently active, e.g. a bold button is "on" when the selection is in the bold text. + * + * To disable the button, use {@link #isEnabled} instead. + * + * @observable + * @member {Boolean} #isOn + */ + +/** + * Controls whether the button view is enabled, i.e. it can be clicked and execute an action. + * + * To change the "on" state of the button, use {@link #isOn} instead. + * + * @observable + * @member {Boolean} #isEnabled + */ + +/** + * Controls whether the button view is visible. Visible by default, buttons are hidden + * using a CSS class. + * + * @observable + * @member {Boolean} #isVisible + */ + +/** + * (Optional) Controls whether the label of the button is hidden (e.g. an icon–only button). + * + * @observable + * @member {Boolean} #withText + */ + +/** + * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. + * When defined, an `iconView` should be added to the button. + * + * @observable + * @member {String} #icon + */ + +/** + * (Optional) Controls the `tabindex` HTML attribute of the button. By default, the button is focusable + * but does not included in the Tab order. + * + * @observable + * @default -1 + * @member {String} #tabindex + */ +/** + * Fired when the button view is clicked. It won't be fired when the button {@link #isEnabled} + * is `false`. + * + * @event execute + */ diff --git a/src/button/buttonview.js b/src/button/buttonview.js index cd3291ed..699fc165 100644 --- a/src/button/buttonview.js +++ b/src/button/buttonview.js @@ -32,6 +32,7 @@ import '../../theme/components/button/button.css'; * document.body.append( view.element ); * * @extends module:ui/view~View + * @implements module:ui/button/buttoninterface~ButtonInterface */ export default class ButtonView extends View { /** diff --git a/src/dropdown/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js index 3a61055f..5b57793f 100644 --- a/src/dropdown/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -15,7 +15,7 @@ import IconView from '../../icon/iconview'; /** * The default dropdown button view class. * - * const view = new SplitButtonView(); + * const view = new DropdownButtonView(); * * view.set( { * label: 'A button', @@ -28,6 +28,7 @@ import IconView from '../../icon/iconview'; * document.body.append( view.element ); * * @extends module:ui/view~View + * @implements module:ui/dropdown/dropdownbuttoninterface~DropdownButtonInterface */ export default class DropdownButtonView extends ButtonView { /** @@ -37,15 +38,21 @@ export default class DropdownButtonView extends ButtonView { super( locale ); /** - * A secondary button of split button that opens dropdown. + * An icon that displays arrow to indicate a dropdown button. * * @readonly - * @member {module:ui/button/buttonview~ButtonView} + * @member {module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} */ this.arrowView = this._createArrowView(); // Dropdown expects "select" event on button view upon which the dropdown will open. this.delegate( 'execute' ).to( this, 'select' ); + + /** + * Fired when the view is clicked. It won't be fired when the button {@link #isEnabled} is `false`. + * + * @event select + */ } /** @@ -58,11 +65,10 @@ export default class DropdownButtonView extends ButtonView { } /** - * Creates a {@link module:ui/button/buttonview~ButtonView} instance as {@link #arrowView} and binds it with main split button - * attributes. + * Creates a {@link module:ui/icon/iconview~IconView} instance as {@link #arrowView}. * * @private - * @returns {module:ui/button/buttonview~ButtonView} + * @returns {module:ui/icon/iconview~IconView} */ _createArrowView() { const arrowView = new IconView(); diff --git a/src/dropdown/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js index 307b5698..67e92bee 100644 --- a/src/dropdown/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -33,6 +33,7 @@ import '../../../theme/components/button/splitbutton.css'; * document.body.append( view.element ); * * @extends module:ui/view~View + * @implements module:ui/dropdown/dropdownbuttoninterface~DropdownButtonInterface */ export default class SplitButtonView extends View { /** @@ -60,7 +61,7 @@ export default class SplitButtonView extends View { this.set( 'label' ); /** - * (Optional) The keystroke associated with the button, i.e. CTRL+B, + * (Optional) The keystroke associated with the button, i.e. Ctrl+B, * in the string format compatible with {@link module:utils/keyboard}. * * @observable @@ -143,8 +144,8 @@ export default class SplitButtonView extends View { * Instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}. It manages * keystrokes of the split button: * - * * moves focus to select view when action view is focused, - * * moves focus to action view when select view is focused. + * * moves focus to arrow view when action view is focused, + * * moves focus to action view when arrow view is focused. * * @readonly * @member {module:utils/keystrokehandler~KeystrokeHandler} diff --git a/src/dropdown/dropdownbuttoninterface.jsdoc b/src/dropdown/dropdownbuttoninterface.jsdoc new file mode 100644 index 00000000..ac7783af --- /dev/null +++ b/src/dropdown/dropdownbuttoninterface.jsdoc @@ -0,0 +1,21 @@ +/** + * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module ui/dropdown/dropdownbuttoninterface + */ + +/** + * The dropdown button interface. + * + * @interface module:ui/dropdown/dropdownbuttoninterface~DropdownButtonInterface + * @extends module:ui/button/buttoninterface~ButtonInterface + */ + +/** + * Fired when the arrow view is clicked. It won't be fired when the button {@link #isEnabled} is `false`. + * + * @event select + */ diff --git a/src/dropdown/dropdownpanelfocusable.jsdoc b/src/dropdown/dropdownpanelfocusable.jsdoc index 509809b7..7d4bf1b0 100644 --- a/src/dropdown/dropdownpanelfocusable.jsdoc +++ b/src/dropdown/dropdownpanelfocusable.jsdoc @@ -8,7 +8,7 @@ */ /** - * The dropdown panel interface interface for focusable contents. It provides two methods for managing focus of the contents + * The dropdown panel interface for focusable contents. It provides two methods for managing focus of the contents * of dropdown's panel. * * @interface module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable From 1413c2ebeade4fa8d10ed283250aabc2819ec09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 6 Feb 2018 10:07:45 +0100 Subject: [PATCH 71/78] Docs: Update dropdown buttons documentation. --- .../dropdownbuttoninterface.jsdoc | 4 ++-- src/dropdown/button/dropdownbuttonview.js | 4 +++- src/dropdown/button/splitbuttonview.js | 6 +++-- src/dropdown/utils.js | 24 ++++--------------- .../{button => dropdown}/splitbutton.css | 0 5 files changed, 14 insertions(+), 24 deletions(-) rename src/dropdown/{ => button}/dropdownbuttoninterface.jsdoc (72%) rename theme/components/{button => dropdown}/splitbutton.css (100%) diff --git a/src/dropdown/dropdownbuttoninterface.jsdoc b/src/dropdown/button/dropdownbuttoninterface.jsdoc similarity index 72% rename from src/dropdown/dropdownbuttoninterface.jsdoc rename to src/dropdown/button/dropdownbuttoninterface.jsdoc index ac7783af..0d51a3c7 100644 --- a/src/dropdown/dropdownbuttoninterface.jsdoc +++ b/src/dropdown/button/dropdownbuttoninterface.jsdoc @@ -4,13 +4,13 @@ */ /** - * @module ui/dropdown/dropdownbuttoninterface + * @module ui/dropdown/button/dropdownbuttoninterface */ /** * The dropdown button interface. * - * @interface module:ui/dropdown/dropdownbuttoninterface~DropdownButtonInterface + * @interface module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface * @extends module:ui/button/buttoninterface~ButtonInterface */ diff --git a/src/dropdown/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js index 5b57793f..05fb60a3 100644 --- a/src/dropdown/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -27,8 +27,10 @@ import IconView from '../../icon/iconview'; * * document.body.append( view.element ); * + * Also see {@link module:ui/dropdown/utils~createDropdown}. + * * @extends module:ui/view~View - * @implements module:ui/dropdown/dropdownbuttoninterface~DropdownButtonInterface + * @implements module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface */ export default class DropdownButtonView extends ButtonView { /** diff --git a/src/dropdown/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js index 67e92bee..810d94de 100644 --- a/src/dropdown/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -15,7 +15,7 @@ import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker'; import dropdownArrowIcon from '../../../theme/icons/dropdown-arrow.svg'; -import '../../../theme/components/button/splitbutton.css'; +import '../../../theme/components/dropdown/splitbutton.css'; /** * The split button view class. @@ -32,8 +32,10 @@ import '../../../theme/components/button/splitbutton.css'; * * document.body.append( view.element ); * + * Also see {@link module:ui/dropdown/utils~createDropdown}. + * * @extends module:ui/view~View - * @implements module:ui/dropdown/dropdownbuttoninterface~DropdownButtonInterface + * @implements module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface */ export default class SplitButtonView extends View { /** diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index c334f3f1..b710512a 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -20,7 +20,7 @@ import '../../theme/components/dropdown/toolbardropdown.css'; /** * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of - * a button class passed as `ButtonClass` parameter` + * a button class passed as `ButtonClass` parameter. * * The default value of `ButtonClass` is {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} class. * @@ -39,28 +39,14 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * // Will render a dropdown labeled "A dropdown" with an empty panel. * document.body.appendChild( dropdown.element ); * - * The second supported button class is {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} - * - * import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview'; - * - * const dropdown = createDropdown( model, SplitButtonView ); - * - * // Configure dropdown properties: - * dropdown.set( { - * label: 'A dropdown', - * isEnabled: true, - * isOn: false - * } ); - * - * dropdown.render(); - * - * // Will render a dropdown labeled "A dropdown" with an empty panel. - * document.body.appendChild( dropdown.element ); + * The supported button classes for dropdown are: + * * {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} + * * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} * * Also see {@link module:ui/dropdown/utils~addListToDropdown} and {@link module:ui/dropdown/utils~addToolbarToDropdown}. * * @param {module:utils/locale~Locale} locale The locale instance. - * @param {Function} ButtonClass The dropdown button view class. + * @param {module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} ButtonClass The dropdown button view class. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { diff --git a/theme/components/button/splitbutton.css b/theme/components/dropdown/splitbutton.css similarity index 100% rename from theme/components/button/splitbutton.css rename to theme/components/dropdown/splitbutton.css From 41e1bbe8c9c4c15402a101cbb9092805e43a40e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 6 Feb 2018 10:36:57 +0100 Subject: [PATCH 72/78] Docs: Update dropdown documentation. --- src/dropdown/dropdownview.js | 2 +- src/dropdown/utils.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index faabfa12..308d26fc 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -172,7 +172,7 @@ export default class DropdownView extends View { render() { super.render(); - // Toggle the the dropdown when it's button has been clicked. + // Toggle the the dropdown when its button has been clicked. this.listenTo( this.buttonView, 'select', () => { this.isOpen = !this.isOpen; } ); diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index b710512a..6b462750 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -26,11 +26,9 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * * const dropdown = createDropdown( model ); * - * // Configure dropdown properties: - * dropdown.set( { + * // Configure dropdown's button properties: + * dropdown.buttonView.set( { * label: 'A dropdown', - * isEnabled: true, - * isOn: false, * withText: true * } ); * From 2b0291d97cd7082a8d60dde87d8726d7d17a84a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 6 Feb 2018 10:40:56 +0100 Subject: [PATCH 73/78] Docs: Add missing docs for DropdownPanelView. --- src/dropdown/dropdownpanelview.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/dropdown/dropdownpanelview.js b/src/dropdown/dropdownpanelview.js index 11236e37..92c0b88e 100644 --- a/src/dropdown/dropdownpanelview.js +++ b/src/dropdown/dropdownpanelview.js @@ -66,12 +66,22 @@ export default class DropdownPanelView extends View { } ); } + /** + * Focuses the view element or first item in view collection on opening dropdown's panel. + * + * See also {@link module:ui/dropdown/dropdownpanelfocusable}. + */ focus() { if ( this.children.length ) { this.children.first.focus(); } } + /** + * Focuses the view element or last item in view collection on opening dropdown's panel. + * + * See also {@link module:ui/dropdown/dropdownpanelfocusable}. + */ focusLast() { if ( this.children.length ) { const lastChild = this.children.last; From 83447752a6587b152e6e077d70cea6b1c89f1c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 6 Feb 2018 10:48:58 +0100 Subject: [PATCH 74/78] Changed: Rename `DropdownButtonInterface#select` event to `#open`. --- src/dropdown/button/dropdownbuttoninterface.jsdoc | 2 +- src/dropdown/button/dropdownbuttonview.js | 4 ++-- src/dropdown/button/splitbuttonview.js | 2 +- src/dropdown/dropdownview.js | 4 ++-- tests/dropdown/button/dropdownbuttonview.js | 4 ++-- tests/dropdown/button/splitbuttonview.js | 4 ++-- tests/dropdown/dropdownview.js | 6 +++--- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/dropdown/button/dropdownbuttoninterface.jsdoc b/src/dropdown/button/dropdownbuttoninterface.jsdoc index 0d51a3c7..6c2027ad 100644 --- a/src/dropdown/button/dropdownbuttoninterface.jsdoc +++ b/src/dropdown/button/dropdownbuttoninterface.jsdoc @@ -17,5 +17,5 @@ /** * Fired when the arrow view is clicked. It won't be fired when the button {@link #isEnabled} is `false`. * - * @event select + * @event open */ diff --git a/src/dropdown/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js index 05fb60a3..088b60d0 100644 --- a/src/dropdown/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -47,8 +47,8 @@ export default class DropdownButtonView extends ButtonView { */ this.arrowView = this._createArrowView(); - // Dropdown expects "select" event on button view upon which the dropdown will open. - this.delegate( 'execute' ).to( this, 'select' ); + // Dropdown expects "open" event on button view upon which the dropdown will open. + this.delegate( 'execute' ).to( this, 'open' ); /** * Fired when the view is clicked. It won't be fired when the button {@link #isEnabled} is `false`. diff --git a/src/dropdown/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js index 810d94de..03d13f7c 100644 --- a/src/dropdown/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -250,7 +250,7 @@ export default class SplitButtonView extends View { arrowView.bind( 'isEnabled' ).to( this ); - arrowView.delegate( 'execute' ).to( this, 'select' ); + arrowView.delegate( 'execute' ).to( this, 'open' ); return arrowView; } diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 308d26fc..3d98062c 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -172,8 +172,8 @@ export default class DropdownView extends View { render() { super.render(); - // Toggle the the dropdown when its button has been clicked. - this.listenTo( this.buttonView, 'select', () => { + // Toggle the dropdown when its button has been clicked. + this.listenTo( this.buttonView, 'open', () => { this.isOpen = !this.isOpen; } ); diff --git a/tests/dropdown/button/dropdownbuttonview.js b/tests/dropdown/button/dropdownbuttonview.js index 9fb6ad93..8bf9425d 100644 --- a/tests/dropdown/button/dropdownbuttonview.js +++ b/tests/dropdown/button/dropdownbuttonview.js @@ -34,10 +34,10 @@ describe( 'DropdownButtonView', () => { } ); describe( 'bindings', () => { - it( 'delegates view#execute to view#select', () => { + it( 'delegates view#execute to view#open', () => { const spy = sinon.spy(); - view.on( 'select', spy ); + view.on( 'open', spy ); view.fire( 'execute' ); diff --git a/tests/dropdown/button/splitbuttonview.js b/tests/dropdown/button/splitbuttonview.js index 1bad0577..ad16d68e 100644 --- a/tests/dropdown/button/splitbuttonview.js +++ b/tests/dropdown/button/splitbuttonview.js @@ -150,10 +150,10 @@ describe( 'SplitButtonView', () => { expect( view.actionView.label ).to.equal( 'foo' ); } ); - it( 'delegates arrowView#execute to view#select', () => { + it( 'delegates arrowView#execute to view#open', () => { const spy = sinon.spy(); - view.on( 'select', spy ); + view.on( 'open', spy ); view.arrowView.fire( 'execute' ); diff --git a/tests/dropdown/dropdownview.js b/tests/dropdown/dropdownview.js index 268a5f51..123f0630 100644 --- a/tests/dropdown/dropdownview.js +++ b/tests/dropdown/dropdownview.js @@ -72,9 +72,9 @@ describe( 'DropdownView', () => { values.push( view.isOpen ); } ); - view.buttonView.fire( 'select' ); - view.buttonView.fire( 'select' ); - view.buttonView.fire( 'select' ); + view.buttonView.fire( 'open' ); + view.buttonView.fire( 'open' ); + view.buttonView.fire( 'open' ); expect( values ).to.have.members( [ true, false, true ] ); } ); From 110f4f884971b3c6d5f953f62d318de33a46d6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Thu, 8 Feb 2018 12:39:54 +0100 Subject: [PATCH 75/78] Improved ButtonInterface and how other classes implement it. --- src/button/buttoninterface.jsdoc | 28 ++-- src/button/buttonview.js | 134 ++---------------- .../button/dropdownbuttoninterface.jsdoc | 3 +- src/dropdown/button/dropdownbuttonview.js | 14 +- src/dropdown/button/splitbuttonview.js | 103 ++++---------- tests/dropdown/button/splitbuttonview.js | 44 ++++++ 6 files changed, 101 insertions(+), 225 deletions(-) diff --git a/src/button/buttoninterface.jsdoc b/src/button/buttoninterface.jsdoc index 48f17b95..20681c2d 100644 --- a/src/button/buttoninterface.jsdoc +++ b/src/button/buttoninterface.jsdoc @@ -10,26 +10,9 @@ /** * The button interface. * - * @interface module:ui/button/buttoninterface~ButtonInterface - */ - -/** - * The button view class. - * - * const view = new ButtonView(); - * - * view.set( { - * label: 'A button', - * keystroke: 'Ctrl+B', - * tooltip: true, - * withText: true - * } ); + * TODO * - * view.render(); - * - * document.body.append( view.element ); - * - * @extends module:ui/view~View + * @interface module:ui/button/buttoninterface~ButtonInterface */ /** @@ -72,7 +55,7 @@ * * @observable * @default 's' - * @member {'s'|'n'} #position + * @member {'s'|'n'} #tooltipPosition */ /** @@ -89,6 +72,7 @@ * To disable the button, use {@link #isEnabled} instead. * * @observable + * @default true * @member {Boolean} #isOn */ @@ -98,6 +82,7 @@ * To change the "on" state of the button, use {@link #isOn} instead. * * @observable + * @default true * @member {Boolean} #isEnabled */ @@ -106,6 +91,7 @@ * using a CSS class. * * @observable + * @default true * @member {Boolean} #isVisible */ @@ -113,6 +99,7 @@ * (Optional) Controls whether the label of the button is hidden (e.g. an icon–only button). * * @observable + * @default false * @member {Boolean} #withText */ @@ -132,6 +119,7 @@ * @default -1 * @member {String} #tabindex */ + /** * Fired when the button view is clicked. It won't be fired when the button {@link #isEnabled} * is `false`. diff --git a/src/button/buttonview.js b/src/button/buttonview.js index 699fc165..d02a43df 100644 --- a/src/button/buttonview.js +++ b/src/button/buttonview.js @@ -43,118 +43,19 @@ export default class ButtonView extends View { const bind = this.bindTemplate; - /** - * The label of the button view visible to the user when {@link #withText} is `true`. - * It can also be used to create a {@link #tooltip}. - * - * @observable - * @member {String} #label - */ - this.set( 'label' ); - - /** - * (Optional) The keystroke associated with the button, i.e. CTRL+B, - * in the string format compatible with {@link module:utils/keyboard}. - * - * @observable - * @member {Boolean} #keystroke - */ + // Implement ButtonInterface. + this.set( 'icon' ); + this.set( 'isEnabled', true ); + this.set( 'isOn', false ); + this.set( 'isVisible', true ); this.set( 'keystroke' ); - - /** - * (Optional) Tooltip of the button, i.e. displayed when hovering the button with the mouse cursor. - * - * * If defined as a `Boolean` (e.g. `true`), then combination of `label` and `keystroke` will be set as a tooltip. - * * If defined as a `String`, tooltip will equal the exact text of that `String`. - * * If defined as a `Function`, `label` and `keystroke` will be passed to that function, which is to return - * a string with the tooltip text. - * - * const view = new ButtonView( locale ); - * view.tooltip = ( label, keystroke ) => `A tooltip for ${ label } and ${ keystroke }.` - * - * @observable - * @default false - * @member {Boolean|String|Function} #tooltip - */ + this.set( 'label' ); + this.set( 'tabindex', -1 ); this.set( 'tooltip' ); - - /** - * (Optional) The position of the tooltip. See {@link module:ui/tooltip/tooltipview~TooltipView#position} - * to learn more about the available position values. - * - * **Note:** It makes sense only when the {@link #tooltip `tooltip` attribute} is defined. - * - * @observable - * @default 's' - * @member {'s'|'n'} #position - */ this.set( 'tooltipPosition', 's' ); - - /** - * The HTML type of the button. Default `button`. - * - * @observable - * @member {'button'|'submit'|'reset'|'menu'} #type - */ this.set( 'type', 'button' ); - - /** - * Controls whether the button view is "on". It makes sense when a feature it represents - * is currently active, e.g. a bold button is "on" when the selection is in the bold text. - * - * To disable the button, use {@link #isEnabled} instead. - * - * @observable - * @member {Boolean} #isOn - */ - this.set( 'isOn', false ); - - /** - * Controls whether the button view is enabled, i.e. it can be clicked and execute an action. - * - * To change the "on" state of the button, use {@link #isOn} instead. - * - * @observable - * @member {Boolean} #isEnabled - */ - this.set( 'isEnabled', true ); - - /** - * Controls whether the button view is visible. Visible by default, buttons are hidden - * using a CSS class. - * - * @observable - * @member {Boolean} #isVisible - */ - this.set( 'isVisible', true ); - - /** - * (Optional) Controls whether the label of the button is hidden (e.g. an icon–only button). - * - * @observable - * @member {Boolean} #withText - */ this.set( 'withText', false ); - /** - * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. - * When defined, an {@link #iconView} will be added to the button. - * - * @observable - * @member {String} #icon - */ - this.set( 'icon' ); - - /** - * (Optional) Controls the `tabindex` HTML attribute of the button. By default, the button is focusable - * but does not included in the Tab order. - * - * @observable - * @default -1 - * @member {String} #tabindex - */ - this.set( 'tabindex', -1 ); - /** * Collection of the child views inside of the button {@link #element}. * @@ -179,6 +80,13 @@ export default class ButtonView extends View { */ this.labelView = this._createLabelView(); + /** + * (Optional) The icon view of the button. Only present when the {@link #icon icon attribute} is defined. + * + * @readonly + * @member {module:ui/icon/iconview~IconView} #iconView + */ + /** * Tooltip of the button bound to the template. * @@ -195,13 +103,6 @@ export default class ButtonView extends View { this._getTooltipString.bind( this ) ); - /** - * (Optional) The icon view of the button. Only present when the {@link #icon icon attribute} is defined. - * - * @readonly - * @member {module:ui/icon/iconview~IconView} #iconView - */ - this.setTemplate( { tag: 'button', @@ -237,13 +138,6 @@ export default class ButtonView extends View { } ) } } ); - - /** - * Fired when the button view is clicked. It won't be fired when the button {@link #isEnabled} - * is `false`. - * - * @event execute - */ } /** diff --git a/src/dropdown/button/dropdownbuttoninterface.jsdoc b/src/dropdown/button/dropdownbuttoninterface.jsdoc index 6c2027ad..e3737b1f 100644 --- a/src/dropdown/button/dropdownbuttoninterface.jsdoc +++ b/src/dropdown/button/dropdownbuttoninterface.jsdoc @@ -15,7 +15,8 @@ */ /** - * Fired when the arrow view is clicked. It won't be fired when the button {@link #isEnabled} is `false`. + * Fired when the dropdown should be opened. + * It will not be fired when the button {@link #isEnabled is disabled}. * * @event open */ diff --git a/src/dropdown/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js index 088b60d0..9a3ad32a 100644 --- a/src/dropdown/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -27,10 +27,10 @@ import IconView from '../../icon/iconview'; * * document.body.append( view.element ); * - * Also see {@link module:ui/dropdown/utils~createDropdown}. + * Also see the {@link module:ui/dropdown/utils~createDropdown `createDropdown()` util}. * - * @extends module:ui/view~View * @implements module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface + * @extends module:ui/button/buttonview~ButtonView */ export default class DropdownButtonView extends ButtonView { /** @@ -43,18 +43,12 @@ export default class DropdownButtonView extends ButtonView { * An icon that displays arrow to indicate a dropdown button. * * @readonly - * @member {module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} + * @member {module:ui/icon/iconview~IconView} */ this.arrowView = this._createArrowView(); - // Dropdown expects "open" event on button view upon which the dropdown will open. + // DropdownButtonInterface expects the open event upon which the dropdown will open. this.delegate( 'execute' ).to( this, 'open' ); - - /** - * Fired when the view is clicked. It won't be fired when the button {@link #isEnabled} is `false`. - * - * @event select - */ } /** diff --git a/src/dropdown/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js index 03d13f7c..ce815d1b 100644 --- a/src/dropdown/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -32,10 +32,10 @@ import '../../../theme/components/dropdown/splitbutton.css'; * * document.body.append( view.element ); * - * Also see {@link module:ui/dropdown/utils~createDropdown}. + * Also see the {@link module:ui/dropdown/utils~createDropdown `createDropdown()` util}. * - * @extends module:ui/view~View * @implements module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface + * @extends module:ui/view~View */ export default class SplitButtonView extends View { /** @@ -44,79 +44,20 @@ export default class SplitButtonView extends View { constructor( locale ) { super( locale ); - /** - * Controls whether the button view is enabled, i.e. it can be clicked and execute an action. - * - * To change the "on" state of the button, use {@link #isOn} instead. - * - * @observable - * @member {Boolean} #isEnabled - */ - this.set( 'isEnabled', true ); + const bind = this.bindTemplate; - /** - * Used to create a {@link #tooltip}. - * - * @observable - * @member {String} #label - */ - this.set( 'label' ); - - /** - * (Optional) The keystroke associated with the button, i.e. Ctrl+B, - * in the string format compatible with {@link module:utils/keyboard}. - * - * @observable - * @member {Boolean} #keystroke - */ - this.set( 'keystroke' ); - - /** - * (Optional) An XML {@link module:ui/icon/iconview~IconView#content content} of the icon. - * When defined, an {@link module:ui/button/buttonview~ButtonView#iconView} will be added to the {@link #actionView} button. - * - * @observable - * @member {String} #icon - */ + // Implement ButtonInterface. this.set( 'icon' ); - - /** - * (Optional) Tooltip of the button, i.e. displayed when hovering the button with the mouse cursor. - * - * * If defined as a `Boolean` (e.g. `true`), then combination of `label` and `keystroke` will be set as a tooltip. - * * If defined as a `String`, tooltip will equal the exact text of that `String`. - * * If defined as a `Function`, `label` and `keystroke` will be passed to that function, which is to return - * a string with the tooltip text. - * - * const view = new ButtonView( locale ); - * view.tooltip = ( label, keystroke ) => `A tooltip for ${ label } and ${ keystroke }.` - * - * @observable - * @default false - * @member {Boolean|String|Function} #tooltip - */ - this.set( 'tooltip' ); - - /** - * Controls whether the button view is "on". It makes sense when a feature it represents - * is currently active, e.g. a bold button is "on" when the selection is in the bold text. - * - * To disable the button, use {@link #isEnabled} instead. - * - * @observable - * @member {Boolean} #isOn - */ - this.set( 'isOn', false ); - - /** - * Controls whether the button view is enabled, i.e. it can be clicked and execute an action. - * - * To change the "on" state of the button, use {@link #isOn} instead. - * - * @observable - * @member {Boolean} #isEnabled - */ this.set( 'isEnabled', true ); + this.set( 'isOn', false ); + this.set( 'isVisible', true ); + this.set( 'keystroke' ); + this.set( 'label' ); + this.set( 'tabindex', -1 ); + this.set( 'tooltip' ); + this.set( 'tooltipPosition', 's' ); + this.set( 'type', 'button' ); + this.set( 'withText', false ); /** * Collection of the child views inside of the split button {@link #element}. @@ -166,7 +107,10 @@ export default class SplitButtonView extends View { tag: 'div', attributes: { - class: 'ck-splitbutton' + class: [ + 'ck-splitbutton', + bind.if( 'isVisible', 'ck-hidden', value => !value ) + ] }, children: this.children @@ -223,7 +167,18 @@ export default class SplitButtonView extends View { _createActionView() { const buttonView = new ButtonView(); - buttonView.bind( 'icon', 'isEnabled', 'label', 'isOn', 'tooltip', 'keystroke' ).to( this ); + buttonView.bind( + 'icon', + 'isEnabled', + 'isOn', + 'keystroke', + 'label', + 'tabindex', + 'tooltip', + 'tooltipPosition', + 'type', + 'withText' + ).to( this ); buttonView.delegate( 'execute' ).to( this ); diff --git a/tests/dropdown/button/splitbuttonview.js b/tests/dropdown/button/splitbuttonview.js index ad16d68e..8c669bce 100644 --- a/tests/dropdown/button/splitbuttonview.js +++ b/tests/dropdown/button/splitbuttonview.js @@ -40,6 +40,17 @@ describe( 'SplitButtonView', () => { expect( view.element.classList.contains( 'ck-splitbutton' ) ).to.be.true; } ); + it( 'binds #isVisible to the template', () => { + expect( view.element.classList.contains( 'ck-hidden' ) ).to.be.false; + + view.isVisible = false; + + expect( view.element.classList.contains( 'ck-hidden' ) ).to.be.true; + + // There should be no binding to the action view. Only the entire split button should react. + expect( view.actionView.element.classList.contains( 'ck-hidden' ) ).to.be.false; + } ); + describe( 'activates keyboard navigation for the toolbar', () => { it( 'so "arrowright" on view#arrowView does nothing', () => { const keyEvtData = { @@ -167,6 +178,39 @@ describe( 'SplitButtonView', () => { expect( view.arrowView.isEnabled ).to.be.false; } ); + + it( 'binds actionView#tabindex to view', () => { + expect( view.actionView.tabindex ).to.equal( -1 ); + + view.tabindex = 1; + + expect( view.actionView.tabindex ).to.equal( 1 ); + } ); + + // Makes little sense for split button but ButtonInterface specifies it, so let's support it. + it( 'binds actionView#type to view', () => { + expect( view.actionView.type ).to.equal( 'button' ); + + view.type = 'submit'; + + expect( view.actionView.type ).to.equal( 'submit' ); + } ); + + it( 'binds actionView#withText to view', () => { + expect( view.actionView.withText ).to.equal( false ); + + view.withText = true; + + expect( view.actionView.withText ).to.equal( true ); + } ); + + it( 'binds actionView#tooltipPosition to view', () => { + expect( view.actionView.tooltipPosition ).to.equal( 's' ); + + view.tooltipPosition = 'n'; + + expect( view.actionView.tooltipPosition ).to.equal( 'n' ); + } ); } ); describe( 'focus()', () => { From a908e039478d25ffab711b7b9be8179bd47ca118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Thu, 8 Feb 2018 13:53:49 +0100 Subject: [PATCH 76/78] Improved docs. --- src/button/buttoninterface.jsdoc | 6 ++-- src/dropdown/dropdownpanelview.js | 4 +-- src/dropdown/dropdownview.js | 44 +++++++++++++++++++++++---- src/dropdown/utils.js | 49 ++++++++++++++++++++++++++----- src/view.js | 2 +- tests/dropdown/utils.js | 2 +- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/button/buttoninterface.jsdoc b/src/button/buttoninterface.jsdoc index 20681c2d..a7e1d198 100644 --- a/src/button/buttoninterface.jsdoc +++ b/src/button/buttoninterface.jsdoc @@ -8,9 +8,9 @@ */ /** - * The button interface. - * - * TODO + * The button interface. Implemented by, among others, {@link module:ui/button/buttonview~ButtonView}, + * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} and + * {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView}. * * @interface module:ui/button/buttoninterface~ButtonInterface */ diff --git a/src/dropdown/dropdownpanelview.js b/src/dropdown/dropdownpanelview.js index 92c0b88e..7218206f 100644 --- a/src/dropdown/dropdownpanelview.js +++ b/src/dropdown/dropdownpanelview.js @@ -69,7 +69,7 @@ export default class DropdownPanelView extends View { /** * Focuses the view element or first item in view collection on opening dropdown's panel. * - * See also {@link module:ui/dropdown/dropdownpanelfocusable}. + * See also {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable}. */ focus() { if ( this.children.length ) { @@ -80,7 +80,7 @@ export default class DropdownPanelView extends View { /** * Focuses the view element or last item in view collection on opening dropdown's panel. * - * See also {@link module:ui/dropdown/dropdownpanelfocusable}. + * See also {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable}. */ focusLast() { if ( this.children.length ) { diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 3d98062c..5615e1d1 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -14,13 +14,36 @@ import KeystrokeHandler from '@ckeditor/ckeditor5-utils/src/keystrokehandler'; import '../../theme/components/dropdown/dropdown.css'; /** - * The dropdown view class. + * The dropdown view class. It manages the dropdown button and dropdown panel. * - * const button = new ButtonView( locale ); + * In most cases, the easiest way to create a dropdown is by using the {@link module:ui/dropdown/utils~createDropdown} + * util: + * + * const dropdown = createDropdown( locale ); + * + * // Configure dropdown's button properties: + * dropdown.buttonView.set( { + * label: 'A dropdown', + * withText: true + * } ); + * + * dropdown.render(); + * + * dropdown.panelView.element.textContent = 'Content of the panel'; + * + * // Will render a dropdown with a panel containing a "Content of the panel" text. + * document.body.appendChild( dropdown.element ); + * + * If you want to add a richer content to the dropdown panel, you can use the {@link module:ui/dropdown/utils~addListToDropdown} + * and {@link module:ui/dropdown/utils~addToolbarToDropdown} helpers. See more examples in + * {@link module:ui/dropdown/utils~createDropdown} documentation. + * + * If you want to create a completely custom dropdown, then you can compose it manually: + * + * const button = new DropdownButtonView( locale ); * const panel = new DropdownPanelView( locale ); * const dropdown = new DropdownView( locale, button, panel ); * - * panel.element.textContent = 'Content of the panel'; * button.set( { * label: 'A dropdown', * withText: true @@ -28,16 +51,27 @@ import '../../theme/components/dropdown/dropdown.css'; * * dropdown.render(); * + * panel.element.textContent = 'Content of the panel'; + * * // Will render a dropdown with a panel containing a "Content of the panel" text. * document.body.appendChild( dropdown.element ); * - * Also see {@link module:ui/dropdown/utils~createDropdown} to learn about dropdown creation helper. + * However, dropdown created this way will contain little behavior. You will need to implement handlers for actions + * such as {@link module:ui/bindings/clickoutsidehandler~clickOutsideHandler clicking outside an open dropdown} + * (which should close it) and support for arrow keys inside the panel. Therefore, unless you really know what + * you do and you really need to do it, it is recommended to use the {@link module:ui/dropdown/utils~createDropdown} helper. * * @extends module:ui/view~View */ export default class DropdownView extends View { /** - * @inheritDoc + * Creates an instance of the dropdown. + * + * Also see {@link #render}. + * + * @param {module:utils/locale~Locale} [locale] The localization services instance. + * @param {module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} buttonView + * @param {module:ui/dropdown/dropdownpanelview~DropdownPanelView} panelView */ constructor( locale, buttonView, panelView ) { super( locale ); diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 6b462750..4ce87154 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -19,10 +19,14 @@ import clickOutsideHandler from '../bindings/clickoutsidehandler'; import '../../theme/components/dropdown/toolbardropdown.css'; /** - * A helper which creates an instance of {@link module:ui/dropdown/dropdownview~DropdownView} class with an instance of - * a button class passed as `ButtonClass` parameter. + * A helper for creating dropdowns. It creates an instance of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown}, + * with a {@link module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface button}, + * {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView panel} and all standard dropdown's behaviors. * - * The default value of `ButtonClass` is {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} class. + * # Creating dropdowns + * + * By default, the default {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} class is used as + * definition of the button: * * const dropdown = createDropdown( model ); * @@ -37,14 +41,43 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * // Will render a dropdown labeled "A dropdown" with an empty panel. * document.body.appendChild( dropdown.element ); * - * The supported button classes for dropdown are: - * * {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} - * * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} + * You can also provide other button views (they need to implement the + * {module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} interface). For instance, you can use + * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} to create a dropdown with a split button. + * + * const dropdown = createDropdown( model, SplitButtonView ); + * + * // Configure dropdown's button properties: + * dropdown.buttonView.set( { + * label: 'A dropdown', + * withText: true + * } ); + * + * dropdown.buttonView.on( 'execute', () => { + * // Add the behavior of the "action part" of the split button. + * // Split button consists of the "action part" and "arrow part". + * // The arrow opens the dropdown while the action part can have some other behavior. + * } ); + * + * dropdown.render(); + * + * // Will render a dropdown labeled "A dropdown" with an empty panel. + * document.body.appendChild( dropdown.element ); + * + * # Adding content to the dropdown's panel + * + * The content of the panel can be inserted directly into the `dropdown.panelView.element`: + * + * dropdown.panelView.element.textContent = 'Content of the panel'; * - * Also see {@link module:ui/dropdown/utils~addListToDropdown} and {@link module:ui/dropdown/utils~addToolbarToDropdown}. + * However, most of the time you will want to add there either a {@link module:ui/list/listview~ListView list of options} + * or a list of buttons (i.e. a {@link module:ui/toolbar/toolbarview~ToolbarView toolbar}). + * To simplify the task, you can use, respectively, {@link module:ui/dropdown/utils~addListToDropdown} or + * {@link module:ui/dropdown/utils~addToolbarToDropdown} utils. * * @param {module:utils/locale~Locale} locale The locale instance. - * @param {module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} ButtonClass The dropdown button view class. + * @param {Function} ButtonClass The dropdown button view class. Needs to implement the + * {@link module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} interface. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { diff --git a/src/view.js b/src/view.js index 1f5bb6dc..d8806955 100644 --- a/src/view.js +++ b/src/view.js @@ -111,7 +111,7 @@ export default class View { * * const view = new SampleView(); * - * // Renders the #template + * // Renders the #template. * view.render(); * * // Append the HTML element of the view to . diff --git a/tests/dropdown/utils.js b/tests/dropdown/utils.js index 6b02b799..ec22fe52 100644 --- a/tests/dropdown/utils.js +++ b/tests/dropdown/utils.js @@ -91,7 +91,7 @@ describe( 'utils', () => { } ); } ); - describe( 'hasDefaultBehavior', () => { + describe( 'has default behavior', () => { describe( 'closeDropdownOnBlur()', () => { beforeEach( () => { dropdownView.render(); From 07925bb84cb144e8d91f5c588ad1423cef750a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Thu, 8 Feb 2018 14:42:45 +0100 Subject: [PATCH 77/78] Tests: Update dropdown manual tests. --- tests/dropdown/manual/dropdown.html | 12 ++--- tests/dropdown/manual/dropdown.js | 71 +++++++++++++++++++---------- tests/dropdown/manual/dropdown.md | 6 +++ 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/tests/dropdown/manual/dropdown.html b/tests/dropdown/manual/dropdown.html index 8c234f59..8722cc3b 100644 --- a/tests/dropdown/manual/dropdown.html +++ b/tests/dropdown/manual/dropdown.html @@ -2,18 +2,18 @@

Empty

-

ListDropdown

+

Dropdown with ListView

-

Shared Model

- - -

Long label (truncated)

-

ButtonDropdown

+

Dropdown with ToolbarView

+ +

SplitButton Dropdown

+ + diff --git a/tests/dropdown/manual/dropdown.js b/tests/dropdown/manual/dropdown.js index c30bdc21..a04c3770 100644 --- a/tests/dropdown/manual/dropdown.js +++ b/tests/dropdown/manual/dropdown.js @@ -14,15 +14,16 @@ import alignLeftIcon from '@ckeditor/ckeditor5-core/theme/icons/object-left.svg' import alignRightIcon from '@ckeditor/ckeditor5-core/theme/icons/object-right.svg'; import alignCenterIcon from '@ckeditor/ckeditor5-core/theme/icons/object-center.svg'; import ButtonView from '../../../src/button/buttonview'; +import SplitButtonView from '../../../src/dropdown/button/splitbuttonview'; import { createDropdown, addToolbarToDropdown, addListToDropdown } from '../../../src/dropdown/utils'; const ui = testUtils.createTestUIView( { dropdown: '#dropdown', listDropdown: '#list-dropdown', - dropdownShared: '#dropdown-shared', dropdownLabel: '#dropdown-label', - toolbarDropdown: '#dropdown-toolbar' + toolbarDropdown: '#dropdown-toolbar', + splitButton: '#dropdown-splitbutton' } ); function testEmpty() { @@ -72,26 +73,6 @@ function testList() { window.Model = Model; } -function testSharedModel() { - const dropdownSettings = { - label: 'Shared Model', - isEnabled: true, - isOn: false, - withText: true - }; - - const dropdownView1 = createDropdown( {} ); - const dropdownView2 = createDropdown( {} ); - - dropdownView1.set( dropdownSettings ); - dropdownView2.set( dropdownSettings ); - - ui.dropdownShared.add( dropdownView1 ); - ui.dropdownShared.add( dropdownView2 ); - - dropdownView1.panelView.element.innerHTML = dropdownView2.panelView.element.innerHTML = 'Empty panel.'; -} - function testLongLabel() { const dropdownView = createDropdown( {} ); @@ -107,7 +88,7 @@ function testLongLabel() { dropdownView.panelView.element.innerHTML = 'Empty panel. There is no child view in this DropdownPanelView.'; } -function testButton() { +function testToolbar() { const locale = {}; const icons = { left: alignLeftIcon, right: alignRightIcon, center: alignCenterIcon }; @@ -131,13 +112,53 @@ function testButton() { addToolbarToDropdown( toolbarDropdown, buttonViews ); + // This will change icon to button with `isOn = true`. + toolbarDropdown.buttonView.bind( 'icon' ).toMany( buttons, 'isOn', ( ...areActive ) => { + // Get the index of an active button. + const index = areActive.findIndex( value => value ); + + // If none of the commands is active, display either defaultIcon or the first button's icon. + if ( index < 0 ) { + return buttons[ 0 ].icon; + } + + // Return active button's icon. + return buttons[ index ].icon; + } ); + + // This will disable dropdown button when all buttons have `isEnabled = false`. + toolbarDropdown.bind( 'isEnabled' ).toMany( buttons, 'isEnabled', ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) ); + ui.toolbarDropdown.add( toolbarDropdown ); window.buttons = buttons; } +function testSplitButton() { + const dropdownView = createDropdown( {}, SplitButtonView ); + + dropdownView.buttonView.set( { + label: 'Dropdown', + icon: alignCenterIcon + } ); + + ui.splitButton.add( dropdownView ); + + dropdownView.panelView.element.innerHTML = 'Empty panel. There is no child view in this DropdownPanelView.'; + + dropdownView.buttonView.on( 'execute', () => { + /* global console */ + console.log( 'SplitButton#execute' ); + } ); + + dropdownView.buttonView.on( 'open', () => { + /* global console */ + console.log( 'SplitButton#open' ); + } ); +} + testEmpty(); testList(); -testSharedModel(); testLongLabel(); -testButton(); +testToolbar(); +testSplitButton(); diff --git a/tests/dropdown/manual/dropdown.md b/tests/dropdown/manual/dropdown.md index 27e4a90b..e864797f 100644 --- a/tests/dropdown/manual/dropdown.md +++ b/tests/dropdown/manual/dropdown.md @@ -33,3 +33,9 @@ listDropdownCollection.add( 1. Play with `buttons[ n ].isOn` to control buttonDropdown active icon. 2. Play with `buttons[ n ].isEnabled` to control buttonDropdown disabled state (all buttons must be set to `false`). 3. Play with `toolbarDropdown.toolbarView.isVertical` to control buttonDropdown vertical/horizontal alignment. + +## SplitButton Dropdown + +* It should have two distinct fields: action button and arrow. +* Click on action button should be logged in console. +* Click on arrow button should open panel and should be logged in console. From 7c0e6c9bee45c953783da10e948ecbfbfa4ca62a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotrek=20Koszuli=C5=84ski?= Date: Thu, 8 Feb 2018 14:35:43 +0100 Subject: [PATCH 78/78] Avoid using the word Interface in interface names by simply stripping it. --- src/button/{buttoninterface.jsdoc => button.jsdoc} | 4 ++-- src/button/buttonview.js | 4 ++-- .../{dropdownbuttoninterface.jsdoc => dropdownbutton.jsdoc} | 6 +++--- src/dropdown/button/dropdownbuttonview.js | 4 ++-- src/dropdown/button/splitbuttonview.js | 4 ++-- src/dropdown/dropdownview.js | 2 +- src/dropdown/utils.js | 6 +++--- tests/dropdown/button/splitbuttonview.js | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) rename src/button/{buttoninterface.jsdoc => button.jsdoc} (97%) rename src/dropdown/button/{dropdownbuttoninterface.jsdoc => dropdownbutton.jsdoc} (61%) diff --git a/src/button/buttoninterface.jsdoc b/src/button/button.jsdoc similarity index 97% rename from src/button/buttoninterface.jsdoc rename to src/button/button.jsdoc index a7e1d198..fc6a9b0e 100644 --- a/src/button/buttoninterface.jsdoc +++ b/src/button/button.jsdoc @@ -4,7 +4,7 @@ */ /** - * @module ui/button/buttoninterface + * @module ui/button/button */ /** @@ -12,7 +12,7 @@ * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} and * {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView}. * - * @interface module:ui/button/buttoninterface~ButtonInterface + * @interface module:ui/button/button~Button */ /** diff --git a/src/button/buttonview.js b/src/button/buttonview.js index d02a43df..2c0d702f 100644 --- a/src/button/buttonview.js +++ b/src/button/buttonview.js @@ -32,7 +32,7 @@ import '../../theme/components/button/button.css'; * document.body.append( view.element ); * * @extends module:ui/view~View - * @implements module:ui/button/buttoninterface~ButtonInterface + * @implements module:ui/button/button~Button */ export default class ButtonView extends View { /** @@ -43,7 +43,7 @@ export default class ButtonView extends View { const bind = this.bindTemplate; - // Implement ButtonInterface. + // Implement the Button interface. this.set( 'icon' ); this.set( 'isEnabled', true ); this.set( 'isOn', false ); diff --git a/src/dropdown/button/dropdownbuttoninterface.jsdoc b/src/dropdown/button/dropdownbutton.jsdoc similarity index 61% rename from src/dropdown/button/dropdownbuttoninterface.jsdoc rename to src/dropdown/button/dropdownbutton.jsdoc index e3737b1f..cbe57a4e 100644 --- a/src/dropdown/button/dropdownbuttoninterface.jsdoc +++ b/src/dropdown/button/dropdownbutton.jsdoc @@ -4,14 +4,14 @@ */ /** - * @module ui/dropdown/button/dropdownbuttoninterface + * @module ui/dropdown/button/dropdownbutton */ /** * The dropdown button interface. * - * @interface module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface - * @extends module:ui/button/buttoninterface~ButtonInterface + * @interface module:ui/dropdown/button/dropdownbutton~DropdownButton + * @extends module:ui/button/button~Button */ /** diff --git a/src/dropdown/button/dropdownbuttonview.js b/src/dropdown/button/dropdownbuttonview.js index 9a3ad32a..17428960 100644 --- a/src/dropdown/button/dropdownbuttonview.js +++ b/src/dropdown/button/dropdownbuttonview.js @@ -29,7 +29,7 @@ import IconView from '../../icon/iconview'; * * Also see the {@link module:ui/dropdown/utils~createDropdown `createDropdown()` util}. * - * @implements module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface + * @implements module:ui/dropdown/button/dropdownbutton~DropdownButton * @extends module:ui/button/buttonview~ButtonView */ export default class DropdownButtonView extends ButtonView { @@ -47,7 +47,7 @@ export default class DropdownButtonView extends ButtonView { */ this.arrowView = this._createArrowView(); - // DropdownButtonInterface expects the open event upon which the dropdown will open. + // The DropdownButton interface expects the open event upon which will open the dropdown. this.delegate( 'execute' ).to( this, 'open' ); } diff --git a/src/dropdown/button/splitbuttonview.js b/src/dropdown/button/splitbuttonview.js index ce815d1b..b50ae1d5 100644 --- a/src/dropdown/button/splitbuttonview.js +++ b/src/dropdown/button/splitbuttonview.js @@ -34,7 +34,7 @@ import '../../../theme/components/dropdown/splitbutton.css'; * * Also see the {@link module:ui/dropdown/utils~createDropdown `createDropdown()` util}. * - * @implements module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface + * @implements module:ui/dropdown/button/dropdownbutton~DropdownButton * @extends module:ui/view~View */ export default class SplitButtonView extends View { @@ -46,7 +46,7 @@ export default class SplitButtonView extends View { const bind = this.bindTemplate; - // Implement ButtonInterface. + // Implement the Button interface. this.set( 'icon' ); this.set( 'isEnabled', true ); this.set( 'isOn', false ); diff --git a/src/dropdown/dropdownview.js b/src/dropdown/dropdownview.js index 5615e1d1..a7ab9440 100644 --- a/src/dropdown/dropdownview.js +++ b/src/dropdown/dropdownview.js @@ -70,7 +70,7 @@ export default class DropdownView extends View { * Also see {@link #render}. * * @param {module:utils/locale~Locale} [locale] The localization services instance. - * @param {module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} buttonView + * @param {module:ui/dropdown/button/dropdownbutton~DropdownButton} buttonView * @param {module:ui/dropdown/dropdownpanelview~DropdownPanelView} panelView */ constructor( locale, buttonView, panelView ) { diff --git a/src/dropdown/utils.js b/src/dropdown/utils.js index 4ce87154..150545fe 100644 --- a/src/dropdown/utils.js +++ b/src/dropdown/utils.js @@ -20,7 +20,7 @@ import '../../theme/components/dropdown/toolbardropdown.css'; /** * A helper for creating dropdowns. It creates an instance of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown}, - * with a {@link module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface button}, + * with a {@link module:ui/dropdown/button/dropdownbutton~DropdownButton button}, * {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView panel} and all standard dropdown's behaviors. * * # Creating dropdowns @@ -42,7 +42,7 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * document.body.appendChild( dropdown.element ); * * You can also provide other button views (they need to implement the - * {module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} interface). For instance, you can use + * {module:ui/dropdown/button/dropdownbutton~DropdownButton} interface). For instance, you can use * {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} to create a dropdown with a split button. * * const dropdown = createDropdown( model, SplitButtonView ); @@ -77,7 +77,7 @@ import '../../theme/components/dropdown/toolbardropdown.css'; * * @param {module:utils/locale~Locale} locale The locale instance. * @param {Function} ButtonClass The dropdown button view class. Needs to implement the - * {@link module:ui/dropdown/button/dropdownbuttoninterface~DropdownButtonInterface} interface. + * {@link module:ui/dropdown/button/dropdownbutton~DropdownButton} interface. * @returns {module:ui/dropdown/dropdownview~DropdownView} The dropdown view instance. */ export function createDropdown( locale, ButtonClass = DropdownButtonView ) { diff --git a/tests/dropdown/button/splitbuttonview.js b/tests/dropdown/button/splitbuttonview.js index 8c669bce..d34ff756 100644 --- a/tests/dropdown/button/splitbuttonview.js +++ b/tests/dropdown/button/splitbuttonview.js @@ -187,7 +187,7 @@ describe( 'SplitButtonView', () => { expect( view.actionView.tabindex ).to.equal( 1 ); } ); - // Makes little sense for split button but ButtonInterface specifies it, so let's support it. + // Makes little sense for split button but the Button interface specifies it, so let's support it. it( 'binds actionView#type to view', () => { expect( view.actionView.type ).to.equal( 'button' );