widget/rating/rating.js

import Widget from '../../js/widget';
import { isNumber } from '../../js/utils';
import events from '../../js/event';

/**
 * @augments RangeWidget
 */
class RatingWidget extends Widget {
    /**
     * @type {string}
     */
    static get selector() {
        return '.or-appearance-rating input[type="number"]';
    }

    _init() {
        const fragment = document.createRange().createContextualFragment( this._getHtmlStr() );
        this.element.after( fragment );
        this.element.classList.add( 'hide' );
        this.widget = this.question.querySelector( '.widget' );

        let ratingStars = '',
            name = Math.random().toString( 36 ).substring( 2, 15 );
        for ( let i = this.props.min; i <= this.props.max; i += this.props.step ) {
            ratingStars += `<input type=radio name="rating-stars__${name}" class="rating-widget__rating__star ignore" value="${i}"/>`;
        }

        this.widget.querySelector( '.rating-widget__rating' )
            .append( document.createRange().createContextualFragment( ratingStars ) );

        this.rating = this.widget.querySelector( '.rating-widget__rating' );
        this.stars = this.rating.querySelectorAll( '.rating-widget__rating__star' );
        this.stars.forEach( el => {
            el.addEventListener( 'change', () => {
                const selected = this.rating.querySelector( 'input:checked' );
                // contains class 'empty' means first load
                if ( ( this.value != selected.value || this.rating.classList.contains( 'empty' ) ) ) {
                    this.value = selected.value;
                }
            } );
        } );

        // loads the default value if exists
        this.update();

        if ( this.props.readonly ) {
            this.disable();
        }
    }

    /**
     * This is separated so it can be extended (in the analog-scale widget)
     *
     * @return {string} HTML string
     */
    _getHtmlStr() {
        return `<div class="widget rating-widget">
                	<div class="rating-widget__rating empty"></div>
            	</div>`;
    }

    /**
     * Disables widget
     */
    disable() {
        this.stars.forEach( el => el.disabled = true );
    }

    /**
     * Enables widget
     */
    enable() {
        this.stars.forEach( el => el.disabled = false );
    }

    /**
     * Updates widget
     */
    update() {
        const value = this.element.value;
        if ( isNumber( value ) ) {
            this.stars.forEach( ( star ) => {
                if ( star.value === value ) {
                    star.checked = true;
                    star.dispatchEvent( events.Change() );
                }
            } );
        } else {
            this._reset();
        }
    }

    /**
     * Resets widget
     */
    _reset() {
        const selected = this.rating.querySelector( 'input:checked' );
        if ( selected ) {
            this.value = '';
            selected.checked = false;
        }
    }

    /**
     * @type {object}
     */
    get props() {
        const props = this._props;
        const min = isNumber( this.element.getAttribute( 'min' ) ) ? this.element.getAttribute( 'min' ) : 0;
        const max = isNumber( this.element.getAttribute( 'max' ) ) ? this.element.getAttribute( 'max' ) : 10;
        const step = isNumber( this.element.getAttribute( 'step' ) ) ? this.element.getAttribute( 'step' ) : 1;

        props.min = Number( min );
        props.max = Number( max );
        props.step = Number( step );

        return props;
    }

    /**
     * @type {string}
     */
    get value() {
        return this.originalInputValue;
    }

    set value( value ) {
        this.originalInputValue = value;
        this.rating.classList.toggle( 'empty', value === '' );
    }

}

export default RatingWidget;