瀏覽代碼

Merge pull request #458 from ckeditor/t/ckeditor5/1449

BREAKING CHANGE: The `EditableUIView#externalElement` property has been removed.
BREAKING CHANGE: The `EditableUIView#editableElement` property has been made a private property and should not be accessed directly from outside of this class or its subclasses. Use `EditorUI#getEditableElement()` method instead to access editor editable element.
Piotr Jasiun 6 年之前
父節點
當前提交
a4d86e574d

+ 36 - 38
packages/ckeditor5-ui/docs/_snippets/examples/bootstrap-ui-inner.js

@@ -7,9 +7,8 @@
 
 // Basic classes to create an editor.
 import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
+import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
 import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
-import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
-import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
 import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
 import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
@@ -64,22 +63,12 @@ export default class BootstrapEditor extends Editor {
 		// When editor#element is a textarea inside a form element
 		// then content of this textarea will be updated on form submit.
 		attachToForm( this );
-
-		// A helper to easily replace the editor#element with editor.editable#element.
-		this._elementReplacer = new ElementReplacer();
-	}
-
-	get element() {
-		return this.ui.view.element;
 	}
 
 	destroy() {
 		// When destroyed, editor sets the output of editor#getData() into editor#element...
 		this.updateSourceElement();
 
-		// ...and restores the original editor#element...
-		this._elementReplacer.restore();
-
 		// ...and destroys the UI.
 		this.ui.destroy();
 
@@ -89,22 +78,13 @@ export default class BootstrapEditor extends Editor {
 	static create( element, config ) {
 		return new Promise( resolve => {
 			const editor = new this( element, config );
-			const editable = editor.ui.view.editable;
 
 			resolve(
 				editor.initPlugins()
-					.then( () => {
-						// Initialize the UI first. See the BootstrapEditorUI class to learn more.
-						editor.ui.init();
-
-						// Replace the editor#element with editor.editable#element.
-						editor._elementReplacer.replace( element, editable.element );
-
-						// Tell the world that the UI of the editor is ready to use.
-						editor.fire( 'uiReady' );
-					} )
+					// Initialize the UI first. See the BootstrapEditorUI class to learn more.
+					.then( () => editor.ui.init( element ) )
 					// Bind the editor editing layer to the editable in DOM.
-					.then( () => editor.editing.view.attachDomRoot( editable.element ) )
+					.then( () => editor.editing.view.attachDomRoot( editor.ui.getEditableElement() ) )
 					// Fill the editable with the initial data.
 					.then( () => editor.data.init( getDataFromElement( element ) ) )
 					// Fire the events that announce that the editor is complete and ready to use.
@@ -123,17 +103,20 @@ mix( BootstrapEditor, DataApiMixin );
 mix( BootstrapEditor, ElementApiMixin );
 
 // The class organizing the UI of the editor, binding it with existing Bootstrap elements in DOM.
-class BootstrapEditorUI {
+class BootstrapEditorUI extends EditorUI {
 	constructor( editor ) {
-		this.editor = editor;
+		super( editor );
+
+		// A helper to easily replace the editor#element with editor.editable#element.
+		this._elementReplacer = new ElementReplacer();
 
 		// The global UI view of the editor. It aggregates various Bootstrap DOM elements.
-		const view = this.view = new EditorUIView( editor.locale );
+		const view = this._view = new EditorUIView( editor.locale );
 
-		// This is the main editor element in DOM.
+		// This is the main editor element in the DOM.
 		view.element = $( '.ck-editor' );
 
-		// This is the editable view in DOM. It will replace the data container in DOM.
+		// This is the editable view in the DOM. It will replace the data container in the DOM.
 		view.editable = new InlineEditableUIView( editor.locale );
 
 		// References to the dropdown elements for further usage. See #_setupBootstrapHeadingDropdown.
@@ -144,39 +127,54 @@ class BootstrapEditorUI {
 		view.toolbarButtons = {};
 
 		[ 'bold', 'italic', 'underline', 'undo', 'redo' ].forEach( name => {
-			// Retrieve the jQuery object corresponding with the button in DOM.
+			// Retrieve the jQuery object corresponding with the button in the DOM.
 			view.toolbarButtons[ name ] = view.element.find( `#${ name }` );
 		} );
+	}
 
-		// Mandatory EditorUI interface components.
-		this.componentFactory = new ComponentFactory( editor );
-		this.focusTracker = new FocusTracker();
+	get view() {
+		return this._view;
 	}
 
-	init() {
+	init( replacementElement ) {
 		const editor = this.editor;
 		const view = this.view;
 
-		// Render the editable component in DOM first.
+		// Render the editable component in the DOM first.
 		view.editable.render();
 
 		// Create an editing root in the editing layer. It will correspond with the
 		// document root created in the constructor().
 		const editingRoot = editor.editing.view.document.getRoot();
 
-		// Bind the basic attributes of the editable in DOM with the editing layer.
+		// Bind the basic attributes of the editable in the DOM with the editing layer.
 		view.editable.bind( 'isReadOnly' ).to( editingRoot );
 		view.editable.bind( 'isFocused' ).to( editor.editing.view.document );
 		view.editable.name = editingRoot.rootName;
 
+		// Register editable element so it is available via getEditableElement() method.
+		this._editableElements.set( view.editable.name, view.editable.element );
+
 		// Setup the existing, external Bootstrap UI so it works with the rest of the editor.
 		this._setupBootstrapToolbarButtons();
 		this._setupBootstrapHeadingDropdown();
+
+		// Replace the editor#element with editor.editable#element.
+		this._elementReplacer.replace( replacementElement, view.editable.element );
+
+		// Tell the world that the UI of the editor is ready to use.
+		this.fire( 'ready' );
 	}
 
 	destroy() {
-		this.view.editable.destroy();
-		this.view.destroy();
+		// Restore the original editor#element.
+		this._elementReplacer.restore();
+
+		// Destroy the view.
+		this._view.editable.destroy();
+		this._view.destroy();
+
+		super.destroy();
 	}
 
 	// This method activates Bold, Italic, Underline, Undo and Redo buttons in the toolbar.

+ 33 - 32
packages/ckeditor5-ui/docs/framework/guides/external-ui.md

@@ -18,6 +18,7 @@ The ready–to–use builds of CKEditor like {@link examples/builds/classic-edit
 ```js
 // Basic classes to create an editor.
 import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
+import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
 import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
 import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
 import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
@@ -81,22 +82,12 @@ export default class BootstrapEditor extends Editor {
 		// When editor#element is a textarea inside a form element,
 		// the content of this textarea will be updated on form submit.
 		attachToForm( this );
-
-		// A helper to easily replace the editor#element with editor.editable#element.
-		this._elementReplacer = new ElementReplacer();
-	}
-
-	get element() {
-		return this.ui.view.element;
 	}
 
 	destroy() {
 		// When destroyed, the editor sets the output of editor#getData() into editor#element...
 		this.updateSourceElement();
 
-		// ...and restores the original editor#element...
-		this._elementReplacer.restore();
-
 		// ...and destroys the UI.
 		this.ui.destroy();
 
@@ -106,23 +97,14 @@ export default class BootstrapEditor extends Editor {
 	static create( element, config ) {
 		return new Promise( resolve => {
 			const editor = new this( element, config );
-			const editable = editor.ui.view.editable;
 
 			resolve(
 				editor.initPlugins()
-					.then( () => {
-						// Initialize the UI first. See the BootstrapEditorUI class to learn more.
-						editor.ui.init();
-
-						// Replace the editor#element with editor.editable#element.
-						editor._elementReplacer.replace( element, editable.element );
-
-						// Tell the world that the UI of the editor is ready to use.
-						editor.fire( 'uiReady' );
-					} )
-					// Bind the editor editing layer to the editable in the DOM.
-					.then( () => editor.editing.view.attachDomRoot( editable.element ) )
-					// Fill the editable with the intial data.
+					// Initialize the UI first. See the BootstrapEditorUI class to learn more.
+					.then( () => editor.ui.init( element ) )
+					// Bind the editor editing layer to the editable in DOM.
+					.then( () => editor.editing.view.attachDomRoot( editor.ui.getEditableElement() ) )
+					// Fill the editable with the initial data.
 					.then( () => editor.data.init( getDataFromElement( element ) ) )
 					// Fire the events that announce that the editor is complete and ready to use.
 					.then( () => {
@@ -256,12 +238,15 @@ Define the `BootstrapEditorUI` and then have a closer look at the content of the
 
 ```js
 // The class organizing the UI of the editor, binding it with existing Bootstrap elements in the DOM.
-class BootstrapEditorUI {
+class BootstrapEditorUI extends EditorUI {
 	constructor( editor ) {
-		this.editor = editor;
+		super( editor );
+
+		// A helper to easily replace the editor#element with editor.editable#element.
+		this._elementReplacer = new ElementReplacer();
 
 		// The global UI view of the editor. It aggregates various Bootstrap DOM elements.
-		const view = this.view = new EditorUIView( editor.locale );
+		const view = this._view = new EditorUIView( editor.locale );
 
 		// This is the main editor element in the DOM.
 		view.element = $( '.ck-editor' );
@@ -280,13 +265,13 @@ class BootstrapEditorUI {
 			// Retrieve the jQuery object corresponding with the button in the DOM.
 			view.toolbarButtons[ name ] = view.element.find( `#${ name }` );
 		} );
+	}
 
-		// Mandatory EditorUI interface components.
-		this.componentFactory = new ComponentFactory( editor );
-		this.focusTracker = new FocusTracker();
+	get view() {
+		return this._view;
 	}
 
-	init() {
+	init( replacementElement ) {
 		const editor = this.editor;
 		const view = this.view;
 
@@ -302,13 +287,29 @@ class BootstrapEditorUI {
 		view.editable.bind( 'isFocused' ).to( editor.editing.view.document );
 		view.editable.name = editingRoot.rootName;
 
+		// Register editable element so it is available via getEditableElement() method.
+		this._editableElements.set( view.editable.name, view.editable.element );
+
 		// Setup the existing, external Bootstrap UI so it works with the rest of the editor.
 		this._setupBootstrapToolbarButtons();
 		this._setupBootstrapHeadingDropdown();
+
+		// Replace the editor#element with editor.editable#element.
+		this._elementReplacer.replace( replacementElement, view.editable.element );
+
+		// Tell the world that the UI of the editor is ready to use.
+		this.fire( 'ready' );
 	}
 
 	destroy() {
-		this.view.editable.destroy();
+		// Restore the original editor#element.
+		this._elementReplacer.restore();
+
+		// Destroy the view.
+		this._view.editable.destroy();
+		this._view.destroy();
+
+		super.destroy();
 	}
 
 	// This method activates Bold, Italic, Underline, Undo and Redo buttons in the toolbar.

+ 15 - 17
packages/ckeditor5-ui/src/editableui/editableuiview.js

@@ -27,10 +27,6 @@ export default class EditableUIView extends View {
 
 		const bind = this.bindTemplate;
 
-		if ( editableElement ) {
-			this.element = this.editableElement = editableElement;
-		}
-
 		this.setTemplate( {
 			tag: 'div',
 			attributes: {
@@ -64,32 +60,34 @@ export default class EditableUIView extends View {
 		this.set( 'isFocused', false );
 
 		/**
-		 * An external {@link #editableElement} passed into the constructor, which also means
-		 * the view will not render its {@link #template}.
+		 * The element which is the main editable element (usually the one with `contentEditable="true"`).
 		 *
-		 * @member {HTMLElement} #externalElement
+		 * @private
+		 * @member {HTMLElement} #_editableElement
 		 */
-		this.externalElement = editableElement;
+		this._editableElement = editableElement;
 
 		/**
-		 * The element which is the main editable element (usually the one with `contentEditable="true"`).
+		 * Whether an external {@link #_editableElement} was passed into the constructor, which also means
+		 * the view will not render its {@link #template}.
 		 *
-		 * @readonly
-		 * @member {HTMLElement} #editableElement
+		 * @private
+		 * @member {Boolean} #_hasExternalElement
 		 */
+		this._hasExternalElement = !!this._editableElement;
 	}
 
 	/**
 	 * Renders the view by either applying the {@link #template} to the existing
-	 * {@link #editableElement} or assigning {@link #element} as {@link #editableElement}.
+	 * {@link #_editableElement} or assigning {@link #element} as {@link #_editableElement}.
 	 */
 	render() {
 		super.render();
 
-		if ( this.externalElement ) {
-			this.template.apply( this.element = this.externalElement );
+		if ( this._hasExternalElement ) {
+			this.template.apply( this.element = this._editableElement );
 		} else {
-			this.editableElement = this.element;
+			this._editableElement = this.element;
 		}
 	}
 
@@ -97,8 +95,8 @@ export default class EditableUIView extends View {
 	 * @inheritDoc
 	 */
 	destroy() {
-		if ( this.externalElement ) {
-			this.template.revert( this.externalElement );
+		if ( this._hasExternalElement ) {
+			this.template.revert( this._editableElement );
 		}
 
 		super.destroy();

+ 3 - 3
packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.js

@@ -52,7 +52,7 @@ export default class BalloonToolbar extends Plugin {
 		this.toolbarView = this._createToolbarView();
 
 		/**
-		 * Tracks the focus of the {@link module:ui/editableui/editableuiview~EditableUIView#editableElement}
+		 * Tracks the focus of the {@link module:core/editor/editorui~EditorUI#getEditableElement editable element}
 		 * and the {@link #toolbarView}. When both are blurred then the toolbar should hide.
 		 *
 		 * @readonly
@@ -61,8 +61,8 @@ export default class BalloonToolbar extends Plugin {
 		this.focusTracker = new FocusTracker();
 
 		// Wait for the EditorUI#init. EditableElement is not available before.
-		editor.once( 'uiReady', () => {
-			this.focusTracker.add( editor.ui.view.editableElement );
+		editor.ui.once( 'ready', () => {
+			this.focusTracker.add( editor.ui.getEditableElement() );
 			this.focusTracker.add( this.toolbarView.element );
 		} );
 

+ 2 - 2
packages/ckeditor5-ui/src/toolbar/block/blocktoolbar.js

@@ -318,7 +318,7 @@ export default class BlockToolbar extends Plugin {
 
 		this.panelView.pin( {
 			target: this.buttonView.element,
-			limiter: this.editor.ui.view.editableElement
+			limiter: this.editor.ui.getEditableElement()
 		} );
 
 		if ( !wasVisible ) {
@@ -349,7 +349,7 @@ export default class BlockToolbar extends Plugin {
 	_attachButtonToElement( targetElement ) {
 		const contentStyles = window.getComputedStyle( targetElement );
 
-		const editableRect = new Rect( this.editor.ui.view.editableElement );
+		const editableRect = new Rect( this.editor.ui.getEditableElement() );
 		const contentPaddingTop = parseInt( contentStyles.paddingTop, 10 );
 		// When line height is not an integer then thread it as "normal".
 		// MDN says that 'normal' == ~1.2 on desktop browsers.

+ 5 - 5
packages/ckeditor5-ui/tests/editableui/editableuiview.js

@@ -27,19 +27,19 @@ describe( 'EditableUIView', () => {
 		it( 'sets initial values of attributes', () => {
 			expect( view.isReadOnly ).to.be.false;
 			expect( view.isFocused ).to.be.false;
-			expect( view.externalElement ).to.be.undefined;
+			expect( view._externalElement ).to.be.undefined;
 		} );
 
 		it( 'renders element from template when no editableElement', () => {
 			view = new EditableUIView( locale );
 
 			view.render();
-			expect( view.element ).to.equal( view.editableElement );
+			expect( view.element ).to.equal( view._editableElement );
 			expect( view.element.classList.contains( 'ck' ) ).to.be.true;
 			expect( view.element.classList.contains( 'ck-editor__editable' ) ).to.be.true;
 			expect( view.element.classList.contains( 'ck-content' ) ).to.be.true;
 			expect( view.element.classList.contains( 'ck-rounded-corners' ) ).to.be.true;
-			expect( view.externalElement ).to.be.undefined;
+			expect( view._externalElement ).to.be.undefined;
 			expect( view.isRendered ).to.be.true;
 		} );
 
@@ -48,11 +48,11 @@ describe( 'EditableUIView', () => {
 
 			view.render();
 			expect( view.element ).to.equal( editableElement );
-			expect( view.element ).to.equal( view.editableElement );
+			expect( view.element ).to.equal( view._editableElement );
 			expect( view.element.classList.contains( 'ck' ) ).to.be.true;
 			expect( view.element.classList.contains( 'ck-editor__editable' ) ).to.be.true;
 			expect( view.element.classList.contains( 'ck-rounded-corners' ) ).to.be.true;
-			expect( view.externalElement ).to.equal( editableElement );
+			expect( view._hasExternalElement ).to.be.true;
 			expect( view.isRendered ).to.be.true;
 		} );
 	} );

+ 1 - 1
packages/ckeditor5-ui/tests/editableui/inline/inlineeditableuiview.js

@@ -31,7 +31,7 @@ describe( 'InlineEditableUIView', () => {
 		it( 'accepts editableElement', () => {
 			view = new InlineEditableUIView( locale, editableElement );
 
-			expect( view.element ).to.equal( editableElement );
+			expect( view._editableElement ).to.equal( editableElement );
 		} );
 
 		it( 'creates view#element from template when no editableElement provided', () => {

+ 3 - 0
packages/ckeditor5-ui/tests/toolbar/block/blocktoolbar.js

@@ -43,6 +43,9 @@ describe( 'BlockToolbar', () => {
 	} );
 
 	afterEach( () => {
+		// Blur editor so `blockToolbar.buttonView` `window.resize` listener is detached.
+		editor.ui.focusTracker.isFocused = false;
+
 		element.remove();
 		return editor.destroy();
 	} );