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

Introduced select UI component #530

Closed
wants to merge 9 commits into from
129 changes: 129 additions & 0 deletions src/select/selectview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module ui/select/selectview
*/

import View from '../view';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';

/**
* The select view class.
*
* @extends module:ui/view~View
*/
export default class SelectView extends View {
/**
* Creates an instance of the {@link module:ui/select/selectview~SelectView} class.
*
* Also see {@link #render}.
*
* @param {module:utils/locale~Locale} locale The localization services instance.
* @param {Array.<module:ui/select/selectview~SelectViewItem>} items Items to choose from.
*/
constructor( locale, items ) {
super( locale );

/**
* Fired when the user selects an item. Corresponds to the native
* DOM `input` event.
*
* @event input
*/

/**
* The value of the select.
*
* @observable
* @member {String} #value
*/
this.set( 'value' );

/**
* The `id` attribute of the select (i.e. to pair with a `<label>` element).
*
* @observable
* @member {String} #id
*/
this.set( 'id' );

/**
* Items to choose from.
*
* @type {Array.<module:ui/select/selectview~SelectViewItem>}
* @private
*/
this._items = items;

const bind = this.bindTemplate;

this.setTemplate( {
tag: 'select',
attributes: {
class: [
'ck',
'ck-input',
'ck-input-select',
],
id: bind.to( 'id' )
},
on: {
input: bind.to( 'input' )
}
} );
}

/**
* @inheritDoc
*/
render() {
super.render();

for ( const item of this._items ) {
this.element.options.add( new global.window.Option( item.label, item.value ) );
}

this.on( 'change:value', ( evt, name, value ) => {
if ( !this._isAvailableValue( value ) ) {
return;
}

this.element.value = value;
} );
}

/**
* Focuses the component.
*/
focus() {
this.element.focus();
}

/**
* Checks whether a specified `value` is available in the select's options.
*
* @private
* @param {String} value A value to check.
* @returns {Boolean}
*/
_isAvailableValue( value ) {
for ( const item of this._items ) {
if ( item.value === value ) {
return true;
}
}

return false;
}
}

/**
* @typedef {Object} module:ui/select/selectview~SelectViewItem
*
* @property {String} value A value of the item.
*
* @property {String} label A human friendly label that represents the value.
*/
114 changes: 114 additions & 0 deletions tests/select/selectview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global Event */

import SelectView from '../../src/select/selectview';

describe( 'SelectView', () => {
describe( 'constructor()', () => {
it( 'should creates element from template', () => {
const view = new SelectView( null, [
{ label: 'Foo', value: 'foo' },
{ label: 'Bar', value: 'bar' },
] );

view.render();

expect( view.element.tagName ).to.equal( 'SELECT' );
expect( view.element.classList.contains( 'ck' ) ).to.be.true;
expect( view.element.classList.contains( 'ck-input' ) ).to.be.true;
expect( view.element.classList.contains( 'ck-input-select' ) ).to.be.true;

expect( view.element.childElementCount ).to.equal( 2 );

expect( view.element.children[ 0 ].tagName ).to.equal( 'OPTION' );
expect( view.element.children[ 0 ].value ).to.equal( 'foo' );
expect( view.element.children[ 0 ].innerText ).to.equal( 'Foo' );

expect( view.element.children[ 1 ].tagName ).to.equal( 'OPTION' );
expect( view.element.children[ 1 ].value ).to.equal( 'bar' );
expect( view.element.children[ 1 ].innerText ).to.equal( 'Bar' );

view.destroy();
} );
} );

describe( 'DOM bindings', () => {
let view;

beforeEach( () => {
view = new SelectView( null, [
{ label: 'Foo', value: 'foo' },
{ label: 'Bar', value: 'bar' },
{ label: 'Baz', value: 'baz' },
] );

view.render();

view.value = 'foo';
view.id = 'bar';
} );

afterEach( () => {
view.destroy();
} );

describe( 'value', () => {
it( 'should react on view#value', () => {
expect( view.element.value ).to.equal( 'foo' );

view.value = 'baz';

expect( view.element.value ).to.equal( 'baz' );

// To be sure that value can be changed multiple times using inline value attribute.
view.value = 'bar';

expect( view.element.value ).to.equal( 'bar' );
} );

it( 'ignores requests for setting a wrong value', () => {
view.value = 'boom';

expect( view.element.value ).to.be.equal( 'foo' );
} );
} );

describe( 'id', () => {
it( 'should react on view#id', () => {
expect( view.element.id ).to.equal( 'bar' );

view.id = 'baz';

expect( view.element.id ).to.equal( 'baz' );
} );
} );

describe( 'input event', () => {
it( 'triggers view#input', () => {
const spy = sinon.spy();

view.on( 'input', spy );

view.element.dispatchEvent( new Event( 'input' ) );
sinon.assert.calledOnce( spy );
} );
} );
} );

describe( 'focus()', () => {
it( 'focuses the select in DOM', () => {
const view = new SelectView( null, [] );
view.render();

const spy = sinon.spy( view.element, 'focus' );

view.focus();

sinon.assert.calledOnce( spy );
} );
} );
} );