Index: lams_build/lib/lams/lams.jar
===================================================================
diff -u -rab2b6923490ce38dc647ea66d6768966fbc40cfd -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
Binary files differ
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_central/src/java/org/lamsfoundation/lams/web/PresenceServlet.java'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_central/src/java/org/lamsfoundation/lams/webservice/PresenceChatAction.java'.
Fisheye: No comparison available. Pass `N' to diff?
Index: lams_common/src/java/org/lamsfoundation/lams/commonContext.xml
===================================================================
diff -u -r078ffcc5cce6214e179857ace04486fa04a7e3a2 -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision 078ffcc5cce6214e179857ace04486fa04a7e3a2)
+++ lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -226,11 +226,6 @@
-
-
-
-
-
@@ -508,11 +503,6 @@
-
-
-
-
-
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_common/src/java/org/lamsfoundation/lams/presence/dao/IPresenceChatDAO.java'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_common/src/java/org/lamsfoundation/lams/presence/dao/hibernate/PresenceChatDAO.java'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_common/src/java/org/lamsfoundation/lams/presence/model/PresenceChatMessage.java'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_common/src/java/org/lamsfoundation/lams/presence/model/PresenceChatUser.java'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_common/src/java/org/lamsfoundation/lams/presence/service/IPresenceChatService.java'.
Fisheye: No comparison available. Pass `N' to diff?
Fisheye: Tag 9c603f82a3332d5e2e5b290bcc550f263c09a8f8 refers to a dead (removed) revision in file `lams_common/src/java/org/lamsfoundation/lams/presence/service/PresenceChatService.java'.
Fisheye: No comparison available. Pass `N' to diff?
Index: lams_learning/.classpath
===================================================================
diff -u -r0fdf00ad8ffebc0cc6d79de96a216c08ce0d4cdf -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/.classpath (.../.classpath) (revision 0fdf00ad8ffebc0cc6d79de96a216c08ce0d4cdf)
+++ lams_learning/.classpath (.../.classpath) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -18,6 +18,7 @@
+
Index: lams_learning/build.xml
===================================================================
diff -u -re59bc835a5ec91886980d67af70c0f05a0f7ae73 -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/build.xml (.../build.xml) (revision e59bc835a5ec91886980d67af70c0f05a0f7ae73)
+++ lams_learning/build.xml (.../build.xml) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -3,5 +3,23 @@
+
+
+
+
+ ${ant.project.name}: Copying additional Java classes to WAR
+
+
\ No newline at end of file
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml
===================================================================
diff -u -ra6641bf9262a01d07740a517643f8fe187ec5b1f -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml (.../learningApplicationContext.xml) (revision a6641bf9262a01d07740a517643f8fe187ec5b1f)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml (.../learningApplicationContext.xml) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -97,8 +97,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+
+
+
+
+
+
+
+
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/PresenceWebsocketServer.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/PresenceWebsocketServer.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/PresenceWebsocketServer.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,391 @@
+package org.lamsfoundation.lams.learning.presence;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.websocket.CloseReason;
+import javax.websocket.CloseReason.CloseCodes;
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.apache.tomcat.util.json.JSONArray;
+import org.apache.tomcat.util.json.JSONException;
+import org.apache.tomcat.util.json.JSONObject;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatMessage;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatUser;
+import org.lamsfoundation.lams.learning.presence.service.IPresenceChatService;
+import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager;
+import org.lamsfoundation.lams.web.session.SessionManager;
+import org.lamsfoundation.lams.web.util.AttributeNames;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+/**
+ * Receives, processes and sends Lesson Chat messages to Learners.
+ *
+ * @author Marcin Cieslak
+ */
+@ServerEndpoint("/presenceChatWebsocket")
+public class PresenceWebsocketServer {
+
+ /**
+ * Identifies a single connection. There can be more than one connection for the same user: multiple windows open or
+ * the same user in an another role.
+ */
+ private static class Websocket {
+ private final Session session;
+ private final String nickName;
+
+ private Websocket(Session session) {
+ this.session = session;
+ this.nickName = session.getRequestParameterMap().get("nickname").get(0);
+ }
+ }
+
+ /**
+ * A singleton which updates Learners with messages and roster.
+ */
+ private static class SendWorker extends Thread {
+ private boolean stopFlag = false;
+ // how ofter the thread runs
+ private static final long CHECK_INTERVAL = 2000;
+ // mapping lessonId -> timestamp when the check was last performed, so the thread does not run too often
+ private final Map lastSendTimes = new TreeMap();
+
+ @Override
+ public void run() {
+ while (!stopFlag) {
+ try {
+ // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually
+ // A new session needs to be created on each thread run as the session keeps stale Hibernate data (single transaction).
+ HibernateSessionManager.bindHibernateSessionToCurrentThread(true);
+
+ // synchronize websockets as a new Learner entering chat could modify this collection
+ synchronized (PresenceWebsocketServer.websockets) {
+ Iterator>> entryIterator = PresenceWebsocketServer.websockets
+ .entrySet().iterator();
+ // go through lessons and update registered learners with messages and roster
+ while (entryIterator.hasNext()) {
+ Entry> entry = entryIterator.next();
+ Long lessonId = entry.getKey();
+ Long lastSendTime = lastSendTimes.get(lessonId);
+ if ((lastSendTime == null)
+ || ((System.currentTimeMillis() - lastSendTime) >= SendWorker.CHECK_INTERVAL)) {
+ send(lessonId, null);
+ }
+
+ // if all learners left the chat, remove the obsolete mapping
+ Set lessonWebsockets = entry.getValue();
+ if (lessonWebsockets.isEmpty()) {
+ entryIterator.remove();
+ PresenceWebsocketServer.rosters.remove(lessonId);
+ lastSendTimes.remove(lessonId);
+ }
+ }
+ }
+
+ Thread.sleep(SendWorker.CHECK_INTERVAL);
+ } catch (InterruptedException e) {
+ PresenceWebsocketServer.log.warn("Stopping Presence Chat worker thread");
+ stopFlag = true;
+ } catch (Exception e) {
+ // error caught, but carry on
+ PresenceWebsocketServer.log.error("Error in Presence Chat worker thread", e);
+ }
+ }
+ }
+
+ /**
+ * Cheks for stale connections and feeds the opened ones with messages and roster.
+ */
+ private void send(Long lessonId, String nickName) {
+ Long lastSendTime = lastSendTimes.get(lessonId);
+ // fetch messages a bit earlier than the last run, in case there was a lag somewhere
+ // JS code will filter out duplicates
+ lastSendTime = (lastSendTime == null) || (nickName != null) ? 0 : lastSendTime - 1000;
+ List messages = PresenceWebsocketServer.getPresenceChatService()
+ .getNewMessages(lessonId, new Date(lastSendTime));
+ // update the timestamp, if this is a regular run
+ if (nickName == null) {
+ lastSendTimes.put(lessonId, System.currentTimeMillis());
+ }
+
+ Set lessonWebsockets = PresenceWebsocketServer.websockets.get(lessonId);
+ Roster roster = PresenceWebsocketServer.rosters.get(lessonId);
+ JSONArray rosterJSON = roster.getRosterJSON();
+ // synchronize websockets as a new Learner entering chat could modify this collection
+ synchronized (lessonWebsockets) {
+ Iterator websocketIterator = lessonWebsockets.iterator();
+ while (websocketIterator.hasNext()) {
+ Websocket websocket = websocketIterator.next();
+ // if this run is meant only for one learner, skip the others
+ if ((nickName != null) && !nickName.equals(websocket.nickName)) {
+ continue;
+ }
+
+ // check whether the connection is not stale
+ if (!websocket.session.isOpen()) {
+ // remove the closed connection
+ websocketIterator.remove();
+
+ if (PresenceWebsocketServer.log.isDebugEnabled()) {
+ PresenceWebsocketServer.log.debug(
+ "User " + websocket.nickName + " left Presence Chat with lessonId: " + lessonId);
+ }
+ continue;
+ }
+
+ // the connection is valid, carry on
+ JSONObject responseJSON = new JSONObject();
+
+ try {
+ // if it is just roster, skip messages
+ if (roster.imEnabled) {
+ JSONArray messagesJSON = PresenceWebsocketServer.filterMessages(messages,
+ websocket.nickName);
+ responseJSON.put("messages", messagesJSON);
+ }
+ responseJSON.put("roster", rosterJSON);
+
+ // send the payload to the Learner's browser
+ websocket.session.getBasicRemote().sendText(responseJSON.toString());
+ } catch (Exception e) {
+ PresenceWebsocketServer.log.error("Error while building message JSON", e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Keeps information of learners present in a lesson. Needs to work with DB so presence is visible in clustered
+ * environment.
+ */
+ private static class Roster {
+ private final Long lessonId;
+ private final boolean imEnabled;
+ // timestamp when DB was last hit
+ private long lastDBCheckTime = 0;
+
+ // Learners who are currently active
+ private final Set activeUsers = new TreeSet();
+
+ private Roster(Long lessonId, boolean imEnabled) {
+ this.lessonId = lessonId;
+ this.imEnabled = imEnabled;
+ }
+
+ /**
+ * Checks which Learners
+ */
+ private JSONArray getRosterJSON() {
+ Set localActiveUsers = new TreeSet();
+ Set sessionWebsockets = PresenceWebsocketServer.websockets.get(lessonId);
+ // find out who is active locally
+ for (Websocket websocket : sessionWebsockets) {
+ localActiveUsers.add(websocket.nickName);
+ }
+
+ // is it time to sync with the DB yet?
+ long currentTime = System.currentTimeMillis();
+ if ((currentTime - lastDBCheckTime) > IPresenceChatService.PRESENCE_IDLE_TIMEOUT) {
+ // store Learners active on this node
+ PresenceWebsocketServer.getPresenceChatService().updateUserPresence(lessonId, localActiveUsers);
+
+ // read active Learners from all nodes
+ List storedActiveUsers = PresenceWebsocketServer.getPresenceChatService()
+ .getUsersActiveByLessonId(lessonId);
+ // refresh current collection
+ activeUsers.clear();
+ for (PresenceChatUser activeUser : storedActiveUsers) {
+ activeUsers.add(activeUser.getNickname());
+ }
+
+ lastDBCheckTime = currentTime;
+ } else {
+ // add users active on this node; no duplicates - it is a set, not a list
+ activeUsers.addAll(localActiveUsers);
+ }
+
+ return new JSONArray(activeUsers);
+ }
+ }
+
+ private static Logger log = Logger.getLogger(PresenceWebsocketServer.class);
+
+ private static IPresenceChatService presenceChatService;
+
+ private static final SendWorker sendWorker = new SendWorker();
+ private static final Map rosters = Collections.synchronizedMap(new TreeMap());
+ private static final Map> websockets = Collections
+ .synchronizedMap(new TreeMap>());
+
+ static {
+ // run the singleton thread
+ PresenceWebsocketServer.sendWorker.start();
+ }
+
+ /**
+ * Registeres the Learner for processing by SendWorker.
+ */
+ @OnOpen
+ public void registerUser(Session session) throws IOException {
+ Long lessonId = Long.valueOf(session.getRequestParameterMap().get(AttributeNames.PARAM_LESSON_ID).get(0));
+ Set sessionWebsockets = PresenceWebsocketServer.websockets.get(lessonId);
+ if (sessionWebsockets == null) {
+ sessionWebsockets = Collections.synchronizedSet(new HashSet());
+ PresenceWebsocketServer.websockets.put(lessonId, sessionWebsockets);
+ }
+ Websocket websocket = new Websocket(session);
+ sessionWebsockets.add(websocket);
+
+ Roster roster = PresenceWebsocketServer.rosters.get(lessonId);
+ if (roster == null) {
+ boolean imEnabled = Boolean.valueOf(session.getRequestParameterMap().get("imEnabled").get(0));
+ // build a new roster object
+ roster = new Roster(lessonId, imEnabled);
+ PresenceWebsocketServer.rosters.put(lessonId, roster);
+ }
+
+ // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually
+ HibernateSessionManager.bindHibernateSessionToCurrentThread(false);
+ // update the chat window immediatelly
+ PresenceWebsocketServer.sendWorker.send(lessonId, websocket.nickName);
+
+ if (PresenceWebsocketServer.log.isDebugEnabled()) {
+ PresenceWebsocketServer.log
+ .debug("User " + websocket.nickName + " entered Chat with toolSessionId: " + lessonId);
+ }
+ }
+
+ /**
+ * If there was something wrong with the connection, put it into logs.
+ */
+ @OnClose
+ public void unregisterUser(CloseReason reason) {
+ if (!(reason.getCloseCode().equals(CloseCodes.GOING_AWAY)
+ || reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE))) {
+ PresenceWebsocketServer.log.warn("Abnormal Presence Chat websocket close. Code: " + reason.getCloseCode()
+ + ". Reason: " + reason.getReasonPhrase());
+ }
+ }
+
+ /**
+ * Receives a message sent by Learner via a websocket.
+ *
+ * @throws IOException
+ */
+ @OnMessage
+ public void receiveRequest(String input, Session session) throws JSONException, IOException {
+ if (StringUtils.isBlank(input)) {
+ return;
+ }
+ JSONObject requestJSON = new JSONObject(input);
+ String requestType = requestJSON.getString("type");
+ if (requestType.equals("message")) {
+ PresenceWebsocketServer.storeMessage(requestJSON, session);
+ } else if (requestType.equals("fetchConversation")) {
+ PresenceWebsocketServer.sendConversation(requestJSON, session);
+ }
+ }
+
+ /**
+ * Stores a message sent by a Learner.
+ */
+ private static void storeMessage(JSONObject requestJSON, Session session) throws JSONException {
+ String message = requestJSON.getString("message");
+ if (StringUtils.isBlank(message)) {
+ return;
+ }
+ Long lessonId = requestJSON.getLong("lessonID");
+
+ // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually
+ HibernateSessionManager.bindHibernateSessionToCurrentThread(false);
+
+ String from = session.getRequestParameterMap().get("nickname").get(0);
+ String to = requestJSON.getString("to");
+ if (StringUtils.isBlank(to)) {
+ to = null;
+ }
+
+ PresenceWebsocketServer.getPresenceChatService().createPresenceChatMessage(lessonId, from, to, new Date(),
+ message);
+ }
+
+ /**
+ * Sends the whole message history between the current and the chosen learner.
+ */
+ private static void sendConversation(JSONObject requestJSON, Session session) throws JSONException, IOException {
+ Long lessonId = requestJSON.getLong("lessonID");
+
+ // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually
+ HibernateSessionManager.bindHibernateSessionToCurrentThread(false);
+
+ String to = requestJSON.getString("to");
+ String from = session.getRequestParameterMap().get("nickname").get(0);
+
+ List messages = PresenceWebsocketServer.getPresenceChatService()
+ .getMessagesByConversation(lessonId, from, to);
+ JSONArray messagesJSON = new JSONArray();
+
+ for (PresenceChatMessage message : messages) {
+ JSONObject messageJSON = PresenceWebsocketServer.buildMessageJSON(message);
+ messagesJSON.put(messageJSON);
+ }
+
+ JSONObject responseJSON = new JSONObject();
+ responseJSON.put("messages", messagesJSON);
+ // send the payload to the Learner's browser
+ session.getBasicRemote().sendText(responseJSON.toString());
+ }
+
+ /**
+ * Filteres messages meant for the given user (group or personal).
+ */
+ private static JSONArray filterMessages(List messages, String nickName) throws JSONException {
+ JSONArray messagesJSON = new JSONArray();
+
+ for (PresenceChatMessage message : messages) {
+ String messageTo = message.getTo();
+ if ((messageTo == null) || message.getTo().equals(nickName) || message.getFrom().equals(nickName)) {
+ JSONObject messageJSON = PresenceWebsocketServer.buildMessageJSON(message);
+ messagesJSON.put(messageJSON);
+ }
+ }
+
+ return messagesJSON;
+ }
+
+ private static JSONObject buildMessageJSON(PresenceChatMessage message) throws JSONException {
+ JSONObject messageJSON = new JSONObject();
+ messageJSON.put("uid", message.getUid());
+ messageJSON.put("from", message.getFrom());
+ messageJSON.put("to", message.getTo());
+ messageJSON.put("dateSent", message.getDateSent());
+ messageJSON.put("message", message.getMessage());
+ return messageJSON;
+ }
+
+ private static IPresenceChatService getPresenceChatService() {
+ if (PresenceWebsocketServer.presenceChatService == null) {
+ WebApplicationContext ctx = WebApplicationContextUtils
+ .getWebApplicationContext(SessionManager.getServletContext());
+ PresenceWebsocketServer.presenceChatService = (IPresenceChatService) ctx.getBean("presenceChatService");
+ }
+ return PresenceWebsocketServer.presenceChatService;
+ }
+}
\ No newline at end of file
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/dao/IPresenceChatDAO.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/dao/IPresenceChatDAO.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/dao/IPresenceChatDAO.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,43 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+
+package org.lamsfoundation.lams.learning.presence.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatMessage;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatUser;
+
+public interface IPresenceChatDAO {
+
+ void insertOrUpdate(Object object);
+
+ List getNewMessages(Long lessonId, Date lastCheck);
+
+ List getMessagesByConversation(Long lessonId, String from, String to);
+
+ List getUsersByLessonIdAndLastPresence(Long lessonId, Date oldestLastPresence);
+}
\ No newline at end of file
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/dao/hibernate/PresenceChatDAO.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/dao/hibernate/PresenceChatDAO.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/dao/hibernate/PresenceChatDAO.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,77 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+
+package org.lamsfoundation.lams.learning.presence.dao.hibernate;
+
+import java.util.Date;
+import java.util.List;
+
+import org.lamsfoundation.lams.dao.hibernate.LAMSBaseDAO;
+import org.lamsfoundation.lams.learning.presence.dao.IPresenceChatDAO;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatMessage;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatUser;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class PresenceChatDAO extends LAMSBaseDAO implements IPresenceChatDAO {
+ // main query for getting new messages
+ private static final String MESSAGE_NEW = "FROM " + PresenceChatMessage.class.getName()
+ + " msg WHERE msg.lessonId=:lessonId AND msg.dateSent > :lastCheck ORDER BY msg.dateSent ASC";
+
+ // main query for getting new messages
+ private static final String MESSAGE_BY_CONVERSATION = "FROM " + PresenceChatMessage.class.getName()
+ + " msg WHERE msg.lessonId=:lessonId AND ((msg.to = :to AND msg.from = :from) OR (msg.from = :to AND msg.to = :from)) "
+ + "ORDER BY msg.dateSent ASC";
+
+ private static final String USER_BY_LESSON_ID_AND_TIME = "from " + PresenceChatUser.class.getName() + " r"
+ + " where r.lessonId=? and r.lastPresence > ?";
+
+ @Override
+ public void insertOrUpdate(Object object) {
+ super.insertOrUpdate(object);
+ flush();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List getNewMessages(Long lessonId, Date lastCheck) {
+ return (List) (doFindByNamedParam(PresenceChatDAO.MESSAGE_NEW,
+ new String[] { "lessonId", "lastCheck" }, new Object[] { lessonId, lastCheck }));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List getMessagesByConversation(Long lessonId, String from, String to) {
+ return (List) (doFindByNamedParam(PresenceChatDAO.MESSAGE_BY_CONVERSATION,
+ new String[] { "lessonId", "from", "to" }, new Object[] { lessonId, from, to }));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List getUsersByLessonIdAndLastPresence(Long lessonId, Date oldestLastPresence) {
+ return (List) this.doFind(PresenceChatDAO.USER_BY_LESSON_ID_AND_TIME,
+ new Object[] { lessonId, oldestLastPresence });
+ }
+}
\ No newline at end of file
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/model/PresenceChatMessage.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/model/PresenceChatMessage.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/model/PresenceChatMessage.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,126 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+
+package org.lamsfoundation.lams.learning.presence.model;
+
+import java.util.Date;
+
+/**
+ * @hibernate.class table="lams_presence_chat_msgs"
+ */
+public class PresenceChatMessage implements java.io.Serializable, Cloneable {
+
+ private Long uid;
+
+ private Long lessonId;
+
+ private String from;
+
+ private String to;
+
+ private Date dateSent;
+
+ private String message;
+
+ public PresenceChatMessage() {
+
+ }
+
+ public PresenceChatMessage(Long lessonId, String from, String to, Date dateSent, String message) {
+ super();
+ this.lessonId = lessonId;
+ this.from = from;
+ this.to = to;
+ this.dateSent = dateSent;
+ this.message = message;
+ }
+
+ /**
+ * @hibernate.id generator-class="native" type="java.lang.Long" column="uid"
+ */
+ public Long getUid() {
+ return uid;
+ }
+
+ public void setUid(Long uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * @hibernate.property column="lesson_id"
+ */
+ public Long getLessonId() {
+ return lessonId;
+ }
+
+ public void setLessonId(Long lessonId) {
+ this.lessonId = lessonId;
+ }
+
+ /**
+ * @hibernate.property column="from_user"
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ /**
+ * @hibernate.property column="to_user"
+ */
+ public String getTo() {
+ return to;
+ }
+
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ /**
+ * @hibernate.property column="date_sent"
+ */
+ public Date getDateSent() {
+ return dateSent;
+ }
+
+ public void setDateSent(Date dateSent) {
+ this.dateSent = dateSent;
+ }
+
+ /**
+ * @hibernate.property column="message"
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+}
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/model/PresenceChatUser.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/model/PresenceChatUser.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/model/PresenceChatUser.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,82 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+
+package org.lamsfoundation.lams.learning.presence.model;
+
+import java.util.Date;
+
+/**
+ * @hibernate.class table="lams_presence_user"
+ */
+public class PresenceChatUser implements java.io.Serializable, Cloneable {
+
+ private String nickname;
+
+ private Long lessonId;
+
+ private Date lastPresence;
+
+ public PresenceChatUser() {
+ }
+
+ public PresenceChatUser(String nickname, Long lessonId, Date lastPresence) {
+ this.nickname = nickname;
+ this.lessonId = lessonId;
+ this.lastPresence = lastPresence;
+ }
+
+ /**
+ * @hibernate.id generator-class="assigned" column="nickname"
+ */
+ public String getNickname() {
+ return nickname;
+ }
+
+ public void setNickname(String nickname) {
+ this.nickname = nickname;
+ }
+
+ /**
+ * @hibernate.property column="lesson_id"
+ */
+ public Long getLessonId() {
+ return lessonId;
+ }
+
+ public void setLessonId(Long lessonId) {
+ this.lessonId = lessonId;
+ }
+
+ /**
+ * @hibernate.property column="last_presence"
+ */
+ public Date getLastPresence() {
+ return lastPresence;
+ }
+
+ public void setLastPresence(Date lastPresence) {
+ this.lastPresence = lastPresence;
+ }
+}
\ No newline at end of file
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/service/IPresenceChatService.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/service/IPresenceChatService.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/service/IPresenceChatService.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,47 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+
+package org.lamsfoundation.lams.learning.presence.service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatMessage;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatUser;
+
+public interface IPresenceChatService {
+ final long PRESENCE_IDLE_TIMEOUT = 15 * 1000;
+
+ void createPresenceChatMessage(Long lessonId, String from, String to, Date dateSent, String message);
+
+ List getNewMessages(Long lessonId, Date lastCheck);
+
+ List getMessagesByConversation(Long lessonId, String from, String to);
+
+ void updateUserPresence(Long lessonId, Set users);
+
+ List getUsersActiveByLessonId(Long lessonId);
+}
\ No newline at end of file
Index: lams_learning/src/java/org/lamsfoundation/lams/learning/presence/service/PresenceChatService.java
===================================================================
diff -u
--- lams_learning/src/java/org/lamsfoundation/lams/learning/presence/service/PresenceChatService.java (revision 0)
+++ lams_learning/src/java/org/lamsfoundation/lams/learning/presence/service/PresenceChatService.java (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -0,0 +1,77 @@
+/****************************************************************
+ * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org)
+ * =============================================================
+ * License Information: http://lamsfoundation.org/licensing/lams/2.0/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2.0
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ *
+ * http://www.gnu.org/licenses/gpl.txt
+ * ****************************************************************
+ */
+
+/* $Id$ */
+
+package org.lamsfoundation.lams.learning.presence.service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import org.lamsfoundation.lams.learning.presence.dao.IPresenceChatDAO;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatMessage;
+import org.lamsfoundation.lams.learning.presence.model.PresenceChatUser;
+
+public class PresenceChatService implements IPresenceChatService {
+
+ private IPresenceChatDAO presenceChatDAO;
+
+ @Override
+ public void createPresenceChatMessage(Long lessonId, String from, String to, Date dateSent, String message) {
+ PresenceChatMessage presenceChatMessage = new PresenceChatMessage(lessonId, from, to, dateSent, message);
+ presenceChatDAO.insertOrUpdate(presenceChatMessage);
+ }
+
+ /**
+ * Stores information when users with given UIDs were last seen in their Chat session.
+ */
+ @Override
+ public void updateUserPresence(Long lessonId, Set users) {
+ Date now = new Date();
+ for (String nickname : users) {
+ PresenceChatUser rosterEntry = new PresenceChatUser(nickname, lessonId, now);
+ presenceChatDAO.insertOrUpdate(rosterEntry);
+ }
+ }
+
+ @Override
+ public List getUsersActiveByLessonId(Long lessonId) {
+ Date oldestLastPresence = new Date(System.currentTimeMillis() - IPresenceChatService.PRESENCE_IDLE_TIMEOUT);
+ return presenceChatDAO.getUsersByLessonIdAndLastPresence(lessonId, oldestLastPresence);
+ }
+
+ @Override
+ public List getNewMessages(Long lessonId, Date lastCheck) {
+ return presenceChatDAO.getNewMessages(lessonId, lastCheck);
+ }
+
+ @Override
+ public List getMessagesByConversation(Long lessonId, String from, String to) {
+ return presenceChatDAO.getMessagesByConversation(lessonId, from, to);
+ }
+
+ public void setPresenceChatDAO(IPresenceChatDAO presenceChatDAO) {
+ this.presenceChatDAO = presenceChatDAO;
+ }
+}
\ No newline at end of file
Index: lams_learning/web/WEB-INF/tags/Head.tag
===================================================================
diff -u -rab2b6923490ce38dc647ea66d6768966fbc40cfd -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/web/WEB-INF/tags/Head.tag (.../Head.tag) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd)
+++ lams_learning/web/WEB-INF/tags/Head.tag (.../Head.tag) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -32,7 +32,6 @@
<%@ tag body-content="scriptless"%>
-
Index: lams_learning/web/css/presence.css
===================================================================
diff -u -rd1915c892bc5ca61b4c5065d7fc1a188fdc0b1f2 -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/web/css/presence.css (.../presence.css) (revision d1915c892bc5ca61b4c5065d7fc1a188fdc0b1f2)
+++ lams_learning/web/css/presence.css (.../presence.css) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -1,15 +1,5 @@
-/*
-*************************************************
-LAMS 2.2
-Date modified: 29/04/2009
-
-*************************************************
-Presence stylesheet
-********************************************** */
-
-#presenceUserListings {
- margin: 5px;
- padding: 2px;
+.startHidden {
+ display: none;
}
#presenceChat {
@@ -20,15 +10,6 @@
z-index: 10;
}
-#presenceChatWarning {
- position: absolute;
- width: 400px;
- right: 18px;
- z-index: 10;
- padding: 10px 10px 10px 40px;
- margin: 0px;
-}
-
#presenceChatTabs {
position: absolute;
bottom: 0px;
@@ -37,70 +18,54 @@
height: 260px;
}
+#presenceChatTabs li a {
+ border-bottom: none;
+}
+
+#presenceChatTabs li .ui-icon-close {
+ margin-top: 5px;
+ cursor: pointer;
+}
+
#presenceChatRoster {
position: absolute;
right: 0pt;
bottom: 0pt;
width: 25%;
height: 100%;
+ background-color: #FFFFFF;
+ border: #AAAAAA thin solid;
}
+#presenceUserCount {
+ height: 23px;
+ border-bottom: #AAAAAA thin solid;
+ text-align: center;
+ padding-top: 7px;
+ cursor: pointer;
+}
+
#presenceUserListings{
+ padding: 5px;
height: 255px;
overflow-x: hidden;
overflow-y: scroll;
}
-#minMaxIcon{
- position:absolute;
- top: 0px;
- right:0px;
-}
-
-#tabsHolder {
- float: left;
- width: 90%;
- overflow:hidden;
-}
-
-#leftSliderDiv {
- float: left;
- width: 5%;
- text-align: left;
-}
-
-#rightSliderDiv {
- float: left;
- width: 5%;
- text-align: right;
-}
-
-.smallImage {
- width: 16px;
- height: 16px;
- border: none;
-}
-
-.startHidden {
- display: none;
-}
-
.presenceListing {
- width:"100%";
+ cursor: pointer;
}
.presenceListing:hover {
- width:"100%";
background-color: #CBDBFF;
- cursor: pointer;
}
.presenceListingNewMessage {
background-color: #88FF99;
}
-.presenceTabNewMessage {
- text-decoration: underline;
+.presenceTabNewMessage a {
+ text-decoration: underline !important;
}
.presenceMessage {
@@ -111,26 +76,15 @@
font-weight: bold;
}
-.presenceMessageEmote {
- font-style: italic;
- padding-bottom: 4px;
-}
-
-.chatPanel {
- width: 95%;
- height: 220px;
-}
-
.messageArea {
width: 100%;
- height: 80%;
+ height: 190px;
overflow-x: hidden;
overflow-y: scroll;
}
.sendArea {
- width: 100%;
- height: 20%;
+ width: 95%;
}
.messageInput {
@@ -140,20 +94,4 @@
.sendButton {
float: left;
-}
-
-table.tabLabelTable {
- width: auto;
-}
-
-.ui-tabs .ui-tabs-nav li.ui-tabs-active a{
- cursor: pointer;
-}
-
-#presenceChatRoster li {
- width: 100%;
-}
-
-#presenceChatRoster li a{
- width: 83%;
}
\ No newline at end of file
Index: lams_learning/web/includes/javascript/presence.js
===================================================================
diff -u -rd1915c892bc5ca61b4c5065d7fc1a188fdc0b1f2 -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/web/includes/javascript/presence.js (.../presence.js) (revision d1915c892bc5ca61b4c5065d7fc1a188fdc0b1f2)
+++ lams_learning/web/includes/javascript/presence.js (.../presence.js) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -1,111 +1,174 @@
-var windowHeight;
-var pollInProgress = false;
+$(document).ready(function (){
+ presenceChat = $("#presenceChat");
+ rosterDiv = $("#presenceUserListings");
-var roster = {
- // association nick -> localId, the latter being just some ID made in this script
- users : {},
- maxUserLocalId : 0,
- // map "tab" -> "last message in tab", so we don't fetch all messages every time, only new ones
- lastMessageUids : [],
-
- // when user clicked another user in roster
- handleUserClicked : function(localId) {
- var nick = null;
- $.each(roster.users, function(key, value){
- if (value == localId) {
- nick = key;
- return false;
+ // if presence IM is enabled
+ if (presenceEnabled) {
+ // make visible
+ presenceChat.removeClass("startHidden");
+
+ // create chat tabs
+ presenceChatTabs = $("#presenceChatTabs").tabs({
+ 'activate' : function(event, ui) {
+ // remove visual indicators (underline) of new message
+ var nick = getUserFromTabIndex(presenceChatTabs.tabs('option','active')),
+ tag = nickToTag(nick),
+ tabLabel = tagToTabLabel(tag);
+ $("#" + tabLabel).removeClass('presenceTabNewMessage');
}
});
-
- if(nick != nickname){
- var tag = nickToTag(nick);
- var tabLabel = tagToTabLabel(tag);
- var tab = $('#' + tabLabel);
- // if the clicked user's tab is open
- if(tab.length){
- // make the sender's tab label unbold
- tab.html(nick);
- } else {
- // if the clicked user's tab is not already open, create it and select it
- addTab(nick, tag);
+ }
+});
+
+
+var windowHeight = null,
+ roster = {
+ // association nick -> localId, the latter being just some ID made in this script
+ users : {},
+ maxUserLocalId : 0,
+ lastMessageUids : {},
+
+ // when user clicked another user in roster
+ handleUserClicked : function(localId) {
+ var nick = null;
+ // find the nick based on the ID
+ $.each(roster.users, function(key, value){
+ if (value == localId) {
+ nick = key;
+ return false;
+ }
+ });
+
+ if (nick != nickname){
+ var tag = nickToTag(nick),
+ tabLabel = tagToTabLabel(tag),
+ tab = $('#' + tabLabel);
+ // if the clicked learner's tab is open
+ if (tab.length == 0){
+ // if the clicked user's tab is not already open, create it and select it
+ addTab(nick, tag);
+ }
+ // select the added tab
+ presenceChatTabs.tabs( "option", "active", $('li', presenceChatTabs).length - 1);
}
+ },
+
+ updateDisplay : function(users) {
+ // repopulate user objects array
+ var rosterUsers = {};
+ $.each(users, function(index, nick){
+ var localId = roster.users[nick];
+ rosterUsers[nick] = localId ? localId : ++roster.maxUserLocalId;
+ });
+ this.users = rosterUsers;
- // select the added tab
- presenceChatTabs.tabs('select' , tag);
+ // add HTML entries for each user
+ rosterDiv.empty();
+ jQuery.each(this.users, function(nick, localId){
+ var tag = nickToTag(nick),
+ listingName = tagToListing(tag),
+ listing = $("#" + listingName);
+ // if no listing in roster exists
+ if (listing.length == 0){
+ // create listing div
+ var listingDiv = $('
'
+ + createPresenceListing(nick, tag)
+ + '
');
+
+ // add the listing div
+ rosterDiv.append(listingDiv);
+ } else {
+ // remove and append at the right place (from sort)
+ rosterDiv.append(listing.remove());
+ }
+ });
+
+ // update presenceTabLabel
+ $("#presenceUserCount").html(labelUsers + " (" + users.length + ")");
}
},
+
+ // init the connection with server using server URL but with different protocol
+ websocket = new WebSocket(APP_URL.replace('http', 'ws') + 'presenceChatWebsocket?lessonID=' + lessonId +
+ '&imEnabled=' + presenceImEnabled + '&nickname=' + encodeURIComponent(nickname));
- updateDisplay : function(users) {
- // sort alphabetically
- users.sort();
- // repopulate user objects array
- var rosterUsers = {};
- $.each(users, function(index, nick){
- var localId = roster.users[nick];
- rosterUsers[nick] = localId ? localId : ++roster.maxUserLocalId;
- });
- this.users = rosterUsers;
-
- jQuery.each(this.users, function(nick, localId){
- var tag = nickToTag(nick);
- var listingName = tagToListing(tag);
- var listing = $("#" + listingName);
- // if no listing in roster exists
- if (listing.length == 0){
- // create listing div
- var listingDiv = $('
'
- + createPresenceListing(nick, tag)
- + '
');
+// when the server pushes new messages and roster to the learner's browser
+websocket.onmessage = function(e){
+ // create JSON object
+ var input = JSON.parse(e.data);
+ if (input.roster) {
+ roster.updateDisplay(input.roster);
+ }
+
+ if (input.messages) {
+ var activeNick = getUserFromTabIndex(presenceChatTabs.tabs('option','active')),
+ selectedTabTag = nickToTag(activeNick);
+
+ jQuery.each(input.messages, function(){
+ // which tab are we talking about?
+ var from = this.to ? (this.from == nickname ? this.to : this.from) : groupChatInfo.nick,
+ lastMessageUid = roster.lastMessageUids[from] || 0;
+
+ // are the messages new?
+ if (this.uid > lastMessageUid) {
+ var tag = nickToTag(from);
+ if (tag != selectedTabTag) {
+ var tab = $("#" + tagToTabLabel(tag));
+ if (tab.length == 0) {
+ // no tab opened yet, create it
+ tab = addTab(from, tag);
+ }
- // add the listing div
- rosterDiv.append(listingDiv);
- } else {
- // remove and append at the right place (from sort)
- rosterDiv.append(listing.remove());
- }
- });
-
- // update presenceTabLabel
- var presenceTabLabelDiv = $("#presence_tabLabel");
- presenceTabLabelDiv.html(labelUsers + " (" + users.length + ")");
- }
-}
+ // notify of new message
+ tab.addClass('presenceTabNewMessage');
+ if (tag != groupChatInfo.tag) {
+ $("#" + tagToListing(tag)).addClass('presenceListingNewMessage');
+ }
+ }
+
+ roster.lastMessageUids[from] = this.uid;
+ var messageArea = $("#" + (nickToMessageArea(from)));
+ messageArea.append(generateMessageHTML(this.from, this.message, this.dateSent));
+ messageArea.scrollTop(messageArea.prop('scrollHeight'));
+ }
+ });
+ }
+
+ // remove conversation tabs with learners who are gone
+ $('li a', presenceChatTabs).each(function() {
+ var nick = $(this).text();
+ if (nick != groupChatInfo.nick && !roster.users[nick]) {
+ var tag = $(this).attr('href');
+ $(tag).remove();
+ $(this).parent().remove();
+ presenceChatTabs.tabs('refresh');
+ }
+ });
+};
/* ******* HTML write Functions ******* */
function createPrivateTabLabel(nick, tag){
- return '
' +
- '
' +
- '
' +
- '
' + nick + '
' +
- '
' +
- '
' +
- '
';
+ return '' + nick + 'Remove Tab';
}
function createPrivateTabContent(nick, tag){
- return ' ' +
- '
' +
- '' +
- '' +
- '
';
+ return '' +
+ '
' +
+ '' +
+ '' +
+ '
';
}
function createPresenceListing(nick, tag){
- return '
' +
- '
' +
- '
' +
- '
' + nick + '
' +
- '
' +
- '
';
+ return '
' + nick + '
';
}
/* ******* Helper Functions ******* */
function generateMessageHTML(nick, message, date) {
- var fromElem = $('
(' + date.substring(11, 19) + ') ' + nick + '
');
- var msgElem = $('
' + message + '
');
+ var fromElem = $('
(' + date.substring(11, 19) + ') ' + nick + '
'),
+ msgElem = $('
' + message + '
');
return completeElem = $('').append(fromElem).append(msgElem);
}
@@ -128,22 +191,29 @@
'top' : windowHeight + "px"
});
}
-
- $("#presenceChatWarning").css({
- 'top' : windowHeight - 10 + "px"
- });
}
function getUserFromTabIndex(tabIndex) {
- return $(".ui-tabs-label")[tabIndex].innerHTML;
+ return tabIndex == 0 ? groupChatInfo.nick : $('li a', presenceChatTabs)[tabIndex].innerHTML;
}
+// Adds a new tab and fetches conversation history
function addTab(nick, tag) {
- // add a tab with the the nick specified
- presenceChatTabs.tabs('add', '#' + tag,
- createPrivateTabLabel(nick, tag));
- // add the content
- $("#" + tag).html(createPrivateTabContent(nick, tag));
+ var tab = $('').attr('id', tag + '_tabLabel').html(createPrivateTabLabel(nick, tag)).appendTo($('ul', presenceChatTabs));
+ $('').attr('id', tag).html(createPrivateTabContent(nick, tag)).appendTo(presenceChatTabs);
+ presenceChatTabs.tabs('refresh');
+
+ // fetch all messages from the start
+ roster.lastMessageUids[nick] = null;
+ var data = {
+ 'type' : 'fetchConversation',
+ 'lessonID' : lessonId,
+ 'to' : nick
+ };
+
+ websocket.send(JSON.stringify(data));
+
+ return tab;
}
function nickToTag(nick) {
@@ -163,10 +233,10 @@
}
function getTime() {
- var currentTime = new Date();
- var hours = currentTime.getHours();
- var minutes = currentTime.getMinutes();
- var seconds = currentTime.getSeconds();
+ var currentTime = new Date(),
+ hours = currentTime.getHours(),
+ minutes = currentTime.getMinutes(),
+ seconds = currentTime.getSeconds();
if (hours < 10) {
hours = "0" + hours;
@@ -186,123 +256,35 @@
/* ******* Main chat functions ******* */
function sendMessage(receiver) {
- var tag = nickToTag(receiver);
- var messageInput = $('#' + tag + '_messageInput');
- var message = messageInput.val();
+ var tag = nickToTag(receiver),
+ messageInput = $('#' + tag + '_messageInput'),
+ message = messageInput.val();
if (!message || message == '') {
return false; // do not send empty messages.
}
messageInput.val('');
messageInput.focus();
- $.ajax({
- url : actionUrl,
- data : {'method' : 'sendMessage',
- 'lessonID' : lessonId,
- 'from' : nickname,
- 'to' : tag == groupChatInfo.tag ? null : receiver,
- 'message' : message
- },
- cache : false,
- complete : updateChat
- });
+
+ var data = {
+ 'type' : 'message',
+ 'lessonID' : lessonId,
+ 'to' : tag == groupChatInfo.tag ? '' : receiver,
+ 'message' : message
+ };
+
+ websocket.send(JSON.stringify(data));
}
-function updateChat(){
- // skip another attempt if previous did not return yet (slow server?)
- if (!pollInProgress) {
- pollInProgress = true;
- var from = null;
- var selected = null;
- var lastMessageUid = null;
- var getMessages = presenceShown && presenceImEnabled;
- if (getMessages) {
- selected = presenceChatTabs.tabs('option','active');
- from = getUserFromTabIndex(selected);
- if (groupChatInfo.nick == from) {
- from = null;
- }
- lastMessageUid = roster.lastMessageUids[from ? from : 'group'];
- }
-
- $.ajax({
- url : actionUrl,
- data : {'method' : 'getChatContent',
- 'lessonID' : lessonId,
- 'getMessages' : getMessages,
- 'lastMessageUid' : lastMessageUid,
- 'to' : nickname,
- 'from' : from
- },
- cache : false,
- dataType : 'json',
- complete : function(){
- pollInProgress = false;
- },
- success : function (result) {
- roster.updateDisplay(result.roster);
-
- // real new messages for the opentab
- if (result.messages) {
- var messageArea = $("#" + (nickToMessageArea(from ? from : groupChatInfo.nick)));
- var lastMessageUid = null;
- jQuery.each(result.messages, function(){
- messageArea.append(generateMessageHTML(this.from, this.message, this.dateSent));
- lastMessageUid = this.uid;
- });
- // store last message uid and get new messages starting from this one
- roster.lastMessageUids[from ? from : 'group'] = lastMessageUid;
- messageArea.scrollTop(messageArea.prop('scrollHeight'));
- }
-
- // check if other users wrote something new
- if (result.newConversations) {
- var selectedTabTag = nickToTag(getUserFromTabIndex(selected));
- jQuery.each(result.newConversations, function(index, nick){
- var tag = nick == 'group' ? groupChatInfo.tag : nickToTag(nick);
- if (tag != selectedTabTag) {
- var tab = $("#" + tagToTabLabel(tag));
- if (tab.length == 0) {
- addTab(this, tag);
- tab = $("#" + tagToTabLabel(tag));
- }
-
- // notify of new message
- tab.addClass('presenceTabNewMessage');
- if (tag != groupChatInfo.tag) {
- $("#" + tagToListing(tag)).addClass('presenceListingNewMessage');
- }
- }
- });
- }
- }
- });
- }
-}
-
-
/* ******* Click handlers ******* */
-
-function handleCloseTab(label){
- var tabLabelsLocal = $(".ui-tabs-label");
- for (var i = 0; i < tabLabelsLocal.length; i++){
- if(tabLabelsLocal[i].innerHTML == label){
- presenceChatTabs.tabs('remove' , i);
- roster.lastMessageUids[label] = null;
- }
- }
+function handleCloseTab(tag){
+ $('#' + tag + '_tabLabel').remove();
+ $('#' + tag).remove();
+ presenceChatTabs.tabs('refresh');
}
-function handleLeftScrollClick(){
- presenceChatTabs.tabs('scrollLeft');
-}
-
-function handleRightScrollClick(){
- presenceChatTabs.tabs('scrollRight');
-}
-
function handlePresenceClick() {
if (presenceShown) {
presenceChat.animate({
Index: lams_learning/web/presenceChat.jsp
===================================================================
diff -u -r2673317d66ead2aa551d93dcb5f5e9afab5f532e -r9c603f82a3332d5e2e5b290bcc550f263c09a8f8
--- lams_learning/web/presenceChat.jsp (.../presenceChat.jsp) (revision 2673317d66ead2aa551d93dcb5f5e9afab5f532e)
+++ lams_learning/web/presenceChat.jsp (.../presenceChat.jsp) (revision 9c603f82a3332d5e2e5b290bcc550f263c09a8f8)
@@ -1,138 +1,52 @@
<%-- css --%>
-
+
<%-- javascript --%>
-
-
+
<%-- initial html / presence.js adds on html into here --%>
<%-- only pop the message box if im is enabled --%>
-
-