Commit 21ab57d0 by Jonathan Piacenti

Several bits of refactoring while preparing for Survey studio view.

parent 9ac33a23
......@@ -57,12 +57,13 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
@XBlock.json_handler
def load_answers(self, data, suffix=''):
return {
'answers': [
'items': [
{
'key': key, 'text': value['label'], 'img': value['img']
'key': key, 'text': value['label'], 'img': value['img'],
'noun': 'answer', 'image': True,
}
for key, value in self.answers
]
],
}
@XBlock.json_handler
......@@ -290,7 +291,7 @@ class PollBlock(PollBase):
if not (img or label):
result['success'] = False
result['errors'].append(
"Answer {0} has no text or img. One is needed.")
"Answer {0} has no text or img. One is needed.".format(answer))
answers.append((key, {'label': label, 'img': img}))
if not len(answers) > 1:
......@@ -338,15 +339,15 @@ class SurveyBlock(PollBase):
display_name = String(default='Survey')
answers = List(
default=(
('Y', {'label': 'Yes', 'img': None}), ('N', {'label': 'No', 'img': None}),
('M', {'label': 'Maybe', 'img': None})),
('Y', 'Yes'), ('N', 'No'),
('M', 'Maybe')),
scope=Scope.settings, help="Answer choices for this Survey"
)
questions = List(
default=(
('enjoy', 'Are you enjoying the course?'),
('recommend', 'Would you recommend this course to your friends?'),
('learn', 'Do you think you will learn a lot?')
('enjoy', {'label': 'Are you enjoying the course?', 'img': None}),
('recommend', {'label': 'Would you recommend this course to your friends?', 'img': None}),
('learn', {'label': 'Do you think you will learn a lot?', 'img': None})
),
scope=Scope.settings, help="Questions for this Survey"
)
......@@ -426,7 +427,7 @@ class SurveyBlock(PollBase):
answer_set = OrderedDict(default_answers)
answer_set.update(source_tally[key])
tally.append({
'text': value,
'text': value['label'],
'answers': [
{
'count': count, 'choice': False,
......@@ -500,12 +501,36 @@ class SurveyBlock(PollBase):
detail, total = self.tally_detail()
return {
'answers': [
value['label'] for value in OrderedDict(self.answers).values()],
value for value in OrderedDict(self.answers).values()],
'tally': detail, 'total': total, 'feedback': markdown(self.feedback),
'plural': total > 1, 'display_name': self.display_name,
}
@XBlock.json_handler
def load_answers(self, data, suffix=''):
return {
'items': [
{
'key': key, 'text': value,
'noun': 'answer', 'image': False,
}
for key, value in self.answers
],
}
@XBlock.json_handler
def load_questions(self, data, suffix=''):
return {
'items': [
{
'key': key, 'text': value['label'], 'img': value['img'],
'noun': 'question', 'image': True,
}
for key, value in self.questions
]
}
@XBlock.json_handler
def vote(self, data, suffix=''):
questions = dict(self.questions)
answers = dict(self.answers)
......
......@@ -37,4 +37,8 @@
.poll-move {
float: right;
}
\ No newline at end of file
}
.poll-setting-label {
text-transform: capitalize;
}
<script id="answer-form-component" type="text/html">
{{#each answers}}
<li class="field comp-setting-entry is-set">
{{#each items}}
<li class="field comp-setting-entry is-set poll-{{noun}}-studio-item">
<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" /><br />
<label class="label setting-label" for="img-answer-{{key}}">Image URL</label>
<input class="input setting-input" name="img-answer-{{key}}" id="img-answer-{{key}}" value="{{img}}" type="text" />
<label class="label setting-label poll-setting-label" for="{{noun}}-{{key}}">{{noun}}</label>
<input class="input setting-input" name="{{noun}}-{{key}}" id="{{noun}}-{{key}}" value="{{text}}" type="text" /><br />
{{#if image}}
<label class="label setting-label" for="img-{{noun}}-{{key}}">Image URL</label>
<input class="input setting-input" name="img-{{noun}}-{{key}}" id="img-{{noun}}-{{key}}" value="{{img}}" type="text" />
{{/if}}
<div class="poll-move">
<div class="poll-move-up">&#9650;</div>
<div class="poll-move-down">&#9660;</div>
</div>
</div>
<span class="tip setting-help">
Enter an answer for the user to select. An answer must have an image URL or text, and can have both.
{{#if image}}This must have an image URL or text, and can have both.{{/if}}
</span>
<a href="#" class="button action-button poll-delete-answer" onclick="return false;">Delete</a>
</li>
......
......@@ -30,6 +30,8 @@
may vote again, but will not lose course progress.
</p>
</li>
<li id="poll-answer-marker"></li>
<li id="poll-answer-end-marker">
</ul>
<div class="xblock-actions">
<ul>
......
......@@ -8,15 +8,15 @@
<thead>
<tr>
<th></th>
{% for answer, details in answers %}
<th class="survey-answer">{{details.label}}</th>
{% for answer, label in answers %}
<th class="survey-answer">{{label}}</th>
{% endfor %}
</tr>
</thead>
{% for key, question in questions %}
<tr class="survey-row">
<td class="survey-question">
{{question}}
{{question.label}}
</td>
{% for answer, answer_details in answers %}
<td class="survey-option">
......
......@@ -9,7 +9,7 @@ function PollUtil (runtime, element, pollType) {
this.tallyURL = runtime.handlerUrl(element, 'get_results');
this.submit = $('input[type=button]', element);
this.answers = $('input[type=radio]', element);
this.resultsTemplate = Handlebars.compile($("#" + self.pollType + "-results-template", element).html());
this.resultsTemplate = Handlebars.compile($("#" + pollType + "-results-template", element).html());
// If the submit button doesn't exist, the user has already
// selected a choice. Render results instead of initializing machinery.
if (! self.submit.length) {
......@@ -114,7 +114,6 @@ function PollUtil (runtime, element, pollType) {
self.answers.unbind("change.enableSubmit");
};
this.pollType = pollType;
var run_init = this.init();
if (run_init) {
var init_map = {'poll': self.pollInit, 'survey': self.surveyInit};
......
function PollEditUtil(runtime, element) {
function PollEditUtil(runtime, element, pollType) {
var self = this;
this.init = function () {
// Set up the editing form for a Poll or Survey.
self.loadAnswers = runtime.handlerUrl(element, 'load_answers');
var temp = $('#answer-form-component', element).html();
self.answerTemplate = Handlebars.compile(temp);
self.pollLineItems =$('#poll-line-items', element);
$(element).find('.cancel-button', element).bind('click', function() {
runtime.notify('cancel', {});
});
$('#poll-add-answer', element).click(function () {
// The degree of precision on date should be precise enough to avoid
// collisions in the real world.
self.pollLineItems.append(self.answerTemplate({'answers': [{'key': new Date().getTime(), 'text': ''}]}));
var new_answer = $(self.pollLineItems.children().last());
self.empowerDeletes(new_answer);
self.empowerArrows(new_answer);
new_answer.fadeOut(250).fadeIn(250);
});
var mapping = self.mappings[pollType]['buttons'];
for (var key in mapping) {
if (mapping.hasOwnProperty(key)) {
$(key, element).click(
// The nature of the closure forces us to make a custom function here.
function (context_key, topMarker, bottomMarker) {
return function () {
// The degree of precision on date should be precise enough to avoid
// collisions in the real world.
var bottom = $(bottomMarker);
$(self.answerTemplate(mapping[context_key]['itemList'])).before(bottom);
var new_item = bottom.prev();
self.empowerDeletes(new_item);
self.empowerArrows(
new_item, mapping[context_key]['topMarker'],
mapping[context_key]['bottomMarker']
);
new_item.fadeOut(250).fadeIn(250);
}
}(key, self.mappings[pollType])
)
}
}
$(element).find('.save-button', element).bind('click', self.pollSubmitHandler);
......@@ -33,23 +47,62 @@ function PollEditUtil(runtime, element) {
});
};
this.extend = function (obj1, obj2) {
// Mimics similar extend functions, making obj1 contain obj2's properties.
for (var attrname in obj2) {
if (obj2.hasOwnProperty(attrname)) {
obj1[attrname] = obj2[attrname]
}
}
return obj1;
};
this.makeNew = function(extra){
// Make a new empty line item, like a question or an answer.
// 'extra' should contain 'image', a boolean value that determines whether
// an image path field should be provided, and 'noun', which should be either
// 'question' or 'answer' depending on what is needed.
return self.extend({'key': new Date().getTime(), 'text': '', 'img': ''}, extra)
};
// This object is used to swap out values which differ between Survey and Poll blocks.
this.mappings = {
'poll': {
'buttons': {
'#poll-add-answer': {
'itemList': {'items': [self.makeNew({'image': true, 'noun': 'answer'})]},
'topMarker': '#poll-answer-marker', 'bottomMarker': '#poll-answer-end-marker'
}
},
'onLoad': {
}
},
'survey': {
'buttons': {
'#poll-add-answer': {
'itemList': {'items': [self.makeNew({'image': false, 'noun': 'answer'})]},
'topMarker': '#poll-answer-marker', 'bottomMarker': '#poll-answer-end-marker'
},
'#poll-add-question': {
'itemList': {'items': [self.makeNew({'image': true, 'noun': 'question'})]}
}
}
}
};
this.empowerDeletes = function (scope) {
// Activates the delete buttons on rendered line items.
$('.poll-delete-answer', scope).click(function () {
$(this).parent().remove();
});
};
this.empowerArrows = function(scope) {
/*
The poll answers need to be reorderable. As the UL they are in is not
easily isolated, we need to start checking their position to make
sure they aren't ordered above the other settings, which are also
in the list.
*/
var starting_point = 3;
this.empowerArrows = function(scope, topMarker, bottomMarker) {
// Activates the arrows on rendered line items.
$('.poll-move-up', scope).click(function () {
var tag = $(this).parents('li');
if (tag.index() <= starting_point){
if (tag.index() <= ($(topMarker).index() + 1)){
return;
}
tag.prev().before(tag);
......@@ -57,7 +110,7 @@ function PollEditUtil(runtime, element) {
});
$('.poll-move-down', scope).click(function () {
var tag = $(this).parents('li');
if ((tag.index() >= (tag.parent().children().length - 1))) {
if ((tag.index() >= ($(bottomMarker).index() - 1))) {
return;
}
tag.next().after(tag);
......@@ -65,13 +118,23 @@ function PollEditUtil(runtime, element) {
});
};
this.displayAnswers = function(data) {
self.pollLineItems.append(self.answerTemplate(data));
self.empowerDeletes(element);
self.empowerArrows(element);
this.displayAnswers = function (data){
self.displayItems(data, '#poll-answer-marker', '#poll-answer-end-marker')
};
this.displayItems = function(data, topMarker, bottomMarker) {
// Loads the initial set of items that the block needs to edit.
$('#poll-answer-end-marker').before(self.answerTemplate(data));
self.empowerDeletes(element, topMarker, bottomMarker);
self.empowerArrows(element, topMarker, bottomMarker);
};
this.check_return = function(data) {
// Handle the return value JSON from the server.
// It would be better if we could have a different function
// for errors, as AJAX calls normally allow, but our version of XBlock
// does not support status codes other than 200 for JSON encoded
// responses.
if (data['success']) {
window.location.reload(false);
return;
......@@ -80,10 +143,12 @@ function PollEditUtil(runtime, element) {
};
this.pollSubmitHandler = function() {
// Take all of the fields, serialize them, and pass them to the
// server for saving.
var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
var data = {'answers': []};
var tracker = [];
$('#poll-form input', element).each(function(i) {
$('#poll-form input', element).each(function() {
var key = 'label';
if (this.name.indexOf('answer-') >= 0){
var name = this.name.replace('answer-', '');
......@@ -117,9 +182,9 @@ function PollEditUtil(runtime, element) {
}
function PollEdit(runtime, element) {
new PollEditUtil(runtime, element);
new PollEditUtil(runtime, element, 'poll');
}
function SurveyEdit(runtime, element) {
new PollEditUtil(runtime, element);
new PollEditUtil(runtime, element, 'survey');
}
<vertical_demo>
<survey tally='{"q1": {"sa": 5, "a": 5, "n": 3, "d": 2, "sd": 5}, "q2": {"sa": 3, "a": 2, "n": 3, "d": 10, "sd": 2}, "q3": {"sa": 2, "a": 7, "n": 1, "d": 4, "sd": 6}, "q4": {"sa": 1, "a": 2, "n": 8, "d": 4, "sd": 5}}'
questions='[["q1", "I feel like this test will pass."], ["q2", "I like testing software"], ["q3", "Testing is not necessary"], ["q4", "I would fake a test result to get software deployed."]]'
answers='[["sa", {"label": "Strongly Agree"}], ["a", {"label": "Agree"}], ["n", {"label": "Neutral"}], ["d", {"label": "Disagree"}], ["sd", {"label": "Strongly Disagree"}]]'
questions='[["q1", {"label": "I feel like this test will pass.", "img": null}], ["q2", {"label": "I like testing software", "img": null}], ["q3", {"label": "Testing is not necessary", "img": null}], ["q4", {"label": "I would fake a test result to get software deployed.", "img": null}]]'
answers='[["sa", "Strongly Agree"], ["a", "Agree"], ["n", "Neutral"], ["d", "Disagree"], ["sd", "Strongly Disagree"]]'
feedback="### Thank you&#10;&#10;for running the tests."/>
</vertical_demo>
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