Return empty field when Listfield has no elements to allow using ()$ …
…around a ()$ block.
This is my first ever Haskell pull request, so apologies for the lack of elegance. This PR is meant to allow using $if()$ blocks surrounding a $for()$ block in templates. It returns empty when a Listfield is empty. This helps for the following (quite common) pattern:
$if(list)$
There is a list!
$for(list)$
It contains : $elem$
$endfor$
$else$
No list...
$endif$
This solution has several problems:
- It breaks compatibility.
- It introduces new overrides. Any empty list will be overridden by later contexts.
A better solution would be something like $if(notnull(list))$. It is possible to implement by modifying applyExpr in Hakyll.Web.Template though it looks a bit ugly.
A better choice might be implementing functions over lists in templates. But a change to the definition of Context would be needed which is pretty fundamental.
The simplest solution I can think of is to define listField' with the same type as listField. It would create not only a list field, but also a bool field with key equal key ++ "?" that checks for emptiness of the list. It solves this particular problem, but doesn't help to solve similar ones.
Hi @jaspervdj I just ran into this problem again trying simply check for empty strings in my template. Could it be that a maybeField with the same approach as the haskell Maybe would work? i.e. in $if(aMaybeField)$ it would just "pattern match" on Nothing or Just _ while with $aMaybeField$ it would return empty string for Nothing and value for Just value ?
You can achieve this already in a reasonably clean way. The code I am using is based on the canonical example, i.e. the one generated by hakyll-init. In that site, we have the following templates/post-list.html:
<ul>
$for(posts)$
<li>
<a href="$url$">$title$</a> - $date$
</li>
$endfor$
</ul>
Let's change that to:
$if(posts)$
<ul>
$for(posts)$
<li>
<a href="$url$">$title$</a> - $date$
</li>
$endfor$
</ul>
$else$
<p>There are no posts.</p>
$endif$
Now, an $if(...)$ in Hakyll templating will fail when an error occurs. We can achieve that behaviour by changing site.hs slightly as well. Currently we have, when generating the index page:
let indexCtx =
listField "posts" postCtx (return posts) `mappend`
constField "title" "Home" `mappend`
defaultContext
The code spitting out the list is simply return posts -- which will never raise an error. So we simply change that to:
let indexCtx =
listField "posts" postCtx
(if null posts then fail "No posts" else return posts)
`mappend`
constField "title" "Home" `mappend`
defaultContext
Does this work for your use case?
Hi Jasper,
Thanks for the answer. I was aware that ~~this worked with listFields~~ (EDIT: it just worked because I use the hack in this pull request, your solution seems less hackish while still I wonder: is there any reason to not make this a default behavior?) but I’m trying to make this work for other type of fields… So for instance in your example how can I wrap the - $date$ in an $if(date) ? And to my point about making this work for other types of field, how do you make it work with $url$ or $title$ fields. So for instance, this would be fairly common (and fairly verbose for a common case of combined field templating):
<ul>
$for(posts)$
<li>
$if(url)$
<a href="$url$">$title$</a> - $date$
$else$
$if(title)$
$title$ $if(date)$ - $date$ $endif$
$else$
$if(date)$ $date$ $endif$
$endif$
$endif$
</li>
$endfor$
</ul>
Another way might be to allow expressions in the if argument such as $if($date$ == 0 || $title$ == '')$...
If that's of any use, here's an example of what I'm trying to do which doesn't work because $twitter$ et al are strings.
Thanks a lot for your patience (and let me know if it's better to move this into a proper issue rather than stay on this half-baked pull request)!
Jun
This sort of behaviour should work -- if twitter is missing from the metadata, then $if(twitter)$ should evaluate to false.
This does not work for date, though, if you are using defaultContext, since that will try to infer a date from your post file name.