Page.js



Wyg.Page = class {

    static initialize(){
        this.instance = new Wyg.Page();
    }

    constructor(){
        this.initializeNodes();
        this.addEventListeners();
        this.initializeParams();
        this.showEditorHtml();
        this.breadcrumbRefs = [];
    }
    initializeParams(){
        this.editor = new Wyg.Editor(this.codeNode.value,this.valuesNode.value);
        this.insertables = new Wyg.Insertables(this.insertables.childNodes);
    }
    initializeNodes(){
        this.codeNode = document.getElementById("templateCode");
        this.valuesNode = document.getElementById('valuesCode');
        this.outputNode = document.getElementById("output");
        this.fields = document.getElementById("fields");
        this.controls = document.getElementById("controls");
        this.breadcrumbs = document.getElementById("breadcrumbs");
        this.editorNode = document.getElementById("editor");
        this.editorNode.outerHTML = this.editorNode.outerHTML;
        this.editorNode = document.getElementById("editor");


        this.insertables = document.getElementById("insertables");
        this.insertables.outerHTML = this.insertables.outerHTML;
        this.insertables = document.getElementById("insertables");


        this.tabBar = document.getElementById("tabBar");



        this.templateIdNode = document.getElementById("templateId");
        this.styleNode = document.getElementById("styleCode");
        this.scriptNode = document.getElementById('scriptCode');
        this.templateName = document.getElementById('templateName');
        this.siteName = document.getElementById('siteName');
        this.groupName = document.getElementById('groupName');
    }


    showEditorHtml(){
        // const node = this.editor.createNode();
        this.editorNode.innerHTML = '';
        this.editorNode.appendChild(this.editor.node);
        this.outputNode.value = this.editor.getOutputCode();

    }


    addEventListeners(){

        this.codeNode.addEventListener('change',this.codeChanged.bind(this));


        this.valuesNode.addEventListener('keyup',this.valuesChanged.bind(this));
        this.editorNode.addEventListener('click',this.editorClicked.bind(this));
        this.editorNode.addEventListener('mouseover',this.editorHovered.bind(this));
        this.editorNode.addEventListener('mouseout',this.editorUnhovered.bind(this));
        this.editorNode.addEventListener('drop',this.templateDropped.bind(this));
        this.editorNode.addEventListener('dragover',event=>event.preventDefault());
        this.setupInsertablesListeners();


        this.tabBar.addEventListener('click',this.tabSelected.bind(this));

    }

    panelByTabName(tabName){
        const panels = document.querySelectorAll('[class=panel]');
        for (const panel of panels){
            if (panel.getAttribute('data-tab')==tabName){
                return panel;
            }
        }
    }

    tabSelected(event){
        // console.log(event.target);
        if (event.target.classList.contains('tab')
            &&!event.target.classList.contains('selected')){

            const selectedNodes = document.querySelectorAll('.selected');
            for (const selected of selectedNodes){
                selected.classList.remove('selected');
            }

            event.target.classList.add('selected');
            const panel = this.panelByTabName(event.target.innerText.trim());
            panel.classList.add('selected');
        }
    }

    codeChanged(){
        // console.log('code changed');
        Wyg.Page.initialize();
    }
    valuesChanged(){

    }

    showBreadcrumbs(fromNode){
        this.breadcrumbs.innerHTML = '';

        const headHtml = '&nbsp;<h3>'+fromNode.tagName+':</h3>&nbsp;';
        this.breadcrumbs.appendChild(Wyg.Parser.htmlAsNode(headHtml));

        let parent = fromNode;
        const breadcrumbNodes = [];
        while(parent.parentNode!==this.editorNode
            &&parent.parentNode!=undefined
            &&parent.parentNode!=null
            &&parent.parentNode.classList!=null
            &&!parent.parentNode.classList.contains('wyg-wrapper')){
            parent = parent.parentNode;
            breadcrumbNodes.push(parent);

        }
        let breadcrumbHtml = '';

        for (const breadcrumb of breadcrumbNodes.reverse()){
            const breadcrumbHtml = '<span class="breadcrumb" onclick="Wyg.Page.instance.goToBreadcrumb(event);">'
                                +breadcrumb.tagName
                            +' -&gt;</span>';
            const span = Wyg.Parser.htmlAsNode(breadcrumbHtml);
            this.breadcrumbRefs.push(
                {"node":breadcrumb,
                "breadcrumb":span}
            );
            this.breadcrumbs.appendChild(span);
        }


    }
    getTargetNode(node){
        // if (node.id=="editor"){
        //     return node.firstChild.firstChild;
        // } else if (node.classList.contains('wyg-wrapper')){
        //     return node.firstChild;
        // } else if (node.nodeName==='#text'){
        //     node = node.parentNode;
        // } else if (node.tagName=='INPUT'){
        //     node = node.parentNode;
        // } else if (node.parentNode.classList.contains('wyg-wrapper')) {
        //     return node;
        // }

        // while (node!==undefined
        //         &&node!==null
        //         &&!this.editor.isContainer(node)){
        //     node = node.parentNode;
        // }

        // return node;
    }

    getFieldsFromTarget(targetNode){
        const refs = [];
        let list = [];
        let iterables = [targetNode];
        while (iterables.length>0){
            const iterable = iterables[0];
            let ref = this.editor.map.refFrom('node',iterable);
            if (ref !== undefined)list.push(...ref.fields);
            for (const target of iterable.childNodes){
                if (this.editor.isContainer(target))continue;
                ref = this.editor.map.refFrom('node',target);
                if (ref !==undefined)list.push(...ref.fields);
                iterables.push(...(target.childNodes || []));
            }
            iterables.splice(0,1);
        }
        iterables = Array.from(new Set(iterables));
        return list;
    }
    editorClicked(event){
        event.preventDefault();
        event.stopPropagation();
        const targetNode = this.getTargetNode(event.target);
        this.editNode(targetNode);
        
    }
    editNode(targetNode){
        this.fields.innerHTML = '';
        this.controls.innerHTML = '';
        this.breadcrumbs.innerHTML = '';

        this.showBreadcrumbs(targetNode);
        this.showChildNodes(targetNode);
        this.showButtons(targetNode);

        const fieldsFromTarget = this.getFieldsFromTarget(targetNode);
        this.showEditables(fieldsFromTarget);
        this.highlightNode(targetNode);

    }
    deleteNode(breadcrumb){
        const node = this.nodeFromBreadcrumb(breadcrumb);
        const newSelected = node.previousSibling || node.nextSibling || node.parentNode;
        this.editor.deleteNode(node);
        this.codeNode.value = this.editor.templateCode;
        this.outputNode.value = this.editor.getOutputCode();
        this.editNode(newSelected);
        // Wyg.Page.initialize();
    }
    moveNode(event,direction){
        const button = event.target;
        const node = this.nodeFromBreadcrumb(button);
        const templateNode = this.editor.map.refFrom('node',node).templateNode;
        let sibling = undefined;
        let templateSibling = undefined;
        switch(direction){
            case "up":
                const parent = node.parentNode;
                const templateParent = templateNode.parentNode;
                if (parent.classList.contains('wyg-wrapper')||parent.id==='editor'){
                    console.log(node);
                    throw "cannot move node up because node is at the root of its container";
                }
                parent.removeChild(node);
                templateParent.removeChild(templateNode);
                parent.parentNode.insertBefore(node,parent);
                templateParent.parentNode.insertBefore(templateNode,templateParent);
                break;
            case "down":
                sibling = node.nextElementSibling;
                if (sibling===undefined||sibling===null||!this.editor.isContainer(sibling)){
                    console.log(sibling);
                    throw 'the next sibling node is not a valid container node.';
                }
                templateSibling = templateNode.nextElementSibling;
                node.parentNode.removeChild(node);
                templateNode.parentNode.removeChild(templateNode);
                sibling.insertBefore(node,sibling.firstChild);
                templateSibling.insertBefore(templateNode,templateSibling.firstChild);
                break;
            case "left":
                sibling = node.previousSibling;
                templateSibling = templateNode.previousSibling;
                if (sibling===null||sibling===undefined){
                    console.log(node);
                    throw 'cannot move node left because previous sibling is undefined';
                }
                sibling.parentNode.removeChild(node);
                sibling.parentNode.insertBefore(node,sibling);
                templateSibling.parentNode.removeChild(templateNode);
                templateSibling.parentNode.insertBefore(templateNode,templateSibling);
                break;
            case "right":
                    sibling = node.nextSibling;
                    templateSibling = templateNode.nextSibling;
                    if (sibling===null||sibling===undefined){
                        console.log(node);
                        throw 'cannot move node right because next sibling is undefined';
                    }
                    sibling.parentNode.removeChild(node);
                    sibling.parentNode.insertBefore(node,sibling.nextSibling);
                    templateSibling.parentNode.removeChild(templateNode);
                    templateSibling.parentNode.insertBefore(templateNode,templateSibling.nextSibling);
                    break;
        }

        this.codeNode.value = this.editor.templateNode.innerHTML;
        this.outputNode.value = this.editor.getOutputCode();
        this.editNode(node);
        
    }
    saveTemplate(){
        console.log('save template');
        const templateCode = this.codeNode.value;
        const templateId = this.templateIdNode.value;
        const templateValues = this.valuesNode.value;
        const css = this.styleNode.value;
        const js = this.scriptNode.value;
        const templateName = this.templateName.value;
        const siteName = this.siteName.value;
        const groupName = this.groupName.value;


        const formData = new FormData();
        formData.append('code',templateCode);
        formData.append('id',templateId);
        formData.append('name',templateName);
        formData.append('values',templateValues);
        formData.append('css',css);
        formData.append('js',js);
        formData.append('site',siteName);
        formData.append('group',groupName);

        const requestArgs = {
            'method':'POST',
            'body':formData
        };
        const request = new Request('/wyg/save_template/',requestArgs);
        fetch(request)
        .then(response=>{
            return response.json();
        })
        .then(json=>{
            const id = json.id;
            this.templateIdNode.value = id;
            console.log('saved successfully (probably)');
            console.log(json);
        });

    }
    showButtons(targetNode){

        const leftNode = Wyg.Parser.htmlAsNode('<button onclick="Wyg.Page.instance.moveNode.call(Wyg.Page.instance,event,\'left\')">&larr;</button>');
        const rightNode = Wyg.Parser.htmlAsNode('<button onclick="Wyg.Page.instance.moveNode.call(Wyg.Page.instance,event,\'right\')">&rarr;</button>');
        const upNode = Wyg.Parser.htmlAsNode('<button onclick="Wyg.Page.instance.moveNode.call(Wyg.Page.instance,event,\'up\')">&uarr;</button>');
        const downNode = Wyg.Parser.htmlAsNode('<button onclick="Wyg.Page.instance.moveNode.call(Wyg.Page.instance,event,\'down\')">&darr;</button>');
        this.breadcrumbRefs.push({'breadcrumb':leftNode,'node':targetNode});
        this.breadcrumbRefs.push({'breadcrumb':rightNode,'node':targetNode});
        this.breadcrumbRefs.push({'breadcrumb':upNode,'node':targetNode});
        this.breadcrumbRefs.push({'breadcrumb':downNode,'node':targetNode});
        this.controls.appendChild(leftNode);
        this.controls.appendChild(rightNode);
        this.controls.appendChild(upNode);
        this.controls.appendChild(downNode);

        const btn1Html = '<button onclick="Wyg.Page.instance.deleteNode(this);">DELETE THIS NODE</button>';
        const btn1 = Wyg.Parser.htmlAsNode(btn1Html);
        this.breadcrumbRefs.push({'breadcrumb':btn1,'node':targetNode});
        this.controls.appendChild(btn1);
    }
    showNextSiblingButton(targetNode){
        if (targetNode.nextSibling==null){
            return;
        } else {
            const name = targetNode.nextSibling.tagName || targetNode.nextSibling.nodeName;
            const nextHtml = '<button>--'+name.toLowerCase()+'--></button>';
            const next = Wyg.Parser.htmlAsNode(nextHtml);
            this.breadcrumbs.appendChild(next);

            next.addEventListener('click',this.nextClicked.bind(this));
            this.breadcrumbRefs.push({"breadcrumb":next,"node":targetNode});

        }
    }
    showPrevSiblingButton(targetNode){
        if (targetNode.previousSibling==null){
            return;
        } else {
            const name = targetNode.previousSibling.tagName || targetNode.previousSibling.nodeName;
            const prevHtml = '<button><--'+name.toLowerCase()+'--</button>';
            const prev = Wyg.Parser.htmlAsNode(prevHtml);
            prev.addEventListener('click',this.prevClicked.bind(this));
            this.breadcrumbs.appendChild(prev);
            this.breadcrumbRefs.push({"breadcrumb":prev,"node":targetNode});


        }
    }
    nextClicked(event){
        // console.log('next clicked');
        // console.log(event.target);
        const next = event.target;
        const node = this.nodeFromBreadcrumb(next);
        const nextNode = node.nextSibling;
        this.editNode(nextNode);
    }
    prevClicked(event){
        // console.log('next clicked');
        // console.log(event.target);
        const prev = event.target;
        const node = this.nodeFromBreadcrumb(prev);
        const prevNode = node.previousSibling;
        this.editNode(prevNode);
    }
    
    showChildNodes(targetNode){
        let html = '';

        this.breadcrumbs.appendChild(Wyg.Parser.htmlAsNode('<br>'));
        this.breadcrumbs.appendChild(Wyg.Parser.htmlAsNode('<br>'));

        this.showPrevSiblingButton(targetNode);

        const select = Wyg.Parser.htmlAsNode('<select onchange="Wyg.Page.instance.goToChild.call(Wyg.Page.instance,event,{\'this\':this,\'target\':this.options[this.selIndex]});"></select>');
        this.breadcrumbs.appendChild(select);
        let option = Wyg.Parser.htmlAsNode('<option>[Child Nodes]</option>');
        select.appendChild(option);
        const i=0;
        for (const child of targetNode.childNodes){
            // i++;
    html = 
`    <option value="${child.tagName || child}">
        ${child.tagName || child}
    </option>\n`;
            option = Wyg.Parser.htmlAsNode(html);
            select.appendChild(option);
            this.breadcrumbRefs.push({"breadcrumb":option,"node":child});
        }


        this.showNextSiblingButton(targetNode);

        
    }
    showEditables(fields){
        for (const field of fields){
            const html = 
                `<label>${field.key} \n`
                +`<input name="${field.key}" type="text" value="${field.value}" placeholder="${field.defaultValue}"
                `+` onkeyup="Wyg.Page.instance.editor.updateValue.call(Wyg.Page.instance.editor,'${field.key}',this.value);">`
                +'</label>';
            const editNode = Wyg.Parser.htmlAsNode(html)
            this.fields.appendChild(editNode);
        }
        
    }

    highlightNode(node){
        const highlightName = 'data-wyg-highlight';
        const nodes = document.querySelectorAll(`[${highlightName}="true"]`);
        for (const oldNode of nodes){
            if (oldNode.hasAttribute(highlightName)){
                oldNode.removeAttribute(highlightName);
            }
        }

        if (node.setAttribute==null)return;
        node.setAttribute(highlightName,'true');
    }
    editorHovered(){

    }
    editorUnhovered(){

    }

    setupInsertablesListeners(){
        this.insertables.addEventListener('click',this.insertablesClicked.bind(this));

        const draggables = document.querySelectorAll('.wyg-template');
        for (const node of draggables){
            node.draggable=true;
            node.addEventListener('dragstart',this.templateDragged.bind(this));
        }


        // console.log(draggables);
    }

    templateDragged(event){
        const node = event.target;
        event.dataTransfer.setData('code',node.children[0].value);
        // console.log('dragstart');
    }
    templateDropped(event){
        event.preventDefault();
        event.stopPropagation();

        let target = event.target;
        if (target===this.editorNode){
            target = target.firstChild;
        }
        if (target.classList.contains('wyg-wrapper')){
            target = target.lastChild;
        }
        // console.log('dropped');
        // console.log(target);
        const enCodedString = event.dataTransfer.getData('code');
        const codeJson = JSON.parse(enCodedString);
        
        const code = this.codeWithFreshKeys(codeJson.code);
        const templateNodes = Wyg.Parser.htmlAsNode(code.trim(),true,'span');
        for (const newNode of templateNodes.childNodes){
            if (target===null||target===undefined){
                this.editor.templateNode.appendChild(newNode);
            } else {
                const ref = this.editor.map.refFrom('node',target);
                const templateNode = ref.templateNode;
                templateNode.parentNode.insertBefore(newNode,templateNode);
            }
        }
        
        if (this.codeNode.value.trim()==''
            &&confirm("Edit existing template?\nOtherwise, you are creating a new template.")){
            this.templateIdNode.value = codeJson.id;
            this.templateName.value = codeJson.name;
            this.siteName.value = codeJson.site;
            this.groupName.value = codeJson.group;
            this.styleNode.value = codeJson.css;
            this.scriptNode.value = codeJson.js;
            this.valuesNode.value = codeJson.values;
        }

        this.codeNode.value = this.editor.templateNode.innerHTML;

        // console.log(this.editor);
        this.editorNode.innerHTML = '';
        Wyg.Page.initialize();

    }
    insertablesClicked(event){
        const node = event.target;
        // console.log(node);
        if (node.tagName=='H2'
            ||node.tagName=='H3'){
            node.parentNode.classList.toggle('selected');

        } else if (node.classList.contains('wyg-template')){
            const codeHolder = node.children[0];
            const code = codeHolder.value;
            // console.log('you gotta drag it!');
        }
    }

    goToChild(event){
        const select = event.target;
        const breadcrumb = select.options[select.selectedIndex];
        const node = this.nodeFromBreadcrumb(breadcrumb);
        
        this.editNode(node);

    }
    goToBreadcrumb(event){
        const breadcrumb = event.target;
        const node = this.nodeFromBreadcrumb(breadcrumb);
        // const fakeEvent = {'target':node,'stopPropagation':function(){},'preventDefault':function(){}};
        this.editNode(node);
    }
    nodeFromBreadcrumb(breadcrumb){
        for (const ref of this.breadcrumbRefs){
            if (ref.breadcrumb===breadcrumb){
                return ref.node;
            }
        }
    }
    codeWithFreshKeys(code){
        const fields = Wyg.Parser.fields(code);
        
        let newFieldCode = code;
        for (const fieldKey in fields){
            const field = fields[fieldKey];
            let newKey = field.key;
            const keyString = field.key;
            let keyCount = 1;
            while (this.keyExists(newKey)){
                newKey = keyString+keyCount;
                keyCount++;
            }
            newFieldCode = newFieldCode.replace('{{'+field.key,'{{'+newKey);
            code.replace(field.code,newFieldCode);
        }
        return newFieldCode;
    }
    keyExists(key){
        const field = this.editor.map.fieldFromKey(key);
        if (field===undefined)return false;
        else return true;
    }
};