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

Introduced ContextualBalloon class for managing contextual balloons. #177

Merged
merged 17 commits into from
Apr 5, 2017
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions src/contextualballoon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module ui/contextualballoon
*/

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import BalloonPanelView from './panel/balloon/balloonpanelview';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';

/**
* Provides the common contextual balloon panel for the editor.
*
* This plugin allows reusing a single {module:ui/panel/balloon/balloonpanelview~BalloonPanelView} instance
* to display multiple contextual balloon panels in the editor.
*
* Child views of such a panel are stored in the stack and the last one in the stack is visible. When the
Copy link
Member

Choose a reason for hiding this comment

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

An afterthought: Since _stack is private we should not talk in the public docs about it. It is of no concern to the devs how the CB handles views.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I wrote about stack I wasn't thinking about var name but about the way how we store it.

* visible view is removed from the stack, the previous view becomes visible, etc. If there are no more
* views in the stack, the balloon panel will hide.
*
* It simplifies managing the views and helps
* avoid the unnecessary complexity of handling multiple {module:ui/panel/balloon/balloonpanelview~BalloonPanelView}
* instances in the editor.
*
* @extends module:core/plugin~Plugin
*/
export default class ContextualBalloon extends Plugin {
/**
* @inheritDoc
*/
init() {
/**
* The common balloon panel view.
*
* @readonly
* @member {module:ui/panel/balloon/balloonpanelview~BalloonPanelView} #view
*/
this.view = new BalloonPanelView();

/**
* Stack of the views injected into the balloon. Last one in the stack is displayed
* as a content of {@link module:ui/contextualballoon~ContextualBalloon#view}.
*
* @private
* @member {Map} #_stack
*/
this._stack = new Map();

// Add balloon panel view to editor `body` collection.
this.editor.ui.view.body.add( this.view );
}

static get pluginName() {
return 'contextualballoon';
}

/**
* Returns configuration of the currently visible view or `null` when there are no
* views in the stack.
*
* @returns {module:ui/contextualballoon~ViewConfig|null}
*/
get visible() {
Copy link
Member

Choose a reason for hiding this comment

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

Boolean properties and variables are always prefixed by an auxiliary verb:

https://github.com/ckeditor/ckeditor5-design/wiki/Code-Style-Naming-Guidelines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This getter returns visible view. It's not checking if given view is visible.

Copy link
Member

Choose a reason for hiding this comment

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

So get visibleView() maybe? I'm trying to make it less ambiguous.

Copy link
Contributor Author

@oskarwrobel oskarwrobel Apr 5, 2017

Choose a reason for hiding this comment

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

Hm... this returns not only the view but the whole configuration { view, position } https://github.com/ckeditor/ckeditor5-ui/pull/177/files#diff-93d63500d90d723217fc16438b6b32a4R193

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But maybe only the view should be returned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm... looks like there is no need to return the balloon position configuration because this position is used internally by ContextualBalloon.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

return this._stack.get( this.view.content.get( 0 ) ) || null;
}

/**
* Returns `true` when the given view is in the stack. Otherwise returns `false`.
*
* @param {module:ui/view~View} view
* @returns {Boolean}
*/
hasView( view ) {
return this._stack.has( view );
}

/**
* Adds a new view to the stack and makes it visible.
*
* @param {module:ui/contextualballoon~ViewConfig} data Configuration of the view.
*/
add( data ) {
if ( this.hasView( data.view ) ) {
/**
* Trying to add configuration of the same view more than once.
*
* @error contextualballoon-add-view-exist
*/
throw new CKEditorError( 'contextualballoon-add-view-exist: Cannot add configuration of the same view twice.' );
}

// When adding view to the not empty balloon.
if ( this.visible ) {
// Remove displayed content from the view.
this.view.content.remove( this.visible.view );
}

// Add new view to the stack.
this._stack.set( data.view, data );
// And display it.
this._show( data );
}

/**
* Removes the given view from the stack. If the removed view was visible,
* then the view preceding it in the stack will become visible instead.
* When there is no view in the stack then balloon will hide.
*
* @param {module:ui/view~View} view A view to be removed from the balloon.
*/
remove( view ) {
if ( !this.hasView( view ) ) {
/**
* Trying to remove configuration of the view not defined in the stack.
*
* @error contextualballoon-remove-view-not-exist
*/
throw new CKEditorError( 'contextualballoon-remove-view-not-exist: Cannot remove configuration of not existing view.' );
}

// When visible view is being removed.
if ( this.visible.view === view ) {
// We need to remove it from the view content.
this.view.content.remove( view );

// And then remove from the stack.
this._stack.delete( view );

// Next we need to check if there is other view in stack to show.
const last = Array.from( this._stack.values() ).pop();

// If it is some other view.
if ( last ) {
// Just show it.
this._show( last );
} else {
// Hide the balloon panel.
this.view.hide();
}
} else {
// Just remove given view from the stack.
this._stack.delete( view );
}
}

/**
* Updates the position of the balloon panel according to position data
* of the first view in the stack.
*/
updatePosition() {
this.view.attachTo( this._getBalloonPosition() );
}

/**
* Sets the view as a content of the balloon and attaches balloon using position
* options of the first view.
*
* @private
* @param {module:ui/contextualballoon~ViewConfig} data Configuration of the view.
*/
_show( data ) {
this.view.content.add( data.view );
this.view.attachTo( this._getBalloonPosition() );
}

/**
* Returns position options of the first view in the stack.
* This keeps the balloon in the same position when view is changed.
*
* @private
* @returns {module:utils/dom/position~Options}
*/
_getBalloonPosition() {
return Array.from( this._stack.values() )[ 0 ].position;
}

/**
* @inheritDoc
*/
destroy() {
this.editor.ui.view.body.remove( this.view );
this.view.destroy();
super.destroy();
}
}

/**
* An object describing configuration of a single view added to the balloon stack.
*
* @typedef {Object} module:ui/contextualballoon~ViewConfig
*
* @property {module:ui/view~View} view Content of the balloon.
* @property {module:utils/dom/position~Options} position Positioning options.
*/
Loading