Index: lams_learning/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -recd3b320baed53ab184cc7c5d1a67def50651fe2 -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision ecd3b320baed53ab184cc7c5d1a67def50651fe2) +++ lams_learning/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -157,7 +157,7 @@ button.kumalive.ok =OK button.kumalive.report.export.all =Export all button.kumalive.report.export.selected =Export selected -button.kumalive.poll =Quick poll +button.kumalive.poll =Poll label.kumalive.poll.question =Question (optional) label.kumalive.poll.question.tip =for example: Why is the sky blue? label.kumalive.poll.answer =Answers @@ -169,6 +169,12 @@ label.kumalive.poll.answer.no =No label.kumalive.poll.answer.custom =Custom... label.kumalive.poll.answer.custom.tip ={Put} {each} {answer} {in} {curly} {brackets} -label.kumalive.poll.answer.custom.error.syntax =Custom answer syntax is incorrect. Example: {firs answer} {second answer} +label.kumalive.poll.answer.custom.error.syntax =Custom answer syntax is incorrect. Example: {first answer} {second answer} +label.kumalive.poll.answer.custom.error.count =Maximum 10 answers allowed button.kumalive.poll.start =Ask now! +button.kumalive.poll.vote =Vote +button.kumalive.poll.finish =Finish +message.kumalive.poll.finish.confirm =Are you sure you want to finish this poll? +button.kumalive.poll.close =Close + #======= End labels: Exported 151 labels for en AU ===== Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java (.../KumaliveWebsocketServer.java) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java (.../KumaliveWebsocketServer.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -1,6 +1,9 @@ package org.lamsfoundation.lams.learning.kumalive; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -22,6 +25,8 @@ import org.apache.tomcat.util.json.JSONException; import org.apache.tomcat.util.json.JSONObject; import org.lamsfoundation.lams.learning.kumalive.model.Kumalive; +import org.lamsfoundation.lams.learning.kumalive.model.KumalivePoll; +import org.lamsfoundation.lams.learning.kumalive.model.KumalivePollAnswer; import org.lamsfoundation.lams.learning.kumalive.model.KumaliveRubric; import org.lamsfoundation.lams.learning.kumalive.service.IKumaliveService; import org.lamsfoundation.lams.security.ISecurityService; @@ -31,6 +36,7 @@ import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.util.Configuration; import org.lamsfoundation.lams.util.ConfigurationKeys; +import org.lamsfoundation.lams.util.JsonUtil; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; import org.springframework.web.context.WebApplicationContext; @@ -49,6 +55,7 @@ private Session websocket; private boolean isTeacher; private boolean roleTeacher; + private Long pollVoted; private KumaliveUser(User user, Session websocket, boolean isTeacher, boolean roleTeacher) { this.userDTO = user.getUserDTO(); @@ -68,6 +75,10 @@ private final Map learners = new ConcurrentHashMap<>(); private final JSONArray rubrics = new JSONArray(); private JSONObject poll = null; + private boolean pollVotesShown = false; + private boolean pollVotersShown = false; + private ArrayList> pollVoters = null; + private JSONArray pollVotersJSON = null; private KumaliveDTO(Kumalive kumalive) throws JSONException { this.id = kumalive.getKumaliveId(); @@ -174,6 +185,12 @@ case "startPoll": startPoll(requestJSON, session); break; + case "finishPoll": + finishPoll(requestJSON, session); + break; + case "closePoll": + closePoll(requestJSON, session); + break; case "finish": finish(requestJSON, session); break; @@ -205,6 +222,12 @@ if (kumalive != null) { kumaliveDTO = new KumaliveDTO(kumalive); kumalives.put(organisationId, kumaliveDTO); + + KumalivePoll poll = KumaliveWebsocketServer.getKumaliveService() + .getPollByKumaliveId(kumalive.getKumaliveId()); + if (poll != null) { + KumaliveWebsocketServer.pollToJSON(kumaliveDTO, poll); + } } } @@ -297,42 +320,72 @@ } responseJSON.put("speaker", kumalive.speaker); - responseJSON.put("poll", kumalive.poll); - // each learner's details JSONArray learnersJSON = new JSONArray(); - JSONObject logins = new JSONObject(); + JSONObject loginsJSON = new JSONObject(); for (KumaliveUser participant : kumalive.learners.values()) { UserDTO participantDTO = participant.userDTO; - JSONObject learnerJSON = new JSONObject(); - learnerJSON.put("id", participantDTO.getUserID()); - learnerJSON.put("firstName", participantDTO.getFirstName()); - learnerJSON.put("lastName", participantDTO.getLastName()); - learnerJSON.put("portraitUuid", participantDTO.getPortraitUuid()); - learnerJSON.put("roleTeacher", participant.roleTeacher); + JSONObject participantJSON = KumaliveWebsocketServer.participantToJSON(participantDTO, + participant.roleTeacher); + loginsJSON.put("user" + participantDTO.getUserID(), participantDTO.getLogin()); - logins.put("user" + participantDTO.getUserID(), participantDTO.getLogin()); - - learnersJSON.put(learnerJSON); + learnersJSON.put(participantJSON); } responseJSON.put("learners", learnersJSON); - String learnerResponse = responseJSON.toString(); - JSONObject teacherResponseJSON = null; + JSONArray pollVotesJSON = null; + // is there a poll running? + if (kumalive.poll != null) { + // build votes count JSON + pollVotesJSON = new JSONArray(); + for (int answerIndex = 0; answerIndex < kumalive.pollVoters.size(); answerIndex++) { + List voters = kumalive.pollVoters.get(answerIndex); + // update voters JSON: add only new voters + synchronized (voters) { + pollVotesJSON.put(voters.size()); + JSONArray votersJSON = kumalive.pollVotersJSON.getJSONArray(answerIndex); + for (int voterIndex = votersJSON.length(); voterIndex < voters.size(); voterIndex++) { + votersJSON.put(KumaliveWebsocketServer.participantToJSON(voters.get(voterIndex), null)); + } + } + } + // put them in response only if teacher released them + kumalive.poll.put("votes", kumalive.pollVotesShown ? pollVotesJSON : null); + kumalive.poll.put("voters", kumalive.pollVotersShown ? kumalive.pollVoters : null); + responseJSON.put("poll", kumalive.poll); + } // send refresh to everyone + Long pollId = kumalive.poll == null ? null : kumalive.poll.getLong("id"); + String teacherResponse = null; + String learnerResponse = responseJSON.toString(); for (KumaliveUser participant : kumalive.learners.values()) { Basic channel = participant.websocket.getBasicRemote(); if (participant.isTeacher) { // send extra information to teachers - if (teacherResponseJSON == null) { - responseJSON.put("logins", logins); - teacherResponseJSON = responseJSON; + if (teacherResponse == null) { + JSONObject teacherResponseJSON = new JSONObject(learnerResponse); + teacherResponseJSON.put("logins", loginsJSON); + // add poll votes and voters, if they were not already released and added for all users + if (kumalive.poll != null) { + if (!kumalive.pollVotesShown) { + teacherResponseJSON.getJSONObject("poll").put("votes", pollVotesJSON); + } + if (!kumalive.pollVotersShown) { + teacherResponseJSON.getJSONObject("poll").put("voters", kumalive.pollVotersJSON); + } + } + teacherResponse = teacherResponseJSON.toString(); } - channel.sendText(teacherResponseJSON.toString()); - } else { + channel.sendText(teacherResponse); + } else if (kumalive.poll == null || kumalive.poll.getBoolean("finished")) { channel.sendText(learnerResponse); + } else { + // mark this learner as voted + responseJSON.getJSONObject("poll").put("voted", + participant.pollVoted != null && pollId != null && participant.pollVoted.equals(pollId)); + channel.sendText(responseJSON.toString()); } } } @@ -343,7 +396,6 @@ private void raiseHandPrompt(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -355,6 +407,7 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); kumalive.raiseHandPrompt = true; if (logger.isDebugEnabled()) { logger.debug("Teacher " + userId + " asked a question in Kumalive " + kumalive.id); @@ -368,7 +421,6 @@ private void downHandPrompt(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -380,6 +432,7 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); kumalive.raiseHandPrompt = false; kumalive.raisedHand.clear(); if (logger.isDebugEnabled()) { @@ -395,7 +448,6 @@ private void raiseHand(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -407,6 +459,7 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); if (!kumalive.raiseHandPrompt) { logger.warn("Raise hand prompt was not sent by teacher yet for organisation " + organisationId); return; @@ -430,7 +483,6 @@ private void downHand(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -442,6 +494,7 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); if (kumalive.raisedHand == null) { return; } @@ -460,7 +513,6 @@ private void speak(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -472,6 +524,7 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); kumalive.speaker = requestJSON.optInt("speaker"); sendRefresh(kumalive); } @@ -512,7 +565,6 @@ private void startPoll(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -524,23 +576,87 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); + KumalivePoll existingPoll = KumaliveWebsocketServer.getKumaliveService().getPollByKumaliveId(kumalive.id); + if (existingPoll != null) { + String warning = "User " + userId + " tried to start a poll in organisation " + organisationId + + " but there is already a running one with ID " + existingPoll.getPollId(); + logger.warn(warning); + return; + } + kumalive.poll = requestJSON.getJSONObject("poll"); - Long pollId = KumaliveWebsocketServer.getKumaliveService().startPoll(kumalive.id, + KumalivePoll poll = KumaliveWebsocketServer.getKumaliveService().startPoll(kumalive.id, kumalive.poll.getString("name"), kumalive.poll.getJSONArray("answers")); - kumalive.poll.put("id", pollId); + if (poll != null) { + KumaliveWebsocketServer.pollToJSON(kumalive, poll); + } if (logger.isDebugEnabled()) { - logger.debug("Teacher " + userId + " started poll " + pollId + " in Kumalive " + kumalive.id); + logger.debug("Teacher " + userId + " started poll " + poll.getPollId() + " in Kumalive " + kumalive.id); } sendRefresh(kumalive); } /** + * Tell learners that the teacher started a poll + */ + private void finishPoll(JSONObject requestJSON, Session websocket) throws IOException, JSONException { + Integer organisationId = Integer + .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); + + User user = getUser(websocket); + Integer userId = user.getUserId(); + + if (!KumaliveWebsocketServer.getSecurityService().hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR }, "kumalive poll start", false)) { + String warning = "User " + userId + " is not a monitor of organisation " + organisationId; + logger.warn(warning); + return; + } + + Long pollId = JsonUtil.optLong(requestJSON, "pollId"); + KumaliveWebsocketServer.getKumaliveService().finishPoll(pollId); + + KumaliveDTO kumalive = kumalives.get(organisationId); + kumalive.poll.put("finished", true); + + if (logger.isDebugEnabled()) { + logger.debug("Teacher " + userId + " finished poll " + pollId + " in Kumalive " + kumalive.id); + } + sendRefresh(kumalive); + } + + private void closePoll(JSONObject requestJSON, Session websocket) throws IOException, JSONException { + Integer organisationId = Integer + .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); + + User user = getUser(websocket); + Integer userId = user.getUserId(); + + if (!KumaliveWebsocketServer.getSecurityService().hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR }, "kumalive poll start", false)) { + String warning = "User " + userId + " is not a monitor of organisation " + organisationId; + logger.warn(warning); + return; + } + + KumaliveDTO kumalive = kumalives.get(organisationId); + kumalive.poll = null; + kumalive.pollVoters = null; + kumalive.pollVotersJSON = null; + + if (logger.isDebugEnabled()) { + logger.debug("Teacher " + userId + " closed poll in Kumalive " + kumalive.id); + } + sendRefresh(kumalive); + } + + /** * End Kumalive */ private void finish(JSONObject requestJSON, Session websocket) throws IOException, JSONException { Integer organisationId = Integer .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); - KumaliveDTO kumalive = kumalives.get(organisationId); User user = getUser(websocket); Integer userId = user.getUserId(); @@ -552,6 +668,7 @@ return; } + KumaliveDTO kumalive = kumalives.get(organisationId); KumaliveWebsocketServer.getKumaliveService().finishKumalive(kumalive.id); kumalives.remove(organisationId); for (KumaliveUser participant : kumalive.learners.values()) { @@ -568,6 +685,37 @@ .getUserByLogin(websocket.getUserPrincipal().getName()); } + private static JSONObject participantToJSON(UserDTO participantDTO, Boolean isTeacher) throws JSONException { + JSONObject participantJSON = new JSONObject(); + participantJSON.put("id", participantDTO.getUserID()); + participantJSON.put("firstName", participantDTO.getFirstName()); + participantJSON.put("lastName", participantDTO.getLastName()); + participantJSON.put("portraitUuid", participantDTO.getPortraitUuid()); + if (isTeacher != null) { + participantJSON.put("roleTeacher", isTeacher); + } + return participantJSON; + } + + private static void pollToJSON(KumaliveDTO kumaliveDTO, KumalivePoll poll) throws JSONException { + JSONObject pollJSON = new JSONObject(); + pollJSON.put("id", poll.getPollId()); + pollJSON.put("name", poll.getName()); + JSONArray answersJSON = new JSONArray(); + kumaliveDTO.pollVoters = new ArrayList>(); + kumaliveDTO.pollVotersJSON = new JSONArray(); + for (KumalivePollAnswer answer : poll.getAnswers()) { + answersJSON.put(answer.getName()); + kumaliveDTO.pollVoters.add(Collections.synchronizedList(new LinkedList())); + kumaliveDTO.pollVotersJSON.put(new JSONArray()); + } + pollJSON.put("answers", answersJSON); + kumaliveDTO.poll = pollJSON; + + kumaliveDTO.pollVotesShown = false; + kumaliveDTO.pollVotersShown = false; + } + private static IKumaliveService getKumaliveService() { if (kumaliveService == null) { WebApplicationContext ctx = WebApplicationContextUtils Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/dao/IKumaliveDAO.java =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/dao/IKumaliveDAO.java (.../IKumaliveDAO.java) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/dao/IKumaliveDAO.java (.../IKumaliveDAO.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -44,5 +44,5 @@ List findKumaliveScore(Long kumaliveId, Integer userId); - KumalivePoll findPoll(Integer organisationId); + KumalivePoll findPollByKumaliveId(Long kumaliveId); } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/dao/hibernate/KumaliveDAO.java =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/dao/hibernate/KumaliveDAO.java (.../KumaliveDAO.java) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/dao/hibernate/KumaliveDAO.java (.../KumaliveDAO.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -48,8 +48,8 @@ + " AS s WHERE s.rubric.kumalive.kumaliveId = ? ORDER BY s.user.firstName "; private static final String FIND_SCORE_BY_KUMALIVE_AND_USER = "FROM " + KumaliveScore.class.getName() + " AS s WHERE s.rubric.kumalive.kumaliveId = ? AND s.user.userId = ?"; - private static final String FIND_CURRENT_POLL_BY_ORGANISATION = "FROM " + KumalivePoll.class.getName() - + " AS p WHERE p.kumalive.organisation.organisationId = ? AND k.finishDate IS NULL"; + private static final String FIND_CURRENT_POLL_BY_KUMALIVE = "FROM " + KumalivePoll.class.getName() + + " AS p WHERE p.kumalive.kumaliveId = ? AND p.finishDate IS NULL"; @Override @SuppressWarnings("unchecked") @@ -110,8 +110,8 @@ @Override @SuppressWarnings("unchecked") - public KumalivePoll findPoll(Integer organisationId) { - List result = (List) doFind(FIND_CURRENT_POLL_BY_ORGANISATION, organisationId); + public KumalivePoll findPollByKumaliveId(Long kumaliveId) { + List result = (List) doFind(FIND_CURRENT_POLL_BY_KUMALIVE, kumaliveId); return result.isEmpty() ? null : result.get(0); } } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/IKumaliveService.java =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/IKumaliveService.java (.../IKumaliveService.java) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/IKumaliveService.java (.../IKumaliveService.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -61,7 +61,9 @@ LinkedHashMap exportKumalives(Integer organisationId); - KumalivePoll getPollByOrganisation(Integer organisationId); + KumalivePoll getPollByKumaliveId(Long kumaliveId); - Long startPoll(Long kumaliveId, String name, JSONArray answersJSON) throws JSONException; + KumalivePoll startPoll(Long kumaliveId, String name, JSONArray answersJSON) throws JSONException; + + void finishPoll(Long pollId) throws JSONException; } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/KumaliveService.java =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/KumaliveService.java (.../KumaliveService.java) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/KumaliveService.java (.../KumaliveService.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -542,15 +542,15 @@ } @Override - public KumalivePoll getPollByOrganisation(Integer organisationId) { - return kumaliveDAO.findPoll(organisationId); + public KumalivePoll getPollByKumaliveId(Long kumaliveId) { + return kumaliveDAO.findPollByKumaliveId(kumaliveId); } /** * Creates a poll */ @Override - public Long startPoll(Long kumaliveId, String name, JSONArray answersJSON) throws JSONException { + public KumalivePoll startPoll(Long kumaliveId, String name, JSONArray answersJSON) throws JSONException { Kumalive kumalive = getKumalive(kumaliveId); if (kumalive == null) { return null; @@ -572,9 +572,24 @@ logger.debug("Teacher started poll " + poll.getPollId()); } - return poll.getPollId(); + return poll; } + @Override + public void finishPoll(Long pollId) throws JSONException { + KumalivePoll poll = (KumalivePoll) kumaliveDAO.find(KumalivePoll.class, pollId); + if (poll.getFinishDate() != null) { + logger.warn("Trying to finish poll " + pollId + " which is already finished"); + return; + } + poll.setFinishDate(new Date()); + kumaliveDAO.update(poll); + + if (logger.isDebugEnabled()) { + logger.debug("Teacher finished poll " + poll.getPollId()); + } + } + public void setSecurityService(ISecurityService securityService) { this.securityService = securityService; } Index: lams_learning/web/css/kumalive.scss =================================================================== diff -u -recd3b320baed53ab184cc7c5d1a67def50651fe2 -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/web/css/kumalive.scss (.../kumalive.scss) (revision ecd3b320baed53ab184cc7c5d1a67def50651fe2) +++ lams_learning/web/css/kumalive.scss (.../kumalive.scss) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -51,7 +51,7 @@ width: initial; } -#pollSetupAnswerCustom, #pollSetupAnswerCustomParseError { +#pollSetup, #pollSetupAnswerCustom, #pollSetupAnswerCustomParseError, #pollSetupAnswerCustomCountError { display: none; } @@ -64,6 +64,33 @@ margin-top: 10px; } +#pollRun, #pollRunAnswerRadioTemplate, #pollRunFinishButton { + display: none; +} + +#pollRun { + text-align: center; +} + +#pollRunQuestion { + margin-top: 20px; +} + +#pollRunAnswerRadios { + display: inline-block; + text-align: left; + margin: 0 auto; +} + +#pollRunVoteButton { + margin-top: 20px; +} + +#pollRunAnswerList .list-group-item { + margin: 5px 20px; + text-align: left; +} + #learnersCell { width: 100%; padding: 0; Index: lams_learning/web/includes/javascript/kumalive.js =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/web/includes/javascript/kumalive.js (.../kumalive.js) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/web/includes/javascript/kumalive.js (.../kumalive.js) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -16,6 +16,10 @@ speakerId = null, // rubrics to evaluate speaker rubrics = null, + // is a poll running now and what ID + pollId = null, + // answers will be numbered + pollAnswerBullets = 'abcdefghij', // index of user icon colour currently used learnerColorIndex = 1, // template of a HTML structure of a learner @@ -170,6 +174,7 @@ if (roleTeacher) { $('#raiseHandPromptButton').click(raiseHandPrompt); $('#downHandPromptButton').click(downHandPrompt); + $('#score i').click(score); $('#pollButton').click(setupPoll).show(); $('#pollSetupAnswer').change(function(){ if ($('#pollSetupAnswer option:selected').val() === 'custom') { @@ -180,7 +185,8 @@ }); $('#pollSetupCancelButton').click(setupPollCancel); $('#pollSetupStartButton').click(startPoll); - $('#score i').click(score); + $('#pollRunFinishButton').click(finishPoll); + $('#pollRunCloseButton').click(closePoll); $('#finishButton').click(finish).show(); } else { $('#raiseHandButton').click(raiseHand); @@ -214,11 +220,12 @@ repeat |= processParticipants(message); repeat |= processRaisedHand(message); repeat |= toggleSpeak(message); + processPoll(message); if (repeat || queuedMessage) { setTimeout(function() { // get the newest message - nextMessage = queuedMessage || message; + var nextMessage = queuedMessage || message; queuedMessage = null; processRefresh(nextMessage); }, REFRESH_DELAY); @@ -316,6 +323,65 @@ } /** + * Display current poll results + */ +function processPoll(message) { + var poll = message.poll; + // is there a poll running now? + if (!poll) { + if (pollId) { + // there is no poll anymore, i.e. current poll was closed + pollId = null; + // close the panel + $('#pollCell').hide(); + $('#pollButton').prop('disabled', false); + } + return; + } + + // open panel if closed + $('#pollCell, #pollRun').show(); + + // init poll fields or make them read only after voting + if (poll.id != pollId || (poll.finished && $('#pollRunVoteButton').is(':visible'))) { + pollId = poll.id; + $('#pollRun button').hide(); + $('#pollRunQuestion').text(poll.name); + var radioList = $('#pollRunAnswerRadios').empty(), + answerList = $('#pollRunAnswerList').empty(); + // teachers can't vote; learner can't vote twice; learner can't vote for finished poll + if (roleTeacher || poll.voted || poll.finished) { + // build simple list of answers + $.each(poll.answers, function(index, answer){ + var answerElement = $('
  • ').addClass('list-group-item').text(pollAnswerBullets[index] + ') ' + answer) + .appendTo(answerList); + if (poll.votes) { + // show votes if user is teacher or votes were released + $('').addClass('badge').text(poll.votes[index]).appendTo(answerElement); + } + }); + $('#pollRunAnswerList').show(); + // extra options for teacher + if (roleTeacher) { + if (poll.finished) { + $('#pollRunCloseButton').show(); + } else { + $('#pollRunFinishButton').show(); + } + } + } else { + // learner can vote, build radio buttons + $.each(poll.answers, function(index, answer){ + $('#pollRunAnswerRadioTemplate').clone().attr('id', null).appendTo(radioList) + .find('label').append($('').text(pollAnswerBullets[index] + ') ' + answer)) + .find('input').val(index); + }); + $('#pollRunVoteButton').show(); + } + } +} + +/** * Add/remove learners who raised hand */ function processRaisedHand(message) { @@ -614,19 +680,36 @@ } } +/** + * Show form where teacher can build poll + */ function setupPoll() { $('#pollButton').prop('disabled', true); - $('#pollCell').show(); + $('#pollRun').hide(); + // reset form inputs + $('#pollSetup input').val(null); + $('#pollSetup select option:first-child').prop('selected', true); + $('#pollSetupAnswerCustom').hide(); + $('#pollCell, #pollSetup').show(); $('#pollSetupQuestion').focus(); } +/** + * Cancel poll building + */ function setupPollCancel() { - $('#pollCell').hide(); - $('#pollCell input').val(null); - $('#pollCell select option:first-child').prop('selected', true); - $('#pollButton').prop('disabled', false); + $('#pollSetup').hide(); + if (pollId) { + $('#pollRun').show(); + } else { + $('#pollCell').hide(); + $('#pollButton').prop('disabled', false); + } } +/** + * Create a poll with parameters set up in form + */ function startPoll(){ var question = $('#pollSetupQuestion').val(), poll = {}; @@ -640,7 +723,7 @@ var selectedOption = $('#pollSetupAnswer option:selected'); if (selectedOption.val() === 'custom'){ - $('#pollSetupAnswerCustomParseError').hide(); + $('#pollSetupAnswerCustomParseError, #pollSetupAnswerCustomCountError').hide(); var answerString = $('#pollSetupAnswerCustom').val(); // check if brackets are closed and there is nothing between them, for example "{aaa} {bb" or "{aaa} bb {ccc}" if (answerString) { @@ -679,6 +762,9 @@ if (answers.length === 0) { $('#pollSetupAnswerCustomGroup').addClass('has-error'); $('#pollSetupAnswerCustomParseError').show(); + } else if (answers.length > 10) { + $('#pollSetupAnswerCustomGroup').addClass('has-error'); + $('#pollSetupAnswerCustomCountError').show(); } else { $('#pollSetupAnswerCustomGroup').removeClass('has-error'); poll.answers = answers; @@ -687,24 +773,51 @@ $('#pollSetupAnswerCustomGroup').addClass('has-error'); } } else { + // parse simple options, for example "True, False" var answers = []; $.each(selectedOption.text().split(','), function() { answers.push(this.trim()); }); poll.answers = answers; } + // there were errors, do not carry on if (!poll.name || !poll.answers) { return; } + $('#pollSetup').hide(); kumaliveWebsocket.send(JSON.stringify({ 'type' : 'startPoll', 'poll' : poll })); } /** + * Prevent learners from voting + */ +function finishPoll() { + if (!confirm(LABELS.POLL_FINISH_CONFIRM)) { + return; + } + kumaliveWebsocket.send(JSON.stringify({ + 'type' : 'finishPoll', + 'pollId' : pollId + })); + $('#pollRunFinishButton').hide(); + $('#pollRunCloseButton').show(); +} + +/** + * Hide poll for everyone + */ +function closePoll() { + kumaliveWebsocket.send(JSON.stringify({ + 'type' : 'closePoll' + })); +} + +/** * Create a new Kumalive */ function create(){ Index: lams_learning/web/kumalive/kumalive.jsp =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 --- lams_learning/web/kumalive/kumalive.jsp (.../kumalive.jsp) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/web/kumalive/kumalive.jsp (.../kumalive.jsp) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) @@ -38,7 +38,9 @@ SPEAK_CONFIRM : decoderDiv.html('').text(), - SPEAK_FINISH : '' + SPEAK_FINISH : '', + + POLL_FINISH_CONFIRM : decoderDiv.html('').text() }; @@ -94,13 +96,17 @@
    - +
    - +
    - + + +
    -
    - +
    +
    +
    +

    +

    +
    + +
    +
    +
    + +
      +
      + + +