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

Commit 6e9c6e4

Browse files
authored
Merge pull request #339 from ckeditor/t/333
Feature: Initial implementation of the `ButtonDropdownView`. Closes #333. Also: * Allowed vertical layout of the `ToolbarView` thanks to the `#isVertical` attribute. * Implemented `ToolbarView#className` attribute. * Implemented `DropdownView#isEnabled` attribute along with the CSS class binding.
2 parents f29fbe1 + 317225e commit 6e9c6e4

22 files changed

+820
-72
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/**
7+
* @module ui/dropdown/button/buttondropdownmodel
8+
*/
9+
10+
/**
11+
* The button dropdown model interface.
12+
*
13+
* @implements module:ui/dropdown/dropdownmodel~DropdownModel
14+
* @interface module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel
15+
*/
16+
17+
/**
18+
* List of buttons to be included in dropdown
19+
*
20+
* @observable
21+
* @member {Array.<module:ui/button/buttonview~ButtonView>} #buttons
22+
*/
23+
24+
/**
25+
* Fired when the button dropdown is executed. It fires when one of the buttons
26+
* {@link module:ui/button/buttonview~ButtonView#event:execute executed}.
27+
*
28+
* @event #execute
29+
*/
30+
31+
/**
32+
* Controls dropdown direction.
33+
*
34+
* @observable
35+
* @member {Boolean} #isVertical=false
36+
*/
37+
38+
/**
39+
* Disables automatic button icon binding. If set to true dropdown's button {@link #icon} will be set to {@link #defaultIcon}.
40+
*
41+
* @observable
42+
* @member {Boolean} #staticIcon=false
43+
*/
44+
45+
/**
46+
* Defines default icon which is used when no button is active.
47+
*
48+
* Also see {@link #icon}.
49+
*
50+
* @observable
51+
* @member {String} #defaultIcon
52+
*/
53+
54+
/**
55+
* Button dropdown icon is set from inner button views.
56+
*
57+
* Also see {@link #defaultIcon} and {@link #staticIcon}.
58+
*
59+
* @observable
60+
* @member {String} #icon
61+
*/
62+
63+
/**
64+
* (Optional) A CSS class set to
65+
* {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView#toolbarView}.
66+
*
67+
* Also see {@link module:ui/toolbar/toolbarview~ToolbarView#className `ToolbarView#className`}.
68+
*
69+
* @observable
70+
* @member {String} #toolbarClassName
71+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/**
7+
* @module ui/dropdown/button/createbuttondropdown
8+
*/
9+
10+
/**
11+
* The button dropdown view.
12+
*
13+
* See {@link module:ui/dropdown/button/createbuttondropdown~createButtonDropdown}.
14+
*
15+
* @abstract
16+
* @class module:ui/dropdown/button/buttondropdownview~ButtonDropdownView
17+
* @extends module:ui/dropdown/dropdownview~DropdownView
18+
*/
19+
20+
/**
21+
* A child toolbar of the dropdown located in the
22+
* {@link module:ui/dropdown/dropdownview~DropdownView#panelView panel}.
23+
*
24+
* @readonly
25+
* @member {module:ui/toolbar/toolbarview~ToolbarView} #toolbarView
26+
*/
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/**
7+
* @module ui/dropdown/button/createbuttondropdown
8+
*/
9+
10+
import createDropdown from '../createdropdown';
11+
12+
import ToolbarView from '../../toolbar/toolbarview';
13+
import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownContentsOnArrows } from '../utils';
14+
15+
import '../../../theme/components/dropdown/buttondropdown.css';
16+
17+
/**
18+
* Creates an instance of {@link module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} class using
19+
* a provided {@link module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel}.
20+
*
21+
* const buttons = [];
22+
*
23+
* buttons.push( new ButtonView() );
24+
* buttons.push( editor.ui.componentFactory.get( 'someButton' ) );
25+
*
26+
* const model = new Model( {
27+
* label: 'A button dropdown',
28+
* isVertical: true,
29+
* buttons
30+
* } );
31+
*
32+
* const dropdown = createButtonDropdown( model, locale );
33+
*
34+
* // Will render a vertical button dropdown labeled "A button dropdown"
35+
* // with a button group in the panel containing two buttons.
36+
* dropdown.render()
37+
* document.body.appendChild( dropdown.element );
38+
*
39+
* The model instance remains in control of the dropdown after it has been created. E.g. changes to the
40+
* {@link module:ui/dropdown/dropdownmodel~DropdownModel#label `model.label`} will be reflected in the
41+
* dropdown button's {@link module:ui/button/buttonview~ButtonView#label} attribute and in DOM.
42+
*
43+
* See {@link module:ui/dropdown/createdropdown~createDropdown}.
44+
*
45+
* @param {module:ui/dropdown/button/buttondropdownmodel~ButtonDropdownModel} model Model of the list dropdown.
46+
* @param {module:utils/locale~Locale} locale The locale instance.
47+
* @returns {module:ui/dropdown/button/buttondropdownview~ButtonDropdownView} The button dropdown view instance.
48+
* @returns {module:ui/dropdown/dropdownview~DropdownView}
49+
*/
50+
export default function createButtonDropdown( model, locale ) {
51+
// Make disabled when all buttons are disabled
52+
model.bind( 'isEnabled' ).to(
53+
// Bind to #isEnabled of each command...
54+
...getBindingTargets( model.buttons, 'isEnabled' ),
55+
// ...and set it true if any command #isEnabled is true.
56+
( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled )
57+
);
58+
59+
// If defined `staticIcon` use the `defautlIcon` without binding it to active a button.
60+
if ( model.staticIcon ) {
61+
model.bind( 'icon' ).to( model, 'defaultIcon' );
62+
} else {
63+
// Make dropdown icon as any active button.
64+
model.bind( 'icon' ).to(
65+
// Bind to #isOn of each button...
66+
...getBindingTargets( model.buttons, 'isOn' ),
67+
// ...and chose the title of the first one which #isOn is true.
68+
( ...areActive ) => {
69+
const index = areActive.findIndex( value => value );
70+
71+
// If none of the commands is active, display either defaultIcon or first button icon.
72+
if ( index < 0 && model.defaultIcon ) {
73+
return model.defaultIcon;
74+
}
75+
76+
return model.buttons[ index < 0 ? 0 : index ].icon;
77+
}
78+
);
79+
}
80+
81+
const dropdownView = createDropdown( model, locale );
82+
const toolbarView = dropdownView.toolbarView = new ToolbarView();
83+
84+
toolbarView.bind( 'isVertical', 'className' ).to( model, 'isVertical', 'toolbarClassName' );
85+
86+
model.buttons.map( view => toolbarView.items.add( view ) );
87+
88+
dropdownView.extendTemplate( {
89+
attributes: {
90+
class: [ 'ck-buttondropdown' ]
91+
}
92+
} );
93+
94+
dropdownView.panelView.children.add( toolbarView );
95+
96+
closeDropdownOnBlur( dropdownView );
97+
closeDropdownOnExecute( dropdownView, toolbarView.items );
98+
focusDropdownContentsOnArrows( dropdownView, toolbarView );
99+
100+
return dropdownView;
101+
}
102+
103+
// Returns an array of binding components for
104+
// {@link module:utils/observablemixin~Observable#bind} from a set of iterable
105+
// buttons.
106+
//
107+
// @private
108+
// @param {Iterable.<module:ui/button/buttonview~ButtonView>} buttons
109+
// @param {String} attribute
110+
// @returns {Array.<String>}
111+
function getBindingTargets( buttons, attribute ) {
112+
return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) );
113+
}

src/dropdown/createdropdown.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,14 @@ import DropdownPanelView from './dropdownpanelview';
4141
*/
4242
export default function createDropdown( model, locale ) {
4343
const buttonView = new ButtonView( locale );
44-
buttonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip' ).to( model );
45-
4644
const panelView = new DropdownPanelView( locale );
45+
const dropdownView = new DropdownView( locale, buttonView, panelView );
46+
47+
dropdownView.bind( 'isEnabled' ).to( model );
48+
buttonView.bind( 'label', 'isEnabled', 'withText', 'keystroke', 'tooltip', 'icon' ).to( model );
49+
buttonView.bind( 'isOn' ).to( model, 'isOn', dropdownView, 'isOpen', ( isOn, isOpen ) => {
50+
return isOn || isOpen;
51+
} );
4752

48-
return new DropdownView( locale, buttonView, panelView );
53+
return dropdownView;
4954
}

src/dropdown/dropdownmodel.jsdoc

+9
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,12 @@
4949
* @observable
5050
* @member {Boolean} #withText
5151
*/
52+
53+
/**
54+
* (Optional) Controls the icon of the dropdown.
55+
*
56+
* Also see {@link module:ui/button/buttonview~ButtonView#withText}.
57+
*
58+
* @observable
59+
* @member {Boolean} #icon
60+
*/
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/**
7+
* @module ui/dropdown/dropdownpanelfocusable
8+
*/
9+
10+
/**
11+
* The dropdown panel interface interface for focusable contents. It provides two methods for managing focus of the contents
12+
* of dropdown's panel.
13+
*
14+
* @interface module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable
15+
*/
16+
17+
/**
18+
* Focuses the view element or first item in view collection on opening dropdown's panel.
19+
*
20+
* @method #focus
21+
*/
22+
23+
/**
24+
* Focuses the view element or last item in view collection on opening dropdown's panel.
25+
*
26+
* @method #focusLast
27+
*/

src/dropdown/dropdownview.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,7 @@ export default class DropdownView extends View {
4444
constructor( locale, buttonView, panelView ) {
4545
super( locale );
4646

47-
// Extend button's template before it's registered as a child of the dropdown because
48-
// by doing so, its #element is rendered and any post–render template extension will
49-
// not be reflected in DOM.
50-
buttonView.extendTemplate( {
51-
attributes: {
52-
class: [
53-
'ck-dropdown__button'
54-
]
55-
}
56-
} );
47+
const bind = this.bindTemplate;
5748

5849
/**
5950
* Button of the dropdown view. Clicking the button opens the {@link #panelView}.
@@ -87,6 +78,16 @@ export default class DropdownView extends View {
8778
*/
8879
this.set( 'isOpen', false );
8980

81+
/**
82+
* Controls whether the dropdown is enabled, i.e. it can be clicked and execute an action.
83+
*
84+
* See {@link module:ui/button/buttonview~ButtonView#isEnabled}.
85+
*
86+
* @observable
87+
* @member {Boolean} #isEnabled
88+
*/
89+
this.set( 'isEnabled', true );
90+
9091
/**
9192
* Tracks information about DOM focus in the dropdown.
9293
*
@@ -112,7 +113,8 @@ export default class DropdownView extends View {
112113

113114
attributes: {
114115
class: [
115-
'ck-dropdown'
116+
'ck-dropdown',
117+
bind.to( 'isEnabled', isEnabled => isEnabled ? '' : 'ck-disabled' )
116118
]
117119
},
118120

@@ -121,6 +123,14 @@ export default class DropdownView extends View {
121123
panelView
122124
]
123125
} );
126+
127+
buttonView.extendTemplate( {
128+
attributes: {
129+
class: [
130+
'ck-dropdown__button',
131+
]
132+
}
133+
} );
124134
}
125135

126136
/**

0 commit comments

Comments
 (0)