Thulium (Tm) is a templating engine written in javascript. It is based on EJS, but attempts to solve several problems with it: It formalizes the syntax and it attempts to make it more debuggable. This was a result of the EJS implementation being in dire need of both specification and debuggability in order to be used with projects at freshout.
It does not support a way of adding helpers or other fancy features, and instead recommends that all/any of these should be added with the context. Included in fancy features is no built-in methods for loading urls or files. The input is a string.
It works the same in both the browser and node environments.
- Javascript compatibility
- Will not support any type of file handling or url expanding.
- It will not support any way of adding "built-in" helpers.
- The user will be able to build a context object independently
- The user will be able to obtain the template string independently
+------------------------------------------------+
+----------+ | +-----------+ +-------------+ |
| Template | ----> | Tm ----> | Tm.Parser | ----> | Tm.Renderer | |
+----------+ | +-----------+ +-------------+ |
+---------------------------------------|--------+
+------+ |
| HTML |<-----+
+------+
An instance of Thulium is created with a string that represents the template. Tm then proceeds to parse the document to create the tokens it will use to render it. When it is to be rendered, Tm takes the tokens created by the parser, and a context object and executes any JS code in the template with the passed context (So any helper methods, variables and such should be passed in the context object). Finally, the render returns an HTML document.
Thulium only has two types of tags: Non-printable code tags, and printable code tags. With just these two, we have found it to be expressive enough, yet simple enough, to allow the developer great flexibility and control.
These tags are based on ERB. Of note is that none of these tags do any escaping, so that has to be handled by the application.
These tags denote javascript code that MUST be executed. Any JS code found inside these tags must be executed, but not printed.
<% for (i = 0; i < entries.length; i++ ) { %>
<%= "Hey, this has been printed before: " + i + " times" %>
<% } %>
These tags denote javascript code that MUST be executed. If a block of code is broken up, it should follow these rules:
- The printable tags should only appear in the opening fragment of the block.
- The closing of the block should be with regular code tags
- The result of the function is to be printed. (ie. Any printing operations inside the block will occurr before the block result)
- If the helper function has acces to an instance of the template
(e.g. by closure), it should be able to access the renderer and from
it the rendering function. (for example, if it has a closure to a
manager, it could easily just use
templateManager.view.renderer.render("hi")
) This is very useful for helpers that need to wrap text around (e.g. a formFor helper that wraps the passed function with the open and close form tags.) - Printable code should not be terminated with a semicolon.
<%= formHelper.formFor(function (f) { %>
This is normal text, so it will be printed when the function is
executed.
<%= "same goes for this" %>
<% }) %>
- Thulium: The main class used to handle the templates.
- Thulium.Parser: Parses a template and generates tokens.
- Thulium.Renderer: Parses a token array and generates a document.
- Neon.js: Class system
- Breaking of Printable Blocks.
- Debuggable.
- Expose a reference to the view to allow for custom printing inside of blocks.
- Should work in node and in browser.
- Should work sync and async.
While the most important part for alternate implementations must be to respect the Thulium API, this document presents a separation of a Parser and Renderer. This is, however, only a recommendation and any implementation could change this as long as the core API behaves as expected.
Thulium
+renderer
+parser
+template
#init(config)
#parse(callback)
#parseSync()
#render(context, callback)
#renderSync(context)
(NOTE: sync and async methods are grouped below. The main difference is that whatever a sync function would return, the async version will pass as an argument to the callback function.)
Reference to the template's renderer.
Reference to the template's parser.
Initializes a new instance of a Thulium template.
- config: Configuration to extend the template.
New instance of a Template.
Instantiates a new parser with self's template property as a string, to
obtain the tokens. Sets self's _tokens
to the resulting tokens.
N/A
The instance of thulium (for chaining)
Instantiates a new renderer and passes to it a token structure returned by the parser.
- context: The context under which to render the template.
The string of the rendered document.
Thulium.Parser
+tokens
-template
#init(config)
#parse(callback)
#parseSync()
A generated structure with tokens. This property does not exist before parsing.
Initializes a new instance of a Thulium parser.
- config: Configuration to extend the parser.
New instance of a Parser.
Parses the view into tokens. The tokens represent an abstract version of the template that is easier to process/render. It will use its template property to do so.
N/A
The resulting token structure.
Thulium.Renderer
+view
+tokens
+context
-preView
-captured
-shouldCapture
#init(config)
#render(callback)
#renderSync()
#print(message)
#capture(toPrint)
The "source code" in javascript of the view. This is what will be evaluated to generate the final render.
The rendered view. A string containing the final HTML string.
Initializes a new instance of a Thulium renderer.
- config: Configuration to extend the renderer.
New instance of a Renderer.
Takes its own tokens property and generates a preView, then evaluates the preView to generate the final HTML view.
N/A
The final view.
Appends the passed message to the view. This is useful for doing custom text insertion with helpers.
- message: The message to append.
N/A
Captures toPrint, so anything it executes that would be printed to the view, is instead returned.
- toCapture: A function that should print something to the view.
The string of whatever the function would have printed to the view.
Thulium
TBD
Thulium.Parser
#init( config )
for every key and value in config
set self's key property to value
if no seld.template is defined
log warning
#parseSync()
if this.template
call _tokenize()
else
log warning
#parse(callback)
if this.template
call _tokenize()
let returnValue be self _tokens
else
log warning
let returnValue be object with error
call callback(returnValue)
#_tokenize( source, nextToken )
if not source
let source be self template
if not nextToken
let self _tokens be an empty array
let nextToken be self lex tagOpen
if nextToken.hasText
let tokenizerRe be concat(self lext text matcher, nextToken.matcher)
else
let tokenizerRe be nextToken.matcher
let tokenizerRe be a regex built using tokenizerRe
let matches be exec tokenizerRe on source
if matches
if nextToken is self lex tagOpen
let textType be 'text'
else
let textType be 'code'
if nextToken.hasText
push object with type textType and value matches[1] to self _tokens
push object with type nextToken.name to self _tokens
if nextToken is self lex tagClose and self _openPints length is greater than 0
call self _countBrackets( matches[1] )
let source be substring of source up to tokenizerRe.lastIndex
let nextToken be call self _nextToken( nextToken )
call self _tokenize( source, nextToken )
else
if nextToken.optional
let source be substring of source up to tokenizerRe.lastIndex
let nextToken be call self _nextToken( nextToken )
call self _tokenize( source, nextToken )
else
warn the coder about syntax error
#_nextToken( token )
switch token
case self lex tagOpen
return self lex printIndicator
case self lex tagClose
return self lex tagOpen
case self lex printIndicator
retun self lex tagClose
#_countBrackets( text )
walk every character on text
if current character is {
self _openBrackets++
let foundBrackets be true
if current character is }
self _openBrackets--
call self _closePint()
let foundBrackets be true
if not foundBrackets
call self _closePrint()
#_closePint
let i be the index of self _openBrackets on self _openPints
if i is greater or equals to 0
push object with type 'closePrintIndicator' to self _tokens
remove token from self _openPrints
Thulium.Renderer
#init(config)
for every key and value in config
set self's key property to value
#render(callback)
call renderSync
if callback
call callback with self's view as arguments
#renderSync()
if self has property tokens
let buffer be an empty string
for every token in self's tokens
if token type is text
sanitize token value, wrap in print statement and append to buffer
if token type is code
append value to buffer
if token type is printIndicator
append open print statement to buffer
if token type is closePrintIndicator
append close print statement to buffer
set self's preView to buffer
create a function with buffer as body, wrapped with self's context property
apply the function to self
return self's view property
else
raise error
#print(message)
let buffer be an empty string
if message is a function
append result of function to buffer
else
append message to buffer
if self should capture
append buffer to self's captured property
else
append buffer to self's view property
#capture(toCapture)
set self's captured to an empty string
set self's should capture to true
call toCapture
set self's should capture to false
return self's captured property
[token, token, token]
Yup, that's it.
{
type: "text",
value: "some text"
}
{
type: "code",
value: "JS Code"
}
{
type: "printIndicator"
}
{
type: "closePrintIndicator"
}