Permalink
Browse files

Merge pull request #942 from julienrf/clean/template-formats

Clean code defining template formats and add support for JavaScript templates
  • Loading branch information...
2 parents d06b7f6 + 0b0b37d commit 633d9af79dd7eb5824096ed5ac57d66c78ceb4e0 @jroper jroper committed Apr 8, 2013
Showing with 318 additions and 153 deletions.
  1. +59 −0 documentation/manual/javaGuide/main/templates/JavaCustomTemplateFormat.md
  2. +2 −2 documentation/manual/javaGuide/main/templates/JavaTemplateUseCases.md
  3. +1 −0 documentation/manual/javaGuide/main/templates/_Sidebar.md
  4. +74 −0 documentation/manual/scalaGuide/main/templates/ScalaCustomTemplateFormat.md
  5. +1 −1 documentation/manual/scalaGuide/main/templates/ScalaTemplateUseCases.md
  6. +1 −0 documentation/manual/scalaGuide/main/templates/_Sidebar.md
  7. +4 −3 framework/project/Tasks.scala
  8. +6 −0 framework/src/play/src/main/scala/play/api/http/ContentTypeOf.scala
  9. +64 −70 framework/src/play/src/main/scala/play/api/templates/Templates.scala
  10. +5 −6 framework/src/sbt-plugin/src/main/scala/PlayCommands.scala
  11. +1 −1 framework/src/sbt-plugin/src/main/scala/PlayKeys.scala
  12. +8 −6 framework/src/sbt-plugin/src/main/scala/PlaySettings.scala
  13. +2 −1 framework/src/templates-compiler/src/main/scala/play/templates/ScalaTemplateCompiler.scala
  14. +3 −0 framework/src/templates-compiler/src/test/scala/FakeRuntime.scala
  15. +1 −1 framework/src/templates-compiler/src/test/scala/TemplateCompilerSpec.scala
  16. +19 −1 framework/src/templates/src/main/scala/play/api/templates/ScalaTemplate.scala
  17. +4 −0 samples/scala/websocket-chat/app/controllers/Application.scala
  18. +1 −56 samples/scala/websocket-chat/app/views/chatRoom.scala.html
  19. +56 −0 samples/scala/websocket-chat/app/views/chatRoom.scala.js
  20. +1 −1 samples/scala/websocket-chat/app/views/main.scala.html
  21. +5 −4 samples/scala/websocket-chat/conf/routes
@@ -0,0 +1,59 @@
+# Adding support for a custom format to the template engine
+
+The built-in template engine supports common template formats (HTML, XML, etc.) but you can easily add support for your own formats, if needed. This page summarizes the steps to follow to support a custom format.
+
+## Overview of the the templating process
+
+The template engine builds its result by appending static and dynamic content parts of a template. Consider for instance the following template:
+
+```
+foo @bar baz
+```
+
+It consists in two static parts (`foo ` and ` baz`) around one dynamic part (`bar`). The template engine concatenates these parts together to build its result. Actually, in order to prevent cross-site scripting attacks, the value of `bar` can be escaped before being concatenated to the rest of the result. This escaping process is specific to each format: e.g. in the case of HTML you want to transform “<” into “&amp;lt;”.
+
+How does the template engine know which format correspond to a template file? It looks at its extension: e.g. if it ends with `.scala.html` it associates the HTML format to the file.
+
+In summary, to support your own template format you need to perform the following steps:
+
+* Implement the text integration process for the format ;
+* Associate a file extension to the format.
+
+## Implement a format
+
+Implement the `play.templates.Format<A>` interface that has the methods `A raw(String text)` and `A escape(String text)` that will be used to integrate static and dynamic template parts, respectively.
+
+The type parameter `A` of the format defines the result type of the template rendering, e.g. `Html` for a HTML template. This type must be a subtype of the `play.templates.Appendable<A>` trait that defines how to concatenates parts together.
+
+For convenience, Play provides a `play.api.templates.BufferedContent<A>` abstract class that implements `play.templates.Appendable<A>` using a `StringBuilder` to build its result and that implements the `play.mvc.Content` interface so Play knows how to serialize it as an HTTP response body.
+
+In short, you need to write to classes: one defining the result (implementing `play.templates.Appendable<A>`) and one defining the text integration process (implementing `play.templates.Format<A>`). For instance, here is how the HTML format could be defined:
+
+```java
+public class Html extends BufferedContent<Html> {
+ public Html(StringBuilder buffer) {
+ super(buffer);
+ }
+ String contentType() {
+ return "text/html";
+ }
+}
+
+public class HtmlFormat implements Format<Html> {
+ Html raw(String text: String) { … }
+ Html escape(String text) { … }
+ public static final HtmlFormat instance = new HtmlFormat(); // The build process needs a static reference to the format (see the next section)
+}
+```
+
+## Associate a file extension to the format
+
+The templates are compiled into a `.scala` files by the build process just before compiling the whole application sources. The `sbt.PlayKeys.templatesTypes` key is a sbt setting of type `Map[String, String]` defining the mapping between file extensions and template formats. For instance, if you want Play to use your onw HTML format implementation you have to write the following in your build file to associate the `.scala.html` files to your custom `my.HtmlFormat` format:
+
+```scala
+templatesTypes += ("html" -> "my.HtmlFormat.instance")
+```
+
+Note that the right side of the arrow contains the fully qualified name of a static value of type `play.templates.Format<?>`.
+
+> **Next:** [[HTTP form submission and validation | JavaForms]]
@@ -127,7 +127,7 @@ Again, there’s nothing special here. You can just call any other template you
```
## moreScripts and moreStyles equivalents
-
+> **Next:** [[HTTP form submission and validation | ScalaForms]]
To define old moreScripts or moreStyles variables equivalents (like on Play! 1.x) on a Scala template, you can define a variable in the main template like this :
```html
@@ -183,4 +183,4 @@ And on an extended template that not need an extra script, just like this :
}
```
-> **Next:** [[HTTP form submission and validation | JavaForms]]
+> **Next:** [[Custom formats | JavaCustomTemplateFormat]]
@@ -2,6 +2,7 @@
- [[Templates syntax | JavaTemplates]]
- [[Common use cases | JavaTemplateUseCases]]
+- [[Custom formats | JavaCustomTemplateFormat]]
### Main concepts
@@ -0,0 +1,74 @@
+# Adding support for a custom format to the template engine
+
+The built-in template engine supports common template formats (HTML, XML, etc.) but you can easily add support for your own formats, if needed. This page summarizes the steps to follow to support a custom format.
+
+## Overview of the the templating process
+
+The template engine builds its result by appending static and dynamic content parts of a template. Consider for instance the following template:
+
+```
+foo @bar baz
+```
+
+It consists in two static parts (`foo ` and ` baz`) around one dynamic part (`bar`). The template engine concatenates these parts together to build its result. Actually, in order to prevent cross-site scripting attacks, the value of `bar` can be escaped before being concatenated to the rest of the result. This escaping process is specific to each format: e.g. in the case of HTML you want to transform “<” into “&amp;lt;”.
+
+How does the template engine know which format correspond to a template file? It looks at its extension: e.g. if it ends with `.scala.html` it associates the HTML format to the file.
+
+Finally, you usually want your template files to be used as the body of your HTTP responses, so you have to define how to make a Play result from a template rendering result.
+
+In summary, to support your own template format you need to perform the following steps:
+
+* Implement the text integration process for the format ;
+* Associate a file extension to the format ;
+* Eventually tell Play how to send the result of a template rendering as an HTTP response body.
+
+## Implement a format
+
+Implement the `play.templates.Format[A]` trait that has the methods `raw(text: String): A` and `escape(text: String): A` that will be used to integrate static and dynamic template parts, respectively.
+
+The type parameter `A` of the format defines the result type of the template rendering, e.g. `Html` for a HTML template. This type must be a subtype of the `play.templates.Appendable[A]` trait that defines how to concatenates parts together.
+
+For convenience, Play provides a `play.api.templates.BufferedContent[A]` abstract class that implements `play.templates.Appendable[A]` using a `StringBuilder` to build its result and that implements the `play.api.mvc.Content` trait so Play knows how to serialize it as an HTTP response body (see the last section of this page for details).
+
+In short, you need to write to classes: one defining the result (implementing `play.templates.Appendable[A]`) and one defining the text integration process (implementing `play.templates.Format[A]`). For instance, here is how the HTML format is defined:
+
+```scala
+// The `Html` result type. We extend `BufferedContent[Html]` rather than just `Appendable[Html]` so
+// Play knows how to make an HTTP result from a `Html` value
+class Html(buffer: StringBuilder) extends BufferedContent[Html](buffer) {
+ val contentType = MimeTypes.HTML
+}
+
+object HtmlFormat extends Format[Html] {
+ def raw(text: String): Html =
+ def escape(text: String): Html =
+}
+```
+
+## Associate a file extension to the format
+
+The templates are compiled into a `.scala` files by the build process just before compiling the whole application sources. The `sbt.PlayKeys.templatesTypes` key is a sbt setting of type `Map[String, String]` defining the mapping between file extensions and template formats. For instance, if HTML was not supported out of the box by Play, you would have to write the following in your build file to associate the `.scala.html` files to the `play.api.templates.HtmlFormat` format:
+
+```scala
+templatesTypes += ("html" -> "play.api.templates.HtmlFormat")
+```
+
+Note that the right side of the arrow contains the fully qualified name of a value of type `play.templates.Format[_]`.
+
+## Tell Play how to make an HTTP result from a template result type
+
+Play can write an HTTP response body for any value of type `A` for which it exists an implicit `play.api.http.Writeable[A]` value. So all you need is to define such a value for your template result type. For instance, here is how to define such a value for HTTP:
+
+```scala
+implicit def writableHttp(implicit codec: Codec): Writeable[Http] =
+ Writeable[Http](result => codec.encode(result.body), Some(ContentTypes.HTTP))
+```
+
+> **Note:** if your template result type extends `play.api.templates.BufferedContent` you only need to define an
+> implicit `play.api.http.ContentTypeOf` value:
+> ```scala
+> implicit def contentTypeHttp(implicit codec: Codec): ContentTypeOf[Http] =
+> ContentTypeOf[Http](Some(ContentTypes.HTTP))
+> ```
+
+> **Next:** [[HTTP form submission and validation | ScalaForms]]
@@ -181,4 +181,4 @@ And on an extended template that not need an extra script, just like this :
}
```
-> **Next:** [[HTTP form submission and validation | ScalaForms]]
+> **Next:** [[Custom format | ScalaCustomTemplateFormat]]
@@ -2,6 +2,7 @@
- [[Scala templates syntax | ScalaTemplates]]
- [[Common use cases | ScalaTemplateUseCases]]
+- [[Custom format | ScalaCustomTemplateFormat]]
### Main concepts
@@ -148,7 +148,8 @@ object Tasks {
(file("src/anorm/src/main/scala") ** "*.scala").get ++
(file("src/play-filters-helpers/src/main/scala") ** "*.scala").get ++
(file("src/play-jdbc/src/main/scala") ** "*.scala").get ++
- (file("src/play/target/scala-" + sbv + "/src_managed/main/views/html/helper") ** "*.scala").get
+ (file("src/play/target/scala-" + sbv + "/src_managed/main/views/html/helper") ** "*.scala").get ++
+ (file("src/templates/src/main/scala") ** "*.scala").get
val options = Seq("-sourcepath", base.getAbsolutePath, "-doc-source-url", "https://github.com/playframework/Play20/tree/" + BuildSettings.buildVersion + "/framework€{FILE_PATH}.scala")
new Scaladoc(10, cs.scalac)("Play " + BuildSettings.buildVersion + " Scala API", sourceFiles, classpath.map(_.data) ++ allJars, file("../documentation/api/scala"), options, s.log)
@@ -192,9 +193,9 @@ object Tasks {
(sourceDirectory ** "*.scala.html").get.foreach {
template =>
- val compile = compiler.getDeclaredMethod("compile", classOf[java.io.File], classOf[java.io.File], classOf[java.io.File], classOf[String], classOf[String], classOf[String])
+ val compile = compiler.getDeclaredMethod("compile", classOf[java.io.File], classOf[java.io.File], classOf[java.io.File], classOf[String], classOf[String])
try {
- compile.invoke(null, template, sourceDirectory, generatedDir, "play.api.templates.Html", "play.api.templates.HtmlFormat", "import play.api.templates._\nimport play.api.templates.PlayMagic._")
+ compile.invoke(null, template, sourceDirectory, generatedDir, "play.api.templates.HtmlFormat", "import play.api.templates._\nimport play.api.templates.PlayMagic._")
} catch {
case e: java.lang.reflect.InvocationTargetException => {
streams.log.error("Compilation failed for %s".format(template))
@@ -59,6 +59,12 @@ trait DefaultContentTypeOfs {
}
/**
+ * Default content type for `JavaScript` values.
+ */
+ implicit def contentTypeOf_JavaScript(implicit codec: Codec): ContentTypeOf[JavaScript] =
+ ContentTypeOf[JavaScript](Some(ContentTypes.JAVASCRIPT))
+
+ /**
* Default content type for `String` values (`text/plain`).
*/
implicit def contentTypeOf_String(implicit codec: Codec): ContentTypeOf[String] = {
Oops, something went wrong.

0 comments on commit 633d9af

Please sign in to comment.