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

Commit

Permalink
Merge 2d08713 into 6d92aca
Browse files Browse the repository at this point in the history
  • Loading branch information
oleq committed Jan 29, 2019
2 parents 6d92aca + 2d08713 commit 37efd44
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 86 deletions.
53 changes: 40 additions & 13 deletions src/editableui/editableuiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,31 @@ 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: {
class: [
'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.
Expand All @@ -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;
}

/**
Expand All @@ -89,6 +96,9 @@ export default class EditableUIView extends View {
} else {
this._editableElement = this.element;
}

this.on( 'change:isFocused', () => this._updateIsFocusedClasses() );
this._updateIsFocusedClasses();
}

/**
Expand All @@ -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 );
} );
}
}
37 changes: 19 additions & 18 deletions src/editableui/inline/inlineeditableuiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
} );
}
}
67 changes: 27 additions & 40 deletions tests/editableui/editableuiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,55 @@

/* 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();

beforeEach( () => {
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 );
Expand All @@ -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;
} );
} );
} );
Expand All @@ -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;
} );
} );
} );
Expand Down
26 changes: 13 additions & 13 deletions tests/editableui/inline/inlineeditableuiview.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
} );

Expand All @@ -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 );
} );
Expand All @@ -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', () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/toolbar/balloon/balloontoolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
} );

Expand Down Expand Up @@ -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 );
} );

Expand Down

0 comments on commit 37efd44

Please sign in to comment.