Commit 30017854 by Jonathan Piacenti

Refactored JS and Python classes for better code reuse.

parent 7fc0f64f
...@@ -35,7 +35,35 @@ class ResourceMixin(object): ...@@ -35,7 +35,35 @@ class ResourceMixin(object):
return frag return frag
class PollBlock(XBlock, ResourceMixin, PublishEventMixin): class PollBase(XBlock, ResourceMixin, PublishEventMixin):
"""
Base class for Poll-like XBlocks.
"""
event_namespace = 'xblock.pollbase'
@XBlock.json_handler
def load_answers(self, data, suffix=''):
return {
'answers': [
{
'key': key, 'text': value['label'], 'img': value['img']
}
for key, value in self.answers
]
}
@XBlock.json_handler
def get_results(self, data, suffix=''):
self.publish_event_from_dict(self.event_namespace + '.view_results', {})
detail, total = self.tally_detail()
return {
'question': markdown(self.question), 'tally': detail,
'total': total, 'feedback': markdown(self.feedback),
'plural': total > 1,
}
class PollBlock(PollBase):
""" """
Poll XBlock. Allows a teacher to poll users, and presents the results so Poll XBlock. Allows a teacher to poll users, and presents the results so
far of the poll to the user when finished. far of the poll to the user when finished.
...@@ -54,16 +82,7 @@ class PollBlock(XBlock, ResourceMixin, PublishEventMixin): ...@@ -54,16 +82,7 @@ class PollBlock(XBlock, ResourceMixin, PublishEventMixin):
scope=Scope.user_state_summary, scope=Scope.user_state_summary,
help="Total tally of answers from students.") help="Total tally of answers from students.")
choice = String(scope=Scope.user_state, help="The student's answer") choice = String(scope=Scope.user_state, help="The student's answer")
event_namespace = 'xblock.poll'
@XBlock.json_handler
def get_results(self, data, suffix=''):
self.publish_event_from_dict('xblock.poll.view_results', {})
detail, total = self.tally_detail()
return {
'question': markdown(self.question), 'tally': detail,
'total': total, 'feedback': markdown(self.feedback),
'plural': total > 1,
}
def clean_tally(self): def clean_tally(self):
""" """
...@@ -173,17 +192,6 @@ class PollBlock(XBlock, ResourceMixin, PublishEventMixin): ...@@ -173,17 +192,6 @@ class PollBlock(XBlock, ResourceMixin, PublishEventMixin):
context, "public/html/poll.html", "public/css/poll.css", context, "public/html/poll.html", "public/css/poll.css",
"public/js/poll.js", "PollBlock") "public/js/poll.js", "PollBlock")
@XBlock.json_handler
def load_answers(self, data, suffix=''):
return {
'answers': [
{
'key': key, 'text': value['label'], 'img': value['img']
}
for key, value in self.answers
]
}
def studio_view(self, context=None): def studio_view(self, context=None):
if not context: if not context:
context = {} context = {}
...@@ -321,7 +329,7 @@ class PollBlock(XBlock, ResourceMixin, PublishEventMixin): ...@@ -321,7 +329,7 @@ class PollBlock(XBlock, ResourceMixin, PublishEventMixin):
] ]
class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin): class SurveyBlock(PollBase):
display_name = String(default='Survey') display_name = String(default='Survey')
answers = List( answers = List(
default=( default=(
...@@ -345,6 +353,7 @@ class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin): ...@@ -345,6 +353,7 @@ class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin):
help="Total tally of answers from students." help="Total tally of answers from students."
) )
choices = Dict(help="The user's answers") choices = Dict(help="The user's answers")
event_namespace = 'xblock.survey'
def student_view(self, context=None): def student_view(self, context=None):
""" """
...@@ -354,10 +363,14 @@ class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin): ...@@ -354,10 +363,14 @@ class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin):
if not context: if not context:
context = {} context = {}
js_template = self.resource_string(
'/public/handlebars/poll_results.handlebars')
context.update({ context.update({
'choices': self.choices, 'choices': self.choices,
# Offset so choices will always be True. # Offset so choices will always be True.
'answers': self.answers, 'answers': self.answers,
'js_template': js_template,
'questions': self.questions, 'questions': self.questions,
# Mustache is treating an empty string as true. # Mustache is treating an empty string as true.
'feedback': markdown(self.feedback) or False, 'feedback': markdown(self.feedback) or False,
...@@ -367,7 +380,7 @@ class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin): ...@@ -367,7 +380,7 @@ class SurveyBlock(XBlock, ResourceMixin, PublishEventMixin):
return self.create_fragment( return self.create_fragment(
context, "public/html/survey.html", "public/css/poll.css", context, "public/html/survey.html", "public/css/poll.css",
"public/js/poll.js", "PollBlock") "public/js/poll.js", "SurveyBlock")
@staticmethod @staticmethod
def workbench_scenarios(): def workbench_scenarios():
......
{{ js_template|safe }}
<div class="survey-block"> <div class="survey-block">
{# If no form is present, the Javascript will load the results instead. #} {# If no form is present, the Javascript will load the results instead. #}
{% if not choices %} {% if not choices %}
......
/* Javascript for PollBlock. */ /* Javascript for PollBlock. */
var PollUtil = { function PollUtil (runtime, element) {
init: function(runtime, element) { this.init = function(runtime, element) {
this.voteUrl = runtime.handlerUrl(element, 'vote'); this.voteUrl = runtime.handlerUrl(element, 'vote');
this.tallyURL = runtime.handlerUrl(element, 'get_results'); this.tallyURL = runtime.handlerUrl(element, 'get_results');
this.element = element; this.element = element;
this.runtime = runtime; this.runtime = runtime;
this.submit = $('input[type=button]', element); this.submit = $('input[type=button]', element);
this.answers = $('input[type=radio]', element);
this.resultsTemplate = Handlebars.compile($("#poll-results-template", element).html()); this.resultsTemplate = Handlebars.compile($("#poll-results-template", element).html());
}, };
poll_init: function(){ this.pollInit = function(){
// If the submit button doesn't exist, the user has already // If the submit button doesn't exist, the user has already
// selected a choice. // selected a choice.
var self = this; var self = this;
var enableSubmit = self.enableSubmit();
var getResults = self.getResults();
if (self.submit.length) { if (self.submit.length) {
var radio = $('input[name=choice]:checked', self.element); var radio = $('input[name=choice]:checked', self.element);
self.submit.click(function (event) { self.submit.click(function () {
// Refresh. // Refresh.
radio = $(radio.selector, element); radio = $(radio.selector, self.element);
var choice = radio.val(); var choice = radio.val();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: self.voteUrl, url: self.voteUrl,
data: JSON.stringify({"choice": choice}), data: JSON.stringify({"choice": choice}),
success: self.getResults success: getResults
}); });
}); });
// If the user has refreshed the page, they may still have an answer // If the user has refreshed the page, they may still have an answer
// selected and the submit button should be enabled. // selected and the submit button should be enabled.
var answers = $('input[type=radio]', self.element); var answers = $('input[type=radio]', self.element);
if (! radio.val()) { if (! radio.val()) {
answers.bind("change.EnableSubmit", self.enableSubmit); answers.bind("change.EnableSubmit", enableSubmit);
} else { } else {
self.enableSubmit(); enableSubmit();
} }
} else { } else {
self.getResults({'success': true}); getResults({'success': true});
} }
}, };
getResults: function(data) { this.surveyInit = function () {
};
this.getResults = function () {
var self = this; var self = this;
if (! data['success']) { return function(data) {
alert(data['errors'].join('\n')); if (!data['success']) {
} alert(data['errors'].join('\n'));
$.ajax({
// Semantically, this would be better as GET, but we can use helper
// functions with POST.
type: "POST",
url: self.tallyURL,
data: JSON.stringify({}),
success: function (data) {
$('div.poll-block', self.element).html(self.resultsTemplate(data));
} }
}) $.ajax({
}, // Semantically, this would be better as GET, but we can use helper
// functions with POST.
type: "POST",
url: self.tallyURL,
data: JSON.stringify({}),
success: function (data) {
console.log(self);
$('div.poll-block', self.element).html(self.resultsTemplate(data));
}
})
}
};
enableSubmit: function () { this.enableSubmit = function () {
this.submit.removeAttr("disabled"); var self = this;
this.answers.unbind("change.EnableSubmit"); return function () {
} self.submit.removeAttr("disabled");
}; self.answers.unbind("change.EnableSubmit");
}
};
this.init(runtime, element);
}
function PollBlock(runtime, element) { function PollBlock(runtime, element) {
PollUtil.init(runtime, element); var util = new PollUtil(runtime, element);
PollUtil.poll_init(); util.pollInit();
}
function SurveyBlock(runtime, element) {
var util = new PollUtil(runtime, element);
util.surveyInit();
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment