creative-connections/Bodylight.js-Components

View on GitHub
src_aurelia-bodylight-plugin/src/elements/range.js

Summary

Maintainability
D
2 days
Test Coverage
import {bindable} from 'aurelia-framework';
import _ from "lodash";

export class Range {
    @bindable min;
    @bindable max;
    @bindable default;
    @bindable step;
    @bindable value;
    @bindable title;
    @bindable showicons = true;
    @bindable globalanim = false;
    @bindable animobj;
    //@bindable firedata = false; //'position'
    @bindable fireevent = 'input'; //name of the event to be dispatched - should be same as fmi eventlisten
    refinput;
    refnumber;
    @bindable listenkey; //true or false
    @bindable activationkey; //if defined - then
    actived = false;
    @bindable ids; //optional comma separated id to send value change ,e.g. id1,id2,id3
    @bindable convertors;//comma separated xpression with x as value e.g. (100-2-x)/3,(100-2-x)/3,(100-2-x)/3
    //optional twoway settings - if set - fmudata may set the value of range
    @bindable fromid; //id of fmu component
    @bindable refindex; //index of variable to be listened
    @bindable id;
    @bindable throttle = 1000; //throttle update value from fromid, default every 1s
    @bindable smooth = false;
    @bindable smoothstep = 1;
    @bindable initdefault = false;
    counterToInit = 5;

    constructor() {
        this.handleValueChange = e => {
            //sets data to dataset
            //apply value convert among all data
            if (this.fromid) {
                if (this.refindex) {
                    let rawdata = e.detail.data[this.refindex];
                    this.value = this.operation[0](rawdata);
                    //  else this.value = rawdata;
                    //console.log('Range received rawdata '+rawdata+' converted value '+this.value);
                    //console.log('this operation',this.operation)
                    this.updatevalue(); //call function - it may be throttled 
                } else {
                    if (this.smooth) {
                        //do smooth step 
                    }
                }
            }
        }
    }

    bind() {
        if (typeof (this.smooth) === 'string') this.smooth = this.smooth === 'true';
        if (typeof (this.step) === 'string') this.step = parseFloat(this.step);
        if (typeof (this.showicons) === 'string') this.showicons = this.showicons === 'true';
        if (typeof (this.globalanim) === 'string') this.globalanim = this.globalanim === 'true';
        if (this.listenkey && this.listenkey === 'true') {
            if (this.activationkey && this.activationkey === 'A') this.actived = true; //first activationkey 'A' is by default actived
            document.onkeypress = function (e) {
                //e = e || window.event;// use e.keyCode
                //if (window.listenrange)
                if (this.activationkey && e.charCode >= 65 && e.charCode <= 90) { //'A' ..'Z' is pressed
                    this.actived = (e.key === this.activationkey);
                }
                if (!(this.activationkey) || (this.actived)) { //activationkey not defined or actived - 'A' or
                    let number = e.charCode - 97; //0..9
                    let mappedvalue = parseInt(this.min);
                    if (number > 0) { //a..j interpolates to values between min and max
                        if (number < 9) mappedvalue = parseInt(this.min) + (parseInt(this.max) - parseInt(this.min)) * number / 10;
                        else mappedvalue = parseInt(this.max);
                    }
                    this.setValue(mappedvalue);
                }
            }
        }
        if (this.ids) this.ids2send = this.ids.split(',');
        //configure convertors - used to convert units received from fmi
        this.operation = [];
        if (this.convertors) {
            let convertvalues = this.convertors.split(';');
            let identity = x => x;
            for (let i = 0; i < convertvalues.length; i++) {
                if (convertvalues[i].includes(',')) {
                    //convert values are in form numerator,denominator contains comma ','
                    let convertitems = convertvalues[i].split(',');
                    if (convertitems[0] === '1' && convertitems[1] === '1') this.operation.push(identity);
                    else {
                        let numerator = parseFloat(convertitems[0]);
                        let denominator = parseFloat(convertitems[1]);
                        let addend = (convertitems.length > 2) ? parseFloat(convertitems[2]) : 0;
                        this.operation.push(x => ((x * numerator / denominator) + addend));
                    }
                } else {
                    //convert values are in form of expression, do not contain comma
                    if (convertvalues === '1/x') this.operation.push(x => 1 / x);

                    else {
                        // for eval() security filter only allowed characters:
                        // algebraic, digits, e, dot, modulo, parenthesis and 'x' and 'e' is allowed
                        let expression = convertvalues[i].replace(/[^-\d/*+.()%xe]/g, '');
                        console.log('chartjs bind(), evaluating expression:' + convertvalues[i] + ' securely filtered to :' + expression);
                        // eslint-disable-next-line no-eval
                        this.operation.push(x => eval(expression));
                    }
                }
            }
        }
        //register throttled update function
        if ((typeof this.throttle) === 'string') this.throttle = parseInt(this.throttle, 10);
        if (this.throttle > 0) {//throttle
            this.updatevalue = _.throttle(this.setCurrentValue.bind(this), this.throttle);
        } else {//directly call update
            this.updatevalue = this.setCurrentValue.bind(this);
        }
        if (typeof this.initdefault === 'string') this.initdefault = this.initdefault === 'true';
    }

    attached() {
        let maxlength = 4 + this.max.length + ((this.step && this.step.toString().includes('.')) ? this.step.toString().length : 1);
        this.refnumber.style = 'width:' + maxlength + 'ch';
        if (this.fromid) {
            if (this.operation.length == 0) {
                console.warn('fromid defined, identity convertor added.');
                let identity = x => x;
                this.operation.push(identity)
            }
            //add event listener
            const fromidel = document.getElementById(this.fromid)
            if (fromidel) fromidel.addEventListener('fmidata', this.handleValueChange)
            else console.warn('range fromid element not found with id:',this.fromid);

        }
        //send event about default value        
        if (this.initdefault) this.checkSetDefault(); 
    }

    checkSetDefault(){
        if (window.thisfmi) this.setDefault();
        else {
         if (this.counterToInit-- > 0 ) _.debounce(this.checkSetDefault.bind(this), 1000); //try after 1 s but only 5 times
        }
    }

    setDefault() {
        this.setValue(this.default);
    }

    setValue(value) {
        if (this.refnumber) this.refnumber.value = value;
        if (this.refinput) {
            this.refinput.value = value;
            this.refinput.dispatchEvent(new Event(this.fireevent, {
                bubbles: true,
                cancelable: true                
            }));
        }
    }

    setCurrentValue() {
        this.setValue(this.value);
    }

    valueChanged(newValue, oldValue) {
        //if (oldValue !== newValue)
        if (this.ids) {
            //semaphore only one change in time is allowed
            if (!window.rangebinding) {
                window.rangebinding = true;
                //sending value converted to other ids
                //if (this.ids2send.length !== this.values2send.length) {console.log('warning ids and values contain different number of items.', this.ids2send, this.values2send); return;}
                for (let i = 0; i < this.ids2send.length; i++) {
                    let inputel = document.getElementById(this.ids2send[i]);
                    if (inputel) {
                    inputel.value = this.operation[i](newValue);
                    console.log('range valuechange id,converted value:', this.ids2send[i], inputel.value);
                    let event = new Event(this.fireevent);
                    inputel.dispatchEvent(event);
                    } else { console.warn('inputel not found for id', this,ids2send[i])}
                }
                window.rangebinding = false;
            }
        } else {
            //single value is change e.g. externally
            this.setValue(newValue);
            if (this.globalanim) {
                if (this.animobj) {
                    //animate local object
                    if (!window.ani.animationstarted ) window.ani.enableAnimation();

                    if (window.ani) window.ani.setAnimationValue(this.animobj, newValue);
                }
                else {
                    //animate globally
                    if (window.ani && window.ani.exportRoot) {
                        for (animchild of window.ani.exportRoot.children) {
                        if (typeof animchild.gotoAndStop === 'function')
                           animchild.gotoAndStop (newValue);                    
                        }
                    }
                }
            }
        }
    }
}