1

I've got problem with this for a few days, I'm working on a client side framework and recently I've switched it to use ES Modules. The problem is that my templating engine now got a scope problem: I can't call helper functions anymore in my views.

Here is the code:


class templateEngine {

    // Parse template (content) with parameters
    static parse(content, params) {
        let re = /{#(.+?)#}/g,
            reExp = /(^\s*(if|for|else|switch|case|break|{|})).*/g,
            code = 'with(obj) { var r=[];\n',
            cursor = 0,
            result,
            match;
        let add = function (line, js) {
            js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
                (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
            return add;
        }
        while (match = re.exec(content)) {
            add(content.slice(cursor, match.index))(match[1], true);
            cursor = match.index + match[0].length;
        }
        add(content.substr(cursor, content.length - cursor));
        code = (code + 'return r.join(""); }').replace(/[\r\t\n]/g, ' ');
        try {
            test("test"); // This console log "test".
            result = new Function('obj', code).call(this, params);
        }
        catch (err) {
            console.error(err.message, "falling back to raw content.");
            return content;
        }
        return result;
    };
};

function test(s) {
    console.log(s);
    return s;
}

export default templateEngine;

Here is how I call the parse function (this load function is part of the View class):

// Load parsed template view into "viewroot"
    load() {
        let params = this.params;
        let xhr = new XMLHttpRequest();
        xhr.overrideMimeType("text/html");
        xhr.open('GET', this.path, true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && xhr.status == "200") {

                // Parse view template
                let parsedview = templateEngine.parse(this.responseText, params);

                // Load parsed html into viewroot element
                document.querySelector("viewroot").innerHTML = parsedview;

                // Change page title with view title
                let title = document.querySelector("viewhead").querySelector("title");
                document.title = title.text;
            }
        };
        xhr.send(null);
    }

And here is the html template:

<viewhead>
    <title>Welcome!</title>
</viewhead>

<viewbody>
    <div class="container">
        <h1 class="title">Page title</h1>
        <p class="description">welcome to this page</p>
        <p>{# test("test") #}</p>
    </div>
</viewbody>

The "test" function is no longer accessible from the "result" Function object.

Vanilla javascript and non-module functions are accessible and working but module ones are not.

Hope you can help me with this one.

Call_in
  • 66
  • 5
  • I'd say it's a good thing that `test` is no longer a global function! – Bergi Nov 23 '19 at 13:15
  • Have a look at [this answer](https://stackoverflow.com/a/24032179/1048572). Alternatively put `test.toString()` inside the template code. – Bergi Nov 23 '19 at 13:17
  • @Bergi The fact that test isn't a global function is good but I can't even use it in my Function object. Where do you want me to put `test.toString()` ? – Call_in Nov 23 '19 at 15:00
  • You'd just put it in the `code`. – Bergi Nov 23 '19 at 16:02

1 Answers1

0

I've found the answer by experimenting, I just added the functions that I want to access with my template in a scope object and use apply(scope, [params]).

Here is the code:

let scope = params;
scope.myFunction = myFunction;
result = new Function('obj', code).apply(scope, [params]);

Hope it helps people coming across this answer.

Call_in
  • 66
  • 5
  • But then how are you going to access `test`? Surely not just as `test()`. – Bergi Nov 23 '19 at 16:03
  • @Bergi Here I've used a generic exemple so `myFunction` replaces `test`. To use this in the template you just need to call `myFunction()` with my template syntax it will be `{# myFunction() #}`. Hope it's clear. – Call_in Nov 23 '19 at 17:05
  • But no, that should not work, at least not with the same `code` as in the code in your question. If you pass that `scope` object as the `this` context, you'd need to access `this.myFunction` (or `this.test` respectively) instead. – Bergi Nov 23 '19 at 18:47
  • @Bergi No you don't need to use `this` because your context is your object, you can see here the whole code : https://github.com/ColinEspinas/ward/blob/master/core/templating/Engine.js – Call_in Nov 23 '19 at 19:03
  • Oh, now I see, `scope === params`. Though imo that doesn't really make sense. Just install the helpers on the `params` object that you use in the `with` statement, do not use `apply` to pass the same value under a different name as the `this` argument. – Bergi Nov 23 '19 at 20:00