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,
Html
andString
can be eliminated. The job of a view is to generate aString
that is sent to a client. - Testable
- Debuggable
- Optimizable, by defining portions as
lazy val
s 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
pfview
dependency:
"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
class
that accepts an instance ofEnvironment
. Pass thePFView
constructor theEnvironment
instance. Theclass
would normally define a method calledapply
that 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")
}
PFView
instances 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
object
that 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@if
expression, spaces can exist anywhere in anIf
expression.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, iffilePath
is specified asblah.html
andlang
has the valueen-US
then the fileblah_en-US.html
is searched for, then if not foundblah_en.html
is searched for and finallyblah.html
is 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.md
file 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")