Now we are going to concentrate on the Slider Thumb component.

$ ember g component aria-slider-thumb 1 $ ember g component aria - slider - thumb

We will start with a basic component skeleton in Ember.

import Component from '@ember/component'; import layout from '../templates/components/aria-slider-thumb'; export default Component.extend({ layout, }); 1 2 3 4 5 6 import Component from '@ember/component' ; import layout from '../templates/components/aria-slider-thumb' ; export default Component . extend ( { layout , } ) ;

Next we need to add the necessary classnames and attributes, by means of the classNameBindings and attributeBindings properties for a component in Ember.

import Component from '@ember/component'; import layout from '../templates/components/aria-slider-thumb'; export default Component.extend({ layout, classNames: ['thumb'], classNameBindings: ['focusClass:focus'], attributeBindings: [ 'currentValue:aria-valuenow', 'label:aria-label', 'maxValue:aria-valuemax', 'minValue:aria-valuemin', 'role', 'src', 'tabindex', 'valueText:aria-valuetext', 'labelledBy:aria-labelledby' ], role: 'slider', tabindex: 0, focusClass: false, }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import Component from '@ember/component' ; import layout from '../templates/components/aria-slider-thumb' ; export default Component . extend ( { layout , classNames : [ 'thumb' ] , classNameBindings : [ 'focusClass:focus' ] , attributeBindings : [ 'currentValue:aria-valuenow' , 'label:aria-label' , 'maxValue:aria-valuemax' , 'minValue:aria-valuemin' , 'role' , 'src' , 'tabindex' , 'valueText:aria-valuetext' , 'labelledBy:aria-labelledby' ] , role : 'slider' , tabindex : 0 , focusClass : false , } ) ;

Next we are going to create a computed property called ‘valueText’ which will give you the current value of the slider along with units.

import Component from '@ember/component'; import layout from '../templates/components/aria-slider-thumb'; import { computed } from '@ember/object'; export default Component.extend({ ... valueText: computed('currentValue', function() { let units = this.get('units') || ''; let _valueText = `${this.get('currentValue')}${units}`; if(this.get('unitPrefix')){ _valueText = `${units}${this.get('currentValue')}`; } return _valueText; }), }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Component from '@ember/component' ; import layout from '../templates/components/aria-slider-thumb' ; import { computed } from '@ember/object' ; export default Component . extend ( { . . . valueText : computed ( 'currentValue' , function ( ) { let units = this . get ( 'units' ) || '' ; let _valueText = ` $ { this . get ( 'currentValue' ) } $ { units } ` ; if ( this . get ( 'unitPrefix' ) ) { _valueText = ` $ { units } $ { this . get ( 'currentValue' ) } ` ; } return _valueText ; } ) , } ) ;

Now we are going to concentrate on the keyDown event for the component where we will move the slider accordingly based on the keystroke by identifying the KEYCODES

First we need to define the keycode mappings using a constant.

const KEYCODES = { 'left': 37, 'up': 38, 'right': 39, 'down': 40, 'pageUp': 33, 'pageDown': 34, 'end': 35, 'home': 36 }; 1 2 3 4 5 6 7 8 9 10 const KEYCODES = { 'left' : 37 , 'up' : 38 , 'right' : 39 , 'down' : 40 , 'pageUp' : 33 , 'pageDown' : 34 , 'end' : 35 , 'home' : 36 } ;

Now the implementation of the keyDown event should like the following.

keyDown(event) { var flag = false; let currentVal = this.get('currentValue'); switch (event.keyCode) { case KEYCODES.left: case KEYCODES.down: this.moveSliderTo(currentVal - 1); flag = true; break; case KEYCODES.right: case KEYCODES.up: this.moveSliderTo(currentVal + 1); flag = true; break; case KEYCODES.pageDown: this.moveSliderTo(currentVal - 10); flag = true; break; case KEYCODES.pageUp: this.moveSliderTo(currentVal + 10); flag = true; break; case KEYCODES.home: this.moveSliderTo(this.get('minValue')); flag = true; break; case KEYCODES.end: this.moveSliderTo(this.get('maxValue')); flag = true; break; default: break; } if (flag) { event.preventDefault(); event.stopPropagation(); } }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 keyDown ( event ) { var flag = false ; let currentVal = this . get ( 'currentValue' ) ; switch ( event . keyCode ) { case KEYCODES . left : case KEYCODES . down : this . moveSliderTo ( currentVal - 1 ) ; flag = true ; break ; case KEYCODES . right : case KEYCODES . up : this . moveSliderTo ( currentVal + 1 ) ; flag = true ; break ; case KEYCODES . pageDown : this . moveSliderTo ( currentVal - 10 ) ; flag = true ; break ; case KEYCODES . pageUp : this . moveSliderTo ( currentVal + 10 ) ; flag = true ; break ; case KEYCODES . home : this . moveSliderTo ( this . get ( 'minValue' ) ) ; flag = true ; break ; case KEYCODES . end : this . moveSliderTo ( this . get ( 'maxValue' ) ) ; flag = true ; break ; default : break ; } if ( flag ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; } } ,

Let us add an handler for mouseDown event for the slider-thumb component.

mouseDown(e) { let parentNode = e.target.parentNode; let minValue = this.get('minValue'); let maxValue = this.get('maxValue'); let handleMouseMove = (event) => { let diffX = event.pageX - parentNode.offsetLeft; let valueNow = minValue + parseInt(((maxValue - minValue) * diffX) / RAIL_WIDTH); this.moveSliderTo(valueNow); event.preventDefault(); event.stopPropagation(); }; var handleMouseUp = function() { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; // bind a mousemove event handler to move pointer document.addEventListener('mousemove', handleMouseMove); // bind a mouseup event handler to stop tracking mouse movements document.addEventListener('mouseup', handleMouseUp); e.preventDefault(); e.stopPropagation(); // Set focus to the clicked handle e.target.focus(); }, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 mouseDown ( e ) { let parentNode = e . target . parentNode ; let minValue = this . get ( 'minValue' ) ; let maxValue = this . get ( 'maxValue' ) ; let handleMouseMove = ( event ) = > { let diffX = event . pageX - parentNode . offsetLeft ; let valueNow = minValue + parseInt ( ( ( maxValue - minValue ) * diffX ) / RAIL_WIDTH ) ; this . moveSliderTo ( valueNow ) ; event . preventDefault ( ) ; event . stopPropagation ( ) ; } ; var handleMouseUp = function ( ) { document . removeEventListener ( 'mousemove' , handleMouseMove ) ; document . removeEventListener ( 'mouseup' , handleMouseUp ) ; } ; // bind a mousemove event handler to move pointer document . addEventListener ( 'mousemove' , handleMouseMove ) ; // bind a mouseup event handler to stop tracking mouse movements document . addEventListener ( 'mouseup' , handleMouseUp ) ; e . preventDefault ( ) ; e . stopPropagation ( ) ; // Set focus to the clicked handle e . target . focus ( ) ; } ,

Let us add the focusIn and focusOut action handlers also to the component.

focusIn() { this.set('focusClass', true); }, focusOut() { this.set('focusClass', false); }, 1 2 3 4 5 6 7 focusIn ( ) { this . set ( 'focusClass' , true ) ; } , focusOut ( ) { this . set ( 'focusClass' , false ) ; } ,

Let us initialize the component. One thing we have to note here is that once the component is rendered on the page, we should move the thumb to the exact location based on the currentValue property set to the component. This is what we will be doing in the init function of the component.

init() { this._super(...arguments); run.schedule('afterRender', () => { this.moveSliderTo(this.get('currentValue')); }); }, 1 2 3 4 5 6 init ( ) { this . _super ( . . . arguments ) ; run . schedule ( 'afterRender' , ( ) = > { this . moveSliderTo ( this . get ( 'currentValue' ) ) ; } ) ; } ,

Now let’s get to the meat of the component, the moveSliderTo method where we will be actually placing the slider-thumb in the exact location using css positioning.

moveSliderTo(value) { let minValue = this.get('minValue'); let maxValue = this.get('maxValue'); let _value = value; if (value < minValue) { _value = minValue; } if ( value > maxValue) { _value = maxValue; } this.set('currentValue', _value); if(value < minValue || value > maxValue) { return; } let pos = Math.round((_value * RAIL_WIDTH) / (maxValue - minValue)) - (THUMB_WIDTH / 2); let left = ''; left = pos + 'px'; this.element.style.left = left; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 moveSliderTo ( value ) { let minValue = this . get ( 'minValue' ) ; let maxValue = this . get ( 'maxValue' ) ; let _value = value ; if ( value < minValue ) { _value = minValue ; } if ( value > maxValue ) { _value = maxValue ; } this . set ( 'currentValue' , _value ) ; if ( value < minValue || value > maxValue ) { return ; } let pos = Math . round ( ( _value * RAIL_WIDTH ) / ( maxValue - minValue ) ) - ( THUMB_WIDTH / 2 ) ; let left = '' ; left = pos + 'px' ; this . element . style . left = left ; }

As you can see we have used two constants called RAIL_WIDTH and THUMB_WIDTH, let’s define them at the top of the component file as global constants.