HTML Templating With Heist - snapframework/snap GitHub Wiki
Heist is a template system for HTML 5.
Compiled Splices
Interpreted Splices
Interpreted splices can be divided into two broad categorizations: substitutions and filters. Substitution splices are where the spliced tag disappears in the output. If the spliced tag is preserved in the output, then it's a filter splice.
Substitution splices
Filter splices
One of my (Doug's) blog posts on splice subtleties said that you must call stopRecursion in filter splices otherwise you'll get infinite recursion. This is true, but it's not the whole story. To see what I'm talking about let's look at a real world example.
Application: CSRF Protection
Our application is protection against cross-site request forgery (CSRF) attacks. To protect against CSRF, we want to add a hidden field to every form on our whole website. The value of this field (call it "_csrf") is a secure token unique to every session. Every time we get a POST request, we want to check the value of the _csrf field and make sure it matches the one that we created for the session. We can do this very nicely by creating a splice to bind to the form tag. Here's a naive approach:
secureForm1 :: MonadIO m
=> m Text
-- ^ A computation in the runtime monad that gets the CSRF
-- protection token.
-> Splice m
secureForm1 csrfToken = do
stopRecursion
n <- getParamNode
token <- lift csrfToken
let input = X.Element "input"
[("type", "hidden"), ("name", "_csrf"), ("value", token)] []
case n of
X.Element nm as cs -> return [X.Element nm as (input : cs)]
_ -> return [n] -- "impossible"
But this has a problem. If you try it, you'll see that your form tags are getting multiple _csrf fields added. Calling stopRecursion does not prevent your splice from getting executed more than once, it only prevents it from recursively calling itself more than once. We need to add a check to see if we've already added the field.
secureForm2 :: MonadIO m
=> m Text
-- ^ A computation in the runtime monad that gets the CSRF
-- protection token.
-> Splice m
secureForm2 csrfToken = do
stopRecursion
n <- getParamNode
token <- lift csrfToken
let input = X.Element "input"
[("type", "hidden"), ("name", "_csrf"), ("value", token)] []
case n of
X.Element nm as cs -> do
let newCs = if take 1 cs == [input] then cs else (input : cs)
return [X.Element nm as newCs]
_ -> return [n] -- "impossible"
But that still doesn't work properly.
secureForm3 :: MonadIO m
=> m Text
-- ^ A computation in the runtime monad that gets the CSRF
-- protection token.
-> Splice m
secureForm3 csrfToken = do
stopRecursion
n <- getParamNode
token <- lift csrfToken
let input = X.Element "input"
[("type", "hidden"), ("name", "_csrf"), ("value", token)] []
case n of
X.Element nm as cs -> do
cs' <- runNodeList cs
let newCs = if take 1 cs' == [input] then cs' else (input : cs')
return [X.Element nm as newCs]
_ -> return [n] -- "impossible"