Writing your own Zotonic module -- part III
Continuing from part II, we will add a custom filter to our gazonk module.
We're building on the source from part II.
Custom filters
In module (remember that a site is treated just the same way as a module with some extras) you can define you own filters to use in templates.
Just as with the module file, the filter file travels light; a single exported fun is all it takes. It should take two or three arguments, depending on if the filter is expected to take arguments or not in the template when invoked (or export both versions, if that makes sense).
The name of the erlang module for the filter should begin with `filter_' followed by the filter name, and the exported fun is also the filter name. So, a filter named test should be defined in filters/filter_test.erl and export test/2 and/or test/3.
The arguments passed to the filter fun is:
- The input value being filtered.
- The arguments passed to the filter, if any.
- The zotonic context.
The fun is expected to return the new filtered value, whatever that may be.
And, that's all there is to it, really. A few things to note however.
If you only export one of the two possible fun's (the two are your_filter/2 and your_filter/3), but use the other one in a template, it will result in a crash when being rendered, giving the user a nasty 500 error page.
Custom filter: list_links
For our exercise, I wanted a filter that would extract all anchor links from a text. I will improve it in a latter article, but for now, it simply returns a list with the substring consisting of the raw link copied directly from the text.
The code for this filter is rather simple, when implemented with a reg exp:
% file: mod_gazonk/filters/filter_list_links.erl
-export([list_links/2]).
list_links(In, _Context) ->
case re:run(In, "<[aA][^>]*>[^<]*</[aA]>", [global, {capture, first, binary}]) of
nomatch -> [];
{match, M} -> lists:flatten(M)
end.
Example use (from within a template file):
{% for link in id.body|list_links %}
<br />{{ link }}
{% empty %}
No links in body.
{% endfor %}
Concluding remarks
I have always been curious about filters, and thought it would be quite a study to get to grips with them. But as it turned out, they're really easy to work with!
I would have expected that the filter wouldn't work unless the defining module was first activated, but it doesn't seem to be the case (in version 0.7-dev). It works just fine even when the module is deactivated. Perhaps this is a bug that should be filed in zotonic's bug tracker.
Filters are compiled to ebin, @Zotonic doesn't track which filters belong to which module, so you can't hide them (- yet :)
See the hg repo for the full source code.
Comments
douglasv
Posted 1 year, 9 months ago.
could the * following [^>]*>
be typed [^>].*> (with the period .*)?
case re:run(In, "<[aA][^>]*>[^<]*</[aA]>",
Andreas Stenius
Posted 9 months, 27 days ago.
I don't think that would work too well. Since the . could eat the closing >.