How can I have a prototype.js Template encode HTML entities?

If I have a prototype.js Template which generates HTML code from a JavaScript object, how can I have it automatically escape HTML entities in the JavaScript object's data so that the HTML doesn't break?

Sample JavaScript code:

var data = {
    name: 'Josh',
    url: 'http://josh.gitlin.name/',
    statement: 'I\'m a JavaScript and PHP developer. Find me on Stack Overflow and say "hi"!'
}

var template = new Template(
    '<div><label>Name: <input name=\"name\" value=\"#{name}\"></label></div>'+
    '<div><label>URL: <input name=\"url\" value=\"#{url}\"></label></div>'+
    '<div><label>Personal Statement: <input name=\"statement\" value=\"#{statement}\"></label></div>'
);

$('test').update(template.evaluate(data));

JSFiddle code for the above

The above code produces a malformed <input> tag because data.statement contains a quotation mark ". How can I have the Template automatically escape HTML entities in the above example without modifying the data object nor cloning the data object? ‚Äč

Answers


Reading the source code, there is nothing in the Template that would escape html. Furthermore, the provided String#escapeHTML is pretty terrible as it attempts to strips tags (wtf) with regex (wtf2) and doesn't even escape quotes.

You could manually escape your strings with this:

function escapeHTML(text) {
    var map = {
        "&" : "amp",
        "'": "#39",
        '"': "quot",
        "<": "lt",
        ">": "gt"

    };

    return text.replace( /[&'"<>]/g, function(m) {
        return "&" + map[m] + ";";
    });
}

In order to do this, two things need to happen:

  1. The escape method of the Template class must know to escape HTML entities
  2. The escapeHTML method which Prototype.js adds to the String object must be extended to encode quotation marks into &quot; entities. (And possibly more. It's way too simplistic right now...)

Both these two can be achieved with the following code:

String.prototype.escapeHTML = String.prototype.escapeHTML.wrap(function(proceed){
    return proceed().replace(/"/g,'&quot;');
});

Template.addMethods({
    evaluateEscapeHTML: function(object) {
        if (object && Object.isFunction(object.toTemplateReplacements))
          object = object.toTemplateReplacements();

        return this.template.gsub(this.pattern, function(match) {
          if (object == null) return (match[1] + '');

          var before = match[1] || '';
          if (before == '\\') return match[2];

          var ctx = object, expr = match[3],
              pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;

          match = pattern.exec(expr);
          if (match == null) return before;

          while (match != null) {
            var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
            ctx = ctx[comp];
            if (null == ctx || '' == match[3]) break;
            expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
            match = pattern.exec(expr);
          }

          return before + String.interpret(ctx).escapeHTML();
        });
    }
});

This code will extend Prototype.js and add a new evaluateEscapeHTML method to Templates, so this code should be run once, and from then on, any Template will now have a new evaluateEscapeHTML method:

var template = new Template(
    '<div><label>Name: <input name=\"name\" value=\"#{name}\"></label></div>'+
    '<div><label>URL: <input name=\"url\" value=\"#{url}\"></label></div>'+
    '<div><label>Personal Statement: <input name=\"statement\" value=\"#{statement}\"></label></div>'
);

$('test').update(template.evaluate(data));

JSFiddle to test this

Note that I had to duplicate the entire evaluate method from the Template class from prototype.js version 1.7. This has a major downside in that if future versions of the evaluate method are improved, my code needs to be improved also. I couldn't find any better way to do this, however.

The specific modification to the evaluate method which I made was to change this line:

return before + String.interpret(ctx);

to:

return before + String.interpret(ctx).escapeHTML();

That's all, which is why it's such a shame I couldn't somehow extend or wrap the rest of the code...


Need Your Help

phpmailer SMTP connect() failed on localhost

php phpmailer

I'm running php5.6 on a localhost using xampp, and I'm trying to send a message using phpmailer, but it gives me this error : SMTP connect() failed.

Changing View Controllers Behind a Modal View Controller

ios xcode swift uistoryboardsegue unwind-segue

I want to create something similar to done/edit page for the Contacts app. It seems that there is a change in a view behind a modal view, however I'm not sure how that produce that behavior.