diff --git a/src/labeledinput/labeledinputview.js b/src/labeledinput/labeledinputview.js index 23809c6c..1f5c8a0c 100644 --- a/src/labeledinput/labeledinputview.js +++ b/src/labeledinput/labeledinputview.js @@ -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. @@ -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. * @@ -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; @@ -107,7 +141,7 @@ export default class LabeledInputView extends View { children: [ this.labelView, this.inputView, - this.errorView + this.statusView ] } ); } @@ -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 ); @@ -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; } /** diff --git a/tests/labeledinput/labeledinputview.js b/tests/labeledinput/labeledinputview.js index f47614f3..4c026191 100644 --- a/tests/labeledinput/labeledinputview.js +++ b/tests/labeledinput/labeledinputview.js @@ -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 ); } ); @@ -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 ); } ); } ); @@ -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', () => { @@ -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' ); } ); } ); } );