Modules¶
Modules are the building blocks of Zotonic.
Examples of modules are the /admin
site, Atom feeds, the
sitemap.xml
, video embed code handling and SEO optimization.
Modules also augment the functionality of other modules by adding
extra Templates and accompanying logic or adding handlers for
internal Zotonic events. Good examples are the modules extending the
mod_admin.
A module is a directory containing the module’s Erlang code, templates, controllers, dispatch rules and more, all contained in a single module directory tree.
See also
listing of all Modules.
Looking for more modules?
Check out the Zotonic Module Index:, an index with additional user-contributed modules which are not part of the core Zotonic distribution.
Structure¶
A module groups related functions together into a single directory. It contains an Erlang module (from here on called the ‘module file’) and subdirectories for templates, actions, scomps, dispatch rules and more.
The generic structure is:
mod_example/
mod_example.erl
templates/
actions/
etcetera.../
The module file¶
The name of the module file is an Erlang file that must be the same as the name of the module’s directory. Zotonic scans this file for metadata about the module and uses it to start the module.
The code of the smallest possible module is below:
-module(mod_example).
-author("Nomen Nescio <nomen@example.com>").
-mod_title("Your module title").
-mod_description("Description what this module does.").
-mod_prio(500).
In this case, the module code only consists of some metadata properties, there is no real code in there. This is fine for a lot of modules: since Zotonic already provides so many functions, there is often little need to write custom code.
The mod_title
and mod_description
properties describe your
module in natural language: these properties will be visible on the
admin modules page. The mod_prio
property defines the priority of
the module. The highest Priority is 1, the default
is 500. Modules with higher priority are checked first for templates,
actions, custom tags, etc. Modules with the same priority are sorted
by ascending module name.
In cases where you need to execute code when the module starts, you
can export an optional init/1
function. The parameter is a context
record initialized for the site the module will be running in. This is
useful when you need to initialize the database or other data
structures for which you don’t need a running process. When you also
need to execute code when a module stops you can export an optional
terminate/2
function. This function will be called when the module
terminates. The first parameter is a Reason parameter which indicates
why the module stopped. The second a context record similar to the one
in the init/1
function.
When you do need a running process, read about those in the next topic, gen_server based modules.
Module subdirectories¶
Besides the module code file, a module usually has one or more subdirectories. These are specially named; different parts of Zotonic scan through different folders.
This section describes what each of the module folders hold.
actions/¶
This directory holds the actions defined by the
module. Every action name must be prefixed with the word “action” and
the module name (without the mod_). For example the filename for the
action dialog_open
in the module mod_base
will be
action_base_dialog_open.erl
See also
dispatch/¶
This directory contains files with dispatch rules. You can name your files however you want, just
don’t give them the extension .erl
, because then the Makefile will
try to compile them.
See also
lib/¶
The lib (short for library) directory contains static images, css and javascript files. These files will be served with via the lib tag using the lib dispatch rule. The usual layout of the lib directory is:
lib/css/
lib/images/
lib/js/
lib/misc/
See also
the lib template tag.
scomps/¶
Any custom tags that you define yourself go into the scomps/
directory.
Scomps are prefixed in the same way as actions, except that the word
“scomp” is used. For example the scomp button
in the module
mod_base
has as file name scomp_base_button.erl
.
See also
controllers/¶
This directory contains Erlang modules which define controllers which are called from the dispatch system to handle incoming HTTP requests.
Controllers must have unique names, as they are compiled and loaded in
the Erlang system. The convention is to prefix every controller with
controller_
and the name of the module, for example
controller_admin_edit.erl
.
See also
models/¶
This directory contains Erlang modules, each of which is a model.
The module name of a model always starts with m_
, for example
m_comment
. This model is then to be used in the templates as
m.comment
. Be careful to give your models a unique name to
prevent name clashes with other models and Erlang modules.
See also
templates/¶
This directory contains all Templates. Templates do not have any prefix in their name, as they are not (directly) compiled as Erlang modules.
The following naming conventions for templates are used:
- All templates have the extension “.tpl”
- Templates used as a complete page can have any name: ”my_special_page.tpl”
- Templates used as the base of other templates, using the extends tag, have the word “base” in them: ”base.tpl”; “email_base.tpl”.
- Templates only used by including them in other templates start their name with an underscore: “_example.tpl“
- The template for the home page of a site is called “home.tpl”
- Templates for displaying resources are called “page.tpl”
See also
filters/¶
This directory holds Erlang modules, each of which defines a template filter.
Each filter must have an unique name, reflecting the filter’s
name. For example, the filter “tail” resides in the Erlang module
filter_tail.erl
and exports the function tail/1
. Filters are
added in the filters directory. The template compiler will insert
references to the correct modules into the compiled templates. A
missing filter will result in a crash of the compiled template.
See also
validators/¶
This directory holds Erlang modules, each of which defines a validator.
Validators are prefixed in the same way as actions and scomps, except that the word “validator” is used. For example the validator “email” in the module “mod_base” has the file name: “validator_base_email.erl”
See also
services/¶
The services folder holds Erlang modules, each of which functions as an API method that you can use to access Zotonic from another application. These are invoked by controller_api.
Services are named a bit differently: the name of the module is
always used in the service name: The service base/export
will be
found in the file mod_base/services/service_base_export.erl
. This
particular service can then be found at
http://yoursite.com/api/base/export
.
See also
Changing / recompiling files¶
Changes to the Erlang files in a module are visible after issuing the
zotonic update
CLI command, or z:m().
from the Zotonic
shell. Any new lib or template files, or changes in the dispatch rules
are visible after the module indexer has rescanned all modules. You
can do this with the “rescan modules” button on the modules page in
the admin. Changes to templates are directly visible.
Priority¶
A site’s mod_prio metadata attribute is usually set to 1, to make sure that it is the first module where Zotonic looks for template lookups and the like.
Dependencies¶
Modules can have dependencies on other modules. These are expressed via the module’s metadata, as follows:
-mod_depends([mod_admin]).
This states that the current module is dependent on mod_admin
to
be installed.
Sometimes, explicitly depending on a module name is not a good idea:
there might be more modules that perform the same functions but are
competing in implementation. In that case, such modules can export a
mod_provides
meta tag, so that dependent modules can depend on
what one of these modules provide.
Example: mod_a
and mod_b
both provide some functionality, foo
:
-module(mod_a).
-mod_provides([foo]).
and:
-module(mod_b).
-mod_provides([foo]).
Now, another module, mod_bar
, needs the “foo” functionality:
-module(mod_bar).
-mod_depends([foo]).
Now, the module manager will require either (or both!) of the
mod_a
and mod_b
modules to be activated, before mod_bar
can be activated.
A module automatically provides its own module name, as well as its
name minus the mod_
. So, mod_bar
has implicitly the
following provides constructs:
-module(mod_bar).
-mod_provides([mod_bar, bar]).
These two provides are there even when a module adds its own
provides
clauses.
Module startup order¶
Note that when a site start, its modules are started up in order of module dependency, in such a way that a module’s dependencies are always started before the module itsef starts.
Module versioning¶
Modules can export a -module_schema()
attribute which contains an
integer number, denoting the current module’s version. On module
initialization, Module:manage_schema/2
is called which handles
installation and upgrade of data.
Minimal example:
-module(mod_twitter).
-mod_title("Twitter module").
-mod_schema(3). %% we are currently at revision 3
-export([manage_schema/2]).
.... more code here...
manage_schema(install, Context) ->
% .. code to install your stuff here, for instance:
#datamodel{categories=
[
{tweet,
text,
[{title, <<"Tweet">>}]}
]};
manage_schema({upgrade, 2}, Context) ->
%% code to upgrade from 1 to 2
ok;
manage_schema({upgrade, 3}, Context) ->
%% code to upgrade from 2 to 3
ok.
Note that the install function should always be kept up-to-date
according to the latest schema version. When you install a module for
the first time, no upgrade functions are called, but only the
install
clause. The upgrade functions exist for migrating old
data, not for newly installing a module.
Data model notification¶
In the #datamodel
record you can manage categories, predicates, resources,
media and edges. You can also set the data
property, which will send out
a first notification. To subscribe to that
notification, export observe_manage_data/2
in your site or module.
Using categories defined by other modules¶
When your site needs to add resources which are defined by other
module’s manage_schema
functions, you need to make sure that those
modules manage functions are called first. This can be realised by
adding a dependency to those modules, as explained in
Module startup order.
For instance, when you want to create a custom menu for your site:
manage_schema(install, _Context) ->
#datamodel{
resources=[
{help_menu, menu, [
{title, "Help"},
{menu, [...]}
]}
]
}.
You also need to make sure that you add a dependency
to mod_menu
, which creates the menu
category for you:
-mod_depends([mod_menu]).
gen_server based modules¶
When you need a running process, e.g., a module that does something in the background, then it is possible to implement your module as a gen_server. A gen_server is a standard way to implement a reliable Erlang worker process.
In that case you will need to add the behaviour and gen_server
functions. You also need to change the init/1
function to accept
an property list, which contains the site definition and a {context,
Context}
property.
This server module will be started for every site in a Zotonic system where the module is enabled, so it can’t be a named server.
See also
A minimal example¶
-module(mod_example).
-author("Nomen Nescio <nomen@example.com>").
-behaviour(gen_server).
-mod_title("Your module title").
-mod_description("Description what this module does.").
-mod_prio(500).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([start_link/1]).
-include_lib("zotonic.hrl").
-record(state, {context}).
%% Module API
start_link(Args) when is_list(Args) ->
gen_server:start_link(?MODULE, Args, []).
%% gen_server callbacks
init(Args) ->
{context, Context} = proplists:lookup(context, Args),
{ok, #state{context=z_context:new(Context)}}.
handle_call(Message, _From, State) ->
{stop, {unknown_call, Message}, State}.
handle_cast(Message, State) ->
{stop, {unknown_cast, Message}, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
As you can see, this code is almost identical to the standard Erlang
gen_server
boilerplate, with the exception of the metadata on top.
You also see that the start_link/1
function is already
implemented. Note that in this function the gen_server is started
without registering the server under a name: this is done because the
module can be started multiple times; once for each site that needs
it.
The init/1
function contains some more boilerplate for getting the
context{}
argument from the arguments, and storing this context
into the server’s state. This way, you’ll always have access to the
current context of the site in the rest of the gen_server’s functions.