widget/analog-scale/analog-scalepicker.js

import RangeWidget from '../../widget/range/range-widget';
import { isNumber } from '../../js/utils';
import support from '../../js/support';
import events from '../../js/event';

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

    _init() {
        super._init();
        if ( this.props.vertical ) {
            this.question.classList.add( 'or-appearance-vertical' );
        }
        this.question.classList.add( 'or-analog-scale-initialized' );
        this._renderLabels();
        this._setResizeListener();
    }

    /**
     * @return {string} HTML string
     */
    _getHtmlStr() {
        const html =
            `<div class="widget analog-scale-widget">
                ${super._getHtmlStr()}
            </div>`;

        return html;
    }

    _updateMercury() {}

    /**
     * (re-)Renders the widget labels based on the current content of .question-label.active
     */
    _renderLabels() {
        const fragment = document.createRange().createContextualFragment( '<div class="label-content widget"></div>' );
        const wrapper = fragment.querySelector( '.label-content' );

        this.question.querySelectorAll( '.question-label, .or-hint, .or-required-msg, [class*="or-constraint"]' )
            .forEach( el => wrapper.append( el ) );

        this.question.prepend( fragment );

        this.labelContent = this.question.querySelector( '.label-content' );
        this._updateLabels();
    }

    /**
     * Updates labels
     */
    _updateLabels() {
        if ( !this.question.classList.contains( 'or-analog-scale-initialized' ) ) {
            return;
        }
        const labelEl = this.labelContent.querySelector( '.question-label.active:not(.widget)' );
        const labels = labelEl.innerHTML.split( /\|/ ).map( label => label.trim() );

        const existingLabel = this.labelContent.querySelector( '.question-label.widget' );
        if ( existingLabel ) {
            existingLabel.remove();
        }
        const labelFragment = document.createRange().createContextualFragment( `<span class="question-label widget active">${labels[ 0 ]}</span>` );
        labelEl.after( labelFragment );

        const existingMaxLabel = this.widget.querySelector( '.max-label' );
        if ( existingMaxLabel ) {
            existingMaxLabel.remove();
        }
        const maxLabel = document.createRange().createContextualFragment( `<div class="max-label">${labels[ 1 ]}</div>` );
        this.widget.prepend( maxLabel );

        const existingMinLabel = this.widget.querySelector( '.min-label' );
        if ( existingMinLabel ) {
            existingMinLabel.remove();
        }
        const minLabel = document.createRange().createContextualFragment( `<div class="min-label">${labels[ 2 ]}</div>` );
        this.widget.append( minLabel );

        const showValue = this.labelContent.querySelector( '.show-value' );
        if ( showValue ) {
            showValue.remove();
        }
        if ( labels[ 3 ] ) {
            const showValueBox = document.createRange().createContextualFragment(
                `<div class="widget show-value">
                    <div class="show-value__box">${labels[ 3 ]}<span class="show-value__value">${this.value}</span></div>
                <div>` );
            this.labelContent.append( showValueBox );
            this.current = this.labelContent.querySelector( '.show-value__value' );
        }
    }

    /**
     * Stretch the question to full page height.
     * Doing this with pure css flexbox using "flex-direction: column" interferes with the Grid theme
     * because that theme relies on flexbox with "flex-direction: row".
     */
    _setResizeListener() {
        if ( this.props.vertical ) {
            // Will only be triggered if question by itself constitutes a page.
            // It will not be triggered if question is contained inside a group with fieldlist appearance.
            this.question.addEventListener( events.PageFlip().type, this._stretchHeight.bind( this ) );
        }
    }

    /**
     * Stretches height
     */
    _stretchHeight() {
        this.question.style[ 'min-height' ] = 'auto';
    }

    /**
     * Updates with labels
     */
    update() {
        super.update();
        this._updateLabels();
    }

    /**
     * @type {object}
     */
    get props() {
        const props = this._props;
        props.touch = support.touch;
        props.vertical = !props.appearances.includes( 'horizontal' );
        props.ticks = !props.appearances.includes( 'no-ticks' );
        props.showScale = props.appearances.includes( 'show-scale' ) && props.vertical && props.ticks;
        const min = isNumber( this.element.getAttribute( 'min' ) ) ? this.element.getAttribute( 'min' ) : 0;
        const max = isNumber( this.element.getAttribute( 'max' ) ) ? this.element.getAttribute( 'max' ) : 100;
        const step = isNumber( this.element.getAttribute( 'step' ) ) ? this.element.getAttribute( 'step' ) : ( props.showScale ? 10 : 1 ); //( props.type === 'decimal' ? 0.1 : 1 );
        props.min = Number( min );
        props.max = Number( max );
        props.step = Number( step );
        props.maxTicks = 10;

        return props;
    }

    /**
     * @type {*}
     */
    get value() {
        return super.value;
    }

    set value( value ) {
        super.value = value;
    }
}

export default AnalogScaleWidget;