Archive

HTML helpers in the "jquery.tmpl.js view engine"

In my last post I was showing how you can build a JavaScript MVC framework around the jquery.tmpl.js plugin. To finish this up, every good MVC framework needs HTML helpers. The jquery.tmpl.js plugin already supports calling functions from the template, but has a hook that encodes all HTML that come out of these functions. What I was hoping to do was to have a nice little helper that would create button based on the text and href params, something like this:

...

{{each pages}}
        <tr class="cr_${id} alt">
            <td>
                <a href="/${page_url}">${page_title}</a>
            </td>
            <td>
                ${page_url}
            </td>
            <td>
                ${page_status}
            </td>
            <td>
                ${page_date_modified}
            </td>
            <td>
               
                ${CMS.Helpers.button("edit", "#ajax/edit")}

            </td>
        </tr>
        {{/each}} ...

Obviously, the helper needs to be defined as a global object, so let's create something like this:

        window.CMS = {

            Helpers: {

                button: function (text, href) {

                    return "<a href=\"" + href + "\" class=\"button\">" + text + "</a>";
                }

            }

        };

Now in order to make this work, I had to comment out the line where it HTML encodes all content returned by functions from the template:

From this:

....

encode: function (text) {
            // Do HTML encoding replacing < > & and ' and " by corresponding entities.
            return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'");
        }

....

To this:

....

encode: function (text) {
            return text;
        }
....

Update: so I figured out a proper way render HTML helpers using the "HTML property" in the markup and the "options" which let's you define private methods used within the template. It is not quite obvious it's possible at first, but it actually works out pretty sweet. So here is the magic:

(function ($) {

    //HTML helpers
    var CMS = {

        Helpers: {

            button: function (text, href) {

                return "<a href=\"" + href + "\" class=\"button\">" + text + "</a>";
            }

        }

    };

    var sucessIndex = 0,
            view,
            model,
            contentDiv = $("#content_div"),
            controler = function (m, v) {

                var tmplOptions = CMS,
                    html = $.tmpl(v, m, tmplOptions), //merge page model with view
                    content = $(html), //turn html into jQuery object
                    links = $("a", content); //query this object (before it even touches DOM)

                links.click(function () {

                    alert("You clicked me!");

                    return false;
                });

                //put content on the page (in the DOM)
                contentDiv.html(content);

            };

    //get view
    $.ajax({
        type: "Get",
        url: "ajax/getView/test",
        dataType: "html",
        success: function (v) {

            view = v;

            //checks if model was loaded borefore running controler
            if (sucessIndex > 0) {
                controler(model, view);
            }
            else {
                sucessIndex++;
            }

        }
    });

    //get model
    $.ajax({
        type: "Get",
        url: "ajax/jPageModle",
        dataType: "json",
        success: function (m) {

            model = m;

            //checks if view was loaded borefore running controler
            if (sucessIndex > 0) {
                controler(model, view);
            }
            else {
                sucessIndex++;
            }

        }
    });


})(jQuery);

... and the template would now look something like this:

...

            
                {{html $item.Helpers.button("edit", "#ajax/edit")}}
            
...

So another great benefit of this approach is that we can fully avoid using global variables. Also like the fact that the "options" object can go "deep" meaning, you can "import" a full namespace of functions (as I am doing by putting the "helpers" in the "Helpers" namespace, that can be referenced in the view. This is really powerful!

I love this new paradigm, and if the browser can handle it, I suppose the only thing left on the wish list is that Boris would together with is Scott Guthrie from the MVC3 and base ASP.NET MVC3 Razor and jquery.tmpl.js on the same syntax. That would really kick ass!

Comments: