3

I'm attempting to precompile JsRender templates from a class library written in C#, using the Jurassic script engine to execute JsRender.

Here is my code:

var engine = new Jurassic.ScriptEngine();
engine.Execute(JsRenderContents);    
var precompiledTemplate = engine.CallGlobalFunction<string>(String.Concat("$.templates(\"", template, "\");"));

I've taken the JavaScript function call, $.templates(), from this page which states that

$.templates(markupOrSelector) returns: Compiled template object

And my sample HTML template is simply

<li>{{:Name}}</li>

However, my code produces the exception:

'$.templates("<li>{{:Name}}</li>");' is not a function.

Now, I'm not 100% clear whether I can use the $ operator without jQuery being present. The author includes jQuery in several of his examples, but also states that jQuery is not required.

So what's going wrong? Is the documentation out of date for the version of JsRender taken from GitHub on the same day that I posted this question? (I'm aware that JsRender is still in beta.) Or maybe I'm misusing Jurassic?

EDIT:

I believe this is actually more a Jurassic question than a JsRender question. Specifically, I think this relates to Jurassic's global object, as JsRender is wrapped in an Immediately Invoked Function which passes this, and I'm not certain than Jurassic provides this.

It appears that I'm not the first to face this question. I've taken the advice from the last post on this page and changed my code to the following:

var engine = new Jurassic.ScriptEngine();
engine.Execute(JsRenderContents);
engine.Global["window"] = engine.Global;
var precompiledTemplate = engine.CallGlobalFunction<string>(String.Concat("window.jsviews.templates(\"", template, "\");"));

which didn't work - probably because JsRender's IIF still passes this instead of window, and I don't want to modify the script.

Can anyone help push this forward? How can I call any JsRender function from Jurassic, given that Jurassic... I don't know... perhaps there's some notional difference in the way that Jurassic implements the global object.

awj
  • 7,482
  • 10
  • 66
  • 120

2 Answers2

1

I'm using jsRender + Jurassic to precompile my templates and generate js-files in T4. I spent a lot of time solving this problem and didn't find the answer, but read some articles, that helped.

See my code. It's working in my case. I'm sure I can help you to solve the issue, if this will not help:

var engine = new Jurassic.ScriptEngine();

var jsRenderPath = "/pathToDir/jsrender.js";
var jsUnevalPath = "/pathToDir/jsRenderUtils.js";
engine.ExecuteFile(jsRenderPath);
engine.ExecuteFile(jsUnevalPath);

engine.Evaluate("function renderTemplate(name, markup) { var tmpl = this.jsviews.templates(name, markup); return uneval(tmpl); }");

var compiledTemplateString = engine.CallGlobalFunction<string>("renderTemplate", templateName, templateString);

var result = "$.templates['" + templateName + "'] = " + compiledTemplateString + ";";

jsRenderUtils.js contents (uneval function)

function uneval(obj, known) {
    var root = (known === undefined), result;
    known = known || [];

    // some values fail eval() if not wrapped in a ( ) parenthesises
    var wrapRoot = function (result) {
        return root ? ("(" + result + ")") : result;
    };

    // special objects
    if (obj === null)
        return "null";
    if (obj === undefined)
        return "undefined";
    if (obj !== obj) // isNaN does type coercion, so can't use that.
        return "NaN";
    if (obj === Infinity)
        return "Infinity";
    if (obj === -Infinity)
        return "-Infinity";

    // atoms
    switch (typeof obj) {
        case 'function':
            return wrapRoot(obj.toString());
        case 'number':
        case 'boolean':
            return obj.toString();
        case 'string':
            return "\"" + obj.toString() + "\"";
    }

    // circular reference check for non-atoms
    if (known.indexOf(obj) !== -1)
        return "null";//throw new Error("Circular references detected while unevaling.");

    known.push(obj);

    // specialized types
    if (obj instanceof Array)
        return "[" + obj.map(function (o) { return uneval(o, known); }).join(",") + "]";

    if (obj instanceof Date)
        return wrapRoot("new Date('" + obj.toString() + "')");

    // hashes
    var key, pairs = [];
    for (key in obj) {
        var val;
        switch (key) {
            case "render":
                val = "$.fn.render";
                break;
            case "markup":
                val = "null";
                break;
            default:
                val = uneval(obj[key], known);
        }
        pairs.push("\r\n" + key + " : " + val);
    }

    return wrapRoot("{" + pairs.join(",") + "}");

};

UPDATED: If you will render templates without jquery, you should add this:

$ = window.jsviews;
$.fn = {
    render: function (data, view, j, u) {
        return this.fn(data, view, j, u);
    }
};
Marat Gallyamov
  • 197
  • 1
  • 7
  • I have a very simple template consisting of `
  • {{:Name}}
  • `, and this outputs (due to SO formatting, you'll have to copy-paste this into a text editor and format it yourself to check): `testJSTemplate: ({ markup : null, tmpls : [], links : {}, tags : {}, bnds : [], _is : "template", render : $.fn.render, tmplName : "testJSTemplate", htmlTag : "li", fn : function anonymous(data, view, j, u) { // testJSTemplate var j=j||jsviews,v,ret=""; try{ ret+="
  • "; ret+=(v=data.Name)!=u?v:""; ret+="
  • "; return ret; }catch(e){ return j._err(e); } } })` – awj Jun 17 '14 at 19:30
  • Does that look correct to you? I can't *quite* check by myself because I'm encountering other problems with using the template, but didn't want to leave it too long before responding to your answer. – awj Jun 17 '14 at 19:31
  • @awj Yes, it is correct. Important notice. You should use `$.templates['" + templateName + "']` to be able to use nested templates (`include` and `for`) I created jsfiddle and found out, that without jquery you have to define `$` and `$.fn.render`. PLease, check it out: [http://jsfiddle.net/krPnp/2/](http://jsfiddle.net/krPnp/2/) – Marat Gallyamov Jun 18 '14 at 17:41
  • Many thanks for your help, @Marat. I had abandoned this idea until your answer. – awj Jun 18 '14 at 19:39
  • You are welcome!) I'm happy that's my research on this helped somebody else besides me. – Marat Gallyamov Jun 19 '14 at 16:01
  • @MaratGallyamov hey was wondering if this code should work for rendering with jquery on the page. The Jurassic part works for me but when I run. $.templates['_Artist_Square'].render([]) I get Cannot read property 'getAttribute' of undefined due to function $fastRender(data, context, noIteration) { var tmplElem = this.jquery && (this[0] || error('Unknown template: "' + this.selector + '"')), tmpl = tmplElem.getAttribute(tmplAttr); – Spaceman Jul 23 '15 at 13:23
  • @Spaceman, yes it should work with jquery. But may be there was some updates in jsrender. Try to download some previous version, or try to adjust my code. I plan to do it later too. – Marat Gallyamov Aug 15 '15 at 18:44
  • @MaratGallyamov I have temporarily gone with handlebars to implement it with another post i found on SO but i will most likely be going back to try get jsRender to work with it as i prefer is to handlebars. If i have any luck ill update this comment, If you have any luck id love to here about it. – Spaceman Aug 17 '15 at 06:10