Managing Plugins in WPMU

John Resig released a small script that let’s you easily do some “quick-and-dirty” templating on the client side. I love the idea, but I’m less than a fan of the implementation, but I’ll get to that in a minute. First the love: This small script lets you essentially copy and paste some HTML and hot swap out new data as it’s [presumably] ajax’d in, and that is incredibly handy. You even get fancy template style syntax like “<% for(..) %>”  So how is this done?

Templates are be defined in script blocks (notice the type attribute):

<script type="text/html" id="user_tmpl">
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users&#91;i&#93;.url%>"><%=users&#91;i&#93;.name%></a></li>
<% } %>
</script>

And to use the template is simply:

var results = document.getElementById("results");
results.innerHTML = tmpl("item_tmpl", dataObject);

It’s easy, to be sure, and the results are slick. I’ve found myself needing to do this sort of copy/paste of DOM structure many many many times when building Jotlet. So why don’t I like it? Well let’s take a look at the magic that makes this possible:

// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
  
  this.tmpl = function tmpl(str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
      
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
        
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
        
        // Convert the template into pure JavaScript
        str
          .replace(/[rtn]/g" ")
          .split("<%").join("t")
          .replace(/((^|%>)[^t]*)'/g, "$1r")
          .replace(/t=(.*?)%>/g, "',$1,'")
          .split("t").join("');")
          .split("%>").join("p.push('")
          .split("r").join("'")
      + "');}return p.join('');");
    
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
})();

So what’s going on here? The template string input is being converted to raw JavaScript that, when executed, will return HTML. The “new Function()” basically wraps that JS in an closure to be eval()’d if/when there’s data. If data was passed in to templ(), then the Function() is executed immediately and the result HTML is returned. if no data is passed in, then the Function is returned.

I’ll be honest, when I see String data being turned into code I shudder. I subscribe to the “don’t make it any more complicated than it needs to be” paradigm, and storing code as data or vice versa does exactly that. It adds more complication for IMO no benefit. Also – there’s the security risk. If you never mix code and data, then if your data becomes compromised or corrupted, you can still be 100% sure that your code is still safe. But if you mix code and data, and your data gets fudged, then your code is equally screwed.

It’s just good practice: data is data and code is code.

Luckily, one of Resig’s commenters, Iraê, proposes in my view a much better (and safer) solution:

markup={};
markup.user = function() {
html='
<dl>
<dt>'+this.name+'</dt>
<dd>'+this.about+'</dd>
</dl>';
return html;
}

And to use it:

$('#output').html(markup.user.apply(data));

This way is so much cleaner! The only Strings in this solution is the data itself, and the ‘code’ of the template is… JavaScript itself! Less confusing. Less code to write. Less security risk. What’s not to like?

 

Last Thoughts:

I mentioned we had a use for this sort of thing when we built Jotlet. So what did we do? We built classes for each of our UI elements. Code looked something like:

var taskRow = new TaskRow();
taskRow.setTask(taskObj);
document.body.appendChild(taskRow.getDOM());

The DOM for that task could either be cached, or used for another task with a call to:

taskRow.setTask(otherTaskObj);

It’s a far different solution (and heavier weight) than what’s being talked about here. I’ll save details for another post 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *