All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.compile.js Maven / Gradle / Ivy

// COMPILER BLOCK -->
import { DEBUG, SUPPORT_WEB_COMPONENTS, SUPPORT_EVENTS, SUPPORT_REACTIVE } from "./config.js";
// <-- COMPILER BLOCK
import { Template, TemplateDOM } from "./type.js";
import { idl_attributes } from "./factory.js";

const event_types = SUPPORT_EVENTS && {

    "tap": 1,
    "change": 1,
    "click": 1,
    "dblclick": 1,
    "input": 1,
    "keydown": 1,
    "keypress": 1,
    "keyup": 1,
    "mousedown": 1,
    //"mouseenter": 1, // non-bubbling
    "mouseover": 1,
    //"mouseleave": 1, // non-bubbling
    "mouseout": 1,
    "mousemove": 1,
    "mouseup": 1,
    "mousewheel": 1,
    "touchstart": 1,
    "touchmove": 1,
    "touchend": 1,
    "touchcancel": 1,
    "reset": 1,
    "select": 1,
    "submit": 1,
    "toggle": 1,
    //"focus": 1, // non-bubbling
    "focusin": 1,
    //"blur": 1, // non-bubbling
    "focusout": 1,
    "resize": 1,
    "scroll": 1,

    // non-bubbling events
    "error": 1,
    "load": 1,
};

const event_bubble = {

    "blur": "focusout",
    "focus": "focusin",
    "mouseleave": "mouseout",
    "mouseenter": "mouseover"
};

// function escape_single_quotes(str){
//
//     return str.replace(/\\([\s\S])|(')/ig, "\\$1$2");
// }
//
// function escape_single_quotes_expression(str){
//
//     //console.log(str.replace(/{{(.*)?(\\)?([\s\S])|(')([^}]+)?/ig, "{{$1$2$3$4$5"))
//
//     return str.replace(/{{(.*)?(\\)?([\s\S])|(')(.*)?(}})/ig, "{{$1$2$3$4$5$6");
// }

function replaceComments(str){

    return str.replace(//g, "");
}

function strip(str){

    return str.replace(/({{|}})/g, "").trim();
}

let message = 0;
let counter = 0;

/**
 * @param {!string|HTMLTemplateElement|Element|Node} node
 * @param {boolean|Function=} callback
 * @param {Array|Function>=} _inc
 * @param {Array|Object=} _fn
 * @param {Object=} _index
 * @param {boolean|number=} _recursive
 * @return {Template|TemplateDOM|Promise}
 */

export default function compile(node, callback, _inc, _fn, _index, _recursive){

    if(DEBUG){

        if(!message){

            message = 1;
            console.info("If this page has set a Content-Security-Policy (CSP) header field, using the inline compiler has disadvantage when not configure \"script-src 'unsafe-eval'\". It is recommended to use the Mikado native compiler, which is CSP-friendly and also can optimize your templates more powerful.");
        }
    }

    if(callback){

        return new Promise(function(resolve){

            const tpl = compile(node);
            if(typeof callback === "function") callback(tpl);
            resolve(tpl);
        });
    }

    if(!_index){

        _fn = /** @type {Object} */ ([]);
        _inc = [_fn];
        _fn.index = _index = {

            // the actual index of element p[i]
            current: -1,

            // counts every index change (next element, style, text node)
            count: 0,

            // the value of the last index counter to identify if counter has increased
            // and current index should also be increased by 1
            last: -1,

            // the actual index of inc[i]
            inc: 0,

            // a state to identify if one of the 3 include types was applied
            included: false,

            // a cache to identify repeating template structures
            // not supported by the inline compiler
            //cache: {}
        };
    }

    const template = _recursive
        ? /** @type {TemplateDOM} */ ({})
        : /** @type {Template} */ ({
            tpl: /** @type {TemplateDOM} */ ({})
        }
    );

    const tpl = _recursive
        ? template
        : template.tpl;

    if(!_recursive){

        if(typeof node === "string"){

            if(/<.*>/.test(node)){

                const tmp = document.createElement("div");
                tmp.innerHTML = node;
                node = tmp.firstElementChild;
            }
            else{

                template.name = node;
                node = document.getElementById(node);
            }

            if(DEBUG){

                if(!node){

                    throw new Error("The template was not found.");
                }
            }
        }

        node = /** @type {HTMLTemplateElement} */ (node);

        if(node.content){

            if(!template.name){

                template.name = node.id || node.getAttribute("name");
            }

            node = node.content.firstElementChild;
        }
    }

    const tagName = node.tagName;

    if(!tagName || tagName === "SCRIPT"){

        // a text node or inline code has no tag

        let value;

        if((value = (tagName ? node.firstChild : node).nodeValue)){

            if(value && value.trim()){

                if(value.includes("{{@")){

                    let js = value.replace(/{{@([\s\S]+)}}/g, "$1").trim();
                    value = /{{[\s\S]+}}/.test(js) ? js.replace(/{{([\s\S]+)}}/g, "{{$1}}") : "";
                    js && (js = js.replace(/{{([\s\S]+)}}/g, ""));
                    js && _fn.push(js);

                    // using the script tag allows the runtime compiler
                    // to place code to a specific place

                    if(tagName === "SCRIPT"){

                        if(value.trim()){

                            tpl.text = value;
                            tpl.tag = tagName;
                        }

                        return tpl;
                    }
                }

                if(value && value.trim()){

                    //if(/{{[!?]?#/.test(value)){
                    if(value.includes("{{#")){

                        handle_value(tpl, "html", value, false, null, _index, _inc, _fn);
                    }
                    else{

                        _index.count++;

                        handle_value(tpl, "text", value, false, null, _index, _inc, _fn);
                    }
                }
            }
        }

        if(!tagName){

            return /*tpl.js ||*/ (value && value.trim()) ? tpl : null;
        }
    }

    if(tagName){

        tpl.tag = tagName;
    }

    let attributes = node.attributes;

    if(attributes && attributes.length){

        const tmp = {};

        // collect and normalize attributes

        for(let i = 0; i < attributes.length; i++){

            let attr_name = attributes[i].nodeName;
            let attr_value = node.getAttribute(attr_name);

            // the foreach needs to be handled in the switch below,
            // otherwise it could collide with native "for" attribute

            // if(attr_name === "foreach") attr_name = "for";
            if(attr_name === "include") attr_name = "inc";

            tmp[attr_name] = attr_value;
        }

        attributes = /** @type {TemplateDOM} */ (tmp);

        for(let attr_name in attributes){

            let attr_value = attributes[attr_name];
            let handler;
            let attr;

            switch(attr_name){

                case "class":
                case "style":

                    handler = attr_name;
                    break;

                case "include":

                    attr_name = "inc";
                    // fallthrough

                case "inc":

                    handler = attr_name;
                    break;

                case "if":

                    handler = attr_name;
                    break;

                case "foreach":

                    attr_name = "for";
                    handler = attr_name;
                    break;

                case "js":

                    // is already pushed to fn stack
                    break;

                case "key":

                    template.key = strip(attr_value).replace("data.", "");
                    break;

                case "cache":

                    break;

                default:

                    if(SUPPORT_EVENTS && event_bubble[attr_name]){

                        attr = tpl.event || (tpl.event = {});

                        if(DEBUG){

                            console.info("The assigned event '" + attr_name + "' was replaced by the event '" + event_bubble[attr_name] + "'.");
                        }

                        attr_name = event_bubble[attr_name];
                    }
                    else if(SUPPORT_EVENTS && event_types[attr_name]){

                        attr = tpl.event || (tpl.event = {});
                    }
                    else{

                        // derive template name from outer element when it is not a template
                        // skip, when it is an expression

                        if(!_recursive && (attr_name === "id" || attr_name === "name") && !template.name){

                            if(!/{{[\s\S]+}}/.test(attr_value)){

                                template.name = attr_value;
                            }
                        }

                        attr = tpl.attr || (tpl.attr = {});
                    }

                    handler = attr_name;
            }

            if(handler){

                handle_value(attr || tpl, handler, attr_value, !!attr, attributes, _index, _inc, _fn);
            }
        }
    }

    // from here all attributes was processed by handle_value()

    // process