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

Scandalbars

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Mark Wunsch 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

Avatar for Mark Wunsch

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.