Commit f56c62d4 by Jonathan Piacenti

CMS editing implemented but not working quite right yet.

parent 70d645fd
"""TO-DO: Write a description of what this XBlock is.""" """TO-DO: Write a description of what this XBlock is."""
from collections import OrderedDict
from django.template import Template, Context from django.template import Template, Context
import pkg_resources import pkg_resources
...@@ -15,16 +16,18 @@ class PollBlock(XBlock): ...@@ -15,16 +16,18 @@ class PollBlock(XBlock):
""" """
question = String(default='What is your favorite color?') question = String(default='What is your favorite color?')
answers = List( answers = List(
default=['Red', 'Blue', 'Green', 'Other'], default=(('Red', 'Red'), ('Blue', 'Blue'), ('Green', 'Green'),
('Other', 'Other')),
scope=Scope.settings, help="The questions on this poll." scope=Scope.settings, help="The questions on this poll."
) )
tally = Dict(default={1: 0, 2: 0, 3: 0, 4: 0}, scope=Scope.user_state_summary, tally = Dict(default={'Red': 0, 'Blue': 0, 'Green': 0, 'Other': 0},
scope=Scope.user_state_summary,
help="Total tally of answers from students.") help="Total tally of answers from students.")
# No default. Hopefully this will yield 'None', or do something # No default. Hopefully this will yield 'None', or do something
# distinctive when queried. # distinctive when queried.
# Choices are always one above their place in the index so that choice # Choices are always one above their place in the index so that choice
# is never false if it's provided. # is never false if it's provided.
choice = Integer(scope=Scope.user_state, help="The student's answer") choice = String(scope=Scope.user_state, help="The student's answer")
def resource_string(self, path): def resource_string(self, path):
"""Handy helper for getting resources from our kit.""" """Handy helper for getting resources from our kit."""
...@@ -39,15 +42,17 @@ class PollBlock(XBlock): ...@@ -39,15 +42,17 @@ class PollBlock(XBlock):
""" """
Tally all results. Tally all results.
""" """
# TODO: Cache this. tally = []
tally = [{'count': 0, 'answer': answer, 'top': False} answers = OrderedDict(self.answers)
for answer in self.answers
]
total = 0 total = 0
for key, value in self.tally.items(): for key, value in answers.items():
key, value = int(key), int(value) tally.append({
tally[key - 1]['count'] = value 'count': int(self.tally.get(key, 0)),
total += value 'answer': value,
'key': key,
'top': False
})
total += tally[-1]['count']
for answer in tally: for answer in tally:
try: try:
...@@ -71,15 +76,15 @@ class PollBlock(XBlock): ...@@ -71,15 +76,15 @@ class PollBlock(XBlock):
when viewing courses. when viewing courses.
""" """
if not context: if not context:
context = Context() context = {}
js_template = self.resource_string( js_template = self.resource_string(
'/static/handlebars/results.handlebars') '/public/handlebars/results.handlebars')
context.update({ context.update({
'choice': self.choice, 'choice': self.choice,
# Offset so choices will always be True. # Offset so choices will always be True.
'answers': zip(range(1, len(self.answers) + 1), self.answers), 'answers': self.answers,
'question': self.question, 'question': self.question,
'js_template': js_template, 'js_template': js_template,
}) })
...@@ -87,23 +92,91 @@ class PollBlock(XBlock): ...@@ -87,23 +92,91 @@ class PollBlock(XBlock):
if self.choice: if self.choice:
context.update({'tally': self.tally_detail()}) context.update({'tally': self.tally_detail()})
html = self.resource_string("static/html/poll.html") context = Context(context)
html = self.resource_string("public/html/poll.html")
html = Template(html).render(context) html = Template(html).render(context)
frag = Fragment(html) frag = Fragment(html)
frag.add_css(self.resource_string("static/css/poll.css")) frag.add_css(self.resource_string("public/css/poll.css"))
frag.add_javascript(self.resource_string("static/js/vendor/handlebars.js")) frag.add_javascript_url(
frag.add_javascript(self.resource_string("static/js/src/poll.js")) self.runtime.local_resource_url(
self, 'public/js/vendor/handlebars.js'))
frag.add_javascript(self.resource_string("public/js/poll.js"))
frag.initialize_js('PollBlock') frag.initialize_js('PollBlock')
return frag return frag
# TO-DO: change this handler to perform your own actions. You may need more @XBlock.json_handler
# than one handler, or you may not need any handlers at all. def load_answers(self, data, suffix=''):
return {'answers': [{'key': key, 'text': answer}
for key, answer in self.answers
]}
def studio_view(self, context=None):
if not context:
context = {}
js_template = self.resource_string('/public/handlebars/studio.handlebars')
context.update({
'question': self.question,
'js_template': js_template
})
context = Context(context)
html = self.resource_string("public/html/poll_edit.html")
html = Template(html).render(context)
frag = Fragment(html)
frag.add_javascript_url(
self.runtime.local_resource_url(
self, 'public/js/vendor/handlebars.js'))
frag.add_css(self.resource_string('/public/css/poll_edit.css'))
frag.add_javascript(self.resource_string("public/js/poll_edit.js"))
frag.initialize_js('PollEditBlock')
return frag
@XBlock.json_handler
def studio_submit(self, data, suffix=''):
# I wonder if there's something for live validation feedback already.
result = {'success': True, 'errors': {'fields': {}, 'general': []}}
if 'question' not in data or not data['question']:
result['errors']['question'] = "This field is required."
result['success'] = False
# Aggressively clean/sanity check answers list.
answers = OrderedDict(
(key.replace('answer-', '', 1), value.strip()[:250])
for key, value in data.items()
if (key.startswith('answer-') and not key == 'answer-')
and not value.isspace()
)
if not len(answers) > 1:
result['errors']['general'].append(
"You must include at least two answers.")
result['success'] = False
if not result['success']:
return result
self.answers = answers.items()
self.question = data['question']
tally = self.tally
# Update tracking schema.
for key, value in answers.items():
if key not in tally:
tally[key] = 0
for key, value in tally.items():
if key not in answers:
del tally[key]
return result
@XBlock.json_handler @XBlock.json_handler
def vote(self, data, suffix=''): def vote(self, data, suffix=''):
""" """
An example handler, which increments the data. An example handler, which increments the data.
""" """
result = {'result': 'error'} result = {'success': False}
if self.choice is not None: if self.choice is not None:
result['message'] = 'You cannot vote twice.' result['message'] = 'You cannot vote twice.'
return result return result
...@@ -114,15 +187,14 @@ class PollBlock(XBlock): ...@@ -114,15 +187,14 @@ class PollBlock(XBlock):
return result return result
# Just to show data coming in... # Just to show data coming in...
try: try:
choice = int(choice) OrderedDict(self.answers)[choice]
self.answers[choice] except KeyError:
except (IndexError, ValueError): result['message'] = 'No key "{choice}" in answers table.'.format(choice=choice)
result['message'] = 'No index "{choice}" in answers list.'.format(choice=choice)
return result return result
self.choice = choice self.choice = choice
self.tally[choice - 1] += 1 self.tally[choice] += 1
result['result'] = 'success' result['success'] = True
return result return result
......
/* CSS for PollBlock */
.poll-delete-answer {
float: right;
}
\ No newline at end of file
<script id="answer-form-component" type="text/html">
{{#each answers}}
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="answer-{{key}}">Answer</label>
<input class="input setting-input" name="answer-{{key}}" id="answer-{{key}}" value="{{text}}" type="text" />
</div>
<span class="tip setting-help">Enter an answer for the user to select.</span>
<a href="#" class="button action-button poll-delete-answer" onclick="return false;">Delete</a>
</li>
{{/each}}
</script>
\ No newline at end of file
...@@ -5,14 +5,14 @@ ...@@ -5,14 +5,14 @@
<form> <form>
<h2>{{ question }}</h2> <h2>{{ question }}</h2>
<ul> <ul>
{% for number, answer in answers %} {% for key, answer in answers %}
<li class="poll-answer"> <li class="poll-answer">
<label for="answer-{{number}}">{{answer}}</label> <label for="answer-{{number}}">{{answer}}</label>
<input type="radio" name="choice" id="answer-{{number}}" value="{{number}}"> <input type="radio" name="choice" id="answer-{{number}}" value="{{key}}">
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<input type="submit" name="poll-submit" onclick="return false;"> <input type="submit" name="poll-submit" onclick="return false;" disabled>
</form> </form>
{% endif %} {% endif %}
</div> </div>
\ No newline at end of file
{{js_template|safe}}
<div class="wrapper-comp-settings is-active editor-with-buttons" id="settings-tab">
<form id="poll-form">
<ul class="list-input settings-list" id="poll-line-items">
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="question">Question</label>
<input class="input setting-input" name="question" id="question" value="{{question}}" type="text" />
</div>
<span class="tip setting-help">Example: 'What is your favorite color?'</span>
</li>
</ul>
<div class="xblock-actions">
<ul>
<li class="action-item" id="poll-add-question">
<a href="#" class="button action-button" onclick="return false;">Add Question</a>
</li>
<li class="action-item">
<input type="submit" class="button action-primary save-button" value="Save" onclick="return false;" />
</li>
<li class="action-item">
<a href="#" class="button cancel-button">Cancel</a>
</li>
</ul>
</div>
</form>
</div>
\ No newline at end of file
...@@ -5,10 +5,8 @@ function PollBlock(runtime, element) { ...@@ -5,10 +5,8 @@ function PollBlock(runtime, element) {
var tallyURL = runtime.handlerUrl(element, 'get_results'); var tallyURL = runtime.handlerUrl(element, 'get_results');
var submit = $('input[type=submit]', element); var submit = $('input[type=submit]', element);
var resultsTemplate = Handlebars.compile($("#results", element).html()); var resultsTemplate = Handlebars.compile($("#results", element).html());
function getResults(data) {
function getResults() {
$.ajax({ $.ajax({
// Semantically, this would be better as GET, but I can use helper // Semantically, this would be better as GET, but I can use helper
// functions with POST. // functions with POST.
...@@ -16,9 +14,6 @@ function PollBlock(runtime, element) { ...@@ -16,9 +14,6 @@ function PollBlock(runtime, element) {
url: tallyURL, url: tallyURL,
data: JSON.stringify({}), data: JSON.stringify({}),
success: function (data) { success: function (data) {
result = resultsTemplate(data);
console.log("Running...");
console.log(result);
element.innerHTML = resultsTemplate(data); element.innerHTML = resultsTemplate(data);
} }
}) })
...@@ -27,6 +22,8 @@ function PollBlock(runtime, element) { ...@@ -27,6 +22,8 @@ function PollBlock(runtime, element) {
if (submit.length) { if (submit.length) {
var radios = $('input[name=choice]:checked', element); var radios = $('input[name=choice]:checked', element);
submit.click(function (event) { submit.click(function (event) {
// Refresh.
radios = $(radios.selector);
var choice = radios.val(); var choice = radios.val();
console.log(choice); console.log(choice);
$.ajax({ $.ajax({
......
/* Javascript for ReferenceBlock. */
function PollEditBlock(runtime, element) {
var loadAnswers = runtime.handlerUrl(element, 'load_answers');
var temp = $('#answer-form-component', element).html();
var answerTemplate = Handlebars.compile(temp);
var pollLineItems =$('#poll-line-items', element);
// We probably don't need something this complex, but UUIDs are the
// standard.
function generateUUID(){
var d = new Date().getTime();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x7|0x8)).toString(16);
})
}
function empowerDeletes() {
$('.poll-delete-answer', element).click(function () {
$(this).parent().remove();
});
}
function displayAnswers(data) {
pollLineItems.append(answerTemplate(data));
empowerDeletes();
}
$('#poll-add-question', element).click(function () {
pollLineItems.append(answerTemplate({'answers': [{'key': generateUUID(), 'text': ''}]}));
empowerDeletes();
pollLineItems.last().scrollTop();
});
$(element).find('.cancel-button').bind('click', function() {
runtime.notify('cancel', {});
});
$(element).find('.save-button').bind('click', function() {
var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
var data = {};
$('#poll-form input', element).each(function() {
data[this.name] = this.value;
});
$.ajax({
type: "POST",
url: handlerUrl,
data: JSON.stringify(data),
success: function () {}
});
});
$(function ($) {
$.ajax({
type: "POST",
url: loadAnswers,
data: JSON.stringify({}),
success: displayAnswers
});
});
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
/* CSS for PollBlock */
.poll_block .count {
font-weight: bold;
}
.poll_block p {
cursor: pointer;
}
\ No newline at end of file
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