Archive

javascript MVC view engine with jquery.tmpl.js

I have been looking on JavaScript MVC frameworks laterally and was able to ceate a demo build on the query.tmpl.js jQuery plugin developed by Boris Moore (also see John Resig the author of jQuery being involved in this project). My goal was to mimic full MVC paradigm where JSON would be the model, and view could be separated into an .HTML file, so that when editing, I would get highlighting, and editing it would be user friendly. In this demo, I am using ASP.NET MVC3 and VS 2010, but this paradigm could be used with any type of backend.

First the view: in a separated file, called test.html, I wrote this markup representing the page view (since this content is to be loaded via ajax, into an "ajax div" there is not header or footer, since these are already on the page):

<div class="hr">
<h1 class="icon_page_l">
    ${page_title} <span>Test</span>
</h1>
</div>

<table class="data_table width_full">
    <tbody>
        <tr>
            <th>
                Title
            </th>
            <th>
                Url
            </th>
            <th>
                Status
            </th>
            <th>
                Date Modified
            </th>
            <th>
            </th>
        </tr>

        {{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>
                <a class="ajax_action button right" href="#ajax/editPage/${id}/">Edit</a>
                <a class="ajax_post_action button margin_right right"
                    href="#ajax/deletePage/${id}/">Delete</a>
            </td>
        </tr>
        {{/each}}

        </tbody>
</table>

This is what I have in the backed ASP.NET MVC, pretty much a mapped string method that will load my HTML file, and minify it (put all in one line) on the fly. Nothing too fancy, but it just allows me to have my "views" nicely formated:

        public string getView(string p1, string p2, string p3)
        {

            string jsView = "~/Content/js/Views/" + p1 + ".html";

            return CMS.Util.Ajax.minifyJs(CMS.Util.Ajax.LoadStaticFile(jsView));

        }

Next, I create my model, which is ASP.NET MVC JSON result. The .NET MVC proved to be really powerful here, with few lines of code I can create wrapper object with two child objects each representing a DB table.

        public JsonResult jPageModle(string p1, string p2, string p3)
        {

            //data context
            var dataContext = new linqDataContext();

            //query zones
            var zones = from m in dataContext.zones
                    select m;

            //query pages
            var pages = from m in dataContext.pages
                    select m;

            //create page model
            var pageModel = new {
                page_title = "Hello World",
                pages = pages,
                zones = zones
            };

            return Json(pageModel, JsonRequestBehavior.AllowGet);

        }

A couple notes here, in ASP.NET MVC3 I believe, by default, JSON results will need to be POSTS, for some security reasons. Well I am kind of purist here, so I am setting it to get.

This is what comes out of the JSON result as our model:

{
    "page_title": "Hello World",
    "pages": [{
        "id": 6,
        "page_url": "index",
        "page_html": "",
        "page_author": "dpirek@gmail.com",
        "page_settings": null,
        "page_title": "účetnictví on-line",
        "page_meta_description": "",
        "page_meta_keywords": "",
        "page_status": 1,
        "page_date_modified": "\/Date(1287169574310)\/"
    },
    {
        "id": 10,
        "page_url": "about",
        "page_html": "",
        "page_author": "",
        "page_settings": null,
        "page_title": "About Page",
        "page_meta_description": "",
        "page_meta_keywords": "",
        "page_status": 1,
        "page_date_modified": "\/Date(1276185200060)\/"
    },
    {
        "id": 31,
        "page_url": "test",
        "page_html": "test",
        "page_author": "",
        "page_settings": null,
        "page_title": "ted a asf asf asf",
        "page_meta_description": "",
        "page_meta_keywords": "",
        "page_status": 1,
        "page_date_modified": "\/Date(1287422731090)\/"
    }]
}

And now comes the controller: (before you run this, make sure you have the references to the latest jQuery and the jquery.tmpl.js plugin.

(function ($) {

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

                var html = $.tmpl(v, m), //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);

A few notes here:

One of the dilemmas was how to make two ajax calls (one for view, one for model) and make sure the controller runs only after both of them are loaded. I originally made the calls nested with was really ugly from performance point of view, so I created "sucessIndex" which basically a number which keeps track on successful ajax calls, and after each new successful ajax call this index is checked if it reached high enough, so that we have all we need and can run our controller.

Another nice feature that I am showing here is how you can bind element events before the content ever reaches the DOM: Creating jQuery object out of HTML string: $(html) which can then be queried inside, and added events. This pattern is commonly used inside of jQuery UI where a lot of HTML flies around, and is the most efficient way to create larger chunks of HTML code with events attached to it.

Comments: