0

I've written some days ago a simple templating function, problem now is that whitespace will kill the tag. So when I type something like this: <div id="str">#{my} #{name} #{is} #{#{a}}</div> it works well, however the following does not: <div id="str">#{my} #{name} #{is} #{ #{a} }</div>.

Here's what I've done so far:

$.tmpl = function(str, obj) {
    do {
        var beforeReplace = str;
        for(var key in obj) {
            str = str.replace("#{" + key + "}", obj[key]);
        }
        var afterReplace = str !== beforeReplace;
    } while (afterReplace);

    return str;
};


var map = {
    my: "Am",
    name: "I",
    is: "<a href='#'>awesome</a>",
    a: "#{b}",
    b: "c",
    c: "?"
};

$("#str").html(function(index, oldhtml) {
    $(this).html( $.tmpl(oldhtml, map) );
});

How can i get it working if I use #{a} and #{ #{a} }. I know, it is possible, and even simple, however I am not a regexp pro.

This works.

This fails.

yckart
  • 32,460
  • 9
  • 122
  • 129
  • [Underscore.js provides a template system](http://stackoverflow.com/questions/4778881/how-to-use-underscore-js-as-a-template-engine) maybe this is all you need :) – jantimon Feb 14 '13 at 16:53
  • @Ghommey certainly, but i love this solution, and further, I've this problem often ;) – yckart Feb 14 '13 at 16:57
  • What do you mean by "will kill the tag"? Could you please provide a jsfiddle? – jantimon Feb 14 '13 at 16:59
  • @Ghommey The tag is not longer replaced... I updated my question. – yckart Feb 14 '13 at 17:04

2 Answers2

4

You need to trim the whitespace from the key you're replacing. To do this, you can use a function as the second parameter to the String.replace() function. You can also replace all the matches for each pass at once using a function like this. So, try the following:

$.tmpl = function(str, obj) {
    do {
        var beforeReplace = str;
        str = str.replace(/#{([^}]+)}/g, function(wholeMatch, key) {
            var substitution = obj[$.trim(key)];
            return (substitution === undefined ? wholeMatch : substitution);
        });
        var afterReplace = str !== beforeReplace;
    } while (afterReplace);

    return str;
};

The regexp /#{([^}]+)}/g finds all occurrences (the "g" means "global") of the any string that starts with "#{", then has at least one character that is not a "}", then ends with a "}". The parentheses simply serve to group the key by itself so that the regexp engine will pull it out for us as a parameter to the function.

The other regexp replacement (/^\s+|\s+$/g) just trims the string. It finds all leading and trailing whitespace and removes it. In ECMAScript 5, there is a String.trim() function that provides this functionality built-in, but that is not widespread enough to reliably use it. See the MDN documentation for details.

However, I'm not sure of the wisdom of doing multiple passes like this. You introduce the possibility of infinite looping:

var map = {
    a: "#{b}",
    b: "#{a}"
};

$.tmpl("#{a}", map);

You also introduce the possibility that the values in the map might accidentally contain #{...} and be expanded without you intending to. This is particularly worrisome if those values are entered by the user. In that case, you have just given him the possibility to make your app loop infinitely or expose the values of other keys. Most template engines do not provide the ability to repeatedly substitute parameters in this fashion.

Eric Galluzzo
  • 3,191
  • 1
  • 20
  • 20
1

(function($) {
  $.fn.tmpl = function(obj) {
    var _this = this,
      el = $(this);

    return (function() {
      var original = el.html();

      el.html(el.html().replace(/{{([^}}]+)}}/g, function(wholeMatch, key) {
        var substitution = obj[$.trim(key)];

        return typeof substitution == 'undefined' ? wholeMatch : substitution;
      }));

      return el.html() == original ? _this : $(el).tmpl(obj);
    })();
  };
})(jQuery);

$($('#template').html())
  .tmpl({
    greeting: 'Hello',
    wish: 'everyone a wonderful day'
  })
  .appendTo($('#results'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<div id="results"></div>

<template id="template">
  <p>{{greeting}}, I just wanted to wish <b>{{wish}}</b>!</p>
</template>
Andrew
  • 569
  • 4
  • 5