Index: lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20171117.sql =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20171117.sql (.../patch20171117.sql) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_common/src/java/org/lamsfoundation/lams/dbupdates/patch20171117.sql (.../patch20171117.sql) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -26,6 +26,17 @@ REFERENCES lams_kumalive_poll (poll_id) ON DELETE CASCADE ON UPDATE CASCADE ); +CREATE TABLE lams_kumalive_poll_vote ( + answer_id BIGINT(20) NOT NULL + , user_id BIGINT(20) NOT NULL + , vote_date DATETIME + , PRIMARY KEY (answer_id, user_id) + , CONSTRAINT FK_lams_kumalive_poll_vote_1 FOREIGN KEY (answer_id) + REFERENCES lams_kumalive_poll_answer (answer_id) ON DELETE CASCADE ON UPDATE CASCADE + , CONSTRAINT FK_lams_kumalive_poll_vote_2 FOREIGN KEY (user_id) + REFERENCES lams_user (user_id) ON DELETE CASCADE ON UPDATE CASCADE +); + -- If there were no errors, commit and restore autocommit to on SET FOREIGN_KEY_CHECKS=0; COMMIT; Index: lams_learning/conf/hibernate/mappings/org/lamsfoundation/lams/learning/kumalive/KumalivePollAnswer.hbm.xml =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/conf/hibernate/mappings/org/lamsfoundation/lams/learning/kumalive/KumalivePollAnswer.hbm.xml (.../KumalivePollAnswer.hbm.xml) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/conf/hibernate/mappings/org/lamsfoundation/lams/learning/kumalive/KumalivePollAnswer.hbm.xml (.../KumalivePollAnswer.hbm.xml) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -19,5 +19,11 @@ class="org.lamsfoundation.lams.learning.kumalive.model.KumalivePoll"> + + + + + + \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java =================================================================== diff -u -r8ee91cb1acb876cf1309d37af3b8e08d476a0412 -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java (.../KumaliveWebsocketServer.java) (revision 8ee91cb1acb876cf1309d37af3b8e08d476a0412) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java (.../KumaliveWebsocketServer.java) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -7,6 +7,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -51,41 +52,29 @@ @ServerEndpoint("/kumaliveWebsocket") public class KumaliveWebsocketServer { - private class KumaliveUser { - private class KumalivePollVote { - private final Long pollId; - private final Date time; - private final int answer; - - private KumalivePollVote(Long pollId, Date time, int answer) { - this.pollId = pollId; - this.time = time; - this.answer = answer; - } - } - + private static class KumaliveUserDTO { private final UserDTO userDTO; private final Session websocket; private final boolean isTeacher; private final boolean roleTeacher; - private KumalivePollVote vote; + private KumalivePollVoteDTO vote; - private KumaliveUser(User user, Session websocket, boolean isTeacher, boolean roleTeacher) { + private KumaliveUserDTO(User user, Session websocket, boolean isTeacher, boolean roleTeacher) { this.userDTO = user.getUserDTO(); this.websocket = websocket; this.isTeacher = isTeacher; this.roleTeacher = roleTeacher; } } - private class KumaliveDTO { + private static class KumaliveDTO { private Long id; private String name; private UserDTO createdBy; private boolean raiseHandPrompt; private final List raisedHand = new CopyOnWriteArrayList<>(); private Integer speaker; - private final Map learners = new ConcurrentHashMap<>(); + private final Map learners = new ConcurrentHashMap<>(); private final JSONArray rubrics = new JSONArray(); private KumalivePollDTO poll; @@ -102,14 +91,31 @@ } } - private class KumalivePollDTO { + private static class KumalivePollDTO { + private final Long pollId; private final JSONObject pollJSON = new JSONObject(); + private final List answerIds = new ArrayList(); private final ArrayList> voters = new ArrayList>(); private final JSONArray votersJSON = new JSONArray(); + private boolean finished = false; private boolean votesShown = false; private boolean votersShown = false; + + private KumalivePollDTO(Long pollId) { + this.pollId = pollId; + } } + private static class KumalivePollVoteDTO { + private final Long pollId; + private final int answer; + + private KumalivePollVoteDTO(Long pollId, int answer) { + this.pollId = pollId; + this.answer = answer; + } + } + private static Logger logger = Logger.getLogger(KumaliveWebsocketServer.class); private static IKumaliveService kumaliveService; @@ -145,7 +151,7 @@ if (kumalive == null) { return; } - KumaliveUser user = kumalive.learners.remove(login); + KumaliveUserDTO user = kumalive.learners.remove(login); if (user != null) { Integer userId = user.userDTO.getUserID(); if (kumalive.raisedHand != null) { @@ -202,6 +208,9 @@ case "startPoll": startPoll(requestJSON, session); break; + case "votePoll": + votePoll(requestJSON, session); + break; case "finishPoll": finishPoll(requestJSON, session); break; @@ -243,7 +252,7 @@ KumalivePoll poll = KumaliveWebsocketServer.getKumaliveService() .getPollByKumaliveId(kumalive.getKumaliveId()); if (poll != null) { - KumaliveWebsocketServer.pollToJSON(kumaliveDTO, poll); + KumaliveWebsocketServer.fillPollDTO(kumaliveDTO, poll); } } } @@ -289,7 +298,7 @@ Role.MONITOR); String role = websocket.getRequestParameterMap().get(AttributeNames.PARAM_ROLE).get(0); - KumaliveUser learner = kumalive.learners.get(login); + KumaliveUserDTO learner = kumalive.learners.get(login); boolean roleTeacher = isTeacher && !Role.LEARNER.equalsIgnoreCase(role) && ("teacher".equalsIgnoreCase(role) || learner == null || learner.roleTeacher); if (learner != null && !learner.websocket.getId().equals(websocket.getId())) { @@ -298,14 +307,21 @@ new CloseReason(CloseCodes.NOT_CONSISTENT, "Another websocket for same user was estabilished")); } - learner = new KumaliveUser(user, websocket, isTeacher, roleTeacher); + learner = new KumaliveUserDTO(user, websocket, isTeacher, roleTeacher); + if (kumalive.poll != null) { + for (int answerIndex = 0; answerIndex < kumalive.poll.voters.size(); answerIndex++) { + if (kumalive.poll.voters.get(answerIndex).contains(learner.userDTO)) { + learner.vote = new KumalivePollVoteDTO(kumalive.poll.pollId, answerIndex); + } + } + } kumalive.learners.put(login, learner); sendInit(kumalive, learner); sendRefresh(kumalive); } - private void sendInit(KumaliveDTO kumalive, KumaliveUser user) throws JSONException, IOException { + private void sendInit(KumaliveDTO kumalive, KumaliveUserDTO user) throws JSONException, IOException { JSONObject responseJSON = new JSONObject(); responseJSON.put("type", "init"); // Kumalive title @@ -340,7 +356,7 @@ // each learner's details JSONArray learnersJSON = new JSONArray(); JSONObject loginsJSON = new JSONObject(); - for (KumaliveUser participant : kumalive.learners.values()) { + for (KumaliveUserDTO participant : kumalive.learners.values()) { UserDTO participantDTO = participant.userDTO; JSONObject participantJSON = KumaliveWebsocketServer.participantToJSON(participantDTO, @@ -351,57 +367,59 @@ } responseJSON.put("learners", learnersJSON); - JSONArray pollVotesJSON = null; + JSONObject pollJSON = null; // is there a poll running? if (kumalive.poll != null) { + pollJSON = new JSONObject(kumalive.poll.pollJSON.toString()); + // build votes count JSON - pollVotesJSON = new JSONArray(); + JSONArray votesJSON = new JSONArray(); for (int answerIndex = 0; answerIndex < kumalive.poll.voters.size(); answerIndex++) { List voters = kumalive.poll.voters.get(answerIndex); // update voters JSON: add only new voters synchronized (voters) { - pollVotesJSON.put(voters.size()); - JSONArray votersJSON = kumalive.poll.votersJSON.getJSONArray(answerIndex); - for (int voterIndex = votersJSON.length(); voterIndex < voters.size(); voterIndex++) { - votersJSON.put(KumaliveWebsocketServer.participantToJSON(voters.get(voterIndex), null)); + votesJSON.put(voters.size()); + JSONArray answerVotersJSON = kumalive.poll.votersJSON.getJSONArray(answerIndex); + for (int voterIndex = answerVotersJSON.length(); voterIndex < voters.size(); voterIndex++) { + answerVotersJSON.put(KumaliveWebsocketServer.participantToJSON(voters.get(voterIndex), null)); } } } - // put them in response only if teacher released them - kumalive.poll.pollJSON.put("votes", kumalive.poll.votesShown ? pollVotesJSON : null); - kumalive.poll.pollJSON.put("voters", kumalive.poll.votersShown ? kumalive.poll.voters : null); - responseJSON.put("poll", kumalive.poll.pollJSON); + + pollJSON.put("votes", votesJSON); + pollJSON.put("voters", kumalive.poll.votersJSON); } - // send refresh to everyone - Long pollId = kumalive.poll == null ? null : kumalive.poll.pollJSON.getLong("id"); - String teacherResponse = null; String learnerResponse = responseJSON.toString(); - for (KumaliveUser participant : kumalive.learners.values()) { + // send extra information to teachers + JSONObject teacherResponseJSON = new JSONObject(learnerResponse); + teacherResponseJSON.put("logins", loginsJSON); + teacherResponseJSON.put("poll", pollJSON); + String teacherResponse = teacherResponseJSON.toString(); + // send refresh to everyone + for (KumaliveUserDTO participant : kumalive.learners.values()) { Basic channel = participant.websocket.getBasicRemote(); - if (participant.isTeacher) { - // send extra information to teachers - 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.poll.votesShown) { - teacherResponseJSON.getJSONObject("poll").put("votes", pollVotesJSON); - } - if (!kumalive.poll.votersShown) { - teacherResponseJSON.getJSONObject("poll").put("voters", kumalive.poll.votersJSON); - } - } - teacherResponse = teacherResponseJSON.toString(); - } + if (participant.roleTeacher) { channel.sendText(teacherResponse); - } else if (kumalive.poll == null || kumalive.poll.pollJSON.getBoolean("finished")) { + } else if (kumalive.poll == null) { channel.sendText(learnerResponse); } else { - // mark this learner as voted - responseJSON.getJSONObject("poll").put("voted", - participant.vote != null && pollId != null && participant.vote.pollId.equals(pollId)); + JSONObject learnerPollJSON = new JSONObject(pollJSON.toString()); + boolean voted = participant.vote != null && kumalive.poll.pollId != null + && participant.vote.pollId.equals(kumalive.poll.pollId); + // put them in response only if teacher released them and user voted + if (!kumalive.poll.votesShown || (!voted && !kumalive.poll.finished)) { + learnerPollJSON.remove("votes"); + } + if (!kumalive.poll.votersShown || (!voted && !kumalive.poll.finished)) { + learnerPollJSON.remove("voters"); + } + if (voted) { + // mark this learner as voted + learnerPollJSON.put("voted", participant.vote.answer); + } + responseJSON.put("poll", learnerPollJSON); + channel.sendText(responseJSON.toString()); } } @@ -606,7 +624,7 @@ KumalivePoll poll = KumaliveWebsocketServer.getKumaliveService().startPoll(kumalive.id, pollJSON.getString("name"), pollJSON.getJSONArray("answers")); if (poll != null) { - KumaliveWebsocketServer.pollToJSON(kumalive, poll); + KumaliveWebsocketServer.fillPollDTO(kumalive, poll); } if (logger.isDebugEnabled()) { logger.debug("Teacher " + userId + " started poll " + poll.getPollId() + " in Kumalive " + kumalive.id); @@ -617,6 +635,47 @@ /** * Tell learners that the teacher started a poll */ + private void votePoll(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.LEARNER }, "kumalive poll vote", false)) { + String warning = "User " + userId + " is not a learner of organisation " + organisationId; + logger.warn(warning); + return; + } + + KumaliveDTO kumalive = kumalives.get(organisationId); + if (kumalive.poll == null || kumalive.poll.finished) { + logger.warn("Learner " + userId + " tried to vote but a poll is not started in Kumalive " + kumalive.id); + return; + } + KumaliveUserDTO learnerDTO = kumalive.learners.get(user.getLogin()); + if (learnerDTO.vote != null && learnerDTO.vote.pollId.equals(kumalive.poll.pollId)) { + logger.warn("Learner " + userId + " tried to vote for poll " + kumalive.poll.pollId + + " but he already voted in Kumalive " + kumalive.id); + return; + } + + Integer answerIndex = requestJSON.getInt("answerIndex"); + Long answerId = kumalive.poll.answerIds.get(answerIndex); + KumaliveWebsocketServer.getKumaliveService().saveVote(answerId, userId); + learnerDTO.vote = new KumalivePollVoteDTO(kumalive.poll.pollId, answerIndex); + kumalive.poll.voters.get(answerIndex).add(learnerDTO.userDTO); + + if (logger.isDebugEnabled()) { + logger.debug( + "Learner " + userId + " voted in poll " + kumalive.poll.pollId + " 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)); @@ -635,6 +694,7 @@ KumaliveWebsocketServer.getKumaliveService().finishPoll(pollId); KumaliveDTO kumalive = kumalives.get(organisationId); + kumalive.poll.finished = true; kumalive.poll.pollJSON.put("finished", true); if (logger.isDebugEnabled()) { @@ -686,7 +746,7 @@ KumaliveDTO kumalive = kumalives.get(organisationId); KumaliveWebsocketServer.getKumaliveService().finishKumalive(kumalive.id); kumalives.remove(organisationId); - for (KumaliveUser participant : kumalive.learners.values()) { + for (KumaliveUserDTO participant : kumalive.learners.values()) { participant.websocket.getBasicRemote().sendText("{ \"type\" : \"finish\"}"); } @@ -712,16 +772,41 @@ return participantJSON; } - private static void pollToJSON(KumaliveDTO kumaliveDTO, KumalivePoll poll) throws JSONException { - kumaliveDTO.poll.pollJSON.put("id", poll.getPollId()); - kumaliveDTO.poll.pollJSON.put("name", poll.getName()); + private static void fillPollDTO(KumaliveDTO kumalive, KumalivePoll poll) throws JSONException { + KumalivePollDTO pollDTO = new KumalivePollDTO(poll.getPollId()); + pollDTO.pollJSON.put("id", poll.getPollId()); + pollDTO.pollJSON.put("name", poll.getName()); JSONArray answersJSON = new JSONArray(); for (KumalivePollAnswer answer : poll.getAnswers()) { answersJSON.put(answer.getName()); - kumaliveDTO.poll.voters.add(Collections.synchronizedList(new LinkedList())); - kumaliveDTO.poll.votersJSON.put(new JSONArray()); + pollDTO.answerIds.add(answer.getAnswerId()); + + List answerVoters = Collections.synchronizedList(new LinkedList()); + pollDTO.voters.add(answerVoters); + JSONArray answerVotersJSON = new JSONArray(); + pollDTO.votersJSON.put(answerVotersJSON); + if (answer.getVotes() != null) { + for (Entry vote : answer.getVotes().entrySet()) { + Integer userId = vote.getKey(); + UserDTO userDTO = null; + for (KumaliveUserDTO learner : kumalive.learners.values()) { + if (userId.equals(learner.userDTO.getUserID())) { + userDTO = learner.userDTO; + break; + } + } + if (userDTO == null) { + User user = (User) KumaliveWebsocketServer.getUserManagementService().findById(User.class, + userId); + userDTO = user.getUserDTO(); + } + answerVoters.add(userDTO); + answerVotersJSON.put(KumaliveWebsocketServer.participantToJSON(userDTO, null)); + } + } } - kumaliveDTO.poll.pollJSON.put("answers", answersJSON); + pollDTO.pollJSON.put("answers", answersJSON); + kumalive.poll = pollDTO; } private static IKumaliveService getKumaliveService() { Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/model/KumalivePollAnswer.java =================================================================== diff -u -redcf26c6dbd1e4ff36858d876e908774abb7910d -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/model/KumalivePollAnswer.java (.../KumalivePollAnswer.java) (revision edcf26c6dbd1e4ff36858d876e908774abb7910d) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/model/KumalivePollAnswer.java (.../KumalivePollAnswer.java) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -23,6 +23,8 @@ package org.lamsfoundation.lams.learning.kumalive.model; import java.io.Serializable; +import java.util.Date; +import java.util.Map; public class KumalivePollAnswer implements Serializable { @@ -32,6 +34,7 @@ private KumalivePoll poll; private Short orderId; private String name; + private Map votes; public KumalivePollAnswer() { } @@ -73,4 +76,12 @@ public void setName(String name) { this.name = name; } + + public Map getVotes() { + return votes; + } + + public void setVotes(Map votes) { + this.votes = votes; + } } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/IKumaliveService.java =================================================================== diff -u -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/IKumaliveService.java (.../IKumaliveService.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/IKumaliveService.java (.../IKumaliveService.java) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -66,4 +66,6 @@ KumalivePoll startPoll(Long kumaliveId, String name, JSONArray answersJSON) throws JSONException; void finishPoll(Long pollId) throws JSONException; + + void saveVote(Long answerId, Integer userId); } \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/KumaliveService.java =================================================================== diff -u -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/KumaliveService.java (.../KumaliveService.java) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/service/KumaliveService.java (.../KumaliveService.java) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -558,7 +559,7 @@ KumalivePoll poll = new KumalivePoll(kumalive, name); kumaliveDAO.insert(poll); - Set answers = new HashSet<>(); + Set answers = new LinkedHashSet<>(); for (Short answerIndex = 0; answerIndex < answersJSON.length(); answerIndex++) { String answerName = answersJSON.getString(answerIndex.intValue()); KumalivePollAnswer answer = new KumalivePollAnswer(poll, answerIndex, answerName); @@ -575,7 +576,28 @@ return poll; } + /** + * Store a user's vote for a poll + */ @Override + public void saveVote(Long answerId, Integer userId) { + KumalivePollAnswer answer = (KumalivePollAnswer) kumaliveDAO.find(KumalivePollAnswer.class, answerId); + if (answer.getVotes().containsKey(userId)) { + logger.warn("Learner " + userId + " tried to vote for answer ID " + answerId + " but he already voted"); + return; + } + answer.getVotes().put(userId, new Date()); + kumaliveDAO.update(answer); + + if (logger.isDebugEnabled()) { + logger.debug("Learner " + userId + " voted for answer ID " + answerId); + } + } + + /** + * Finishes a poll, i.e. prevents learners from voting + */ + @Override public void finishPoll(Long pollId) throws JSONException { KumalivePoll poll = (KumalivePoll) kumaliveDAO.find(KumalivePoll.class, pollId); if (poll.getFinishDate() != null) { Index: lams_learning/web/includes/javascript/kumalive.js =================================================================== diff -u -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/web/includes/javascript/kumalive.js (.../kumalive.js) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) +++ lams_learning/web/includes/javascript/kumalive.js (.../kumalive.js) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -191,6 +191,7 @@ } else { $('#raiseHandButton').click(raiseHand); $('#downHandButton').click(downHand); + $('#pollRunVoteButton').click(votePoll); } // set dialog name @@ -344,41 +345,24 @@ // 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(); - } + initPoll(poll); + } + if (poll.votes) { + // show votes if user is teacher or votes were released + $.each(poll.votes, function(index, count) { + var answerElement = $('#pollAnswer' + index), + badge = $('.badge', answerElement); + // missing badge means that votes were made available just now + if (badge.length === 0) { + badge = $('').addClass('badge').appendTo(answerElement); } - } 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(); - } + badge.text(count); + }); } + if (poll.voted != null) { + // highlight the answer user voted for + $('#pollAnswer' + poll.voted).addClass('active'); + } } /** @@ -708,6 +692,49 @@ } /** + * Create poll widgets: answer list, radio buttons etc. + */ +function initPoll(poll) { + 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 != null) || poll.finished) { + // build simple list of answers + $.each(poll.answers, function(index, answer){ + var answerElement = $('
  • ').addClass('list-group-item').attr('id', 'pollAnswer' + index) + .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(); + } +} + +/** * Create a poll with parameters set up in form */ function startPoll(){ @@ -794,6 +821,21 @@ } /** + * Send vote to the server + */ +function votePoll() { + var checkedAnswer = $('#pollRunAnswerRadios input[name="pollAnswer"]:checked'); + if (checkedAnswer.length !== 1) { + return; + } + pollId = null; + kumaliveWebsocket.send(JSON.stringify({ + 'type' : 'votePoll', + 'answerIndex' : checkedAnswer.val() + })); +} + +/** * Prevent learners from voting */ function finishPoll() { Index: lams_learning/web/kumalive/kumalive.jsp =================================================================== diff -u -ra6756ff12e2b1eaa3c06a2456c87daea872397f2 -r00c630a99ddb827c772bd0fc0d8d903cc0079f7e --- lams_learning/web/kumalive/kumalive.jsp (.../kumalive.jsp) (revision a6756ff12e2b1eaa3c06a2456c87daea872397f2) +++ lams_learning/web/kumalive/kumalive.jsp (.../kumalive.jsp) (revision 00c630a99ddb827c772bd0fc0d8d903cc0079f7e) @@ -148,7 +148,7 @@

    - +