-
-
Save kadzany/1873000 to your computer and use it in GitHub Desktop.
Multi-room Chat server / client with Lift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package myproject | |
package snippet | |
import net.liftweb._ | |
import common.Logger | |
import http._ | |
import js._ | |
import JsCmds._ | |
import JE._ | |
import comet._ | |
import model.User | |
/** | |
* A snippet transforms input to output... it transforms | |
* templates to dynamic content. Lift's templates can invoke | |
* snippets and the snippets are resolved in many different | |
* ways including "by convention". The snippet package | |
* has named snippets and those snippets can be classes | |
* that are instantiated when invoked or they can be | |
* objects, singletons. Singletons are useful if there's | |
* no explicit state managed in the snippet. | |
*/ | |
class ChatIn extends Logger { | |
/** | |
* The render method in this case returns a function | |
* that transforms NodeSeq => NodeSeq. In this case, | |
* the function transforms a form input element by attaching | |
* behavior to the input. The behavior is to send a message | |
* to the ChatServer and then returns JavaScript which | |
* clears the input. | |
*/ | |
def render = SHtml.onSubmit(s => { | |
val roomName = S.param("room") getOrElse "default" | |
info("Processing message at %s " format roomName) | |
ChatManager.find(roomName).get ! ChatServerMsg(User.currentUser.get.firstName.is,s) | |
//SetValById("chat_in", "") & | |
SetValueAndFocus("chat_in","") | |
}) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="pods"> | |
<h3><a href="#">Contact List</a></h3> | |
<div id="contactList"> | |
<div class="lift:comet?type=ContactList;metaname=room"> | |
<div id="jsBlock" style="display: none"></div> | |
<div> | |
<ul id="ul-users"> | |
<li><strong>Jhonny</strong> MyCompany</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<h3><a href="#">Chat</a></h3> | |
<div id="chat"> | |
<div class="lift:comet?type=Chat;metaname=room"> | |
<div id="chat-messages"> | |
<ul id="ul-messages"> | |
<li><strong>User</strong>A message</li> | |
</ul> | |
</div> | |
<div id="chat-input-row"> | |
<form class="lift:form.ajax"> | |
<input class="large lift:ChatIn" id="chat_in"/> | |
<input type="hidden" id="room-name" name="room" value=""/> | |
<button id="chat-send" type="submit" class="btn small">Send</button> | |
</form> | |
</div> | |
</div> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package myproject.comet | |
import net.liftweb._ | |
import actor.LiftActor | |
import common.{Full, Empty, Box} | |
import http._ | |
import js.JE._ | |
import js.JE.JsArray._ | |
import js.JE.JsRaw._ | |
import js.JsCmds._ | |
import util._ | |
import Helpers._ | |
import util.AnyVar._ | |
import xml.NodeSeq | |
import com.foursquare.rogue.Rogue._ | |
import net.liftweb.mongodb._ | |
import net.liftweb.mongodb.record._ | |
import net.liftweb.util._ | |
import net.liftweb.common._ | |
import net.liftweb.json.JsonAST.JString | |
import net.liftweb.json.JsonDSL._ | |
import org.bson.types.ObjectId | |
import net.liftweb.record.LifecycleCallbacks | |
import net.liftweb.record._ | |
import scala.xml._ | |
import net.liftweb.record.field._ | |
import net.liftweb.mongodb.record.field._ | |
import net.liftweb.mongodb.record._ | |
import com.foursquare.rogue.Rogue._ | |
import net.liftweb.mongodb._ | |
import com.mongodb.{BasicDBObject, BasicDBObjectBuilder, DBObject} | |
import br.com.netsar.model.{User, Room} | |
import scala.PartialFunction | |
/* | |
Chat server. Keep and proxy the messages to the comet actors. | |
*/ | |
class ChatServer(val room: String) extends LiftActor with ListenerManager with Logger { | |
//class ChatServer(val room: Room) extends LiftActor with ListenerManager { | |
private var messages = List(("System Bot", "Welcome!")) | |
private var users: List[User] = Nil | |
def createUpdate = ChatServerUpdate(messages, users) | |
// processar as mensagens que chegam | |
override def lowPriority = { | |
case ChatServerMsg(user, msg) => | |
info("mensagem recebida de %s" format user) | |
messages = (user, msg) :: messages | |
updateListeners(NewMsg((user, msg))) | |
case NewUser(user) => | |
info("new user %s on server" format user.firstName) | |
users = user :: users | |
updateListeners(NewServerUser(user)) | |
case UserLeft(user) => { | |
info("server - user %s left room" format user.firstName) | |
info("nova lista de usuários %s" format users.map(_.firstName).toString()) | |
users = users filterNot (u => u._id.is == user._id.is) | |
updateListeners(ChatServerUsers(users)) | |
} | |
case _ => info("Unknown message") | |
} | |
} | |
object ChatManager { | |
//private var rooms = Map(Room where(_.active eqs true).fetch.map(rToPair) :_*) | |
private var rooms = Map("default" :: Nil map (rToPair): _*) | |
private def rToPair(r: String) = r -> new ChatServer(r) | |
def default = synchronized { | |
rooms("default") | |
} | |
def find(room: String) = synchronized { | |
rooms.get(room) orElse { | |
val c = new ChatServer(room) | |
rooms += (room -> c) | |
//rooms += rToPair room | |
rooms.get(room) | |
} | |
} | |
} | |
/* | |
* Chat client actor. Each client session has one instance of this. | |
*/ | |
class Chat extends CometActor with CometListener with Logger { | |
private var messages: List[(String, String)] = Nil | |
override def lifespan = Full(10 seconds) | |
private val user = User.currentUser.get | |
override def localSetup() = { | |
super.localSetup() | |
info("localSetup(%s)" format user.firstName) | |
//sendMessage(NewUser(user)) | |
} | |
def sendMessage(msg: Any) { | |
ChatManager.find(name getOrElse "default").get ! msg | |
} | |
override protected def localShutdown() { | |
// remove from user lists if still there. | |
//sendMessage(UserLeft(user)) | |
super.localShutdown | |
} | |
def registerWith = ChatManager.find(name getOrElse "default").get | |
override def lowPriority = { | |
case ChatServerUpdate(m, u) => | |
info("Iniciando chat local com %s e %s" format(u.map(_.getDisplayName), m)) | |
messages = m | |
reRender(false) | |
case NewMsg(t) => | |
messages = t :: messages | |
partialUpdate(Call("addChatMessage", t._1, t._2)) | |
} | |
def createDisplay(v: List[(String, String)]): NodeSeq = { | |
{ | |
for {item <- v} yield | |
<li> | |
<strong> | |
{item._1} | |
</strong>{item._2} | |
</li> | |
} | |
} | |
def render = { | |
info("chat render para %s " format user.firstName.is) | |
"#ul-messages *" #> createDisplay(messages) & | |
"#room-name [value]" #> name.getOrElse("default") & | |
"#jsBlock *" #> Script(JsRaw("$(function() {initPods();});") | |
) | |
} | |
} | |
/* | |
* Contact List client. Each client session has one instance of this. | |
*/ | |
class ContactList extends CometActor with CometListener with Logger { | |
private var users: List[User] = Nil | |
override def lifespan = Full(10 seconds) | |
private val user = User.currentUser.get | |
override def localSetup() = { | |
super.localSetup() | |
info("localSetup(%s)" format user.firstName) | |
sendMessage(NewUser(user)) | |
} | |
def sendMessage(msg: Any) { | |
ChatManager.find(name getOrElse "default").get ! msg | |
} | |
override protected def localShutdown() { | |
// remove from user lists if still there. | |
sendMessage(UserLeft(user)) | |
super.localShutdown | |
} | |
def registerWith = ChatManager.find(name getOrElse "default").get | |
override def lowPriority = { | |
case ChatServerUpdate(m, u) => | |
info("iniciando chat local com %s e %s" format(u.map(_.firstName), m)) | |
users = u | |
reRender(false) | |
case NewServerUser(u) => | |
info("recebeu novo usuario %s" format u.firstName.is) | |
//if (user.id != u.id) { | |
users = u :: users | |
info("adicionou novo usuario %s" format u.firstName.is) | |
//partialUpdate(Call("addUser", u.firstName.is)) | |
reRender(false) | |
//} | |
case ChatServerUsers(u) => | |
info("recebeu nova lista de usuários %s" format u.map(_.firstName).toString()) | |
users = u | |
reRender(false) | |
} | |
def createDisplayUsers(v: List[User]): NodeSeq = { | |
{ | |
for {item <- v} yield | |
<li> | |
<strong> | |
{item.firstName.is} | |
</strong> | |
</li> | |
} | |
} | |
def render = { | |
val userIds = users.map(c => Str(c.id.toString)).toList | |
info("render para %s com %s" format(user.firstName.is, users.map(_.getDisplayName))) | |
"#ul-users *" #> createDisplayUsers(users) & | |
"#room-name [value]" #> name.getOrElse("default") & | |
"#jsBlock *" #> Script(JsCrVar("connectedUsers", JsArray(userIds)) & | |
JsRaw("$(function() {updateChatRoomUsers();});") | |
) | |
} | |
} | |
case class ChatServerUpdate(msgs: List[(String, String)], usrs: List[User]) | |
case class ChatServerUsers(users: List[User]) | |
case class ChatServerMsg(user: String, msg: String) | |
case class NewUser(user: User) | |
case class NewServerUser(user: User) | |
case class NewMsg(msg: (String, String)) | |
case class UserLeft(user: User) | |
case class ChatInitialUpdate(msgs: List[(String, String)], users: List[User]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment