Playing with Erlang Zotonic Modules

You have a running site in Zotonic from last month. It is time to expand the site by using modules. One of the challenges will be to see if you can make all the changes without needing to restart zotonic.

As an example, let us select the chat module and explore adding it to the blogsite.

Download the source from http://code.google.com/p/zchat/.

Installation and Getting Started

Start the zotonic server and keep it running in a terminal window.

$ cd zotonic

$ ./start.sh

Download the zip file of the source from http://code.google.com/p/zchat/source/browse/ and install it in the modules sub-directory of blogsite as follows in a new terminal window:

$ cd zotonic/priv/sites/blogsite/modules

$ unzip Downloads/zchat-X.zip

$ mv zchat-X mod_chat

You need to enable this module from http://blogsite:8000/admin/modules. Locate the chat module and activate it. You will need a page and a dispatch rule to access chat.

In the directory mod_chat/templates, create a file chat.tpl with the following content:

{% extends "base.tpl" %}

{% block content %}

{% include "_chat_box.tpl” %}

{% endblock %}

Now create a file mod_chat/dispatch/dispatch with following content:

[

{chat, ["chat"], controller_template, [{template, "chat.tpl"}]}

]

From the zotonic shell, give the command to reload the modules

(zotonic001@myhost)1> z:m().

Browsing http://blogsite:8000/chat should display the form for chatting. If you open the same page on another browser, you will see two anonymous users and you can chat between the two browser sessions. You may open the same page on multiple tabs and all the sessions will part of a group chat.

Suppose you want to have the option to chat from the home page. You will need to modify the home.tpl to include "_chat_box.tpl”. You may simply include in the content block. Or if you want to show it in the sidebar, the following lines should be added to blogsite/template/home.tpl:

{% block sidebar %}

{% include "_sidebar.tpl" %}

{% include "_chat_box.tpl" %}

{% endblock %}

If you now browse http://blogspot:8000/ , you should find the chat form in the sidebar. It may not look attractive but it works! Again you can chat with other users on the home page. However, users on the chat page will not be able to chat with users on home page. We will return to this a little later.

Usability

Since chatting with Anonymous users is not particularly interesting, explore the list of modules and see if the usability can be improved.

Authentication module is already enabled. If you look at the dispatch rules for this module (see file zotonic/modules/mod_authentication/dispatch/dispatch), you will find that the two paths needed are /logon and /logoff.

You may activate the “Signup users” module so that the users can create their accounts. The path for this module is /signup. Signup requires verification via email by default. You can disable it by setting mod_signup.request_confirm to false. You will need to edit the zotonic/config file for that. The last few lines will become:

%% Page length

{pagelen, 5},

{mod_signup, [{request_confirm, false}]}

].

You will now need to reload the modules from the zotonic shell:

(zotonic001@myhost)2> z:m().

You can directly go to pages /signup, /logon or /logoff and try them. Now, you are ready to add them to the menu on the navbar. As an administrator, choose the option Content -> Menu. Now, follow the following steps:

  1. Add menu item

  2. Choose 'New Page'. Select the name 'Signup' and, for convenience, select 'Page menu' as the category. Select the 'Published' option. 'Make' the page.

  3. Edit the Signup menu option.

  4. Click on 'Visit full editpage'

  5. In Advanced settings, give the page path '/signup' and unique name as 'signup' (same as in the dispatch rule) and save.

Similarly, you can add the menu options for loging on and off. These options would then be visible on the navigation bar. Caution: signup & logon options are silently ignored if you are already signed in.

Experiment by creating users and signing in from different browsers.

Changing Chat Code

You would have noticed that a user signed in on home page is not visible to the user signed in to the /chat page. View the mod_chat/mod_chat.erl file. Each submission of a chat message is handled by the event function.

The module also implements the behaviour of the gen_server module ( see 'erl -man gen_server' for details). This means that the module implements functions like handle_call, handle_cast etc which are implicitly called by the server.

The event function receives the chat message and notifies the zotonic server. Zotonic server will call handle_cast with appropriate parameters. Viewing the extract from the handle_cast function, you find that the messages are sent to the list ChatBoxes2, which is the list of users signed into chat from the same room. Relevant code lines are:

handle_cast({{chat_done, Chat}, Ctx}, State) ->

case State#state.chat_boxes of

[] -> nop,

{noreply, State};

ChatBoxes ->

ChatBoxes1 = [ X || X <- ChatBoxes, check_process_alive(X#chat_box.pid, Ctx)],

[ChatBox] = [ X || X <- ChatBoxes1, X#chat_box.pid == Ctx#context.page_pid ],

ChatBoxes2 = [ X || X <- ChatBoxes1, X#chat_box.room=:=ChatBox#chat_box.room],

...

[F(P) || #chat_box{pid=P} <- ChatBoxes2]

Delete the definition of ChatBoxes2 and replace the last line by

[F(P) || #chat_box{pid=P} <- ChatBoxes1]

In addition, first time a new chat window is displayed, the function handle_call is executed. Relevant function is:

handle_call({{add_chat_box, ChatBox}, Ctx}, _From, State) ->

ChatBoxes = [ X || X <- [ChatBox|State#state.chat_boxes],

check_process_alive(X#chat_box.pid, Ctx)],

ChatBoxes1 = [ X || X <- ChatBoxes,

X#chat_box.room=:=z_context:get_req_path(Ctx)],

%% add new chat box to all

render_userlist_row([{name, ChatBox#chat_box.name},

{upid, format_pid_for_html(ChatBox#chat_box.pid)}], ChatBoxes1, Ctx),

%% add existing chat boxes to new

[ render_userlist_row([{name, CBox#chat_box.name},

{upid, format_pid_for_html(CBox#chat_box.pid)}],

[ChatBox], Ctx) || CBox <- tl(ChatBoxes1) ],

{reply, ok, State#state{chat_boxes=ChatBoxes}};

In this case, ChatBoxes is the list of all chat sessions which are still alive and ChatBoxes1 is the subset of chat sessions which are on the same room. You can delete the the definition of ChatBoxes1 and replace the references to it by ChatBoxes.

Now, run z:m() from zotonic shell. All chat sessions should now be visible.

This was just a simple change to illustrate the possibilities. A more useful exercise would be to change meaning of a room and each user enters a particular room to chat with others in that room. Try it as an exercise :)

The nice thing is that there was no downtime in all the experiments as not once was a restart of zotonic needed.

Comments