Skip to content
Piotrek Koszuliński edited this page Mar 1, 2016 · 2 revisions

On a high-level we can say that the editor is made of the editing framework, UI and pluggable features. This page describes how the architecture of the UI layer, covering such topics as how it is detached from the rest of the code and how it can be replaced.

The UI of the editor is composed of several packages. Those packages are combined together to create a working UI library and its bindings for CKEditor. The developer is able to choose which packages should be used while building the editor what allows composing custom UI libraries.

Architecture

The most important part of the UI architecture is the UI library. UI library consists of several UI components (button, checkbox, toolbar, tabbed panel, etc.). In the UI library these components are represented first of all by their views, but the UI library can also contain their base models and controllers.

A UI package (which can contain one or more UI components) will contain the following directory structure:

  • src/
    • comp1/
      • component 1 files which this UI library provides; for example: comp1view.js.
    • comp2/
    • ...

The Default UI Library

CKEditor will be provided with a default UI library. It will define the default views of a closed set of UI components, as well as some base model and controllers for some of them.

This set of UI components will define the shape of the UI library, so what the features can expect from it. It's important though to understand that the default UI library is project agnostic. It will be fully decoupled from the editor to be able to be used in other projects.

Check out the default UI library repository.

Customizations

UI components of the UI library can be customized by creating a package which defines the files that needs to be changed. Such files (JS modules) can import the original modules in order to reuse their code.

The Default UI Library Replacement

When a package defines views for all UI components existing in the default UI library it becomes its replacement. This will allow creating UI libraries featuring Bootstrap, Polymer, jQuery UI and similar libraries.

Building

Composition of the UI library is done by copying in a specified order chosen packages to a ckeditor5/ui/ directory. If two packages will contain the same file, the one copied there later stays, what will allow overriding existing components.

At the same time the UI packages will also be available under their usual paths, so e.g. ckeditor5-ui-default will be available in ckeditor5/ui-default. This will allow to import its modules in order to extend them. Package which wants to import some other UI package's modules, should of course define that as a dependency in its package.json.

CKEditor Bindings

The UI library may provide some basic bindings (in form of a component controllers) but it will be fully decoupled from CKEditor. In order to make the UI components usable some of them may require specific bindings to the editor environment – e.g. its internationalization system, theming mechanism, etc. Those will be defined in the core/ui/bindings/* modules (e.g. core/ui/bindings/button.js might extend ckeditor5/ui/button/button.js).

The default UI library may still provide some generic bindings, what means the the feature developer will be able to choose between more tightly bound component or its generic form.

Usage in Features and Creators

Features and creators will import UI component views directly from the ckeditor5/ui/ directory, knowing nothing about which UI library was used. The controllers and models, if defined by the default library (which, as was mentioned, defines the shape of all UI libraries), can be imported from the ckeditor5/ui/ directory or (in case of controllers) also from ckeditor5/core/ui/bindings/ where CKEditor bindings are located.

For instance, assuming that a certain creator wants to create a toolbar it would import it and instantiate as follows:

import Toolbar from '../core/ui/bindings/toolbar.js';
import ToolbarView from '../ui/toolbar/toolbarview.js';
import Model from '../core/ui/model.js';
import Creator from '../core/creator.js';

export default class SomeCreator extends Creator {
	create() {
		super.create()
			.then( () => this._createToolbar() );
	}

	_createToolbar() {
		const toolbarModel = new Model( {
			buttons: this.editor.config.toolbar
		} );
		const toolbarView = new ToolbarView( toolbarModel );
		const toolbar = new Toolbar( toolbarModel, toolbarView );

		this.editor.ui.add( 'top', toolbar );
	}
}

Feature UI Components

A special mechanism needs to be defined to handle UI components which are representing features. For instance, a bold feature is represented by its button. The application (usually a creator) may want to create multiple instances of each button, for instance to display multiple toolbars. All bold buttons share the same model though.

Therefore, the feature needs to register its component to the feature components factory as follows:

import Button from '../core/ui/bindings/button.js';
import ButtonView from '../ui/button/buttonview.js';
import Model from '../core/ui/model.js';
import Feature from '../core/feature.js';

export default class BoldFeature extends Feature {
	init() {
		// Creation of a feature component model will be somehow done based on the bold command
		// in order to automate necessary bindings.
		const boldButtonModel = new Model( {
			label: 'bold',
			icon: 'bold'
		} );

		boldButtonModel.on( 'execute', () => {
			this.editor.execute( 'bold' );
		} );

		this.editor.ui.featureComponents.add( 'bold', Button, ButtonView, boldButtonModel );
	}
}

Further Concerns

There are some topics which haven't yet been taken into consideration and may change the architecture of the UI libraries.

Third-party UI Components

The default UI library won't define all possible UI components, therefore feature authors may implement their own components. Do we want to make them part of the UI library (what could be done by the builder based on package names) to make them overridable in the same way as other components are? (To make it clear – features would import these components from ckeditor5/ui/ directory, just like all other components).

While the idea sounds OK it would create a tough situation for the builder which would need to decide in what order those additional components need to be copied to the ckeditor5/ui/ directory.

Read more in the following discussion.

Defining Features' UI Dependencies in a JSON file

Currently features must import the UI components they use directly to mark that those components are required (which is necessary for code loading and the bundler). That meant that the decision which UI library is used needs to be done on building step which simplifies many things, but sets some limitations as well.

Defining features' UI component dependencies in the package.json (together with assets, which most likely will be defined there) might be done on a higher level as a map feature => component name. This would decouple a feature from the path to UI component, so that could be resolved dynamically which is more flexible. Such solution would require special preparation for the bundling step as well as for running code in the development environment.