# chatola.py # a simple chat engine from twisted.cred import portal, checkers from twisted.web import resource from nevow import renderer from nevow import tags from nevow import liveevil def callJS(func, *args): ## Move this into liveevil. args = ["'%s'" % a.replace("'", "\\'").replace('"', '\\"') for a in args] return "%s(%s);" % (func, ','.join(args)) class ChatolaRealm: """A simple implementor of cred's IRealm. The only interface we support web, this gives us the LoggedIn page. """ __implements__ = portal.IRealm def __init__(self): self.clients = [] def requestAvatar(self, avatarId, client, *interfaces): for iface in interfaces: if iface is resource.IResource: if avatarId is checkers.ANONYMOUS: client.userId = "Anonymous %s" % (len(self.clients) + 1, ) self.broadcast(client, "%s has joined." % client.userId) # The mind when using nevow is a LiveEvil instance # which represents the client side browser. We can # use it to broadcast messages to viewing browsers. self.clients.append(client) # When constructing the page the user will see, we pass the realm # as the data to the page. When the user enters some text to broadcast, # this page can then notify the realm which broadcasts the message to # all other clients. page = Chatola(self, client) ## We are returning an IResource, resc, and when the user logs out we ## wish to remove them from our client list. return resource.IResource, page, lambda: ( self.broadcast(client, "%s has left." % client.userId), self.clients.remove(client)) raise NotImplementedError("Can't support that interface.") def broadcast(self, user, message): """Broadcast a message to every client which is connected to this realm. """ script = callJS("writeContent", "
%s: %s
" % (user.userId, message)) for C in self.clients: C.sendScript(script) if C is user: C.sendScript("focusInput();") class Chatola(renderer.HTMLRenderer): templateFile = "Chatola.html" def __init__(self, realm, client): self.realm = realm self.client = client renderer.Renderer.__init__(self) def render_content(self, context, data): """When a user enters the chat we'll render a list of the people that are currently in it """ return context.tag[ "Userlist: ", [(user.userId, " ") for user in self.realm.clients] ] def render_input(self, context, data): """Render an input form; a text box and a submit button. On the form, we add an onsubmit event handler; we pass a python callable wrapped by the liveevil.handler function which will be called on the server when the client side onsubmit handler is called. liveevil.handler automatically returns false in the browser handler so the normal page submit does not occur. Instead, the second argument to handler, a string, is evaluated as javascript in the browser context and the result is sent to the server-side onSubmit method. """ return tags.form( onsubmit=liveevil.handler(self.onSubmit, "getValue('inputline');") )[ tags.input(type="text", id="inputline"), tags.input(type="submit", value="say") ] def onSubmit(self, client, text): """When the onSubmit method is called on the server in response to the client-side onsubmit javascript handler, pass the text which was in the user's input box to the realm's broadcast method. Broadcast will format the message with the user's nick and send javascript to every connected client (including this user) which will append this message to the "content" div so all users can see the new chat text. """ self.realm.broadcast(client, text) def render_nick(self, context, data): return tags.form( onsubmit=liveevil.handler(self.onNickChange, "getValue('nick');") )[ tags.input(type="text", id="nick", value=self.client.userId), tags.input(type="submit", value="change nick"), tags.span(id="nickChangeNotification") ] def onNickChange(self, client, newval): self.realm.broadcast(client, "%s is now known as %s." % (client.userId, newval)) client.userId = newval client.sendScript(callJS('nickChanged', newval)) def render_glue(self, context, data): """Insert the liveevil glue necessary for the input and output conduits to be present, and thus serverToClient and clientToServer events to be passed. """ return liveevil.glue