Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scandalbars

Mark Wunsch
September 30, 2013

 Scandalbars

At Gilt (gilt.com), we built a Scala implementation of the Handlebars templating language (originally written in JavaScript) to use as our go-to language for building reusable web views. This talk will cover why we would ever attempt such a thing and how we went about doing it.

See: http://tech.gilt.com/post/59708050425/handlebars-scala-a-handlebars-for-scala

Mark Wunsch

September 30, 2013
Tweet

More Decks by Mark Wunsch

Other Decks in Programming

Transcript

  1. Agenda • What is this whole templating thing all about?

    • Why even bother? • How did you do this crazy thing?
  2. <hgroup class="look-name"> <c:if test="${!hideBrandName}"> <h2 class="brand-name"> <c:choose> <c:when test="${!empty detailedBrandOpt

    && detailedBrandOpt.isPresent}"> <a class="primary" href="/brand/$ {detailedBrandOpt.get.urlKey}">${brandName}</a> <gilt:favorite brandId="${brandId}" brandName="$ {brandName}" tooltipLocation="${favoriteToolTipLoc}"/> </c:when> <c:otherwise> <div class="primary">${brandName} <gilt:favorite brandId="${brandId}" brandName="${brandName}" tooltipLocation="$ {favoriteToolTipLoc}"/> </div> </c:otherwise> </c:choose>
  3. // Template <script id="entry-template" type="text/x-handlebars-template"> <div class="entry"> <h1>{{title}}</h1> <div class="body">

    {{body}} </div> </div> </script> // Code var source = $("#entry-template").html(); var template = Handlebars.compile(source); var context = {title: "My New Post", body: "This is my first post!"} var html = template(context); // Result <div class="entry"> <h1>My New Post</h1> <div class="body"> This is my first post! </div> </div>
  4. “Maybe I’ll just write Handlebars in Scala,” I finally said

    to Tom. We were at a bar at the time, and I was a bit tipsy, but there was a “yeah, sure, do that” vibe to our conversation. http://tech.gilt.com/post/59708050425/handlebars-scala-a-handlebars-for-scala
  5. %start root %% root : program EOF { return $1;

    } ; program : simpleInverse statements { $$ = new yy.ProgramNode([], $2); } | statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); } | statements simpleInverse { $$ = new yy.ProgramNode($1, []); } | statements { $$ = new yy.ProgramNode($1); } | simpleInverse { $$ = new yy.ProgramNode([], []); } | "" { $$ = new yy.ProgramNode([]); } ; statements : statement { $$ = [$1]; } | statements statement { $1.push($2); $$ = $1; } ; statement : openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); } | openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); } | mustache { $$ = $1; } | partial { $$ = $1; } | CONTENT { $$ = new yy.ContentNode($1); } | COMMENT { $$ = new yy.CommentNode($1); }
  6. λ

  7. def *: Parser[List[T]] def >>[U]:(fq: (T) => Parser[U]): Parser[U] def

    ^^[U]:(f: (T) => U): Parser[U] def |[U>: T](q:=> Parser[U]): Parser[U] def ~[U](q:=> Parser[U]): Parser[~[T,U]] Combinators
  8. def *: Parser[List[T]] def >>[U]:(fq: (T) => Parser[U]): Parser[U] def

    ^^[U]:(f: (T) => U): Parser[U] def |[U>: T](q:=> Parser[U]): Parser[U] def ~[U](q:=> Parser[U]): Parser[~[T,U]] Combinators
  9. def ident: Parser[String] // Anything that is a valid Java

    identifier, according the The Java Language Spec. def wholeNumber: Parser[String] // An integer, without sign or with a negative sign. def stringLiteral: Parser[String] // Double quotes (") enclosing a sequence of... implicit def regex(r: Regex): Parser[String] implicit def literal(s: String): Parser[String] scala.util.parsing.combinator.JavaTokenParsers
  10. def statements = rep1(statement) def statement = { inverseBlock |

    block | mustache | partial | CONTENT ^^ { Content(_) } | comment }
  11. def statements = rep1(statement) def statement = { inverseBlock |

    block | mustache | partial | CONTENT ^^ { Content(_) } | comment } statements : statement { $$ = [$1]; } | statements statement { $1.push($2); $$ = $1; } ; statement : openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); } | openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); } | mustache { $$ = $1; } | partial { $$ = $1; } | CONTENT { $$ = new yy.ContentNode($1); } | COMMENT { $$ = new yy.CommentNode($1); } ;
  12. def mustache: Parser[Mustache] = { mustachify(pad(inMustache)) ^^ { mustacheable(_) }

    | mustachify("&" ~> pad(inMustache)) ^^ { mustacheable(_, true) } | mustachify("{" ~> pad(inMustache) <~ "}") ^^ { mustacheable(_, true) } }
  13. // Template val template = """ <p>Hello, my name is

    {{name}}. I am from {{hometown}}. I have {{kids.length}} kids:</p> <ul> {{#kids}}<li>{{name}} is {{age}}</li>{{/kids}} </ul> """ // Object object Guy { val name = "Alan" val hometown = "Somewhere, TX" val kids = Seq(Map( "name" -> "Jimmy", "age -> "12" ), Map( "name" -> "Sally", "age" -> "4" )) } // Result scala> val t = Handlebars(template) t: com.gilt.handlebars.Handlebars = com.gilt.handlebars.Handlebars@496d864e scala> t(Guy) res0: String = " <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p> <ul> <li>Jimmy is 12</li><li>Sally is 4</li> </ul> "
  14. Implicit conversions will not work in a template. Because Handlebars.scala

    makes heavy use of reflection. Bummer, I know. This leads me too... Handlebars.scala makes heavy use of reflection. This means that there could be unexpected behavior. Method overloading will behave in bizarre ways. There is likely a performance penalty. I'm not sophisticated enough in the arts of the JVM to know the implications of this.