Source: view4js/utils/BindingUtils.js

/** 
 * @license
 * Copyright (c) 2019 Gaurang Lade
 * 
 * MIT License
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


/**
 * BindingUtil
 * 
 * 
 */
class BindingUtil {

    /**
     * @description 
     * BindingUtils provides One-way and Two-way binding with Just single statement.
     * BindingUtils is Singleton class.
     * 
     * @example Add Binding :
    BindingUtils.addBinding(srcele, "value", "change", destele, "value", false);
 
            @example Remove Binding :
        BindingUtils.removeBinding(srcele, "value");
        
        @example Chain Binding :
        DOM Element to JavaScript Object :
        BindingUtils.addBinding(srcele, "value", "change", this.myCustomModel, "setValue", false);

        JavaScript Object to DOM Element :
        BindingUtils.addBinding(this.myCustomModel, "getValue", "change", destele, "value", false);
        
        @example Two-way Binding :
        BindingUtils.addBinding(srcele, "value", "change", destele, "value", true);
 
     * @memberof BindingUtil
     */
    constructor() {
        if (!BindingUtil.instance) {
            BindingUtil.instance = this;
            this.BindObjDictionary = {};
            this.objCounter = 0;
            this.bindObjIdPrefix = "bindobj";
        }
        return BindingUtil.instance;

    }



    /**
     *
     * @private
     * @param {object} _srcObj - Source DOM Element or Object
     * @param {string} _srcProp - Source Property
     * @param {string} _evtname - Source Object EventName on which Binding Trigger 
     * @param {object} _targObj - Target DOM Element or Object
     * @param {string} _targProp - Target Property
     * @memberof BindingUtil
     */
    Binds(_srcObj, _srcProp, _evtname, _targObj, _targProp) {
        let srcObject = new Object();
        let srcPropStr = _srcProp + "prop";
        let srcObjKey = null;
        this.objCounter++;
        let tmpId = this.bindObjIdPrefix+this.objCounter;
        if (_srcObj.nodeName) {
            
            srcObjKey = tmpId;//_srcObj.id;
            //TODO:: Following line will be enabled for data-id attribute
            // srcObjKey = _srcObj.dataset.id;
            _srcObj.dataset.bindid = srcObjKey;
            this.addToBindDictionary(srcObjKey, _srcObj, _evtname);
            srcObject = this.BindObjDictionary[srcObjKey];
        } else {
            srcObjKey = tmpId;
            _srcObj.bindid = srcObjKey;
            this.addToBindDictionary(srcObjKey, _srcObj, _evtname);
            srcObject = this.BindObjDictionary[srcObjKey];
        }

        if (srcObject.srcPropArray == null) {
            srcObject.srcPropArray = [];
        }
        if (srcObject[srcPropStr] == null) {
            srcObject.srcPropArray.push(_srcProp);
            srcObject[srcPropStr] = new Object();
            if (typeof(_srcObj[_srcProp]) === 'function') {
                srcObject[srcPropStr].value = _srcObj[_srcProp]();
            } else {
                srcObject[srcPropStr].value = _srcObj[_srcProp];
            }

        }
        if (srcObject[srcPropStr].bindObjArray == null) {
            srcObject[srcPropStr].bindObjArray = [];
        }
        let targetObject = this.getTargetObject(_targObj, _targProp);
        srcObject[srcPropStr].bindObjArray.push(targetObject);
        this.BindObjDictionary[srcObjKey] = srcObject;
    }

    addToBindDictionary(key, Obj, _evtname) {
        if (this.BindObjDictionary[key] == null) {
            this.BindObjDictionary[key] = Obj;
        }
        Obj.addEventListener(_evtname, (e) => { this.synchronise(e); });
    }

    
    synchronise(event) {
        event.preventDefault();
        let srcObjfrmEvt = event.target;
        let eleid;
        console.info(typeof srcObjfrmEvt);
        if (srcObjfrmEvt.bindid) {
            eleid = srcObjfrmEvt.bindid;
        } else {
            eleid = srcObjfrmEvt.dataset.bindid;
        }
        let srcObject = this.BindObjDictionary[eleid];
        let srcPropArrayLen = srcObject.srcPropArray.length;
        for (let i = 0; i < srcPropArrayLen; i++) {
            let tmpProp = srcObject.srcPropArray[i];
            let tmpPropStr = tmpProp + "prop";
            // compare
            let tmpval;
            if (typeof(srcObject[tmpProp]) === 'function') {
                tmpval = srcObject[tmpProp]();
            } else {
                tmpval = srcObject[tmpProp];
            }
            if (srcObject[tmpPropStr].value != tmpval) {
                this.synchroniseTargetObj(srcObject, tmpPropStr, tmpProp);
                srcObject[tmpPropStr].value = srcObject[tmpProp];
            }
        }
    }

    synchroniseTargetObj(srcObject, srcPropStr, srcProp) {
        let tarObjArray = srcObject[srcPropStr].bindObjArray;
        let tarObjArrayLen = tarObjArray.length;
        for (let j = 0; j < tarObjArrayLen; j++) {
            let tarObj = tarObjArray[j];
            let tmpval;
            if (typeof(srcObject[srcProp]) === 'function') {
                tmpval = srcObject[srcProp]();
            } else {
                tmpval = srcObject[srcProp];
            }
            let tartmpval;
            if (tarObj.obj != null) {
                if (typeof(tarObj.obj[tarObj.prop]) === 'function') {
                    tarObj.obj[tarObj.prop](tmpval);
                } else {
                    tarObj.obj[tarObj.prop] = tmpval;
                }
            }
        }
    }

    getTargetObject(targObj, targProp) {
        let tarObject = new Object();
        tarObject.obj = targObj;
        tarObject.prop = targProp;
        return tarObject;
    }


    /**
     * @description - Add Binding method binds Source Object property with Target Object Property with Synchronize on SourceObject Event
     * @public
     * @param {object} _srcObj - Source DOM Element or Object
     * @param {string} _srcProp - Source Property
     * @param {string} _evtname - Source Object EventName on which Binding Trigger 
     * @param {object} _targObj - Target DOM Element or Object
     * @param {string} _targProp - Target Property
     * @param {Boolean} twoway - True when Binding is Two-way
     * @example Add Binding :
 BindingUtils.addBinding(srcele, "value", "change", destele, "value", false);
     * @memberof BindingUtil
     */
    addBinding(srcObj, srcProp, evtname, targObj, targProp, twoway) {
        this.Binds(srcObj, srcProp, evtname, targObj, targProp);
        if (twoway) {
            this.Binds(targObj, targProp, evtname, srcObj, srcProp);
        }
    }


    /**
     * 
     * @description - Add Binding method binds Source Object property with Target Object Property with Synchronize on SourceObject Event
     * @public
     * @param {object} _srcObj - Source DOM Element or Object
     * @param {string} _evtname - Source Object EventName on which Binding Trigger 
     * @example Remove Binding :
 BindingUtils.removeBinding(srcele, "value");
     * @memberof BindingUtil
     */
    removeBinding(_srcObj, _evtname) {
        // determine is it dom element or plain object
        let srcObject;
        if (_srcObj == null)
            return;
        if (_srcObj.nodeName) {
            let eleid = _srcObj.dataset.bindid;
            if (this.BindObjDictionary[eleid] != null) {
                srcObject = this.BindObjDictionary[eleid];
                this.removeListeners(srcObject);
            } else {
                srcObject = this.BindObjDictionary[srcObj.bindid];
                this.removeListeners(srcObject);
            }

        } else {
            if (this.BindObjDictionary[srcObj.bindid] != null) {
                srcObject = this.BindObjDictionary[srcObj.bindid];
                this.removeListeners(srcObject);
            }
        }

        if (srcObject.srcPropArray.length > 0) {

            let srcPropArrayLen = srcObject.srcPropArray.length;
            for (let j = 0; j < srcPropArrayLen; j++) {
                let srcProp = srcObject.srcPropArray[j];
                let srcPropStr = srcProp + "prop";
                srcObject[srcPropStr].bindObjArray = [];
            }
        }
    }

    removeListeners(sourceObj, evtname) {
        sourceObj.removeEventListener(evtname, (e) => { this.synchronise(e); });
    }

}

const BindingUtilss = new BindingUtil();

export default BindingUtilss;