Search code examples
knockout.jsjquery-templates

null values in jQuery Templates rendered incorrectly when using Knockout.js


Fearing it is a bug, hoping it is just me:

I am using a jquery.tmpl template in a knockout.js data-binding. The template uses the jquery.tmpl notation to bind to properties: ${Name}. It turns out that when the 'Name' property is null, the template renders a 4 letter string "null" into the HTML. This is awkward. (Note: Name is a ko.observable in this case).

If I use the template manually $("#rowTemplate").tmpl(item).appendTo($("tbody")) and Name is not an observable then jQuery template renders an empty string. This is much more like the behaviour I'd expect.

Drawing the whole thing one step further, if I do a null-check in the template by using:

{{if Name != null}}
    <input value="${Name}" />
{{/if}}

Then plain jQuery template does not render the input box. However, when using the template with knockout.js, the condition Name != null is false, because Name is an observable. On the other hand, {{if Name() != null}} is an invalid jQuery template syntax.

I did not find any way around this issue. Would anyone be able to tell me how to use jQuery templates correctly in conjunction with knockout.js? Or do I have to report a bug to the knockout project?


Example HTML document:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Test Page</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
    <script type="text/javascript" src="jquery.tmpl.min.js"></script>
    <script type="text/javascript" src="knockout-1.3.0beta.js"></script>
    <script type="text/javascript" src="knockout.mapping-latest.js"></script>
</head>
<body>
    <table>
        <thead><tr><th>Name</th></tr></thead>
        <tbody data-bind="template: { name: 'rowTemplate', foreach: items }"> </tbody>
    </table>

    <script id="rowTemplate" type="text/x-jquery-tmpl">
        {{if Name != null}}
            <tr><td>${Name}</td></tr>
        {{/if}}
    </script>
    <script type="text/javascript">
        var rawItems = [{ "Name": null }, { "Name": "Me"}];
        var boundData = {
            items: ko.mapping.fromJS(rawItems)
        };

        $(document).ready(function () {
            // Knockout way:
            ko.applyBindings(ko.mapping.fromJS({ items: rawItems }));

            // Non-knockout way:
            //$.each(rawItems, function () {
            //    $("#rowTemplate").tmpl(this).appendTo($("tbody"));
            //});
        });
    </script>
</body>
</html>

Solution

  • Doing {{if Name() != null}} or even just {{if Name()}} is the right syntax in the if. http://jsfiddle.net/rniemeyer/92ssx/.

    Otherwise, you can switch to <td data-bind="text: Name"></td> which will at least render empty string. One additional alternative is to do ${Name() ? Name : ''}