views/dom/domdiff-view/DOMView.js

class DOMView {
    constructor(){
        this.renders = [];
    }
    
    render(suggestedDelay=10){
        clearTimeout(this.renderTimer);
        this.renderTimer = setTimeout(()=>{
            // Cleanup
            this.renders.forEach((view)=>{
                view.destroy();
            });

            // Render new views
            document.querySelectorAll("dom-view-template").forEach(async (template)=>{
                if (DOMView.DEBUG) console.log("Parsing",template);
                let root = new RootParseNode(template);
                if (DOMView.DEBUG) console.log("Rendering",template);
                this.renders.push(root.render());
                if (DOMView.DEBUG) console.log("Ready for use");
            });
        },suggestedDelay);        
    }
    
    existsAsViewElement(viewName){
        return document.querySelector("dom-view-template [view='"+viewName+"']");
    }    
    
    /**
     * Gets the most specific binding with a value for a name in the scope
     * @param {type} bindingName
     * @param {type} scope
     * @returns {undefined|DOMView.getBindingFromScope.scope}
     */
    static getBindingFromScope(bindingName, scope){
        for (let i = scope.length - 1; i >= 0; i--) {
            if (scope[i].hasBindingFor(bindingName)) {
                return scope[i];
            }
        }
        return undefined;
    }

    /**
     * Convenience method that evaluates a value in scope
     * @param {type} bindingName
     * @param {type} scope
     * @returns {undefined}
     */
    async evaluateValueInScope(bindingName, scope) {
        let binding = DOMView.getBindingFromScope(bindingName, scope);
        if (binding===undefined) return undefined;
        
        return await binding.getValueFor(bindingName);
    }    
      
    /**
     * Gets an ordered list from the scope mapped by the mapper function given
     */
    getFilteredPath(viewElement, mapperFunction){        
        let element = viewElement;
        while (element != null && !element.scope){
            element = element.parentElement;
            if (element==null){
                // No concepts in this tree path at all                
                return [];
            }
        }

        let result = [];
        if(element != null && element.viewParticle != null) {
            for (let binding of element.viewParticle.scope) {
                let mappedValue = mapperFunction(binding);
                if (mappedValue) result.push(mappedValue);
            }
        }
        return result;
    }    
    
    /**
     * Gets an ordered list of concepts instances involved in rendering this view element
     * @param {HTMLElement} viewElement
     * @returns {string[]}
     */
    getConceptPath(viewElement){
        return this.getFilteredPath(viewElement, (binding)=>{
            if (binding instanceof ConceptInstanceBinding) return binding;
        });
    }    
    
    /**
     * Gets an ordered list of template elements involved in rendering this view element
     * @param {HTMLElement} viewElement
     * @returns {string[]}
     */     
    getTemplatePath(viewElement){
        return this.getFilteredPath(viewElement, (binding)=>{
            if (binding instanceof TemplateBinding) return binding.templateElement;
        });
    }
    
    /**
     * Gets an ordered list of properties involved in rendering this view element
     * @param {HTMLElement} viewElement
     * @returns {string[]}
     */    
    getPropertyPath(viewElement){
        return this.getFilteredPath(viewElement, (binding)=>{
            if (binding instanceof PropertyBinding) return {uuid: binding.uuid, property: binding.property};
        });
    }    
}

DOMView.DEBUG = false;
DOMView.DEBUG_PERFORMANCE = false;
DOMView.singleton = new DOMView();
window.DOMView = DOMView;


//If fragments exists postpone the DOMView until all fragments was loaded at least first time. (Fragments added later obviously does not count)
if(typeof Fragment !== "undefined") {
    Fragment.addAllFragmentsLoadedCallback(()=>{
        // We started after autoDOM has run, so no mutations. Bootstrap with what we have
        console.log("DOMDiffView: Full re-render due to initial page load via Codestrates");
        DOMView.singleton.render();        
        
        // STUB: Reload when templates change
        let observer = new MutationObserver((mutations) => {
            let reload = function reloadDueToMutations(){
                console.log("STUB: DOMDiffView: Full re-render due to templates changing");
                DOMView.singleton.render(300);           
            };
            for(let mutation of mutations) {
                // From inside a template
                if (
                       (mutation.target.matches && mutation.target.matches("dom-view-template"))
                    || (mutation.target.closest && mutation.target.closest("dom-view-template"))
                    || (mutation.target.parentElement && mutation.target.parentElement.closest("dom-view-template"))){
                    reload();
                    break;
                }

                // From outside a template
                if (mutation.type==="childList"){
                    for (let node of [...mutation.addedNodes, ...mutation.removedNodes]){
                        if (node.tagName === "DOM-VIEW-TEMPLATE" || (node.querySelector && node.querySelector("dom-view-template"))){
                            reload();
                            break;
                        }
                    }
                }
            };
        });
        observer.observe(document.body, {
            attributes: true,
            attributeOldValue: false,    
            childList: true,
            subtree: true,
            characterData: true,
            characterDataOldValue: false
        });

        VarvEngine.registerEventCallback("engineReloaded", (evt) => {
            console.log("DOMDiffView: Full re-render due to engine reload");
            DOMView.singleton.render();
        });
    });
} else {
    VarvEngine.registerEventCallback("engineReloaded", (evt) => {
        console.log("DOMDiffView: Full re-render due to engine reload");
        DOMView.singleton.render();
    });
}