This project is sponsored by Micronautics Research Corporation, the company that delivers online Scala and Play training via ScalaCourses.com. You can learn exactly how this project works by taking the Introduction to Scala, Intermediate Scala and Introduction to Play courses.
PFView is a drop-in replacement for Play Framework's Twirl template language.
Twirl is good for simple view templates because for simple pages, the HTML structure is left mostly intact.
For more complex pages, PFView is:
- Simpler to write
- Faster, because conversions between XML elements,
HtmlandStringcan be eliminated. The job of a view is to generate aStringthat is sent to a client. - Testable
- Debuggable
- Optimizable, by defining portions as
lazy vals which are evaluated only once instead of every time a page is parsed. - 100% Scala, so IDEs know how to refactor views defined by PFView
The job of a view is to work with data passed from a controller, as well as global state, and render output. No business logic should exist in a view, however traversing data structures requires an expressive computing language.
PFView was created to overcome Twirl's shortcomings. Twirl:
- Generates Scala code which is an unreadable mess, and is horrible to debug
- Cannot be refactored by an IDE
- Components must be stored in separate files, instead of merely defining a class or method
- DSL is less expressive than Scala, and is not a successful functional language.
- All of a web page's contents are created dynamically; no portion can be designated as being immutable
As a result, a non-trivial Twirl template becomes an unholy mess that is difficult to maintain.
As well, Twirl has an awkward syntax and limited capabilities compared to other view templating languages, such as ASP, JSP, JSP EL, etc.
PFView is 100% Scala. It could be made to work to with Play for Java projects, but no work has been done to document how to do this.
When Adobe Flex was popular, it was common to initially write view templates in MXML, then rewrite them in ActionScript as complexity increased. MXML is to Twirl as ActionScript is to PFView.
Add two lines to build.sbt.
- Add the
pfviewdependency:
"com.micronautics" %% "pfview" % "0.0.7" withSources()
- Add this to the
resolvers:
"micronautics/play on bintray" at "http://dl.bintray.com/micronautics/play"
This library has been built against Scala 2.12.1 / Play 2.6.0-M4, Scala 2.11.8 / Play 2.5.14 and Scala 2.10.6 / Play 2.2.6.
Create an PFView instance and invoke the ++ method to provide content to be appended to the PFView's instance's internal StringBuilder buffer.
When the PFView instance has been created, it returns the contents of the buffer as a String – just send that String to the web client.
That's all there is to it!
There are several ways of creating PFView instances. Examples of all of these are provided in the unit tests.
- To define a Twirl-compatible dynamic view, define an injected
classthat accepts an instance ofEnvironment. Pass thePFViewconstructor theEnvironmentinstance. Theclasswould normally define a method calledapplythat returnsHtml.
import javax.inject.Inject
import play.api.Environment
class dynamicView @Inject() (env: Environment) {
def apply(suffix: String): Html = new PFView(env) {
++(s"Feeling $suffix?")
}.toHtml
}
Of course, apply can be defined to have as many arguments and argument lists as required, including typical play signatures such as:
def apply(suffix: String)(implicit request: RequestHeader): Html
- Define a method that creates an anonymous subclass of
PFView, which is then implicitly converted toString. This is useful for complex, dynamic content.
import play.api.Environment
val env: Environment = ???
def simple = new PFView(env) {
++("simple")
}
PFViewinstances can be recursively nested:
import javax.inject.Inject
import play.api.Environment
class nestedViews @Inject() (env: Environment) {
def apply(msg: String="") = new PFView(env) {
def repeatContent(msg: String): String = new PFView(env) {
++(msg * 2)
}.toString
val repeatedContent = repeatContent(msg)
++(repeatedContent)
}
}
- To define a static view, define an
objectthat extendsPFView. This should only be done when assigning the result to a lazy val.
import play.api.Environment
val env: Environment = ???
object staticView extends PFView(env) {
++("<h1>This is a test</h1>")
++{s"""<p>The time is now ${new java.util.Date}
|This is another line</p>
|${ unIf (6==9) { "Somehow 6 equals 9" } }""".stripMargin}
}
The following methods are provided by PFView:
++- appends content to the bufferIf- a convenience method for conditionally appending content to the buffer.If (condition) { thenClause }is equivalent toif (condition) thenClause else "". This method is useful within string interpolation. Unlike Twirl's@ifexpression, spaces can exist anywhere in anIfexpression.includeFile- append the contents of a local file into the buffer; localized versions of files are searched for, according to standard i18n behavior using the value of the implicit Lang parameter. For example, iffilePathis specified asblah.htmlandlanghas the valueen-USthen the fileblah_en-US.htmlis searched for, then if not foundblah_en.htmlis searched for and finallyblah.htmlis searched for.
includeFile("blah.html")
You can also specify the lang argument explicitly:
includeFile("blah.html")(Lang("fr"))
By default, files are searched for in the public directory. You can override this by specifying a value for baseDir; the value can be relative or absolute.
includeFile("blah.html", "/var/tmp")
also:
includeFile("blah.html", "/var/tmp")(Lang("de"))
includeUrl- append the contents of the web page pointed to by a URL into the buffer. Relative URLs are not supported. The default encoding is UTF-8. For example, include thisREADME.mdfile from its GitHub repo like this:
includeUrl("https://raw.githubusercontent.com/mslinn/PFView/master/README.md")
You can specify an alternative encoding like this:
includeUrl("https://raw.githubusercontent.com/mslinn/PFView/master/README.md", "iso-8859-1")
