Could someone give an example of highlighting the "active" link on the menu on the site with Snap? Or at least tell me how you would go about it - as I have no idea.
In other web-frameworks I usually set a context variable called active
to what the active page should be, and then my html simply checks it:
<ul>
{% ifequal active "home" %}
<li class="active">
{% else %}
<li>
{% endifqual %}
<a href="/">Home</a>
</li>
{% ifequal active "about" %}
<li class="active">
{% else %}
<li>
{% endifequal %}
<a href="/about">About Us</a>
</li>
</ul>
There are splices in heist, but I am not sure how you would use them to figure out what the current url is or set the context variable.
Thanks to @mightybyte and @Adam Bergmark, I've settled on the following:
Haskell code:
menuenuEntrySplice :: MonadSnap m => HeistT m Template
menuEntrySplice = do
requestPath <- lift $ withRequest (return . rqURI)
node <- getParamNode
let setActive n = if getAttribute "href" node == Just (decodeUtf8 requestPath)
then setAttribute "class" "active" n
else n
let aNode = Element "a" [("href", fromMaybe "/" $ getAttribute "href" node)] $ [TextNode (nodeText node)]
let liNode = setActive $ Element "li" [] [aNode]
return [liNode]
app :: SnapletInit App App
app = makeSnaplet "app" "An snaplet example application." Nothing $ do
....
addSplices [ ("menuEntry", liftHeist menuEntrySplice) ]
return $ App h s a
And here is now it's used in HTML:
<ul class="nav">
<menuEntry href="/">Home</menuEntry>
<menuEntry href="/contact">Contact</menuEntry>
</ul>
which produces:
<ul class="nav">
<li class="active"> <a href="/">Home</a> </li>
<li> <a href="/contact">Contact</a> </li>
</ul>
In the snapframework.com site, we do it with javascript. At the end of site.js you'll find this code that adds the appropriate class to the appropriate link.
if ($.url.segment(0)) {
$('.nav li.'+$.url.segment(0)).addClass('active');
} else {
$('.nav .home').addClass('active');
}
If you want to do this with Heist, then you'll need to use a paradigm a bit different from what your template above uses. The whole point of Heist (aided by Haskell's strong static type system) is to provide the strongest possible separation between view and business logic, so you can't use Haskell constructs like loops or conditionals directly in your templates. The answer is to create new tag (implemented with a splice) that does exactly what you want. In your template you will use it like this:
<ul>
<menuLink href="/">Home</menuLink>
<menuLink href="/about">About Us</menuLink>
</ul>
First of all, notice how much cleaner this is. In your approach, you had to repeat the same code for every link. Here we're able to keep the template DRY and it reads quite nicely.
To implement the menuLink tag you'll need a splice splice that looks something like this:
import Control.Monad.Trans.Class (lift) -- transformers
import Data.Text.Encoding (decodeUtf8)
import Text.XmlHtml (getAttribute, setAttribute, elementTag)
menuLinkSplice :: MonadSnap m => HeistT m Template
menuLinkSplice = do
requestPath <- lift $ withRequest (return . rqURI)
node <- getParamNode
let addActive n = if getAttribute "href" n == Just (decodeUtf8 requestPath)
then setAttribute "class" "active" n
else n
return [addActive (node { elementTag = "a" })]
Then, to put it all together, you need to bind that splice to the menuLink tag. In a Snap application you'll do that with:
addSplices [ ("menuLink", liftHeist menuLinkSplice) ]
You also might find my blog post Looping and Control Flow in Heist helpful (as well as some of my other posts with the "heist" tag).