I'm writing a chat web application, similar like Omegle, for which
Erlang and Nitrogen are the best tools. They almost seem designed for
it.
I have an abstraction that is room. A room is a process that whenever
it receives a messages sends it to everybody and handles processes
joining and leaving. The web application needs to send and receive
process to/from it.
Receiving actually was quite trivial, it's the same as the sample,
only a little bit more complex:
event(start_chat) ->
wf:comet(fun() -> chat_client() end);
chat_client() ->
Room = room_provider:get_room(),
room:join(Room),
chat_client().
chat_client() ->
receive
{message, Pid, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("~p: ~s", [Pid, Message]) }]),
wf:comet_flush();
end,
chat_client().
The function chat client is run like a comet process, it gets a room,
joins it and from then on puts on the browser every message received.
Now the problem is sending messages. Sending a message works like
this:
room:send_message(Room, Message)
and it should come from the same process that subscribe itself. I have
an event to send messages:
event(send_message) ->
% where is the room?
and there I have a problem, because I don't have the room at that
moment or any way to get ahold of it, and even then, this is another
process. My end solution ended up being quite complex and it's like
this:
chat_client() ->
Room = room_provider:get_room(),
room:join(Room),
chat_client(Room).
chat_client(Room) ->
receive
{send_message, Message} ->
room:send_message(Room, Message);
{leave_room} ->
wf:insert_bottom(messages, [#p{}, #span { text="-------" }]),
wf:comet_flush(),
room:leave(Room);
{message, Pid, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("~p: ~s", [Pid, Message]) }]),
wf:comet_flush();
end,
chat_client(Room).
the chat_client function which is run as a comet process no longer is
a receiver, now it's also a sender. Since that's the process that was
registered with the room and contains the room reference it has to do
both things. This looks ugly and unclean to me, what do you think?
But I still have a problem, event(send_message) doesn't know about it.
That means that event(start_chat) has to keep a reference and pass it
around, which means it's now this:
event(start_chat) ->
Client = wf:comet(fun() -> chat_client() end),
wf:wire(send_message, #event { type=click, postback={send_message,
Client} });
It puts a reference to Client in the send_message button, sending it
in the postback, which allows me to write:
event({send_message, Client}) ->
Message = wf:q(message),
Client ! {send_message, Message};
So, I'm passing state through the postback property of a button. That
looks extremely ugly. What do you think?
Is this good style or not? I haven't coded in Erlang in quite a while
and I've never did a web app with it, so I'm not sure if I'm following
patterns or antipatterns here.
Thanks.
PS: Well, the whole code is much more complex. I have many message
types coming and going and I also have to pass the client to the
start_chat button so that it terminates a previous client before
starting the new one:
-module (web_index).
-include_lib ("nitrogen/include/wf.inc").
-compile(export_all).
main() ->
#template { file="./wwwroot/template.html"}.
body() ->
Body=[
#button { id=start_chat, text="Start chatting",
postback=start_chat},
#p{},
#panel { id=messages, class=messages },
#p{},
#textbox { id=message, next=send_message },
#button { id=send_message, text="Send" }
],
wf:render(Body).
event({start_chat, Client}) ->
Client ! {leave_room},
exit(Client, starting_another_chat),
event(start_chat);
event(start_chat) ->
say("start_chat"),
Client = wf:comet(fun() -> chat_client() end),
wf:wire(send_message, #event { type=click, postback={send_message,
Client} }),
wf:wire(start_chat, #event { type=click, postback={start_chat,
Client} });
event({send_message, Client}) ->
Message = wf:q(message),
say("sending message '~s' through client ~p", [Message, Client]),
Client ! {send_message, Message};
event(_Oops) ->
io:format("Unexpected event ~p~n", [_Oops]).
chat_client() ->
Room = room_provider:get_room(),
room:join(Room),
chat_client(Room).
chat_client(Room) ->
Self = self(),
receive
{send_message, Message} ->
room:send_message(Room, Message);
{leave_room} ->
wf:insert_bottom(messages, [#p{}, #span { text="-------" }]),
wf:comet_flush(),
room:leave(Room);
{joined, _RemotePid} ->
wf:insert_bottom(messages, [#p{}, #span { text="You are now
talking to a stranger! Say hi!" }]),
wf:comet_flush();
{message, Self, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("You: ~s", [Message]) }]),
wf:comet_flush();
{message, _RemotePid, Message} ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("Stranger: ~s", [Message]) }]),
wf:comet_flush();
Message ->
wf:insert_bottom(messages, [#p{}, #span { text=io_lib:format
("~p", [Message]) }]),
wf:comet_flush()
end,
chat_client(Room).
say(Format) ->
say(Format, []).
say(Format, Data) ->
io:format("~p:~p: ~s~n", [?MODULE, self(), io_lib:format(Format,
Data)]).
On Jan 2, 4:09 pm, J. Pablo Fernández <pup...@pupeno.com> wrote: