Modules

Modules are the building blocks of Zotonic. They add functionality to your Zotonic website such as:

  • the admin web interface
  • embedding videos in your content
  • search engine optimization (SEO)
  • social media integration.

Structurally, a module is a directory containing the module’s Erlang code, templates, controllers, dispatch rules and more.

Activating modules

Before you can use a module, you need to activate it. You can do so in two ways.

  1. For testing, you can enable the module in the admin interface, under System > Modules.

  2. If you decide to use the module in your site, it’s best to declare so in your site configuration. This ensures that the module is activated not only for you but also for other developers and on other servers that the website may run on (e.g. a production server). Add the module name to the sites/yoursite/config file, under the modules key:

    [
        % ...
        {modules, [
            % ...,
            mod_example
        ]}
        %...
    ].
    

    Then restart the site for the changes to be picked up.

Module configuration

Some modules can be configured to influence their behaviour. The module’s documentation will tell you about its configuration parameters.

Directory 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, tags, dispatch rules and more.

The generic structure is:

zotonic_mod_example/
    priv/dispatch/
    priv/templates/
    priv/lib/
    priv/lib-src/
    src/mod_example.erl
    src/zotonic_mod_exampe.app.src
    src/models/...
    src/filters/...
    rebar.config

The module file

At the very minimum, a Zotonic module must have a 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:

-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.

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.

priv/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.

priv/lib/

The lib (short for library) directory contains static images, CSS and javascript files. These files will be served with via the lib. The usual layout of the lib directory is:

priv/lib/css/
priv/lib/images/
priv/lib/js/
priv/lib/misc/

priv/lib-src/

This directory contains the source files for the lib directory. Examples are Less, Scss, and other files.

If a file in the lib-src directory changes then the system will search for a Makefile in the directory of the changed file or one of its parent directories. If a Makefile is found then it is executed.

Scss files starting with a _ (like _home.scss) are known to be include files. If no Makefile is found then a Scss file without underscore is searched and used to generate the corresponding css.

If no Makefile is found then any input file is converted to a similar sub-directory in the lib directory. For example lib-src/foo/bar.scss is used to generated lib/foo/bar.css

There are standard file handlers for the following extensions/formats:

  • .scss (Makefile, sassc, or sass)
  • .less (Makefile or lessc)
  • .coffee (Makefile or coffee)

priv/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”

src/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

src/scomps/

Any custom tags that you define yourself go into the src/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.

src/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.

src/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.

src/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.

src/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”

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

The module priority is a number defined in the module’s code and is usually a number between 1 and 1000; the default is 500. A lower number gives a higher priority. Modules with higher priority are checked first for templates, actions, custom tags etc.

The priority is defined by mod_prio in the module file. For a site, the priority is usually set to 1, to make sure that its templates etc override the ones from the Zotonic mouules.

When two modules have the same priority then the modules are sorted by their name. That means that, given the same priority number, mod_aloha has higher priority than mod_hello.

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 activated.

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 provides.

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 starts, its modules are started in order of module dependency, in such a way that a module’s dependencies are always started before the module itself 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.

The manage_schema/2 function returns either ok, a #datamodel{} record or a list of #datamodel{} records:

-spec manage_schame( install | {upgrade, integer()}, z:context() ) ->
    ok | #datamodel{} | [ #datamodel{} ].

In a #datamodel{} record you can define:

  • categories
  • predicates
  • resources
  • edges
  • ACL rules.

After the manage_schema/2 function is called, the optional manage_data/2 function is called. The function manage_data/2 is called if and only if the manage_schema/2 is called. If you only want a manage_data/2 function, then add a dummy manage_schema/2 function that returns ok and does nothing else.

For example:

mod_twitter.erl
-module(mod_twitter).
-mod_title("Twitter module").
-mod_schema(3).  %% we are currently at revision 3

-export([
    manage_schema/2,
    manage_data/2
]).

%% ...

manage_schema(install, Context) ->
    %% Return a #datamodel{} record with items that will be created when
    %% the module starts.

    #datamodel{
        categories = [
            {
                tweet,  %% unique category name
                text,   %% parent category (can be undefined)

                %% category properties
                [
                    {title, <<"Tweet">>}
                ]
            }
        ],
        predicates = [
            {
                tweets,  %% unique predicate name

                %% predicate properties
                [
                    {title, {trans, [
                        {en, <<"Tweets">>},
                        {nl, <<"Twittert">>}
                    ]}}
                ],

                %% predicate from/to categories:
                [
                    {person, tweet}
                ]
            }
        ],
        resources = [
            {
                person_tweeter,  %% resource’s unique name
                person,          %% category

                %% resource properties
                [
                    {title, <<"Test Tweeter">>},
                    {name_first, <<"Sir">>},
                    {name_surname, <<"Tweetalot">>
                ]
            },
            {
                silly_tweet,
                tweet,
                [
                    {body, <<"What’s your favourite colour?"/utf8>>}
                ]
            }
        ],
        edges = [
            %% subject       predicate     object
            {person_tweeter, tweets,       silly_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: update the person_tweeter resource
    #datamodel{
        resources = [
            {
                person_tweeter,
                person,
                [
                    {name_surname, <<"Tweetalot the Second">>}
                ]
            }
        ]
    }.

manage_data(_Version, Context) ->
    %% Whatever data needs to be installed after the datamodel
    %% has been installed.
    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.

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, i.e., a module that does something in the background, then it is possible to implement your module as a gen_server (or supervisor). 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 a 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.

If you want to observe Zotonic’s notifications and handle them through your module’s gen_server, export pid_observe_... functions (instead of the regular observe_... ones). These function will then be passed the gen_server’s PID:

export([
    pid_observe_custom_pivot/3
]).

pid_observe_custom_pivot(Pid, #custom_pivot{} = Msg, _Context) ->
    gen_server:cast(Pid, Msg).

handle_cast(#custom_pivot{id = Id}, State)
    %% Do things here...
    {noreply, State}.

A minimal example

%% Zotonic modules always start with 'mod_'
-module(mod_example).

%% The author - also shown in the admin ui
-author("Nomen Nescio <nomen@example.com>").

%% A module can be a 'gen_server', a 'supervisor', or just a module
%% without behaviour.
-behaviour(gen_server).

%% The title of your module
-mod_title("Your module title").

%% A short description, shown in the admin ui
-mod_description("Description what this module does.").

%% Priority, lower is higher prio, 500 is default.
-mod_prio(500).

%% The modules or services this module depends on.
%% This module is only started after the mentioned modules
%% or services are started.
%% List of atoms.
-mod_depends([]).

%% The modules or services this module provides.
%% A module always provides itself ('mod_example' in this case)
%% List of atoms.
-mod_provides([]).


-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([start_link/1]).

-include_lib("zotonic_core/include/zotonic.hrl").

-record(state, {
        context :: z:context()
    }).

%% Module API

%% The Args is a proplists with the site config and a context.
%% The {context, z:context()} is added to it so there is
%% an instantiated site context. The site context is
%% authenticated as the admin.
start_link(Args) when is_list(Args) ->
    gen_server:start_link(?MODULE, Args, []).

%% gen_server callbacks

init(Args) ->
    {context, Context} = proplists:lookup(context, Args),
    % Instantiate a new, empty, and anonymous site context.
    {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.

Access control Developer Guide Notifications

Referred by

Tags

With tags you add logic and flexibility to your templates.

Customizing the style of an admin page

How to make style customizations to admin pages.

Glossary

Action An action is functionality that can be attached to a HTML element or event. Actions are wired to an element or…

Testing sites

It is possible to create end-to-end integration tests for Zotonic websites. Tests like these are called sitetests .

Site configuration

This chapter describes the configuration options for your sites. There’s also global configuration.

Upgrade notes

These notes list the most important changes between Zotonic versions. Please read these notes carefully when upgrading…

all include

Call all modules to include a certain template.

Templates

Templates are text files marked up using the Zotonic template language. Zotonic interprets that mark-up to dynamically…

Create a custom filter

Create custom template filters to change the way variables are rendered in your templates. By following some simple…

m_modules

Access information about which modules are installed and which ones are active.

Execute tasks asynchronously using the task queue

The Zotonic task queue lets applications perform tasks asynchronously.

mod_export

Provides a generic framework to export resources.

mod_signup

This module presents an interface for letting users register themselves.

Overriding Zotonic

This chapter describes how to override the templates, styling and logic provided by Zotonic.

mod_acl_user_groups

This module adds rule-based access control.

Sites

Zotonic has the capability of serving more than one site at a time. You can have multiple sites enabled, each with its…

Notifications

At different moments in the lifecycle of the web request, Zotonic sends notifications. By observing these notifications…

mod_facebook

The mod_facebook module plugs into the authentication systen to enable Facebook login on your site.

Modules

Zotonic comes with a considerable number of modules that add functionality to your website. This section describes all…

mod_linkedin

The mod_linkedin module plugs into the authentication systen to enable LinkedIn login on your site.