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

Commit

Permalink
Merge pull request #339 from ckeditor/t/333
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
oleq committed Dec 14, 2017
2 parents f29fbe1 + 317225e commit 6e9c6e4
Show file tree
Hide file tree
Showing 22 changed files with 820 additions and 72 deletions.
71 changes: 71 additions & 0 deletions src/dropdown/button/buttondropdownmodel.jsdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* @license Copyright (c) 2003-2017, 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.<module:ui/button/buttonview~ButtonView>} #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
*/

/**
* 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.
*
* Also see {@link #defaultIcon} and {@link #staticIcon}.
*
* @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
*/
26 changes: 26 additions & 0 deletions src/dropdown/button/buttondropdownview.jsdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @license Copyright (c) 2003-2017, 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
*/
113 changes: 113 additions & 0 deletions src/dropdown/button/createbuttondropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module ui/dropdown/button/createbuttondropdown
*/

import createDropdown from '../createdropdown';

import ToolbarView from '../../toolbar/toolbarview';
import { closeDropdownOnBlur, closeDropdownOnExecute, focusDropdownContentsOnArrows } 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 ) {
// 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 )
);

// If defined `staticIcon` use the `defautlIcon` without binding it to active a button.
if ( model.staticIcon ) {
model.bind( 'icon' ).to( model, 'defaultIcon' );
} else {
// 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 dropdownView = createDropdown( model, locale );
const toolbarView = dropdownView.toolbarView = new ToolbarView();

toolbarView.bind( 'isVertical', 'className' ).to( model, 'isVertical', 'toolbarClassName' );

model.buttons.map( view => toolbarView.items.add( view ) );

dropdownView.extendTemplate( {
attributes: {
class: [ 'ck-buttondropdown' ]
}
} );

dropdownView.panelView.children.add( toolbarView );

closeDropdownOnBlur( dropdownView );
closeDropdownOnExecute( dropdownView, toolbarView.items );
focusDropdownContentsOnArrows( dropdownView, toolbarView );

return dropdownView;
}

// Returns an array of binding components for
// {@link module:utils/observablemixin~Observable#bind} from a set of iterable
// buttons.
//
// @private
// @param {Iterable.<module:ui/button/buttonview~ButtonView>} buttons
// @param {String} attribute
// @returns {Array.<String>}
function getBindingTargets( buttons, attribute ) {
return Array.prototype.concat( ...buttons.map( button => [ button, attribute ] ) );
}
11 changes: 8 additions & 3 deletions src/dropdown/createdropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ import DropdownPanelView from './dropdownpanelview';
*/
export default function createDropdown( model, locale ) {
const buttonView = new ButtonView( locale );
buttonView.bind( 'label', 'isOn', 'isEnabled', 'withText', 'keystroke', 'tooltip' ).to( 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 new DropdownView( locale, buttonView, panelView );
return dropdownView;
}
9 changes: 9 additions & 0 deletions src/dropdown/dropdownmodel.jsdoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,12 @@
* @observable
* @member {Boolean} #withText
*/

/**
* (Optional) Controls the icon of the dropdown.
*
* Also see {@link module:ui/button/buttonview~ButtonView#withText}.
*
* @observable
* @member {Boolean} #icon
*/
27 changes: 27 additions & 0 deletions src/dropdown/dropdownpanelfocusable.jsdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module ui/dropdown/dropdownpanelfocusable
*/

/**
* The dropdown panel interface interface for focusable contents. It provides two methods for managing focus of the contents
* of dropdown's panel.
*
* @interface module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable
*/

/**
* Focuses the view element or first item in view collection on opening dropdown's panel.
*
* @method #focus
*/

/**
* Focuses the view element or last item in view collection on opening dropdown's panel.
*
* @method #focusLast
*/
32 changes: 21 additions & 11 deletions src/dropdown/dropdownview.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,7 @@ export default class DropdownView extends View {
constructor( locale, buttonView, panelView ) {
super( locale );

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

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

/**
* Controls whether the dropdown is enabled, i.e. it can be clicked and execute an action.
*
* See {@link module:ui/button/buttonview~ButtonView#isEnabled}.
*
* @observable
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', true );

/**
* Tracks information about DOM focus in the dropdown.
*
Expand All @@ -112,7 +113,8 @@ export default class DropdownView extends View {

attributes: {
class: [
'ck-dropdown'
'ck-dropdown',
bind.to( 'isEnabled', isEnabled => isEnabled ? '' : 'ck-disabled' )
]
},

Expand All @@ -121,6 +123,14 @@ export default class DropdownView extends View {
panelView
]
} );

buttonView.extendTemplate( {
attributes: {
class: [
'ck-dropdown__button',
]
}
} );
}

/**
Expand Down
Loading

0 comments on commit 6e9c6e4

Please sign in to comment.