Index: lams_tool_forum/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r8706c567f8cad35510077cacdfa93068770b487e -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 8706c567f8cad35510077cacdfa93068770b487e) +++ lams_tool_forum/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -236,6 +236,14 @@ label.latest.posting.date =Latest post on label.number.of.replies =Replies message.no.reflection.available =No notebook entry was added. +label.show.more.messages =More posts +label.loading.messages =Loading more posts +label.show.replies=Show Replies +label.hide.replies=Hide Replies +label.showhide.prompt=Show/Hide Replies +message.complete.or.cancel.reply=Please complete or cancel the current reply before starting a new reply. +message.complete.or.cancel.edit=Please complete or cancel the current edit before starting a new edit. +error.cannot.redisplay.please.refresh=Your changes have been saved but cannot be redisplayed. Please select refresh to reload the forum messages. #======= End labels: Exported 230 labels for en AU ===== Index: lams_tool_forum/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r8706c567f8cad35510077cacdfa93068770b487e -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 8706c567f8cad35510077cacdfa93068770b487e) +++ lams_tool_forum/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -236,6 +236,14 @@ label.latest.posting.date =Latest post on label.number.of.replies =Replies message.no.reflection.available =No notebook entry was added. +label.show.more.messages =More posts +label.loading.messages =Loading more posts +label.show.replies=Show Replies +label.hide.replies=Hide Replies +label.showhide.prompt=Show/Hide Replies +message.complete.or.cancel.reply=Please complete or cancel the current reply before starting a new reply. +message.complete.or.cancel.edit=Please complete or cancel the current edit before starting a new edit. +error.cannot.redisplay.please.refresh=Your changes have been saved but cannot be redisplayed. Please select refresh to reload the forum messages. #======= End labels: Exported 230 labels for en AU ===== Index: lams_tool_forum/conf/xdoclet/struts-actions.xml =================================================================== diff -u -r2b4141f8f15fe552ea1ba29d0b302544943cb6b0 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/conf/xdoclet/struts-actions.xml (.../struts-actions.xml) (revision 2b4141f8f15fe552ea1ba29d0b302544943cb6b0) +++ lams_tool_forum/conf/xdoclet/struts-actions.xml (.../struts-actions.xml) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -177,6 +177,21 @@ parameter="viewTopic" scope="request"> + + + + + + + + + + + + + + + ? and message_level = 1"; + private static final String SQL_QUERY_FIND_NEXT_THREAD_MESSAGES = "from " + MessageSeq.class.getName() + + " where root_message_uid = ? and thread_message_uid = ? and message_level > 1"; + private static final String SQL_QUERY_GET_COMPLETE_THREAD = "from " + MessageSeq.class.getName() + + " where thread_message_uid = ?"; + private static final String SQL_QUERY_GET_SEQ_BY_MESSAGE = "from " + MessageSeq.class.getName() + + " where message_uid = ?"; private static final String SQL_QUERY_NUM_POSTS_BY_TOPIC = "select count(*) from " + MessageSeq.class.getName() + " ms where ms.message.createdBy.userId=? and ms.message.isAuthored = false and ms.rootMessage.uid=?"; private static final String SQL_QUERY_NUM_POSTS_BY_ROOT_MESSAGE_AND_DATE = "SELECT count(*) FROM " + MessageSeq.class.getName() + " seq WHERE seq.rootMessage.uid = ? AND seq.message.updated > ?"; + private static final Logger log = Logger.getLogger(MessageSeqDao.class); + + public MessageSeq getById(Long messageSeqId) { + return (MessageSeq) find(MessageSeq.class, messageSeqId); + } + + public MessageSeq getByMessageId(Long messageId) { + List list = doFind(SQL_QUERY_GET_SEQ_BY_MESSAGE, messageId); + if (list != null ) { + if ( list.size() > 1) { + log.warn("Looking up message seq by message id="+messageId+". More than one message seq found!"+list.toString()); + } + return (MessageSeq) list.get(0); + } else { + return null; + } + } + + public List getThreadByThreadId(final Long threadMessageId) { + return doFind(SQL_QUERY_GET_COMPLETE_THREAD, new Object[] { threadMessageId }); + } + + public List getNextThreadByThreadId(final Long rootTopicId, final Long previousThreadMessageId) { + Query queryObject = getSession().createQuery(SQL_QUERY_FIND_NEXT_THREAD_TOP) + .setParameter(0, rootTopicId) + .setParameter(1, previousThreadMessageId) + .setMaxResults(1); + List list = queryObject.list(); + if (list != null && list.size() > 0) { + MessageSeq threadTop = ((MessageSeq) list.get(0)); + List all = doFind(SQL_QUERY_FIND_NEXT_THREAD_MESSAGES, new Object[] { rootTopicId, threadTop.getMessage().getUid() }); + all.add(threadTop); + return all; + } + return list; + } + /* (non-Javadoc) * @see org.lamsfoundation.lams.tool.forum.persistence.hibernate.IMessageSeqDAO#getTopicThread(java.lang.Long) */ @Override - public List getTopicThread(Long rootTopicId) { + public List getCompleteTopic(Long rootTopicId) { return getSession().createCriteria(MessageSeq.class).add(Restrictions.eq("rootMessage.uid", rootTopicId)).list(); -// return doFind(SQL_QUERY_FIND_TOPIC_THREAD, rootTopicId); } /* (non-Javadoc) Index: lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/service/IForumService.java =================================================================== diff -u -r2b4141f8f15fe552ea1ba29d0b302544943cb6b0 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/service/IForumService.java (.../IForumService.java) (revision 2b4141f8f15fe552ea1ba29d0b302544943cb6b0) +++ lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/service/IForumService.java (.../IForumService.java) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -38,6 +38,7 @@ import org.lamsfoundation.lams.tool.forum.persistence.ForumToolSession; import org.lamsfoundation.lams.tool.forum.persistence.ForumUser; import org.lamsfoundation.lams.tool.forum.persistence.Message; +import org.lamsfoundation.lams.tool.forum.persistence.MessageSeq; import org.lamsfoundation.lams.tool.forum.persistence.PersistenceException; import org.lamsfoundation.lams.util.audit.IAuditService; @@ -148,7 +149,7 @@ * @return * @throws PersistenceException */ - Message replyTopic(Long parentId, Long sessionId, Message message) throws PersistenceException; + MessageSeq replyTopic(Long parentId, Long sessionId, Message message) throws PersistenceException; /** * Delete the topic by given topic ID. The function will delete all children topics under this topic. @@ -172,14 +173,33 @@ // *********************Get topic methods ********************** // ************************************************************************************ /** - * Get topic and its children list by given root topic ID. Note that the return type is DTO. + * Get a complete topic and its children list by given root topic ID. Note that the return type is DTO. * * @param rootTopicId * @return List of MessageDTO */ List getTopicThread(Long rootTopicId); + + /** + * Get topic and its children list by given root topic ID, starting from after the sequence number specified. + * Return the number of entries indicated by the paging number. Note that the return type is DTO. + * + * @param rootTopicId + * @param afterSequenceId + * @param pagingSize + * @return List of MessageDTO + */ + public List getTopicThread(Long rootTopicId, Long afterSequenceId, Long pagingSize ); /** + * Get one complete thread within a topic Note that the return type is DTO. + * + * @param threadId + * @return List of MessageDTO + */ + public List getThread(Long threadId ); + + /** * Get root topics by a given sessionID value. Simultanousely, it gets back topics, which author posted in authoring * page for this forum, which is related with the given sessionID value. * @@ -217,6 +237,15 @@ Message getMessage(Long messageUid) throws PersistenceException; /** + * Get message by given message UID, wrapped up in the usual DTO list that is used for the view code in learner. + * + * @param messageUid + * @return Message + * @throws PersistenceException + */ + List getMessageAsDTO(Long messageUid) throws PersistenceException; + + /** * Get message list posted by given user. Note that the return type is DTO. * * @param userId Index: lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/web/actions/LearningAction.java =================================================================== diff -u -r08fe875b45ad68995664f316c1ed602b30ca2f42 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/web/actions/LearningAction.java (.../LearningAction.java) (revision 08fe875b45ad68995664f316c1ed602b30ca2f42) +++ lams_tool_forum/src/java/org/lamsfoundation/lams/tool/forum/web/actions/LearningAction.java (.../LearningAction.java) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -67,6 +67,7 @@ import org.lamsfoundation.lams.tool.forum.persistence.ForumToolSession; import org.lamsfoundation.lams.tool.forum.persistence.ForumUser; import org.lamsfoundation.lams.tool.forum.persistence.Message; +import org.lamsfoundation.lams.tool.forum.persistence.MessageSeq; import org.lamsfoundation.lams.tool.forum.persistence.PersistenceException; import org.lamsfoundation.lams.tool.forum.persistence.Timestamp; import org.lamsfoundation.lams.tool.forum.service.ForumServiceProxy; @@ -109,9 +110,15 @@ } // --------------Topic Level ------------------ - if (param.equals("viewTopic")) { + if (param.equals("viewTopic") || param.equals("viewTopicNext")) { return viewTopic(mapping, form, request, response); } + if (param.equals("viewTopicThread")) { + return viewTopicThread(mapping, form, request, response); + } + if (param.equals("viewMessage")) { + return viewMessage(mapping, form, request, response); + } if (param.equals("newTopic")) { return newTopic(mapping, form, request, response); } @@ -124,12 +131,18 @@ if (param.equals("replyTopic")) { return replyTopic(mapping, form, request, response); } + if (param.equals("replyTopicInline")) { + return replyTopicInline(mapping, form, request, response); + } if (param.equals("editTopic")) { return editTopic(mapping, form, request, response); } if (param.equals("updateTopic")) { return updateTopic(mapping, form, request, response); } + if (param.equals("updateTopicInline")) { + return updateTopicInline(mapping, form, request, response); + } if (param.equals("deleteAttachment")) { return deleteAttachment(mapping, form, request, response); } @@ -468,7 +481,8 @@ // ========================================================================================== /** - * Display read-only page for a special topic. Topic will arrange by Tree structure. + * Display the messages for a particular topic. The Topic will arranged by Tree structure and loaded + * thread by thread (with paging). * * @param mapping * @param form @@ -491,14 +505,31 @@ ForumUser forumUser = getCurrentUser(request, (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)); Forum forum = forumUser.getSession().getForum(); + Long lastMsgSeqId = WebUtil.readLongParam(request, ForumConstants.PAGE_LAST_ID, true); + Long pageSize = WebUtil.readLongParam(request, ForumConstants.PAGE_SIZE, true); + + setupViewTopicPagedDTOList(request, rootTopicId, sessionMapID, forumUser, forum, lastMsgSeqId, pageSize); + + // Should we show the reflection or not? We shouldn't show it when the View Forum screen is accessed + // from the Monitoring Summary screen, but we should when accessed from the Learner Progress screen. + // Need to constantly past this value on, rather than hiding just the once, as the View Forum + // screen has a refresh button. Need to pass it through the view topic screen and dependent screens + // as it has a link from the view topic screen back to View Forum screen. + boolean hideReflection = WebUtil.readBooleanParam(request, ForumConstants.ATTR_HIDE_REFLECTION, false); + sessionMap.put(ForumConstants.ATTR_HIDE_REFLECTION, hideReflection); + + return mapping.findForward("success"); + } + + private void setupViewTopicPagedDTOList(HttpServletRequest request, Long rootTopicId, String sessionMapID, + ForumUser forumUser, Forum forum, Long lastMsgSeqId, Long pageSize) { // get root topic list - List msgDtoList = forumService.getTopicThread(rootTopicId); + List msgDtoList = forumService.getTopicThread(rootTopicId, lastMsgSeqId, pageSize); updateMesssageFlag(msgDtoList); request.setAttribute(ForumConstants.AUTHORING_TOPIC_THREAD, msgDtoList); - // check if we can still make posts in this topic - int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), msgDtoList.get(0).getMessage() - .getUid()); + // check if we can still make posts in this topic + int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), rootTopicId); boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; request.setAttribute(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); @@ -507,21 +538,104 @@ // transfer SessionMapID as well request.setAttribute(ForumConstants.ATTR_SESSION_MAP_ID, sessionMapID); - // Should we show the reflection or not? We shouldn't show it when the View Forum screen is accessed - // from the Monitoring Summary screen, but we should when accessed from the Learner Progress screen. - // Need to constantly past this value on, rather than hiding just the once, as the View Forum - // screen has a refresh button. Need to pass it through the view topic screen and dependent screens - // as it has a link from the view topic screen back to View Forum screen. - boolean hideReflection = WebUtil.readBooleanParam(request, ForumConstants.ATTR_HIDE_REFLECTION, false); - sessionMap.put(ForumConstants.ATTR_HIDE_REFLECTION, hideReflection); - // Saving or updating user timestamp forumService.saveTimestamp(rootTopicId, forumUser); + } + /** + * Display the messages for a particular thread in a particular topic. Returns all messages for this thread - does not need paging. + * + * @param mapping + * @param form + * @param request + * @param response + * @return + */ + private ActionForward viewTopicThread(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + + forumService = getForumManager(); + + Long rootTopicId = WebUtil.readLongParam(request, ForumConstants.ATTR_TOPIC_ID); + Long highlightMessageUid = WebUtil.readLongParam(request, ForumConstants.ATTR_MESS_ID, true); + + String sessionMapID = WebUtil.readStrParam(request, ForumConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + sessionMap.put(ForumConstants.ATTR_ROOT_TOPIC_UID, rootTopicId); + + // get forum user and forum + ForumUser forumUser = getCurrentUser(request, (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)); + Forum forum = forumUser.getSession().getForum(); + + Long threadId = WebUtil.readLongParam(request, ForumConstants.ATTR_THREAD_ID, true); + List msgDtoList = forumService.getThread(threadId); + updateMesssageFlag(msgDtoList); + request.setAttribute(ForumConstants.AUTHORING_TOPIC_THREAD, msgDtoList); + + // check if we can still make posts in this topic + int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), rootTopicId); + boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() + && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; + request.setAttribute(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); + request.setAttribute(ForumConstants.ATTR_NUM_OF_POSTS, numOfPosts); + request.setAttribute(ForumConstants.ATTR_NO_MORE_PAGES, true); + + if ( highlightMessageUid != null ) { + request.setAttribute(ForumConstants.ATTR_MESS_ID, highlightMessageUid); + } + // transfer SessionMapID as well + request.setAttribute(ForumConstants.ATTR_SESSION_MAP_ID, sessionMapID); + return mapping.findForward("success"); } /** + * Display a single message. + * + * @param mapping + * @param form + * @param request + * @param response + * @return + */ + private ActionForward viewMessage(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + + forumService = getForumManager(); + + Long rootTopicId = WebUtil.readLongParam(request, ForumConstants.ATTR_TOPIC_ID); + Long messageUid = WebUtil.readLongParam(request, ForumConstants.ATTR_MESS_ID, true); + + String sessionMapID = WebUtil.readStrParam(request, ForumConstants.ATTR_SESSION_MAP_ID); + SessionMap sessionMap = (SessionMap) request.getSession().getAttribute(sessionMapID); + sessionMap.put(ForumConstants.ATTR_ROOT_TOPIC_UID, rootTopicId); + + // get forum user and forum + ForumUser forumUser = getCurrentUser(request, (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)); + Forum forum = forumUser.getSession().getForum(); + + List msgDtoList = forumService.getMessageAsDTO(messageUid); + updateMesssageFlag(msgDtoList); + request.setAttribute(ForumConstants.AUTHORING_TOPIC_THREAD, msgDtoList); + + // check if we can still make posts in this topic + int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), rootTopicId); + boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() + && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; + request.setAttribute(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); + request.setAttribute(ForumConstants.ATTR_NUM_OF_POSTS, numOfPosts); + request.setAttribute(ForumConstants.ATTR_NO_MORE_PAGES, true); + + if ( messageUid != null ) { + request.setAttribute(ForumConstants.ATTR_MESS_ID, messageUid); + } + // transfer SessionMapID as well + request.setAttribute(ForumConstants.ATTR_SESSION_MAP_ID, sessionMapID); + + return mapping.findForward("success"); + } + + /** * Display empty page for a new topic in forum * * @param mapping @@ -661,7 +775,7 @@ * @return * @throws InterruptedException */ - private ActionForward replyTopic(ActionMapping mapping, ActionForm form, HttpServletRequest request, + private synchronized ActionForward replyTopic(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws InterruptedException { MessageForm messageForm = (MessageForm) form; @@ -690,35 +804,81 @@ // echo back this topic thread into page Long rootTopicId = forumService.getRootTopicId(parentId); - List msgDtoList = forumService.getTopicThread(rootTopicId); - updateMesssageFlag(msgDtoList); - request.setAttribute(ForumConstants.AUTHORING_TOPIC_THREAD, msgDtoList); - request.setAttribute(ForumConstants.ATTR_SESSION_MAP_ID, messageForm.getSessionMapID()); - // check whether allow more posts for this user ForumToolSession session = forumService.getSessionBySessionId(sessionId); Forum forum = session.getForum(); - int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), ((MessageDTO) msgDtoList.get(0)) - .getMessage().getUid()); - boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() - && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; - request.setAttribute(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); - request.setAttribute(ForumConstants.ATTR_NUM_OF_POSTS, numOfPosts); - sessionMap.remove(ForumConstants.ATTR_ORIGINAL_MESSAGE); - - // Saving or updating user timestamp - forumService.saveTimestamp(rootTopicId, forumUser); - + setupViewTopicPagedDTOList(request, rootTopicId, messageForm.getSessionMapID(), forumUser, forum, null, null); + // notify learners and teachers Long forumId = (Long) sessionMap.get(ForumConstants.ATTR_FORUM_ID); forumService.sendNotificationsOnNewPosting(forumId, sessionId, message); - + sessionMap.remove(ForumConstants.ATTR_ORIGINAL_MESSAGE); return mapping.findForward("success"); } /** + * Create a replayed topic for a parent topic. + * + * @param mapping + * @param form + * @param request + * @param response + * @return + * @throws InterruptedException + * @throws JSONException + * @throws IOException + */ + private synchronized ActionForward replyTopicInline(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws InterruptedException, JSONException, IOException { + + MessageForm messageForm = (MessageForm) form; + SessionMap sessionMap = getSessionMap(request, messageForm); + Long parentId = (Long) sessionMap.get(ForumConstants.ATTR_PARENT_TOPIC_ID); + Long sessionId = (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID); + + Message message = messageForm.getMessage(); + boolean isTestHarness = Boolean.valueOf(request.getParameter("testHarness")); + if (isTestHarness) { + message.setBody(request.getParameter("message.body__textarea")); + } + message.setIsAuthored(false); + message.setCreated(new Date()); + message.setUpdated(new Date()); + message.setLastReplyDate(new Date()); + ForumUser forumUser = getCurrentUser(request, sessionId); + message.setCreatedBy(forumUser); + message.setModifiedBy(forumUser); + setAttachment(messageForm, message); + + // save message into database + forumService = getForumManager(); + MessageSeq newMessageSeq = forumService.replyTopic(parentId, sessionId, message); + + // check whether allow more posts for this user + Long rootTopicId = forumService.getRootTopicId(parentId); + ForumToolSession session = forumService.getSessionBySessionId(sessionId); + Forum forum = session.getForum(); + + int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), rootTopicId); + boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() + && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; + + JSONObject JSONObject = new JSONObject(); + JSONObject.put(ForumConstants.ATTR_MESS_ID, newMessageSeq.getMessage().getUid()); + JSONObject.put(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); + JSONObject.put(ForumConstants.ATTR_NUM_OF_POSTS, numOfPosts); + JSONObject.put(ForumConstants.ATTR_THREAD_ID, newMessageSeq.getThreadMessage().getUid()); + JSONObject.put(ForumConstants.ATTR_SESSION_MAP_ID, messageForm.getSessionMapID()); + JSONObject.put(ForumConstants.ATTR_ROOT_TOPIC_UID, rootTopicId); + JSONObject.put(ForumConstants.ATTR_PARENT_TOPIC_ID, newMessageSeq.getMessage().getParent().getUid()); + response.setContentType("application/json;charset=utf-8"); + response.getWriter().print(JSONObject); + return null; + } + + /** * Display a editable form for a special topic in order to update it. * * @param mapping @@ -800,6 +960,19 @@ Long topicId = (Long) sessionMap.get(ForumConstants.ATTR_TOPIC_ID); Message message = messageForm.getMessage(); + doUpdateTopic(request, messageForm, sessionMap, topicId, message); + + // echo back this topic thread into page + Long rootTopicId = forumService.getRootTopicId(topicId); + ForumUser forumUser = getCurrentUser(request, (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)); + Forum forum = forumUser.getSession().getForum(); + setupViewTopicPagedDTOList(request, rootTopicId, messageForm.getSessionMapID(), forumUser, forum, null, null); + + return mapping.findForward("success"); + } + + private void doUpdateTopic(HttpServletRequest request, MessageForm messageForm, SessionMap sessionMap, + Long topicId, Message message) { boolean makeAuditEntry = ToolAccessMode.TEACHER.equals(sessionMap.get(AttributeNames.ATTR_MODE)); String oldMessageString = null; @@ -828,29 +1001,40 @@ // save message into database // if we are in monitoring then we are probably editing some else's entry so log the change. forumService.updateTopic(messagePO); + } - // echo back this topic thread into page - Long rootTopicId = forumService.getRootTopicId(topicId); - List msgDtoList = forumService.getTopicThread(rootTopicId); - updateMesssageFlag(msgDtoList); + /** + * Update a topic. + * + * @param mapping + * @param form + * @param request + * @param response + * @return + * @throws PersistenceException + * @throws JSONException + * @throws IOException + */ + public ActionForward updateTopicInline(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws PersistenceException, JSONException, IOException { - // check if we can still make posts in this topic - ForumUser forumUser = getCurrentUser(request, (Long) sessionMap.get(AttributeNames.PARAM_TOOL_SESSION_ID)); - Forum forum = forumUser.getSession().getForum(); - int numOfPosts = forumService.getNumOfPostsByTopic(forumUser.getUserId(), ((MessageDTO) msgDtoList.get(0)) - .getMessage().getUid()); - boolean noMorePosts = forum.getMaximumReply() != 0 && numOfPosts >= forum.getMaximumReply() - && !forum.isAllowNewTopic() ? Boolean.TRUE : Boolean.FALSE; - request.setAttribute(ForumConstants.ATTR_NO_MORE_POSTS, noMorePosts); - request.setAttribute(ForumConstants.ATTR_NUM_OF_POSTS, numOfPosts); + forumService = getForumManager(); - request.setAttribute(ForumConstants.AUTHORING_TOPIC_THREAD, msgDtoList); - request.setAttribute(ForumConstants.ATTR_SESSION_MAP_ID, messageForm.getSessionMapID()); + MessageForm messageForm = (MessageForm) form; + SessionMap sessionMap = getSessionMap(request, messageForm); + Long topicId = (Long) sessionMap.get(ForumConstants.ATTR_TOPIC_ID); + Message message = messageForm.getMessage(); - // Saving or updating user timestamp - forumService.saveTimestamp(rootTopicId, forumUser); + doUpdateTopic(request, messageForm, sessionMap, topicId, message); - return mapping.findForward("success"); + JSONObject JSONObject = new JSONObject(); + JSONObject.put(ForumConstants.ATTR_MESS_ID, topicId); + JSONObject.put(ForumConstants.ATTR_SESSION_MAP_ID, messageForm.getSessionMapID()); + Long rootTopicId = forumService.getRootTopicId(topicId); + JSONObject.put(ForumConstants.ATTR_ROOT_TOPIC_UID, rootTopicId); + response.setContentType("application/json;charset=utf-8"); + response.getWriter().print(JSONObject); + return null; } /** Index: lams_tool_forum/web/WEB-INF/tiles-defs.xml =================================================================== diff -u -r2b4141f8f15fe552ea1ba29d0b302544943cb6b0 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/WEB-INF/tiles-defs.xml (.../tiles-defs.xml) (revision 2b4141f8f15fe552ea1ba29d0b302544943cb6b0) +++ lams_tool_forum/web/WEB-INF/tiles-defs.xml (.../tiles-defs.xml) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -79,6 +79,8 @@ + + Index: lams_tool_forum/web/WEB-INF/urlrewrite.xml =================================================================== diff -u -r79122091e11a7ce07bd1b0b8fa4826847c22da69 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/WEB-INF/urlrewrite.xml (.../urlrewrite.xml) (revision 79122091e11a7ce07bd1b0b8fa4826847c22da69) +++ lams_tool_forum/web/WEB-INF/urlrewrite.xml (.../urlrewrite.xml) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -32,5 +32,15 @@ /jsps/learning/mobile/viewtopic.jsp + + + Handles the exceptional case of /jsps/learning/message/topicviewwrapper.jsp which doesn't use tiles. + + .*(android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino).*?i + ^(1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|e\\-|e\\/|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\\-|2|g)|yas\\-|your|zeto|zte\\-).*?i + ^/jsps/learning/message/topicviewwrapper.jsp$ + /jsps/learning/mobile/message/topicviewwrapper.jsp + + Index: lams_tool_forum/web/css/jquery.treetable.css =================================================================== diff -u --- lams_tool_forum/web/css/jquery.treetable.css (revision 0) +++ lams_tool_forum/web/css/jquery.treetable.css (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,28 @@ +table.treetable span.indenter { + display: inline-block; + margin: 0; + padding: 0; + text-align: right; + + /* Disable text selection of nodes (for better D&D UX) */ + user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + -webkit-user-select: none; + + /* Force content-box box model for indenter (Bootstrap compatibility) */ + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + + width: 150px; +} + +table.treetable span.indenter a { + background-position: left center; + background-repeat: no-repeat; + display: inline-block; + text-decoration: none; + width: 150px; +} Index: lams_tool_forum/web/css/jquery.treetable.forum.css =================================================================== diff -u --- lams_tool_forum/web/css/jquery.treetable.forum.css (revision 0) +++ lams_tool_forum/web/css/jquery.treetable.forum.css (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,44 @@ +table.treetable span.indenter { + text-align: left; + width: 250px; +} + +table.treetable tr.collapsed span.indenter a { + background-image: url(data:image/png;base64,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); +} + +table.treetable tr.expanded span.indenter a { + background-image: url(data:image/png;base64,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); +} + +table.treetable tr span.indenter a { + outline: none; /* Expander shows outline after upgrading to 3.0 (#141) */ + border-bottom: none; /* Hide the dotted line underneath */ + width: 250px; +} + +table.treetable tr.collapsed.selected span.indenter a { + background-image: url(data:image/png;base64,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); +} + +table.treetable tr.expanded.selected span.indenter a { + background-image: url(data:image/png;base64,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); +} + +table.treetable tr.accept { + background-color: #a3bce4; + color: #fff +} + +table.treetable tr.collapsed.accept td span.indenter a { + background-image: url(data:image/x-png;base64,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); +} + +table.treetable tr.expanded.accept td span.indenter a { + background-image: url(data:image/x-png;base64,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); +} + +div.highlight { + border:2px solid #cacdd1; +} + Index: lams_tool_forum/web/includes/javascript/jquery.jscroll.js =================================================================== diff -u --- lams_tool_forum/web/includes/javascript/jquery.jscroll.js (revision 0) +++ lams_tool_forum/web/includes/javascript/jquery.jscroll.js (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,209 @@ +/*! + * jScroll - jQuery Plugin for Infinite Scrolling / Auto-Paging - v2.2.4 + * http://jscroll.com/ + * + * Copyright 2011-2013, Philip Klauzinski + * http://klauzinski.com/ + * Dual licensed under the MIT and GPL Version 2 licenses. + * http://jscroll.com/#license + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl-2.0.html + * + * @author Philip Klauzinski + * @requires jQuery v1.4.3+ + */ +(function($) { + + // Define the jscroll namespace and default settings + $.jscroll = { + defaults: { + debug: false, + autoTrigger: true, + autoTriggerUntil: false, + loadingHtml: 'Loading...', + padding: 0, + nextSelector: 'a:last', + contentSelector: '', + pagingSelector: '', + callback: false + } + }; + + // Constructor + var jScroll = function($e, options) { + + // Private vars + var _data = $e.data('jscroll'), + _userOptions = (typeof options === 'function') ? { callback: options } : options, + _options = $.extend({}, $.jscroll.defaults, _userOptions, _data || {}), + _isWindow = ($e.css('overflow-y') === 'visible'), + _$next = $e.find(_options.nextSelector).first(), + _$window = $(window), + _$body = $('body'), + _$scroll = _isWindow ? _$window : $e, + _nextHref = $.trim(_$next.attr('href') + ' ' + _options.contentSelector); + + // Initialization + $e.data('jscroll', $.extend({}, _data, {initialized: true, waiting: false, nextHref: _nextHref})); + _wrapInnerContent(); + _preloadImage(); + _setBindings(); + + // Private methods + + // Check if a loading image is defined and preload + function _preloadImage() { + var src = $(_options.loadingHtml).filter('img').attr('src'); + if (src) { + var image = new Image(); + image.src = src; + } + } + + // Wrapper inner content, if it isn't already + function _wrapInnerContent() { + if (!$e.find('.jscroll-inner').length) { + $e.contents().wrapAll('
'); + } + } + + // Find the next link's parent, or add one, and hide it + function _nextWrap($next) { + if (_options.pagingSelector) { + var $parent = $next.closest(_options.pagingSelector).hide(); + } else { + var $parent = $next.parent().not('.jscroll-inner,.jscroll-added').addClass('jscroll-next-parent').hide(); + if (!$parent.length) { + $next.wrap('
').parent().hide(); + } + } + } + + // Remove the jscroll behavior and data from an element + function _destroy() { + return _$scroll.unbind('.jscroll') + .removeData('jscroll') + .find('.jscroll-inner').children().unwrap() + .filter('.jscroll-added').children().unwrap(); + } + + // Observe the scroll event for when to trigger the next load + function _observe() { + _wrapInnerContent(); + var $inner = $e.find('div.jscroll-inner').first(), + data = $e.data('jscroll'), + borderTopWidth = parseInt($e.css('borderTopWidth')), + borderTopWidthInt = isNaN(borderTopWidth) ? 0 : borderTopWidth, + iContainerTop = parseInt($e.css('paddingTop')) + borderTopWidthInt, + iTopHeight = _isWindow ? _$scroll.scrollTop() : $e.offset().top, + innerTop = $inner.length ? $inner.offset().top : 0, + iTotalHeight = Math.ceil(iTopHeight - innerTop + _$scroll.height() + iContainerTop); + + if (!data.waiting && iTotalHeight + _options.padding >= $inner.outerHeight()) { + //data.nextHref = $.trim(data.nextHref + ' ' + _options.contentSelector); + _debug('info', 'jScroll:', $inner.outerHeight() - iTotalHeight, 'from bottom. Loading next request...'); + return _load(); + } + } + + // Check if the href for the next set of content has been set + function _checkNextHref(data) { + data = data || $e.data('jscroll'); + if (!data || !data.nextHref) { + _debug('warn', 'jScroll: nextSelector not found - destroying'); + _destroy(); + return false; + } else { + _setBindings(); + return true; + } + } + + function _setBindings() { + var $next = $e.find(_options.nextSelector).first(); + if (_options.autoTrigger && (_options.autoTriggerUntil === false || _options.autoTriggerUntil > 0)) { + _nextWrap($next); + if (_$body.height() <= _$window.height()) { + _observe(); + } + _$scroll.unbind('.jscroll').bind('scroll.jscroll', function() { + return _observe(); + }); + if (_options.autoTriggerUntil > 0) { + _options.autoTriggerUntil--; + } + } else { + _$scroll.unbind('.jscroll'); + $next.bind('click.jscroll', function() { + _nextWrap($next); + _load(); + return false; + }); + } + } + + // Load the next set of content, if available + function _load() { + var $inner = $e.find('div.jscroll-inner').first(), + data = $e.data('jscroll'); + + data.waiting = true; + $inner.append('
') + .children('.jscroll-added').last() + .html('
' + _options.loadingHtml + '
'); + + return $e.animate({scrollTop: $inner.outerHeight()}, 0, function() { + $inner.find('div.jscroll-added').last().load(data.nextHref, function(r, status, xhr) { + if (status === 'error') { + return _destroy(); + } + var $next = $(this).find(_options.nextSelector).first(); + data.waiting = false; + data.nextHref = $next.attr('href') ? $.trim($next.attr('href') + ' ' + _options.contentSelector) : false; + $('.jscroll-next-parent', $e).remove(); // Remove the previous next link now that we have a new one + _checkNextHref(); + if (_options.callback) { + _options.callback.call(this); + } + _debug('dir', data); + }); + }); + } + + // Safe console debug - http://klauzinski.com/javascript/safe-firebug-console-in-javascript + function _debug(m) { + if (_options.debug && typeof console === 'object' && (typeof m === 'object' || typeof console[m] === 'function')) { + if (typeof m === 'object') { + var args = []; + for (var sMethod in m) { + if (typeof console[sMethod] === 'function') { + args = (m[sMethod].length) ? m[sMethod] : [m[sMethod]]; + console[sMethod].apply(console, args); + } else { + console.log.apply(console, args); + } + } + } else { + console[m].apply(console, Array.prototype.slice.call(arguments, 1)); + } + } + } + + // Expose API methods via the jQuery.jscroll namespace, e.g. $('sel').jscroll.method() + $.extend($e.jscroll, { + destroy: _destroy + }); + return $e; + }; + + // Define the jscroll plugin method and loop + $.fn.jscroll = function(m) { + return this.each(function() { + var $this = $(this), + data = $this.data('jscroll'); + // Instantiate jScroll on this element if it hasn't been already + if (data && data.initialized) return; + var jscroll = new jScroll($this, m); + }); + }; +})(jQuery); \ No newline at end of file Index: lams_tool_forum/web/includes/javascript/jquery.treetable.js =================================================================== diff -u --- lams_tool_forum/web/includes/javascript/jquery.treetable.js (revision 0) +++ lams_tool_forum/web/includes/javascript/jquery.treetable.js (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,634 @@ +/* + * jQuery treetable Plugin 3.2.0 + * http://ludo.cubicphuse.nl/jquery-treetable + * + * Copyright 2013, Ludo van den Boom + * Dual licensed under the MIT or GPL Version 2 licenses. + */ +(function($) { + var Node, Tree, methods; + + Node = (function() { + function Node(row, tree, settings) { + var parentId; + + this.row = row; + this.tree = tree; + this.settings = settings; + + // TODO Ensure id/parentId is always a string (not int) + this.id = this.row.data(this.settings.nodeIdAttr); + + // TODO Move this to a setParentId function? + parentId = this.row.data(this.settings.parentIdAttr); + if (parentId != null && parentId !== "") { + this.parentId = parentId; + } + + this.treeCell = $(this.row.children(this.settings.columnElType)[this.settings.column]); + this.expander = $(this.settings.expanderTemplate); + this.indenter = $(this.settings.indenterTemplate); + this.children = []; + this.initialized = false; + + // Modified Fiona Malikoff 6 Mar 2015 - we want to put the twister triangle after the message not before. + // Changed prepend to append. + // this.treeCell.prepend(this.indenter); + this.treeCell.append(this.indenter); + + } + + Node.prototype.addChild = function(child) { + return this.children.push(child); + }; + + Node.prototype.ancestors = function() { + var ancestors, node; + node = this; + ancestors = []; + while (node = node.parentNode()) { + ancestors.push(node); + } + return ancestors; + }; + + Node.prototype.collapse = function() { + if (this.collapsed()) { + return this; + } + + this.row.removeClass("expanded").addClass("collapsed"); + + this._hideChildren(); + this.expander.attr("title", this.settings.stringExpand); + + if (this.initialized && this.settings.onNodeCollapse != null) { + this.settings.onNodeCollapse.apply(this); + } + + return this; + }; + + Node.prototype.collapsed = function() { + return this.row.hasClass("collapsed"); + }; + + // TODO destroy: remove event handlers, expander, indenter, etc. + + Node.prototype.expand = function() { + if (this.expanded()) { + return this; + } + + this.row.removeClass("collapsed").addClass("expanded"); + + if (this.initialized && this.settings.onNodeExpand != null) { + this.settings.onNodeExpand.apply(this); + } + + if ($(this.row).is(":visible")) { + this._showChildren(); + } + + this.expander.attr("title", this.settings.stringCollapse); + + return this; + }; + + Node.prototype.expanded = function() { + return this.row.hasClass("expanded"); + }; + + Node.prototype.hide = function() { + this._hideChildren(); + this.row.hide(); + return this; + }; + + Node.prototype.isBranchNode = function() { + if(this.children.length > 0 || this.row.data(this.settings.branchAttr) === true) { + return true; + } else { + return false; + } + }; + + Node.prototype.updateBranchLeafClass = function(){ + this.row.removeClass('branch'); + this.row.removeClass('leaf'); + this.row.addClass(this.isBranchNode() ? 'branch' : 'leaf'); + }; + + Node.prototype.level = function() { + return this.ancestors().length; + }; + + Node.prototype.parentNode = function() { + if (this.parentId != null) { + return this.tree[this.parentId]; + } else { + return null; + } + }; + + Node.prototype.removeChild = function(child) { + var i = $.inArray(child, this.children); + return this.children.splice(i, 1) + }; + + Node.prototype.render = function() { + var handler, + settings = this.settings, + target; + + if (settings.expandable === true && this.isBranchNode()) { + handler = function(e) { + $(this).parents("table").treetable("node", $(this).parents("tr").data(settings.nodeIdAttr)).toggle(); + return e.preventDefault(); + }; + + this.indenter.html(this.expander); + target = settings.clickableNodeNames === true ? this.treeCell : this.expander; + + target.off("click.treetable").on("click.treetable", handler); + target.off("keydown.treetable").on("keydown.treetable", function(e) { + if (e.keyCode == 13) { + handler.apply(this, [e]); + } + }); + } + + this.indenter[0].style.paddingLeft = "" + (this.level() * settings.indent) + "px"; + + return this; + }; + + Node.prototype.reveal = function() { + if (this.parentId != null) { + this.parentNode().reveal(); + } + return this.expand(); + }; + + Node.prototype.setParent = function(node) { + if (this.parentId != null) { + this.tree[this.parentId].removeChild(this); + } + this.parentId = node.id; + this.row.data(this.settings.parentIdAttr, node.id); + return node.addChild(this); + }; + + Node.prototype.show = function() { + if (!this.initialized) { + this._initialize(); + } + this.row.show(); + if (this.expanded()) { + this._showChildren(); + } + return this; + }; + + Node.prototype.toggle = function() { + if (this.expanded()) { + this.collapse(); + } else { + this.expand(); + } + return this; + }; + + Node.prototype._hideChildren = function() { + var child, _i, _len, _ref, _results; + _ref = this.children; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + _results.push(child.hide()); + } + return _results; + }; + + Node.prototype._initialize = function() { + var settings = this.settings; + + this.render(); + + if (settings.expandable === true && settings.initialState === "collapsed") { + this.collapse(); + } else { + this.expand(); + } + + if (settings.onNodeInitialized != null) { + settings.onNodeInitialized.apply(this); + } + + return this.initialized = true; + }; + + Node.prototype._showChildren = function() { + var child, _i, _len, _ref, _results; + _ref = this.children; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + _results.push(child.show()); + } + return _results; + }; + + return Node; + })(); + + Tree = (function() { + function Tree(table, settings) { + this.table = table; + this.settings = settings; + this.tree = {}; + + // Cache the nodes and roots in simple arrays for quick access/iteration + this.nodes = []; + this.roots = []; + } + + Tree.prototype.collapseAll = function() { + var node, _i, _len, _ref, _results; + _ref = this.nodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.collapse()); + } + return _results; + }; + + Tree.prototype.expandAll = function() { + var node, _i, _len, _ref, _results; + _ref = this.nodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + _results.push(node.expand()); + } + return _results; + }; + + Tree.prototype.findLastNode = function (node) { + if (node.children.length > 0) { + return this.findLastNode(node.children[node.children.length - 1]); + } else { + return node; + } + }; + + Tree.prototype.loadRows = function(rows) { + var node, row, i; + + if (rows != null) { + for (i = 0; i < rows.length; i++) { + row = $(rows[i]); + + if (row.data(this.settings.nodeIdAttr) != null) { + node = new Node(row, this.tree, this.settings); + this.nodes.push(node); + this.tree[node.id] = node; + + if (node.parentId != null && this.tree[node.parentId]) { + this.tree[node.parentId].addChild(node); + } else { + this.roots.push(node); + } + } + } + } + + for (i = 0; i < this.nodes.length; i++) { + node = this.nodes[i].updateBranchLeafClass(); + } + + return this; + }; + + Tree.prototype.move = function(node, destination) { + // Conditions: + // 1: +node+ should not be inserted as a child of +node+ itself. + // 2: +destination+ should not be the same as +node+'s current parent (this + // prevents +node+ from being moved to the same location where it already + // is). + // 3: +node+ should not be inserted in a location in a branch if this would + // result in +node+ being an ancestor of itself. + var nodeParent = node.parentNode(); + if (node !== destination && destination.id !== node.parentId && $.inArray(node, destination.ancestors()) === -1) { + node.setParent(destination); + this._moveRows(node, destination); + + // Re-render parentNode if this is its first child node, and therefore + // doesn't have the expander yet. + if (node.parentNode().children.length === 1) { + node.parentNode().render(); + } + } + + if(nodeParent){ + nodeParent.updateBranchLeafClass(); + } + if(node.parentNode()){ + node.parentNode().updateBranchLeafClass(); + } + node.updateBranchLeafClass(); + return this; + }; + + Tree.prototype.removeNode = function(node) { + // Recursively remove all descendants of +node+ + this.unloadBranch(node); + + // Remove node from DOM () + node.row.remove(); + + // Remove node from parent children list + if (node.parentId != null) { + node.parentNode().removeChild(node); + } + + // Clean up Tree object (so Node objects are GC-ed) + delete this.tree[node.id]; + this.nodes.splice($.inArray(node, this.nodes), 1); + + return this; + } + + Tree.prototype.render = function() { + var root, _i, _len, _ref; + _ref = this.roots; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + root = _ref[_i]; + + // Naming is confusing (show/render). I do not call render on node from + // here. + root.show(); + } + return this; + }; + + Tree.prototype.sortBranch = function(node, sortFun) { + // First sort internal array of children + node.children.sort(sortFun); + + // Next render rows in correct order on page + this._sortChildRows(node); + + return this; + }; + + Tree.prototype.unloadBranch = function(node) { + // Use a copy of the children array to not have other functions interfere + // with this function if they manipulate the children array + // (eg removeNode). + var children = node.children.slice(0), + i; + + for (i = 0; i < children.length; i++) { + this.removeNode(children[i]); + } + + // Reset node's collection of children + node.children = []; + + node.updateBranchLeafClass(); + + return this; + }; + + Tree.prototype._moveRows = function(node, destination) { + var children = node.children, i; + + node.row.insertAfter(destination.row); + node.render(); + + // Loop backwards through children to have them end up on UI in correct + // order (see #112) + for (i = children.length - 1; i >= 0; i--) { + this._moveRows(children[i], node); + } + }; + + // Special _moveRows case, move children to itself to force sorting + Tree.prototype._sortChildRows = function(parentNode) { + return this._moveRows(parentNode, parentNode); + }; + + return Tree; + })(); + + // jQuery Plugin + methods = { + init: function(options, force) { + var settings; + + settings = $.extend({ + branchAttr: "ttBranch", + clickableNodeNames: false, + column: 0, + columnElType: "td", // i.e. 'td', 'th' or 'td,th' + expandable: false, + expanderTemplate: " ", + indent: 19, + indenterTemplate: "", + initialState: "collapsed", + nodeIdAttr: "ttId", // maps to data-tt-id + parentIdAttr: "ttParentId", // maps to data-tt-parent-id + stringExpand: "Expand", + stringCollapse: "Collapse", + + // Events + onInitialized: null, + onNodeCollapse: null, + onNodeExpand: null, + onNodeInitialized: null + }, options); + + return this.each(function() { + var el = $(this), tree; + + if (force || el.data("treetable") === undefined) { + tree = new Tree(this, settings); + tree.loadRows(this.rows).render(); + + el.addClass("treetable").data("treetable", tree); + + if (settings.onInitialized != null) { + settings.onInitialized.apply(tree); + } + } + + return el; + }); + }, + + destroy: function() { + return this.each(function() { + return $(this).removeData("treetable").removeClass("treetable"); + }); + }, + + collapseAll: function() { + this.data("treetable").collapseAll(); + return this; + }, + + collapseNode: function(id) { + var node = this.data("treetable").tree[id]; + + if (node) { + node.collapse(); + } else { + throw new Error("Unknown node '" + id + "'"); + } + + return this; + }, + + expandAll: function() { + this.data("treetable").expandAll(); + return this; + }, + + expandNode: function(id) { + var node = this.data("treetable").tree[id]; + + if (node) { + if (!node.initialized) { + node._initialize(); + } + + node.expand(); + } else { + throw new Error("Unknown node '" + id + "'"); + } + + return this; + }, + + loadBranch: function(node, rows) { + var settings = this.data("treetable").settings, + tree = this.data("treetable").tree; + + // TODO Switch to $.parseHTML + rows = $(rows); + + if (node == null) { // Inserting new root nodes + this.append(rows); + } else { + var lastNode = this.data("treetable").findLastNode(node); + rows.insertAfter(lastNode.row); + } + + this.data("treetable").loadRows(rows); + + // Make sure nodes are properly initialized + rows.filter("tr").each(function() { + tree[$(this).data(settings.nodeIdAttr)].show(); + }); + + if (node != null) { + // Re-render parent to ensure expander icon is shown (#79) + node.render().expand(); + } + + return this; + }, + + move: function(nodeId, destinationId) { + var destination, node; + + node = this.data("treetable").tree[nodeId]; + destination = this.data("treetable").tree[destinationId]; + this.data("treetable").move(node, destination); + + return this; + }, + + node: function(id) { + return this.data("treetable").tree[id]; + }, + + removeNode: function(id) { + var node = this.data("treetable").tree[id]; + + if (node) { + this.data("treetable").removeNode(node); + } else { + throw new Error("Unknown node '" + id + "'"); + } + + return this; + }, + + reveal: function(id) { + var node = this.data("treetable").tree[id]; + + if (node) { + node.reveal(); + } else { + throw new Error("Unknown node '" + id + "'"); + } + + return this; + }, + + sortBranch: function(node, columnOrFunction) { + var settings = this.data("treetable").settings, + prepValue, + sortFun; + + columnOrFunction = columnOrFunction || settings.column; + sortFun = columnOrFunction; + + if ($.isNumeric(columnOrFunction)) { + sortFun = function(a, b) { + var extractValue, valA, valB; + + extractValue = function(node) { + var val = node.row.find("td:eq(" + columnOrFunction + ")").text(); + // Ignore trailing/leading whitespace and use uppercase values for + // case insensitive ordering + return $.trim(val).toUpperCase(); + } + + valA = extractValue(a); + valB = extractValue(b); + + if (valA < valB) return -1; + if (valA > valB) return 1; + return 0; + }; + } + + this.data("treetable").sortBranch(node, sortFun); + return this; + }, + + unloadBranch: function(node) { + this.data("treetable").unloadBranch(node); + return this; + } + }; + + $.fn.treetable = function(method) { + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + return $.error("Method " + method + " does not exist on jQuery.treetable"); + } + }; + + // Expose classes to world + this.TreeTable || (this.TreeTable = {}); + this.TreeTable.Node = Node; + this.TreeTable.Tree = Tree; +})(jQuery); Index: lams_tool_forum/web/includes/javascript/message.js =================================================================== diff -u -r4bd9dd5189274e3383b6c520c58e916b3b6b4a74 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/includes/javascript/message.js (.../message.js) (revision 4bd9dd5189274e3383b6c520c58e916b3b6b4a74) +++ lams_tool_forum/web/includes/javascript/message.js (.../message.js) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -38,4 +38,10 @@ Element.hide(targetDiv+"_Busy"); } } + + function highlightMessage() { + $('.highlight').filter($('table')).css('background','none'); + $('.highlight').filter($('div')).effect('highlight', {color: "#fcf0ad"}, 6000); + $('.highlight').removeClass('highlight'); + } Index: lams_tool_forum/web/jsps/learning/edit.jsp =================================================================== diff -u -rac12c171d18673d283c3304f1333ebb00e3819d0 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/jsps/learning/edit.jsp (.../edit.jsp) (revision ac12c171d18673d283c3304f1333ebb00e3819d0) +++ lams_tool_forum/web/jsps/learning/edit.jsp (.../edit.jsp) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -1,6 +1,67 @@ <%@ include file="/common/taglibs.jsp"%> - + + + + Index: lams_tool_forum/web/jsps/learning/message/msgview.jsp =================================================================== diff -u --- lams_tool_forum/web/jsps/learning/message/msgview.jsp (revision 0) +++ lams_tool_forum/web/jsps/learning/message/msgview.jsp (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,188 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> +<%@ include file="/common/taglibs.jsp"%> + + + + + + + + + + + + + + + + + highlight + + + + + + +
px;"> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + - + + +
+ + + + + + + + +
+
+ " class="space-left float-left"> + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + + + + + + +
+
+ + <%@ include file="/jsps/learning/ratingStars.jsp"%> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Index: lams_tool_forum/web/jsps/learning/message/msgviewwrapper.jsp =================================================================== diff -u --- lams_tool_forum/web/jsps/learning/message/msgviewwrapper.jsp (revision 0) +++ lams_tool_forum/web/jsps/learning/message/msgviewwrapper.jsp (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,30 @@ + + + + +<%@ include file="/common/taglibs.jsp"%> + + + + + + + + <%@ include file="msgview.jsp"%> + + Index: lams_tool_forum/web/jsps/learning/message/topiceditform.jsp =================================================================== diff -u -r9d8aadf71d51c3a6eaff3326f2166f833d02a4b1 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/jsps/learning/message/topiceditform.jsp (.../topiceditform.jsp) (revision 9d8aadf71d51c3a6eaff3326f2166f833d02a4b1) +++ lams_tool_forum/web/jsps/learning/message/topiceditform.jsp (.../topiceditform.jsp) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -30,13 +30,7 @@
- - - - + Index: lams_tool_forum/web/jsps/learning/message/topiclist.jsp =================================================================== diff -u -r9d8aadf71d51c3a6eaff3326f2166f833d02a4b1 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/jsps/learning/message/topiclist.jsp (.../topiclist.jsp) (revision 9d8aadf71d51c3a6eaff3326f2166f833d02a4b1) +++ lams_tool_forum/web/jsps/learning/message/topiclist.jsp (.../topiclist.jsp) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -1,8 +1,9 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> <%@ include file="/common/taglibs.jsp"%> <%-- If you change this file, remember to update the copy made for CNG-28 --%> - - + +
@@ -30,7 +31,7 @@
- + Index: lams_tool_forum/web/jsps/learning/message/topicreplyform.jsp =================================================================== diff -u -r9d8aadf71d51c3a6eaff3326f2166f833d02a4b1 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/jsps/learning/message/topicreplyform.jsp (.../topicreplyform.jsp) (revision 9d8aadf71d51c3a6eaff3326f2166f833d02a4b1) +++ lams_tool_forum/web/jsps/learning/message/topicreplyform.jsp (.../topicreplyform.jsp) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -31,16 +31,12 @@
- - - - - +
Index: lams_tool_forum/web/jsps/learning/message/topicview.jsp =================================================================== diff -u -r9d8aadf71d51c3a6eaff3326f2166f833d02a4b1 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/jsps/learning/message/topicview.jsp (.../topicview.jsp) (revision 9d8aadf71d51c3a6eaff3326f2166f833d02a4b1) +++ lams_tool_forum/web/jsps/learning/message/topicview.jsp (.../topicview.jsp) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -1,175 +1,122 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> <%@ include file="/common/taglibs.jsp"%> <%-- If you change this file, remember to update the copy made for CNG-28 --%> + + + + + + +expandable:true,initialState:'expanded', + expanderTemplate:'    ${prompt}', + stringCollapse:'${hide}',stringExpand:'${show}', + clickableNodeNames:true,indent:${indent}, + onNodeInitialized:function() { + if (this.level() >= 2) { + this.collapse(); + } + } + + + + - + -
em;"> - - - - - - - - - - + + + + - - - - - - - - - - - - - - -
- - - - - - - - -
- - - - - - - - - - - - - -
- - - - - - - - -
-
- " class="space-left float-left"> - - - - - - - - - - - - -
-
- - -
- -
- - -
- - - - - - - - -
-
- - <%@ include file="/jsps/learning/ratingStars.jsp"%> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + <%-- same test & command appears at bottom of script --%> + +
+ +
+ + +
+ + + - - - - - - - - - - - - - - - - - - - - - - - - -
+ + +
+ + + + <%@ include file="msgview.jsp"%> + + +
-
+ +
+ +
+ + + + + + Index: lams_tool_forum/web/jsps/learning/message/topicviewwrapper.jsp =================================================================== diff -u --- lams_tool_forum/web/jsps/learning/message/topicviewwrapper.jsp (revision 0) +++ lams_tool_forum/web/jsps/learning/message/topicviewwrapper.jsp (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -0,0 +1,18 @@ + + + + +<%@ include file="/common/taglibs.jsp"%> + + + + + + + + + + +<%@ include file="topicview.jsp"%> + + Index: lams_tool_forum/web/jsps/learning/mobile/message/topiclist.jsp =================================================================== diff -u -r09e7cc1a06df8437831c954e5c7b658cd7892f15 -r2ff38cdf504d51a2590b113023d885069d32645e --- lams_tool_forum/web/jsps/learning/mobile/message/topiclist.jsp (.../topiclist.jsp) (revision 09e7cc1a06df8437831c954e5c7b658cd7892f15) +++ lams_tool_forum/web/jsps/learning/mobile/message/topiclist.jsp (.../topiclist.jsp) (revision 2ff38cdf504d51a2590b113023d885069d32645e) @@ -1,5 +1,7 @@ +<%@ page import="org.lamsfoundation.lams.tool.forum.util.ForumConstants"%> <%@ include file="/common/taglibs.jsp"%> +