depend.js

var RB = {};

/**
 * This allows you to declare a class in Javascript, list events on an HTML node, and have functionality from the JS class automatically wired to the node.
 * 
 * 
 * 
 * The basic functionality is:
 *  - Extend RB.DomWire with your JS class
 *  - To auto-wire call YourClassName.autowire();
 *      - AutoWiring is done when the page is finished loading, NOT at the time you call autowire()
 *      - By Default looks for rb-YourNamespacedClassName on DOM Elements
 *      - To customize, you may: 
 *          - call YourClassName.autowire('DomeClass');
 *          - declare YourClassName.querySelector = 'DomClass';
 *          - declare YourClassName.getquerySelector = function(){return 'DomClass';};
 *      - NOTE: Precedence is given to autoWire('DomClass'), then YourClassName.querySelector, then YourClassName.getquerySelector()
 *  - Set Event methods in YourClass corresponding to JS Event Listeners.
 *      - Inside your class, set methods like `onclick(event)`, `mouseover`, `mouseout`, etc & they will be auto-wired to Dom Nodes
 *      - Ex: class YourClass extends RB.DomWire {  onclick(event){console.log(this);    console.log(event);   console.log(this.node)};
 *          - `this` refers to the instance of YourClass
 *          - `this.node` refers to the node which the listener was set on
 *          - `event` refers to the javascript event that was triggered
 *              - `event.target` may be different from `this.node` due to event propagation
 *  - Methods are assigned using theNode.addEventListener. 
 *      - You may declare this.eventMode = 'static' in your __construct() method to use theNode.onclick = YourClass.onclick;
 *  - You are encouraged to override the methods __construct() && __attach() in your subclass. The super method does not do anything
 *      - __construct() is called before attach()
 *      - __attach() is called AFTER attach()
 *      - You may override constructor() && attach() but it is NOT recommended & super method will need to be called
 *  - Set any additional methods and paramaters that you like, 
 *      - except for
 *          params: node, eventNode
 *          methods: getChildMethods, attachMethod (as well as the methods mentioned above)
 * 
 */
RB.DomWire = class {
    
    constructor(node){
        RB.DomWire.list.push({'node':node,'obj':this});
        this.name = this.name ?? this.constructor.name;
        this.eventMode = 'addEventListener';
        this.node = node;
        this.__construct();
        this.attach();
        this.__attach();
        this.didAttach();
    }
    __construct(){}
    __attach(){}
    attach(){
        const childMethods = this.getChildMethods();
        for (const methodName of childMethods){
            this.attachMethod(methodName);
        }
    }
    didAttach(){}   
    getChildMethods(){
        const methodNames = RB.Tools.getObjectMethodNames(this);
        const rootMethods = RB.Tools.getObjectMethodNames(RB.DomWire.prototype);
        const childMethods = methodNames.filter(method => rootMethods.indexOf(method)<0);
        return childMethods;
    }
    attachMethod(methodName){
        if (methodName.substring(0,2)!=='on')return false;
        const method = this[methodName].bind(this);
        if (this.eventMode=='addEventListener'){
            const eventName = methodName.substring(2);
            this.node.addEventListener(eventName,method);
            return true;
        } else if (this.eventMode=='static'){
            this.node[methodName] = method;
            return true;
        } else {
            throw "event mode must be 'addEventListener' or 'static'";
        }
    }

    static autowire(querySelector=null) {
        if (querySelector!=null)this.querySelector = querySelector;
        // console.log("autowire, constructor name: "+this.prototype.constructor.name);
        // console.log(this);
        RB.Tools.onPageLoad(this.wire, this);
    }
    static wire(){
        const nodes = this.getNodes();
        for (const node of nodes){
            this.wireNode(node);
        }
    }

    static fromNode(nodeObj){
        for (const row of this.list){
            if (row.node===nodeObj)return row.obj;
        }
    }
};
RB.DomWire.list = [];

RB.DomWire.getQuerySelector = function(){
    return this.querySelector || '.'+this.name;
}

RB.DomWire.getNodes = function(){
    const querySelector = this.getQuerySelector(); 
    let nodes = [];
    try {
        nodes = document.querySelectorAll(querySelector);
    } catch (e){
        nodes = [];
    }
    return nodes;
}

RB.DomWire.wireNode = function(node){
    // console.log('wire node');
    const object = new this(node);
    return object;
    // console.log('count:'+RB.DomWire.wireCount);
    // RB.DomWire.wireCount--;
    // console.log(object.prototype.__allReady);
}







RB.WireContext = class extends RB.DomWire {

    // uniqueContextName(object){
    //     console.log(object);
    //     RB.WireContext.contextMap = RB.WireContext.contextMap ?? {};
    //     for (const key in RB.WireContext.contextMap){
    //         const ref = RB.WireContext.contextMap[key].anchor;
    //         if (object===ref)return key;
    //     }
    //     const newKey = Math.random().toString(36).substring(7);
    //     RB.WireContext.contextMap[newKey] = {"anchor":object};
    //     return newKey;
    // }
    get(objectName,contextName=null){
        if (contextName==null)contextName = this.context.slice(0,1);
        const map = RB.WireContext.contextMap[contextName];
        const values = map[objectName];
        // console.log(values);
        if (values.length==1)return values[0];
        else if (values.length==0)return null;
        else return values;
    }
    didAttach(){
        // console.log('didattach');
        const map = RB.WireContext.contextMap ?? {};
        if (typeof [] != typeof this.context)this.context = [this.context];
        for (const name of this.context){
            map[name] = map[name] ?? {};
            map[name][this.name] = map[name][this.name] ?? [];
            // console.log(this.name);
            map[name][this.name].push(this);
        }
        RB.WireContext.contextMap = map;
        // const props = Object.keys(this);
        // console.log(this.constructor.prototype);
        const proto = this.constructor.prototype;
        let keys = [];
        let obj = this.constructor.prototype;
        // console.log(obj);
        // console.log(Object.getPrototypeOf(obj));
        do keys = keys.concat(Object.getOwnPropertyNames(obj));
            while ((obj = Object.getPrototypeOf(obj))!=Object.prototype);
        const props = keys.filter(function(value,index,self){return self.indexOf(value)===index;});
        // console.log(keys);
        // console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(proto))))));
        // console.log(proto.constructor.prototype.constructor.prototype);
        // const props = Object.getOwnPropertyNames(this.constructor.prototype);
        for (const prop of props){
            if (prop.charAt(0)=='_'&&prop.charAt(1)!='_'){
                const name = prop.slice(1);
                const ret = this[prop]() ?? name;
                Object.defineProperty( this, name, {
                            get : function(property){
                                if (typeof [] == typeof property){
                                    let i = -1;
                                    let output = '';
                                    for (const val of property){
                                        i++;
                                        if (val==null)continue;
                                        output = val;
                                        break;
                                    }
                                    const contextEntry = this.context[i];
                                    return this.get(output,contextEntry);
                                } else {
                                    return this.get(property);
                                }
                                console.log(property);
                                console.log(this.context);
                            }.bind(this,ret)
                        } );
            }
        }
        // console.log();//.constructor.prototype);
        // console.log(this.context);
    }

    // allReady(){
    //     // console.log(RB.WireContext.contextMap);
    //     // console.log('allready');
    //     return;
    //     this.getter('parent',this.parent);
        
    //     // console.log('allready(). context:');
    //     const context = RB.Map.contextOfObject(this);
    //     // console.log(context);
    //     const names = {};
    //     for (const obj of context.objects){
    //         obj.contextObj = RB.Map.contextOfObject(obj);
    //         // console.log(obj.contextObj);
    //         let className = obj.constructor.name;
    //         className = className.charAt(0).toLowerCase()+className.slice(1);
    //         // console.log('classname:'+className);
    //         names[className] = names[className] ?? {"total":0,"children":0};
    //         names[className] = {"total":1+(names[className]['total'] ?? 0),"children":(names[className]['children']??0)+(this.node.contains(obj.node)?1:0)};
    //         // this.getter.call(this,)
    //     }
    //     for (const name in names){
    //         const total = names[name]['total'];
    //         console.log(name+':'+total);
    //         if (total==1
    //             ||names[name]['children']==1){
    //             // console.log('getter');
    //             this.getter(name,this.context.bind(this,name));
    //         }
    //         if (total>1){
    //             // for ()
    //             // for (const obj of context.)
    //             this.getter(name+'s',this.context.bind(this,name+'s'));
    //         }
    //     }
    //     // console.log('context:');
    //     // console.log(context);
    //     // console.log(RB.Map);
    // }
    // parent(){
    //     let parent = this.node;
    //     let obj;
    //     while (parent.parentNode!=null
    //             &&(parent=parent.parentNode)){
    //         if ((obj=RB.Map.getObj(parent))!==null){
    //             console.log(obj);
    //             return obj;
    //         }
    //     }
    //     // this.undefinedParent = this.undefinedParent ?? {'undefined-parent':'undefined', 'rand:':Math.random()};
    //     // console.log('undefined parent: '+RB.Tools.objectId(this.undefinedParent));
    //     // console.log();
    //     return null;
    //     if (this.contextObj == this.undefinedParent)return
    //     return this.undefinedParent;
    // }

    // context(objectName){
    //     console.log('looking for '+objectName);
    //     const objContext = RB.Map.contextOfObject(this);
    //     console.log(objContext);
    //     if (objContext==null)return null;
    //     // console.log('claled ongtext');
    //     // console.log(objContext.objects);
    //     // console.log('object context: ');
    //     // console.log(objContext);
    //     // const objects = RB.Map.objectsInContext(objContext.anchorObj);
    //     const objs = [];
    //     for (const obj of objContext.objects){
    //         // console.log(obj.node.classList);
    //         if (obj.node.classList.contains(objectName)){
    //             objs.push(obj);
    //             // console.log('pushed');
    //         }
    //     }
    //     console.log(objs);
    //     if (objs.length==0)return null;
    //     else if (objs.length==1)return objs.pop();
    //     else return objs;
    //     return null;
    // }

    // getter(name,fun){
    //     Object.defineProperty( this, name, {
    //         get : fun
    //     } );
    // }


    // static wire(){
    //     // console.log('child wire');
    //     console.log('might wire obj: '+RB.WireContext.wireCount);
    //     super.wire();
    //     if (--RB.WireContext.wireCount===0){
    //         // console.log('time to call the finals');
    //         // console.log(this.wiredObjs);
    //         for (const entry of this.wiredObjs){
    //             entry.obj.allReady();
    //             entry.obj.__allReady();
    //         }
    //     }
    // };

    // static autowire(querySelector=null){
    //     RB.WireContext.wireCount = 1+(RB.WireContext.wireCount ?? 0);
    //     super.autowire.apply(this,querySelector);
    //     // if (querySelector!=null)this.querySelector = querySelector;
    //     // console.log('child autowire');
    //     // RB.Tools.onPageLoad(this.wire, this);

    // }
    // static autowire(querySelector=null) {
    //     // RB.DomWire.wire.apply(this);
    //     RB.Tools.onPageLoad(this.wire, this);
    //     // RB.DomWire.pageLoadCount++;
    // }
}


// RB.WireContext.wireNode = function(node){
//     obj = new this(node);
//     this.map = RB.Map;
//     this.map.pushObjNode(obj,node);

//     if (typeof obj.__allReady == typeof function(){}){//??null!==null){
//         (RB.WireContext.wiredObjs = RB.WireContext.wiredObjs ?? []).push({'isFinalized':false, 'obj':obj});
//     }
//     // console.log(this.map.getNode(obj));
// };


RB.Map = new class {

    nodeMap = [];
    pushObjNode(obj,node){
        this.nodeMap.push({"obj":obj,"node":node});
    }
    getNode(obj){
        const entry = this.getEntry(obj);
        return entry ? entry.node : null;
    }
    getObj(node){
        const entry = this.getEntry(node);
        return entry ? entry.obj : null;
    }
    getEntry(mixed){
        for(const entry of this.nodeMap){
            for (const key in entry){
                const value = entry[key];
                if (value===mixed)return entry;
            }
        }
        return null;
    }


    contextMap = [];
    pushContext(obj,contextAnchor){
        let context;
        if (!(context=this.getContextEntry(contextAnchor)))this.contextMap.push(context={'anchorObj':contextAnchor,'objects':[]});
        if (obj!==null)context.objects.push(obj);
    }
    contextOfObject(object){
        for (const entry of this.contextMap){
            if (entry.objects.indexOf(object)!==-1){
                return entry;
            }
        }
        const nullContext = this.getContextEntry(null);
        this.pushContext(object,nullContext);
        // if (nullContext!==null){
        //     console.log('nullest context');
        // }
        return this.getContextEntry(null);
    }
    getContextEntry(contextAnchor=null){
        if (contextAnchor==null)return (this.nullContext=this.nullContext ?? {'anchorObj':contextAnchor,'objects':[]});
        for (const entry of this.contextMap){
            if (contextAnchor===entry.anchorObj){
                return entry;
            }
        }
        // console.log('in getCOntextEntry()');
        // console.log(this.contextMap);
        // console.log(contextAnchor);
        // const nullContext = this.getContextEntry(null);
        // if (nullContext===null)this.pushContext(null,null);
        return null;//this.getContextEntry(null);
        // return null;
    }
    objectsInContext(contextAnchor){
        const entry = this.getContextEntry(contextAnchor);
        // console.log(entry);
        return entry ? entry.objects : null;
    }
}





RB.Tools = class {

};
RB.Tools.onPageLoad = function (func, thisArg, ...args) {
    func.apply(thisArg,args);
    return;
    if (window.readyState == "complete") {
        func.apply(thisArg, args);
    } else if (window.addEventListener != null) {
        document.addEventListener("DOMContentLoaded", function () {
            if (document.readyState == "interactive") {
                func.apply(thisArg, args);
            }
        });
    } else {
        window.onload = function () {
            func.apply(func, args);

        };
    }
}
RB.Tools.getObjectMethodNames = function(object){
    const properties = new Set();
    let currentObj = object;
    do {
        Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
    } while ((currentObj = Object.getPrototypeOf(currentObj)))
    return [...properties.keys()].filter(item => typeof object[item] === 'function')
}

RB.Tools.objectId = function(object){
    this.objectMap = this.objectMap ?? new WeakMap();
    if (this.objectMap.has(object)){
        return this.objectMap.get(object);
    }
    
    this.idMap = this.idMap ?? {};
    this.counter = this.counter ?? 0;
    const id = object.constructor.name+":"+(new Date()).getMilliseconds() + "-" + Object.keys(this.objectMap).length + "-" + this.counter++ + "-" + Math.random();
    this.idMap[id] = object;

    this.objectMap.set(object,id);
    
    return id;
}.bind(RB.Tools);

RB.Tools.objectFor = function(id){
    return this.idMap[id];
}.bind(RB.Tools);





if (RB===undefined)var RB = {};

RB.Dom = class {

    static cycle(node,attribute,value=null){
        if (value==null
            &&node.hasAttribute(attribute)){
                node.removeAttribute(attribute);
        } else if(value==null){
            node.setAttribute(attribute,'');
        } else {
            // const index = value.indexOf(node[attribute]);
            // index++;
            // index = index%value.length;
            node[attribute] = value[ (1+value.indexOf(node[attribute]))%value.length ];
        }
    }

    static is(tagName,node){
        if (node.tagName.toLowerCase()==tagName.toLowerCase())return true;
        return false;
    }
}

if (typeof RB === 'undefined')var RB = {};
RB.Request = class {
    constructor(url, method){
        this.params = {};
        this.url = url;
        this.method = method ?? 'POST';
    }
    put(key,value){
        if (key in this.params){
            this.params[key] = (typeof this.params[key]==typeof []) ? this.params[key] : [this.params[key]];
            this.params[key].push(value);
        } else {
            this.params[key] = value;
        }
    }

    handleJson(func){
        var formData = new FormData();
        for(var key in this.params){
            const param = this.params[key];
            if (typeof param == typeof []){
                for(const val of param){
                    formData.append(key,val);
                }
            } else formData.append(key,this.params[key]);
        }
        fetch(this.url, {
            method: this.method, 
            mode: "cors",
            // cache: "no-cache",
            // credentials: "same-origin",
            // redirect: "follow",
            // referrerPolicy: "no-referrer",
            // headers: {
            //     'Content-Type': 'application/json'
            //     // 'Content-Type': 'application/x-www-form-urlencoded',
            // },
            body: formData
        }).then(res => {
            return res.json();
        }).then(json => {
            func(json);
        });
    }
    handleText(func){
        var formData = new FormData();
        for(var key in this.params){
            const param = this.params[key];
            if (typeof param == typeof []){
                for(const val of param){
                    formData.append(key,val);
                }
            } else formData.append(key,this.params[key]);
        }
        fetch(this.url, {
            method: this.method, 
            mode: "cors",
            body: formData
        }).then(res => {
            return res.text();
        }).then(text => {
            func(text);
        });
    }
}