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

t/ckeditor5-media-embed/35: Implemented LabeledInputView#infoText to display useful hints next to the input #444

Merged
merged 6 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 56 additions & 20 deletions src/labeledinput/labeledinputview.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class LabeledInputView extends View {
super( locale );

const inputUid = `ck-input-${ uid() }`;
const errorUid = `ck-error-${ uid() }`;
const statusUid = `ck-status-${ uid() }`;

/**
* The text of the label.
Expand Down Expand Up @@ -72,6 +72,21 @@ export default class LabeledInputView extends View {
*/
this.set( 'errorText', null );

/**
* The additional information text displayed next to the {@link #inputView} which can
* be used to inform the user about the purpose of the input, provide help or hints.
*
* Set it to `null` to hide the message.
*
* **Note:** This text will be displayed in the same place as {@link #errorText} but the
* latter always takes precedence: if the {@link #errorText} is set, it replaces
* {@link #errorText} for as long as the value of the input is invalid.
*
* @observable
* @member {String|null} #infoText
*/
this.set( 'infoText', null );

/**
* The label view.
*
Expand All @@ -84,14 +99,33 @@ export default class LabeledInputView extends View {
*
* @member {module:ui/inputtext/inputtextview~InputTextView} #inputView
*/
this.inputView = this._createInputView( InputView, inputUid, errorUid );
this.inputView = this._createInputView( InputView, inputUid, statusUid );

/**
* The status view for the {@link #inputView}. It displays {@link #errorText} and
* {@link #infoText}.
*
* @member {module:ui/view~View} #statusView
*/
this.statusView = this._createStatusView( statusUid );

/**
* The error view for the {@link #inputView}.
* The combined status text made of {@link #errorText} and {@link #infoText}.
* Note that when present, {@link #errorText} always takes precedence in the
* status.
*
* @member {module:ui/view~View} #errorView
* @see #errorText
* @see #infoText
* @see #statusView
* @private
* @observable
* @member {String|null} #_statusText
*/
this.errorView = this._createErrorView( errorUid );
this.bind( '_statusText' ).to(
this, 'errorText',
this, 'infoText',
( errorText, infoText ) => errorText || infoText
);

const bind = this.bindTemplate;

Expand All @@ -107,7 +141,7 @@ export default class LabeledInputView extends View {
children: [
this.labelView,
this.inputView,
this.errorView
this.statusView
]
} );
}
Expand All @@ -134,14 +168,14 @@ export default class LabeledInputView extends View {
* @private
* @param {Function} InputView Input view constructor.
* @param {String} inputUid Unique id to set as inputView#id attribute.
* @param {String} errorUid Unique id of the error for the input's `aria-describedby` attribute.
* @param {String} statusUid Unique id of the status for the input's `aria-describedby` attribute.
* @returns {module:ui/inputtext/inputtextview~InputTextView}
*/
_createInputView( InputView, inputUid, errorUid ) {
const inputView = new InputView( this.locale, errorUid );
_createInputView( InputView, inputUid, statusUid ) {
const inputView = new InputView( this.locale, statusUid );

inputView.id = inputUid;
inputView.ariaDesribedById = errorUid;
inputView.ariaDesribedById = statusUid;
inputView.bind( 'value' ).to( this );
inputView.bind( 'isReadOnly' ).to( this );
inputView.bind( 'hasError' ).to( this, 'errorText', value => !!value );
Expand All @@ -156,34 +190,36 @@ export default class LabeledInputView extends View {
}

/**
* Creates the error view instance.
* Creates the status view instance. It displays {@link #errorText} and {@link #infoText}
* next to the {@link #inputView}. See {@link #_statusText}.
*
* @private
* @param {String} errorUid Unique id of the error, shared with the input's `aria-describedby` attribute.
* @param {String} statusUid Unique id of the status, shared with the input's `aria-describedby` attribute.
* @returns {module:ui/view~View}
*/
_createErrorView( errorUid ) {
const errorView = new View( this.locale );
_createStatusView( statusUid ) {
const statusView = new View( this.locale );
const bind = this.bindTemplate;

errorView.setTemplate( {
statusView.setTemplate( {
tag: 'div',
attributes: {
class: [
'ck',
'ck-labeled-input__error',
bind.if( 'errorText', 'ck-hidden', value => !value )
'ck-labeled-input__status',
bind.if( 'errorText', 'ck-labeled-input__status_error' ),
bind.if( '_statusText', 'ck-hidden', value => !value )
],
id: errorUid
id: statusUid
},
children: [
{
text: bind.to( 'errorText' )
text: bind.to( '_statusText' )
}
]
} );

return errorView;
return statusView;
}

/**
Expand Down
50 changes: 35 additions & 15 deletions tests/labeledinput/labeledinputview.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ describe( 'LabeledInputView', () => {
expect( view.errorText ).to.be.null;
} );

it( 'should set view#infoText', () => {
expect( view.infoText ).to.be.null;
} );

it( 'should create view#inputView', () => {
expect( view.inputView ).to.instanceOf( InputView );
} );
Expand All @@ -36,20 +40,20 @@ describe( 'LabeledInputView', () => {
expect( view.labelView ).to.instanceOf( LabelView );
} );

it( 'should create view#errorView', () => {
expect( view.errorView ).to.instanceOf( View );
it( 'should create view#statusView', () => {
expect( view.statusView ).to.instanceOf( View );

expect( view.errorView.element.tagName ).to.equal( 'DIV' );
expect( view.errorView.element.classList.contains( 'ck' ) ).to.be.true;
expect( view.errorView.element.classList.contains( 'ck-labeled-input__error' ) ).to.be.true;
expect( view.statusView.element.tagName ).to.equal( 'DIV' );
expect( view.statusView.element.classList.contains( 'ck' ) ).to.be.true;
expect( view.statusView.element.classList.contains( 'ck-labeled-input__status' ) ).to.be.true;
} );

it( 'should pair #inputView and #labelView by unique id', () => {
expect( view.labelView.for ).to.equal( view.inputView.id );
} );

it( 'should pair #inputView and #errorView by unique id', () => {
expect( view.inputView.ariaDesribedById ).to.equal( view.errorView.element.id );
it( 'should pair #inputView and #statusView by unique id', () => {
expect( view.inputView.ariaDesribedById ).to.equal( view.statusView.element.id );
} );
} );

Expand All @@ -67,8 +71,8 @@ describe( 'LabeledInputView', () => {
expect( view.template.children[ 1 ] ).to.equal( view.inputView );
} );

it( 'should have the error container', () => {
expect( view.template.children[ 2 ] ).to.equal( view.errorView );
it( 'should have the status container', () => {
expect( view.template.children[ 2 ] ).to.equal( view.statusView );
} );

describe( 'DOM bindings', () => {
Expand All @@ -82,17 +86,33 @@ describe( 'LabeledInputView', () => {
} );
} );

describe( 'error container', () => {
describe( 'status container', () => {
it( 'should react on view#errorText', () => {
const errorContainer = view.element.lastChild;
const statusElement = view.statusView.element;

view.errorText = '';
expect( errorContainer.classList.contains( 'ck-hidden' ) ).to.be.true;
expect( errorContainer.innerHTML ).to.equal( '' );
expect( statusElement.classList.contains( 'ck-hidden' ) ).to.be.true;
expect( statusElement.classList.contains( 'ck-labeled-input__status_error' ) ).to.be.false;
expect( statusElement.innerHTML ).to.equal( '' );

view.errorText = 'foo';
expect( errorContainer.classList.contains( 'ck-hidden' ) ).to.be.false;
expect( errorContainer.innerHTML ).to.equal( 'foo' );
expect( statusElement.classList.contains( 'ck-hidden' ) ).to.be.false;
expect( statusElement.classList.contains( 'ck-labeled-input__status_error' ) ).to.be.true;
expect( statusElement.innerHTML ).to.equal( 'foo' );
} );

it( 'should react on view#infoText', () => {
const statusElement = view.statusView.element;

view.infoText = '';
expect( statusElement.classList.contains( 'ck-hidden' ) ).to.be.true;
expect( statusElement.classList.contains( 'ck-labeled-input__status_error' ) ).to.be.false;
expect( statusElement.innerHTML ).to.equal( '' );

view.infoText = 'foo';
expect( statusElement.classList.contains( 'ck-hidden' ) ).to.be.false;
expect( statusElement.classList.contains( 'ck-labeled-input__status_error' ) ).to.be.false;
expect( statusElement.innerHTML ).to.equal( 'foo' );
} );
} );
} );
Expand Down