Explorar o código

Merge pull request #463 from ckeditor/t/ckeditor5/479

Internal: Moved the `EditableUIView` and  `InlineEditableUIView` attributes management to the engine (see ckeditor/ckeditor5#479). Closes ckeditor/ckeditor5#798.

BREAKING CHANGE: The `EditableUIView#isReadOnly` property has been removed. Use the engine `EditableElement#isReadOnly` instead.

BREAKING CHANGE: The second argument of `EditableUIView.constructor()` and `InlineEditableUIView.constructor()` became the instance of the editing view (previously an optional editable element reference).
Piotr Jasiun %!s(int64=7) %!d(string=hai) anos
pai
achega
9c85cfbf20

+ 40 - 13
packages/ckeditor5-ui/src/editableui/editableuiview.js

@@ -19,14 +19,13 @@ export default class EditableUIView extends View {
 	 * Creates an instance of EditableUIView class.
 	 *
 	 * @param {module:utils/locale~Locale} [locale] The locale instance.
+	 * @param {module:engine/view/view~View} editingView The editing view instance the editable is related to.
 	 * @param {HTMLElement} [editableElement] The editable element. If not specified, this view
 	 * should create it. Otherwise, the existing element should be used.
 	 */
-	constructor( locale, editableElement ) {
+	constructor( locale, editingView, editableElement ) {
 		super( locale );
 
-		const bind = this.bindTemplate;
-
 		this.setTemplate( {
 			tag: 'div',
 			attributes: {
@@ -34,22 +33,17 @@ export default class EditableUIView extends View {
 					'ck',
 					'ck-content',
 					'ck-editor__editable',
-					'ck-rounded-corners',
-					bind.to( 'isFocused', value => value ? 'ck-focused' : 'ck-blurred' ),
-					bind.if( 'isReadOnly', 'ck-read-only' )
-
-				],
-				contenteditable: bind.to( 'isReadOnly', value => !value ),
+					'ck-rounded-corners'
+				]
 			}
 		} );
 
 		/**
-		 * Controls whether the editable is writable or not.
+		 * The name of the editable UI view.
 		 *
-		 * @observable
-		 * @member {Boolean} #isReadOnly
+		 * @member {String} #name
 		 */
-		this.set( 'isReadOnly', false );
+		this.name = null;
 
 		/**
 		 * Controls whether the editable is focused, i.e. the user is typing in it.
@@ -75,6 +69,19 @@ export default class EditableUIView extends View {
 		 * @member {Boolean} #_hasExternalElement
 		 */
 		this._hasExternalElement = !!this._editableElement;
+
+		/**
+		 * The editing view instance the editable is related to. Editable uses the editing
+		 * view to dynamically modify its certain DOM attributes after {@link #render rendering}.
+		 *
+		 * **Note**: The DOM attributes are performed by the editing view and not UI
+		 * {@link module:ui/view~View#bindTemplate template bindings} because once rendered,
+		 * the editable DOM element must remain under the full control of the engine to work properly.
+		 *
+		 * @protected
+		 * @member {module:engine/view/view~View} #isFocused
+		 */
+		this._editingView = editingView;
 	}
 
 	/**
@@ -89,6 +96,9 @@ export default class EditableUIView extends View {
 		} else {
 			this._editableElement = this.element;
 		}
+
+		this.on( 'change:isFocused', () => this._updateIsFocusedClasses() );
+		this._updateIsFocusedClasses();
 	}
 
 	/**
@@ -101,4 +111,21 @@ export default class EditableUIView extends View {
 
 		super.destroy();
 	}
+
+	/**
+	 * Updates the `ck-focused` and `ck-blurred` CSS classes on the {@link #element} according to
+	 * the {@link #isFocused} property value using the {@link #_editingView editing view} API.
+	 *
+	 * @private
+	 */
+	_updateIsFocusedClasses() {
+		const editingView = this._editingView;
+
+		editingView.change( writer => {
+			const viewRoot = editingView.document.getRoot( this.name );
+
+			writer.addClass( this.isFocused ? 'ck-focused' : 'ck-blurred', viewRoot );
+			writer.removeClass( this.isFocused ? 'ck-blurred' : 'ck-focused', viewRoot );
+		} );
+	}
 }

+ 19 - 18
packages/ckeditor5-ui/src/editableui/inline/inlineeditableuiview.js

@@ -19,34 +19,35 @@ export default class InlineEditableUIView extends EditableUIView {
 	 * Creates an instance of the InlineEditableUIView class.
 	 *
 	 * @param {module:utils/locale~Locale} [locale] The locale instance.
+	 * @param {module:engine/view/view~View} editingView The editing view instance the editable is related to.
 	 * @param {HTMLElement} [editableElement] The editable element. If not specified, the
 	 * {@link module:ui/editableui/editableuiview~EditableUIView}
 	 * will create it. Otherwise, the existing element will be used.
 	 */
-	constructor( locale, editableElement ) {
-		super( locale, editableElement );
-
-		const bind = this.bindTemplate;
-		const t = this.t;
-
-		/**
-		 * The name of the editable UI view.
-		 *
-		 * @observable
-		 * @member {String} #name
-		 */
-		this.set( 'name', null );
-
-		const getLabel = value => {
-			return t( 'Rich Text Editor, %0', [ value ] );
-		};
+	constructor( locale, editingView, editableElement ) {
+		super( locale, editingView, editableElement );
 
 		this.extendTemplate( {
 			attributes: {
 				role: 'textbox',
-				'aria-label': bind.to( 'name', getLabel ),
 				class: 'ck-editor__editable_inline'
 			}
 		} );
 	}
+
+	/**
+	 * @inheritDoc
+	 */
+	render() {
+		super.render();
+
+		const editingView = this._editingView;
+		const t = this.t;
+
+		editingView.change( writer => {
+			const viewRoot = editingView.document.getRoot( this.name );
+
+			writer.setAttribute( 'aria-label', t( 'Rich Text Editor, %0', [ this.name ] ), viewRoot );
+		} );
+	}
 }

+ 27 - 40
packages/ckeditor5-ui/tests/editableui/editableuiview.js

@@ -5,13 +5,15 @@
 
 /* globals document */
 
+import EditingView from '@ckeditor/ckeditor5-engine/src/view/view';
+import ViewRootEditableElement from '@ckeditor/ckeditor5-engine/src/view/rooteditableelement';
 import EditableUIView from '../../src/editableui/editableuiview';
 import View from '../../src/view';
 import Locale from '@ckeditor/ckeditor5-utils/src/locale';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 
 describe( 'EditableUIView', () => {
-	let view, editableElement, locale;
+	let view, editableElement, editingView, editingViewRoot, locale;
 
 	testUtils.createSinonSandbox();
 
@@ -19,32 +21,39 @@ describe( 'EditableUIView', () => {
 		locale = new Locale( 'en' );
 		editableElement = document.createElement( 'div' );
 
-		view = new EditableUIView( locale );
+		editingView = new EditingView();
+		editingViewRoot = new ViewRootEditableElement( 'div' );
+		editingViewRoot._document = editingView.document;
+		editingView.document.roots.add( editingViewRoot );
+		view = new EditableUIView( locale, editingView );
+		view.name = editingViewRoot.rootName;
+
 		view.render();
 	} );
 
 	describe( 'constructor()', () => {
 		it( 'sets initial values of attributes', () => {
-			expect( view.isReadOnly ).to.be.false;
+			view = new EditableUIView( locale, editingView );
+
 			expect( view.isFocused ).to.be.false;
+			expect( view.name ).to.be.null;
 			expect( view._externalElement ).to.be.undefined;
+			expect( view._editingView ).to.equal( editingView );
 		} );
 
 		it( 'renders element from template when no editableElement', () => {
-			view = new EditableUIView( locale );
-
-			view.render();
 			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-editor__editable' ) ).to.be.true;
 			expect( view.element.classList.contains( 'ck-rounded-corners' ) ).to.be.true;
 			expect( view._externalElement ).to.be.undefined;
 			expect( view.isRendered ).to.be.true;
 		} );
 
 		it( 'accepts editableElement as an argument', () => {
-			view = new EditableUIView( locale, editableElement );
+			view = new EditableUIView( locale, editingView, editableElement );
+			view.name = editingViewRoot.rootName;
 
 			view.render();
 			expect( view.element ).to.equal( editableElement );
@@ -62,24 +71,12 @@ describe( 'EditableUIView', () => {
 			it( 'reacts on view#isFocused', () => {
 				view.isFocused = true;
 
-				expect( view.element.classList.contains( 'ck-focused' ) ).to.be.true;
-				expect( view.element.classList.contains( 'ck-blurred' ) ).to.be.false;
+				expect( editingViewRoot.hasClass( 'ck-focused' ) ).to.be.true;
+				expect( editingViewRoot.hasClass( 'ck-blurred' ) ).to.be.false;
 
 				view.isFocused = false;
-				expect( view.element.classList.contains( 'ck-focused' ) ).to.be.false;
-				expect( view.element.classList.contains( 'ck-blurred' ) ).to.be.true;
-			} );
-		} );
-
-		describe( 'contenteditable', () => {
-			it( 'reacts on view#isReadOnly', () => {
-				view.isReadOnly = true;
-				expect( view.element.hasAttribute( 'contenteditable' ) ).to.be.false;
-				expect( view.element.classList.contains( 'ck-read-only' ) ).to.be.true;
-
-				view.isReadOnly = false;
-				expect( view.element.hasAttribute( 'contenteditable' ) ).to.be.true;
-				expect( view.element.classList.contains( 'ck-read-only' ) ).to.be.false;
+				expect( editingViewRoot.hasClass( 'ck-focused' ) ).to.be.false;
+				expect( editingViewRoot.hasClass( 'ck-blurred' ) ).to.be.true;
 			} );
 		} );
 	} );
@@ -100,28 +97,18 @@ describe( 'EditableUIView', () => {
 		} );
 
 		describe( 'when #editableElement as an argument', () => {
-			it( 'reverts contentEditable property of editableElement (was false)', () => {
+			it( 'reverts the template of editableElement', () => {
 				editableElement = document.createElement( 'div' );
+				editableElement.classList.add( 'foo' );
 				editableElement.contentEditable = false;
 
-				view = new EditableUIView( locale, editableElement );
-
-				view.render();
-				expect( editableElement.contentEditable ).to.equal( 'true' );
-				view.destroy();
-				expect( editableElement.contentEditable ).to.equal( 'false' );
-			} );
-
-			it( 'reverts contentEditable property of editableElement (was true)', () => {
-				editableElement = document.createElement( 'div' );
-				editableElement.contentEditable = true;
-
-				view = new EditableUIView( locale, editableElement );
+				view = new EditableUIView( locale, editingView, editableElement );
+				view.name = editingViewRoot.rootName;
 
 				view.render();
-				expect( editableElement.contentEditable ).to.equal( 'true' );
 				view.destroy();
-				expect( editableElement.contentEditable ).to.equal( 'true' );
+				expect( view.element.classList.contains( 'ck' ) ).to.be.false;
+				expect( view.element.classList.contains( 'foo' ) ).to.be.true;
 			} );
 		} );
 	} );

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

@@ -5,17 +5,25 @@
 
 /* globals document */
 
+import EditingView from '@ckeditor/ckeditor5-engine/src/view/view';
+import ViewRootEditableElement from '@ckeditor/ckeditor5-engine/src/view/rooteditableelement';
 import InlineEditableUIView from '../../../src/editableui/inline/inlineeditableuiview';
 import Locale from '@ckeditor/ckeditor5-utils/src/locale';
 
 describe( 'InlineEditableUIView', () => {
-	let view, editableElement, locale;
+	let view, editableElement, editingView, editingViewRoot, locale;
 
 	beforeEach( () => {
 		locale = new Locale( 'en' );
 		editableElement = document.createElement( 'div' );
 
-		view = new InlineEditableUIView( locale );
+		editingView = new EditingView();
+		editingViewRoot = new ViewRootEditableElement( 'div' );
+		editingViewRoot._document = editingView.document;
+		editingView.document.roots.add( editingViewRoot );
+		view = new InlineEditableUIView( locale, editingView );
+		view.name = editingViewRoot.rootName;
+
 		view.render();
 	} );
 
@@ -24,12 +32,8 @@ describe( 'InlineEditableUIView', () => {
 			expect( view.locale ).to.equal( locale );
 		} );
 
-		it( 'sets initial values of attributes', () => {
-			expect( view.name ).to.be.null;
-		} );
-
 		it( 'accepts editableElement', () => {
-			view = new InlineEditableUIView( locale, editableElement );
+			view = new InlineEditableUIView( locale, editingView, editableElement );
 
 			expect( view._editableElement ).to.equal( editableElement );
 		} );
@@ -40,18 +44,14 @@ describe( 'InlineEditableUIView', () => {
 	} );
 
 	describe( 'editableElement', () => {
-		const ariaLabel = 'Rich Text Editor, foo';
-
-		beforeEach( () => {
-			view.name = 'foo';
-		} );
+		const ariaLabel = 'Rich Text Editor, main';
 
 		it( 'has proper accessibility role', () => {
 			expect( view.element.attributes.getNamedItem( 'role' ).value ).to.equal( 'textbox' );
 		} );
 
 		it( 'has proper ARIA label', () => {
-			expect( view.element.getAttribute( 'aria-label' ) ).to.equal( ariaLabel );
+			expect( editingViewRoot.getAttribute( 'aria-label' ) ).to.equal( ariaLabel );
 		} );
 
 		it( 'has proper class name', () => {

+ 2 - 2
packages/ckeditor5-ui/tests/toolbar/balloon/balloontoolbar.js

@@ -230,7 +230,7 @@ describe( 'BalloonToolbar', () => {
 
 			const targetViewRange = editingView.domConverter.viewRangeToDom.lastCall.args[ 0 ];
 
-			expect( viewStringify( targetViewRange.root, targetViewRange ) ).to.equal( '<div><p>bar</p><p>{bi}z</p></div>' );
+			expect( viewStringify( targetViewRange.root, targetViewRange, { ignoreRoot: true } ) ).to.equal( '<p>bar</p><p>{bi}z</p>' );
 			expect( targetRect ).to.deep.equal( forwardSelectionRect );
 		} );
 
@@ -289,7 +289,7 @@ describe( 'BalloonToolbar', () => {
 
 			const targetViewRange = editingView.domConverter.viewRangeToDom.lastCall.args[ 0 ];
 
-			expect( viewStringify( targetViewRange.root, targetViewRange ) ).to.equal( '<div><p>b{ar}</p><p>biz</p></div>' );
+			expect( viewStringify( targetViewRange.root, targetViewRange, { ignoreRoot: true } ) ).to.equal( '<p>b{ar}</p><p>biz</p>' );
 			expect( targetRect ).to.deep.equal( backwardSelectionRect );
 		} );