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

Commit aca1ff1

Browse files
author
Piotr Jasiun
authored
Merge pull request #154 from ckeditor/t/ckeditor5/1449
Other: Editor UI classes API refactoring. Closes ckeditor/ckeditor5#1449. BREAKING CHANGE: Removed `EditroWithUI#element` property. The `EditorUI#element` property should be used instead. BREAKING CHANGE: Removed `EditroWithUI#uiReady` event. The `EditorUI#ready` event should be used instead. BREAKING CHANGE: Removed `view` parameter in `EditorUI` constructor. Only subclasses should use it without passing it further to `EditorUI`. BREAKING CHANGE: Removed `EditroUI#view` property. The `view` property from subclasses (like `ClassicEditorUI#view`) should be used directly instead.
2 parents eac8298 + bb3d978 commit aca1ff1

File tree

5 files changed

+183
-67
lines changed

5 files changed

+183
-67
lines changed

src/editor/editorui.js

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ export default class EditorUI {
2323
* Creates an instance of the editor UI class.
2424
*
2525
* @param {module:core/editor/editor~Editor} editor The editor instance.
26-
* @param {module:ui/editorui/editoruiview~EditorUIView} view The view of the UI.
2726
*/
28-
constructor( editor, view ) {
27+
constructor( editor ) {
2928
/**
3029
* The editor that the UI belongs to.
3130
*
@@ -34,14 +33,6 @@ export default class EditorUI {
3433
*/
3534
this.editor = editor;
3635

37-
/**
38-
* The main (top–most) view of the editor UI.
39-
*
40-
* @readonly
41-
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
42-
*/
43-
this.view = view;
44-
4536
/**
4637
* An instance of the {@link module:ui/componentfactory~ComponentFactory}, a registry used by plugins
4738
* to register factories of specific UI components.
@@ -60,10 +51,37 @@ export default class EditorUI {
6051
*/
6152
this.focusTracker = new FocusTracker();
6253

54+
/**
55+
* Stores all editable elements used by the editor instance.
56+
*
57+
* @protected
58+
* @member {Map.<String,HTMLElement>}
59+
*/
60+
this._editableElements = new Map();
61+
6362
// Informs UI components that should be refreshed after layout change.
6463
this.listenTo( editor.editing.view.document, 'layoutChanged', () => this.update() );
6564
}
6665

66+
/**
67+
* The main (outermost) DOM element of the editor UI.
68+
*
69+
* For example, in {@link module:editor-classic/classiceditor~ClassicEditor} it is a `<div>` which
70+
* wraps the editable element and the toolbar. In {@link module:editor-inline/inlineeditor~InlineEditor}
71+
* it is the editable element itself (as there is no other wrapper). However, in
72+
* {@link module:editor-decoupled/decouplededitor~DecoupledEditor} it is set to `null` because this editor does not
73+
* come with a single "main" HTML element (its editable element and toolbar are separate).
74+
*
75+
* This property can be understood as a shorthand for retrieving the element that a specific editor integration
76+
* considers to be its main DOM element.
77+
*
78+
* @readonly
79+
* @member {HTMLElement|null} #element
80+
*/
81+
get element() {
82+
return null;
83+
}
84+
6785
/**
6886
* Fires the {@link module:core/editor/editorui~EditorUI#event:update `update`} event.
6987
*
@@ -79,10 +97,40 @@ export default class EditorUI {
7997
*/
8098
destroy() {
8199
this.stopListening();
82-
this.view.destroy();
100+
83101
this.focusTracker.destroy();
102+
103+
this._editableElements = new Map();
104+
}
105+
106+
/**
107+
* Returns the editable editor element with the given name or null if editable does not exist.
108+
*
109+
* @param {String} [rootName=main] The editable name.
110+
* @returns {HTMLElement|undefined}
111+
*/
112+
getEditableElement( rootName = 'main' ) {
113+
return this._editableElements.get( rootName );
114+
}
115+
116+
/**
117+
* Returns array of names of all editor editable elements.
118+
*
119+
* @returns {Iterable.<String>}
120+
*/
121+
getEditableElementsNames() {
122+
return this._editableElements.keys();
84123
}
85124

125+
/**
126+
* Fired when the editor UI is ready.
127+
*
128+
* Fired after {@link module:core/editor/editor~Editor#event:pluginsReady} and before
129+
* {@link module:core/editor/editor~Editor#event:dataReady}.
130+
*
131+
* @event ready
132+
*/
133+
86134
/**
87135
* Fired whenever the UI (all related components) should be refreshed.
88136
*

src/editor/editorwithui.jsdoc

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,3 @@
2727
* @readonly
2828
* @member {module:core/editor/editorui~EditorUI} #ui
2929
*/
30-
31-
/**
32-
* The main (outermost) DOM element of the editor UI.
33-
*
34-
* For example, in {@link module:editor-classic/classiceditor~ClassicEditor} it is a `<div>` which
35-
* wraps the editable element and the toolbar. In {@link module:editor-inline/inlineeditor~InlineEditor}
36-
* it is the editable element itself (as there is no other wrapper). However, in
37-
* {@link module:editor-decoupled/decouplededitor~DecoupledEditor} it is set to `null` because this editor does not
38-
* come with a single "main" HTML element (its editable element and toolbar are separate).
39-
*
40-
* This property can be understood as a shorthand for retrieving the element that a specific editor integration
41-
* considers to be its main DOM element. There are always other ways to access these elements, too
42-
* (e.g. via {@link #ui `editor.ui`}).
43-
*
44-
* @readonly
45-
* @member {HTMLElement|null} #element
46-
*/
47-
48-
/**
49-
* Fired when the editor UI is ready.
50-
*
51-
* Fired after {@link module:core/editor/editor~Editor#event:pluginsReady} and before
52-
* {@link module:core/editor/editor~Editor#event:dataReady}.
53-
*
54-
* @event uiReady
55-
*/

tests/_utils-tests/classictesteditor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ describe( 'ClassicTestEditor', () => {
122122
class EventWatcher extends Plugin {
123123
init() {
124124
this.editor.on( 'pluginsReady', spy );
125-
this.editor.on( 'uiReady', spy );
125+
this.editor.ui.on( 'ready', spy );
126126
this.editor.on( 'dataReady', spy );
127127
this.editor.on( 'ready', spy );
128128
}
@@ -133,7 +133,7 @@ describe( 'ClassicTestEditor', () => {
133133
plugins: [ EventWatcher ]
134134
} )
135135
.then( editor => {
136-
expect( fired ).to.deep.equal( [ 'pluginsReady', 'uiReady', 'dataReady', 'ready' ] );
136+
expect( fired ).to.deep.equal( [ 'pluginsReady', 'ready', 'dataReady', 'ready' ] );
137137

138138
return editor.destroy();
139139
} );

tests/_utils/classictesteditor.js

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,11 @@ export default class ClassicTestEditor extends Editor {
3333
// Use the HTML data processor in this editor.
3434
this.data.processor = new HtmlDataProcessor();
3535

36-
this.ui = new EditorUI( this, new BoxedEditorUIView( this.locale ) );
36+
this.ui = new ClassicTestEditorUI( this, new BoxedEditorUIView( this.locale ) );
3737

3838
// Expose properties normally exposed by the ClassicEditorUI.
3939
this.ui.view.editable = new InlineEditableUIView( this.ui.view.locale );
4040

41-
// A helper to easily replace the editor#element with editor.editable#element.
42-
this._elementReplacer = new ElementReplacer();
43-
4441
// Create the ("main") root element of the model tree.
4542
this.model.document.createRoot();
4643
}
@@ -49,7 +46,6 @@ export default class ClassicTestEditor extends Editor {
4946
* @inheritDoc
5047
*/
5148
destroy() {
52-
this._elementReplacer.restore();
5349
this.ui.destroy();
5450

5551
return super.destroy();
@@ -66,18 +62,8 @@ export default class ClassicTestEditor extends Editor {
6662
editor.initPlugins()
6763
// Simulate EditorUI.init() (e.g. like in ClassicEditorUI). The ui#view
6864
// should be rendered after plugins are initialized.
69-
.then( () => {
70-
const view = editor.ui.view;
71-
72-
view.render();
73-
view.main.add( view.editable );
74-
view.editableElement = view.editable.element;
75-
} )
76-
.then( () => {
77-
editor._elementReplacer.replace( element, editor.ui.view.element );
78-
editor.fire( 'uiReady' );
79-
} )
80-
.then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) )
65+
.then( () => editor.ui.init( element ) )
66+
.then( () => editor.editing.view.attachDomRoot( editor.ui.getEditableElement() ) )
8167
.then( () => editor.data.init( getDataFromElement( element ) ) )
8268
.then( () => {
8369
editor.fire( 'dataReady' );
@@ -90,5 +76,60 @@ export default class ClassicTestEditor extends Editor {
9076
}
9177
}
9278

79+
/**
80+
* A simplified classic editor ui class.
81+
*
82+
* @memberOf tests.core._utils
83+
* @extends core.editor.EditorUI
84+
*/
85+
class ClassicTestEditorUI extends EditorUI {
86+
/**
87+
* @inheritDoc
88+
*/
89+
constructor( editor, view ) {
90+
super( editor );
91+
92+
// A helper to easily replace the editor#element with editor.editable#element.
93+
this._elementReplacer = new ElementReplacer();
94+
95+
this._view = view;
96+
}
97+
98+
/**
99+
* The main (top–most) view of the editor UI.
100+
*
101+
* @readonly
102+
* @member {module:ui/editorui/editoruiview~EditorUIView} #view
103+
*/
104+
get view() {
105+
return this._view;
106+
}
107+
108+
init( element ) {
109+
const view = this.view;
110+
111+
view.render();
112+
view.main.add( view.editable );
113+
view.editableElement = view.editable.element;
114+
115+
this._editableElements.set( 'main', view.editable.element );
116+
117+
this._elementReplacer.replace( element, view.element );
118+
119+
this.fire( 'ready' );
120+
}
121+
122+
/**
123+
* @inheritDoc
124+
*/
125+
destroy() {
126+
this._elementReplacer.restore();
127+
128+
this._view.destroy();
129+
130+
super.destroy();
131+
}
132+
}
133+
93134
mix( ClassicTestEditor, DataApiMixin );
94135
mix( ClassicTestEditor, ElementApiMixin );

tests/editor/editorui.js

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ import Editor from '../../src/editor/editor';
88

99
import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
1010
import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
11-
import View from '@ckeditor/ckeditor5-ui/src/view';
1211

1312
import testUtils from '../_utils/utils';
1413

14+
/* global document */
15+
1516
describe( 'EditorUI', () => {
16-
let editor, view, ui;
17+
let editor, ui;
1718

1819
testUtils.createSinonSandbox();
1920

2021
beforeEach( () => {
2122
editor = new Editor();
22-
view = new View();
23-
ui = new EditorUI( editor, view );
23+
ui = new EditorUI( editor );
2424
} );
2525

2626
afterEach( () => {
@@ -32,10 +32,6 @@ describe( 'EditorUI', () => {
3232
expect( ui.editor ).to.equal( editor );
3333
} );
3434

35-
it( 'should set #view', () => {
36-
expect( ui.view ).to.equal( view );
37-
} );
38-
3935
it( 'should create #componentFactory factory', () => {
4036
expect( ui.componentFactory ).to.be.instanceOf( ComponentFactory );
4137
} );
@@ -44,6 +40,10 @@ describe( 'EditorUI', () => {
4440
expect( ui.focusTracker ).to.be.instanceOf( FocusTracker );
4541
} );
4642

43+
it( 'should have #element getter', () => {
44+
expect( ui.element ).to.null;
45+
} );
46+
4747
it( 'should fire update event after viewDocument#layoutChanged', () => {
4848
const spy = sinon.spy();
4949

@@ -84,12 +84,65 @@ describe( 'EditorUI', () => {
8484
sinon.assert.called( spy );
8585
} );
8686

87-
it( 'should destroy the #view', () => {
88-
const spy = sinon.spy( view, 'destroy' );
87+
it( 'should reset editables array', () => {
88+
ui._editableElements.set( 'foo', {} );
89+
ui._editableElements.set( 'bar', {} );
90+
91+
expect( ui._editableElements.size ).to.equal( 2 );
8992

9093
ui.destroy();
9194

92-
sinon.assert.called( spy );
95+
expect( ui._editableElements.size ).to.equal( 0 );
96+
} );
97+
} );
98+
99+
describe( 'getEditableElement()', () => {
100+
it( 'should return editable element (default root name)', () => {
101+
const ui = new EditorUI( editor );
102+
const editableMock = { name: 'main', element: document.createElement( 'div' ) };
103+
104+
ui._editableElements.set( editableMock.name, editableMock.element );
105+
106+
expect( ui.getEditableElement() ).to.equal( editableMock.element );
107+
} );
108+
109+
it( 'should return editable element (custom root name)', () => {
110+
const ui = new EditorUI( editor );
111+
const editableMock1 = { name: 'root1', element: document.createElement( 'div' ) };
112+
const editableMock2 = { name: 'root2', element: document.createElement( 'p' ) };
113+
114+
ui._editableElements.set( editableMock1.name, editableMock1.element );
115+
ui._editableElements.set( editableMock2.name, editableMock2.element );
116+
117+
expect( ui.getEditableElement( 'root1' ) ).to.equal( editableMock1.element );
118+
expect( ui.getEditableElement( 'root2' ) ).to.equal( editableMock2.element );
119+
} );
120+
121+
it( 'should return null if editable with specified name does not exist', () => {
122+
const ui = new EditorUI( editor );
123+
124+
expect( ui.getEditableElement() ).to.be.undefined;
125+
} );
126+
} );
127+
128+
describe( 'getEditableElementsNames()', () => {
129+
it( 'should return iterable object of names', () => {
130+
const ui = new EditorUI( editor );
131+
const editableMock1 = { name: 'main', element: document.createElement( 'div' ) };
132+
const editableMock2 = { name: 'root2', element: document.createElement( 'p' ) };
133+
134+
ui._editableElements.set( editableMock1.name, editableMock1.element );
135+
ui._editableElements.set( editableMock2.name, editableMock2.element );
136+
137+
const names = ui.getEditableElementsNames();
138+
expect( names[ Symbol.iterator ] ).to.instanceof( Function );
139+
expect( Array.from( names ) ).to.deep.equal( [ 'main', 'root2' ] );
140+
} );
141+
142+
it( 'should return empty array if no editables', () => {
143+
const ui = new EditorUI( editor );
144+
145+
expect( ui.getEditableElementsNames() ).to.be.empty;
93146
} );
94147
} );
95148
} );

0 commit comments

Comments
 (0)