Can I apply Dart's string interpolation dynamically?

(from the Dart discussion mailing list by Tristan McNab)

I'm trying to build a server-side MVC framework and generating views based on templates and whatnot, and I was wondering if I could apply Dart's string interpolation dynamically. For example, this would be my view template:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>${ViewData["Title"]}</title>
    <link href="/Content/css/site.css" rel="stylesheet" />
  </head>
  <body>
    <h1>${ViewData["Title"]}</h1>
    <div id="container">
      <p>Hello world!</p>
    </div>
  </body>
</html>

And I'd like to apply the ViewData variable using:

static String applyViewData(String html, Map ViewData) {
    // apply interpolation here
}

Is this at all possible at the moment? My searching for the APIs indicates that it isn't.


(posted by Bill Hesse)

By wrapping the string literal in a function that takes the context as a parameter, you can have a Function : context -> String that you can pass around instead of a String. If you need to use some String operations, like concat, on these objects, you can implement these operations on a class encapsulating this type ("lifting" them). This seems like a straightforward way to give the string literal in one place, and give the data you want to interpolate in another.

String interpolation always happens dynamically, each time the literal is used, and the data can easily come from a parameter to a function rather than from the lexical context.

For example:

Function MyTemplate() {
   return (Context context) {
     return "<table><tr><td class=${context.leftColumnClass}>Red Sox</td><td>${context.data}</td></tr></table>";
   }
}

...

var templateHere = MyTemplate();

...

var output = templateHere(context);

You could also skip a level of indirection and just create

String FillMyTemplate(Context context) => '''
    <html><head><title>$context.title</title></head>
''';

and use FillMyTemplate where you need the template.


(posted by Sam McCall)

There's a trick involving noSuchMethod():

class Template {
  var _context;

  noSuchMethod(method, args) {
    if (!method.startsWith("get:")) return super.noSuchMethod(method, args);
    return _context[method.substring(4)];
  }

  abstract String template();

  String evaluate(context) {
    _context = context;
    try {
      return template();
    } finally { _context = null; }
  }
}

And then create a subclass:

class MyTemplate extends Template { template() => """
  <title>$title</title>
  <h1>$title</h1>
""";}

Finally, use it!

final renderedText = new MyTemplate().evaluate({"title": "Hello, world"})