Commit 2d828eed by Piotr Mitros

Starting to revamp UX as per Francis' feedback. Breaking backwards-compatibility

parent 472e6dbc
icons:
nosetests test_icons.py --with-save-baseline
mv screenshots/baseline/face* ../rate/public/default_icons/
mv screenshots/baseline/*png ../rate/public/default_icons/
rm *.log
rm *.pyc
rm *~
rm *test_page*png
......@@ -6,7 +6,7 @@ class IconsPage(PageObject):
GitHub's search page
"""
url = 'https://rawgit.com/pmitros/RateXBlock/master/makeicons/raw_icons.html'
url = 'https://rawgit.com/pmitros/RateXBlock/pmitros/ux-revamp/makeicons/raw_icons.html'
def is_browser_on_page(self):
return self.q(css='#face5').is_present()
return self.q(css='#snum5').is_present()
......@@ -4,19 +4,101 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<style>
.face {
width: 60px;
.icon {
width: 57px;
text-align: center;
display:inline;
display: inline-block;
}
.face {
border-radius: 15px;
}
.num {
border-radius: 30px;
border-style:solid;
width: 45px;
height: 42px;
padding-top:3px
}
.face {
border-radius: 15px;
//border-style:solid;
//border-color: rgba(255,255,255,0);
}
.inactive {
background-color: rgb(255,255,255);
color: rgb(6,86,131);
}
.selected {
background-color: rgb(166, 207, 230);
color: rgb(80,41,59);
}
.active {
color: rgb(255,255,255);
background-color: rgb(0, 121, 188);
}
.inactive .num {
background-color: rgb(255,255,255);
color: rgb(6,86,131);
border-radius: 30px;
border-style: solid;
}
.active .num {
color: rgb(255,255,255);
background-color: rgb(0, 121, 188);
border-radius: 30px;
border-color: rgba(255,255,255,0);
}
</style>
</head>
<bod>
<body>
<font size="7">
<div id="face1" class="face">😁</div>
<div id="face2" class="face">😊</div>
<div id="face3" class="face">😐</div>
<div id="face4" class="face">😞</div>
<div id="face5" class="face">😭</div>
<div id="iface1" class="icon inactive face">😁</div>
<div id="iface2" class="icon inactive face">😊</div>
<div id="iface3" class="icon inactive face">😐</div>
<div id="iface4" class="icon inactive face">😞</div>
<div id="iface5" class="icon inactive face">😭</div>
<br>
<div id="aface1" class="icon active face">😁</div>
<div id="aface2" class="icon active face">😊</div>
<div id="aface3" class="icon active face">😐</div>
<div id="aface4" class="icon active face">😞</div>
<div id="aface5" class="icon active face">😭</div>
<br>
<div id="sface1" class="icon selected face">😁</div>
<div id="sface2" class="icon selected face">😊</div>
<div id="sface3" class="icon selected face">😐</div>
<div id="sface4" class="icon selected face">😞</div>
<div id="sface5" class="icon selected face">😭</div>
<br>
</font>
<font size="6">
<br>
<div id="inum1" class="icon inactive num">1</div>
<div id="inum2" class="icon inactive num">2</div>
<div id="inum3" class="icon inactive num">3</div>
<div id="inum4" class="icon inactive num">4</div>
<div id="inum5" class="icon inactive num">5</div>
<br>
<div id="anum1" class="icon active num">1</div>
<div id="anum2" class="icon active num">2</div>
<div id="anum3" class="icon active num">3</div>
<div id="anum4" class="icon active num">4</div>
<div id="anum5" class="icon active num">5</div>
<br>
<div id="snum1" class="icon selected num">1</div>
<div id="snum2" class="icon selected num">2</div>
<div id="snum3" class="icon selected num">3</div>
<div id="snum4" class="icon selected num">4</div>
<div id="snum5" class="icon selected num">5</div>
</font>
</body>
......@@ -23,11 +23,10 @@ class TestIcons(WebAppTest):
"""
page = IconsPage(self.browser)
page.visit()
self.assertScreenshot("#face1", "face1")
self.assertScreenshot("#face2", "face2")
self.assertScreenshot("#face3", "face3")
self.assertScreenshot("#face4", "face4")
self.assertScreenshot("#face5", "face5")
for i in range(5):
for icon in ["face", "num"]:
for style in "ais":
self.assertScreenshot("#"+style+icon+str(i+1), style+icon+str(i+1))
if __name__ == '__main__':
unittest.main()
......
# coding: utf-8
"""
This is an XBlock designed to allow people to provide feedback on our
course resources.
course resources, and to think and synthesize about their experience
in the course.
"""
import random
......@@ -12,6 +13,22 @@ from xblock.core import XBlock
from xblock.fields import Scope, Integer, String, List, Float
from xblock.fragment import Fragment
"""
We provide default text which is designed to elicit student thought. We'd
like instructors to customize this to something highly structured (not
"What did you think?" and "How did you like it?".
We
"""
default_freeform = "What did you learn from this? What was missing?"
default_likert = "How would you rate this as a learning experience?"
default_default = "Think about the material, and try to synthesize key " \
"lessons learned, as well as key gaps in our presentation."
default_placeholder = "Take a little bit of time to reflect here. " \
"Research shows that a meaningful synthesis will help " \
"you better understand and remember material from this" \
"course."
default_icon = "face"
@XBlock.needs('i18n')
class RateXBlock(XBlock):
......@@ -27,8 +44,9 @@ class RateXBlock(XBlock):
# will default to the ones in default_prompt.
prompts = List(
default=[
{'freeform': "Please provide us feedback on this section",
'likert': "Please rate your overall experience with this section"}
{'freeform': default_freeform,
'default_text': default_default,
'likert': default_likert}
],
scope=Scope.settings,
help="Freeform user prompt",
......@@ -85,10 +103,12 @@ class RateXBlock(XBlock):
_ = self.runtime.service(self, 'i18n').ugettext
prompt = {
'freeform': _("Please provide us feedback on this section."),
'likert': _("Please rate your overall experience "
"with this section."),
'mouseovers': [_("Excellent"),
'freeform': _("Please reflect on this course material"),
'default_text': _("Please take time to meaningfully reflect "
"on your experience with this course "
"material."),
'likert': _("Please rate your overall experience"),
'scale_text': [_("Excellent"),
_("Good"),
_("Average"),
_("Fair"),
......@@ -112,25 +132,73 @@ class RateXBlock(XBlock):
self.prompt_choice = random.randint(0, len(self.prompts) - 1)
prompt = self.get_prompt()
# Now, we render the RateXBlock. This may be redundant, since we
# don't always show it.
# Now, we render the RateXBlock.
html = self.resource_string("static/html/rate.html")
# The replace allows us to format the HTML nicely without getting
# extra whitespace
# Staff see vote totals, so we have slightly different HTML here.
if self.vote_aggregate and self.is_staff():
scale_item = self.resource_string("static/html/staff_item.html")
else:
scale_item = self.resource_string("static/html/scale_item.html")
# The replace allows us to format the HTML nicely without getting
# extra whitespace
scale_item = scale_item.replace('\n', '')
# We have five Likert fields right now, but we'd like this to
# be dynamic
indexes = range(len(prompt['icons']))
# If the user voted before, we'd like to show that
active_vote = ["checked" if i == self.user_vote else ""
for i in indexes]
# Confirm that we do have vote totals (this may be uninitialized
# otherwise). This should probably go into __init__ or similar.
self.init_vote_aggregate()
votes = self.vote_aggregate
# We grab the icons. This should move to a Filesystem field so
# instructors can upload new ones
ina_templ = 'public/default_icons/iface{i}.png'
act_templ = 'public/default_icons/aface{i}.png'
sel_templ = 'public/default_icons/sface{i}.png'
ina_urls = [self.runtime.local_resource_url(self, ina_templ.format(i=i))
for i in range(1,6)]
act_urls = [self.runtime.local_resource_url(self, act_templ.format(i=i))
for i in range(1,6)]
sel_urls = [self.runtime.local_resource_url(self, sel_templ.format(i=i))
for i in range(1,6)]
img_urls = [i if active else a
for (i, active, a)
in zip(ina_urls, active_vote, act_urls)]
# Render the
scale = u"".join(
scale_item.format(level=l, icon=icon, i=i, active=a, votes=v) for
(l, icon, i, a, v) in
zip(prompt['mouseovers'], prompt['icons'], indexes, active_vote, votes)
scale_item.format(scale_text=scale_text,
unicode_icon=unicode_icon,
idx=idx,
active=active,
vote_cnt=vote_cnt,
ina_icon=ina_icon,
act_icon=act_icon,
sel_icon=sel_icon) for
(scale_text,
unicode_icon,
idx,
active,
vote_cnt,
act_icon,
ina_icon,
sel_icon) in
zip(prompt['scale_text'],
prompt['icons'],
indexes,
active_vote,
votes,
act_urls,
ina_urls,
sel_urls
)
)
if self.user_vote != -1:
_ = self.runtime.service(self, 'i18n').ugettext
......@@ -187,7 +255,7 @@ class RateXBlock(XBlock):
# Make sure we're initialized
print self.get_prompt()
if not self.vote_aggregate:
self.vote_aggregate = [0] * (len(self.get_prompt()['mouseovers']))
self.vote_aggregate = [0] * (len(self.get_prompt()['scale_text']))
def vote(self, data):
"""
......@@ -226,14 +294,6 @@ class RateXBlock(XBlock):
self.runtime.publish(self,
'edx.ratexblock.nothing_provided',
{})
if 'freeform' in data:
response = {"success": True,
"response": _("Thank you for your feedback!")}
self.runtime.publish(self,
'edx.ratexblock.freeform_provided',
{'old_freeform': self.user_freeform,
'new_freeform': data['freeform']})
self.user_freeform = data['freeform']
if 'vote' in data:
response = {"success": True,
"response": _("Thank you for voting!")}
......@@ -242,6 +302,14 @@ class RateXBlock(XBlock):
{'old_vote': self.user_vote,
'new_vote': data['vote']})
self.vote(data)
if 'freeform' in data:
response = {"success": True,
"response": _("Thank you for your feedback!")}
self.runtime.publish(self,
'edx.ratexblock.freeform_provided',
{'old_freeform': self.user_freeform,
'new_freeform': data['freeform']})
self.user_freeform = data['freeform']
response.update({
"freeform": self.user_freeform,
......@@ -261,9 +329,9 @@ class RateXBlock(XBlock):
return [
("RateXBlock",
"""<vertical_demo>
<rate p="50"/>
<rate p="50"/>
<rate p="50"/>
<rate p="100"/>
<!--rate p="50"/>
<rate p="50"/-->
</vertical_demo>
"""),
]
......
/* CSS for RateXBlock */
/* Overall block. We limit width, and put a very faint
border around it. */
.rate_block {
text-align: center;
max-width: 400px;
max-width: 450px;
border-width: 1px;
border-style: solid;
border-color: rgba(0,0,0,0.1);
padding: 10px
}
/* Little thank you message div after people vote */
.rate_thank_you {
color: green;
}
.rate_block .rate_header {
font-weight: bold;
font-size: large;
/* Label for the freeform text input. We want a little
space between this and the Likert input.*/
.rate_block .rate_header_div {
margin-top:1em;
}
/* Fieldset for the Likert radio buttons */
.rate_block .rate_likert_field {
padding: 0px;
margin: 0px;
}
/* The div around everything with a radio button */
.rate_block .rate_likert_rating {
cursor: pointer;
border-radius:5px;
display:inline-block;
text-align:center;
padding:0px 10px 0px 10px;
}
/* Hide checked icon */
.rate_icon_active { display:none; }
.rate_icon_inactive { display:inline-block; }
/* But show it if we are checked... */
.rate_block input[type="radio"]:checked ~ .rate_icon_active{
display: inline-block;
}
/* ... while hiding the unchecked icon */
.rate_block input[type="radio"]:checked ~ .rate_icon_inactive{
display: none;
}
.rate_icon {
border-style:solid;
border-width: 1px;
border-color: rgba(255,255,255,0);
padding:1px;
height:60px;
}
.rate_block input[type="radio"]:focus ~ .rate_icon {
border-color: #999999;
}
.rate_block .rate_likert_header {
.rate_block input[type="radio"]:hover ~ .rate_icon {
border-color: #999999;
}
.rate_block .rate_likert_label {
cursor: pointer;
}
.rate_block .rate_freeform_input {
......@@ -53,8 +95,8 @@
opacity:0;
width:1px;
height:1px;
padding:0;
margin:0;
padding:0px;
margin:0px;
position:absolute;
clip:rect(1px,1px,1px,1px);
left:-10000px;
......@@ -68,18 +110,6 @@
.rate_block .rate_likert_field {
border-style:none;
}
.rate_block input[type="radio"]:checked + span{
background-color:yellow;
font-weight:bold;
}
.rate_block input[type="radio"]:focus + span{
border-style:solid;
border-width: 1px;
border-color: #999999;
}
.rate_block .rate_submit_feedback {
width:100%;
}
<div class="rate_block">
<form action="" onsubmit="return false;">
<label class="rate_header" for="rate_freeform_textarea">{freeform_prompt}</label>
<div class="rate_freeform_input">
<textarea id="rate_freeform_textarea" class="rate_freeform_area" rows="4" cols="30">{self.user_freeform}</textarea>
<form action="" onsubmit="return false;">
<fieldset class="rate_likert_field">
<legend class="rate_likert_header">{likert_prompt}</legend>
<div class="rate_likert_scale">{scale}</div>
</fieldset>
<div class="rate_header_div">
<label class="rate_header" for="rate_freeform_textarea">{freeform_prompt}</label>
</div>
<div class="rate_freeform_input">
<textarea id="rate_freeform_textarea" class="rate_freeform_area" rows="6" cols="50">{self.user_freeform}</textarea>
<div class="rate_thank_you" aria-live=polite>{response}</div>
</div>
<fieldset class="rate_likert_field">
<legend class="rate_likert_header">{likert_prompt}</legend>
<div class="rate_likert_scale">{scale}</div>
</fieldset>
<button type="submit" class="rate_submit_feedback"> Submit Feedback </button>
</form>
</div>
<button type="submit" class="rate_submit_feedback"> Submit Feedback </button>
</form>
</div>
<label title="{level}">
<input id="radio_{i}" name="rate_scale" class="rate_radio" type="radio" {active}>
<span aria-hidden="true">{icon}</span>
<span class="rate_sr_text">{level}</span>
</label>
<div style="display:inline-block; text-align:center; padding:0px 10px 0px 10px;">
<label title="{scale_text}">
<input id="radio_{idx}" name="rate_scale" class="rate_radio" type="radio" {active}/>
<img src="{ina_icon}"/>
<br/>
<span>{scale_text}</span>
</label>
</div>
<div style="display:inline-block; text-align:center; padding:0px 10px 0px 10px;">
<label title="{level}">
<input id="radio_{i}" name="rate_scale" class="rate_radio" type="radio" {active}/>
<span aria-hidden="true">{icon}</span>
<span class="rate_sr_text">{level}</span>
<div class="rate_likert_rating">
<label title="{scale_text}" class="rate_likert_label">
<input id="radio_{idx}" name="rate_scale" class="rate_radio" type="radio" {active}/>
<span class="rate_icon rate_icon_inactive">
<img src="{ina_icon}" alt="{unicode_icon}"/>
</span>
<span class="rate_icon rate_icon_active">
<img src="{act_icon}" alt="{unicode_icon}"/>
</span>
<br/>
({votes})
<span>{scale_text}</span>
<br/>
({vote_cnt})
</label>
</div>
......@@ -2,13 +2,19 @@
<p> This XBlock allows you to collect student feedback on pieces of
the course. This may be helpful either for course improvement, or
to give students a chance to reflect on what they have done.</p>
<p>Please note that the feedback needs to be analyzed as part of
the <a href="https://edx.readthedocs.org/en/latest/">edX research
dumps</a>. For now, we do not provide any kind of visual analytics
associated with this block.</p>
<p>Please note that the text feedback needs to be analyzed as part
of the <a href="https://edx.readthedocs.org/en/latest/">edX
research dumps</a>. For now, we do not provide any kind of visual
analytics associated with this block. We do display numbers of
students giving specific feedback.</p>
<p>We do recommend matrix sampling. This can be done by using the
<a href="http://edx.readthedocs.org/projects/edx-partner-course-staff/en/latest/content_experiments/content_experiments_configure.html">randomized
control trials framework</a>.</p>
<p> We suggest asking specific questions which provide students
clear guidance. For the Likert prompt, it is helpful to be very
specific: Was this assignment easy to understand? Was the length
appropriate? For the freeform response, you should provide students
clear guidance on what kind of feedback you would like.</p>
<ul class="list-input settings-list">
<li class="field comp-setting-entry is-set">
......
......@@ -3,45 +3,57 @@
if (typeof Logger === 'undefined') {
var Logger = {
log: function(a, b) {
console.log("<<Log>>");
console.log(a);
console.log(b);
console.log("<</Log>>");
console.log(JSON.stringify(a)+"/"+JSON.stringify(a));
}
};
}
function RateXBlock(runtime, element) {
var feedback_handler = runtime.handlerUrl(element, 'feedback');
$(".rate_submit_feedback", element).click(function(eventObject) {
var freeform = $(".rate_freeform_area", element).val();
function likert_vote() {
var vote = 0;
if ($(".rate_radio:checked", element).length === 0) {
vote = -1;
} else {
vote = parseInt($(".rate_radio:checked", element).attr("id").split("_")[1]);
}
var feedback = {"freeform": freeform,
"vote": vote};
return vote;
}
function feedback() {
return $(".rate_freeform_area", element).val();
}
function submit_feedback(freeform, vote) {
var feedback = {};
if(freeform) {
feedback['freeform'] = freeform;
}
if(vote != -1) {
feedback['vote'] = vote;
}
Logger.log("edx.ratexblock.submitted", feedback);
$.ajax({
type: "POST",
url: feedback_handler,
url: runtime.handlerUrl(element, 'feedback'),
data: JSON.stringify(feedback),
success: function(data) {$('.rate_thank_you', element).text(data.response);}
success: function(data) {
$('.rate_thank_you', element).text("");
$('.rate_thank_you', element).text(data.response);
}
});
}
$(".rate_submit_feedback", element).click(function(eventObject) {
submit_feedback(feedback(), -1);
});
$('.rate_radio', element).change(function(eventObject) {
var target_id = eventObject.target.id;
var vote = parseInt(target_id.split('_')[1]);
Logger.log("edx.ratexblock.likert_clicked", {"vote":vote});
Logger.log("edx.ratexblock.likert_changed", {"vote":likert_vote()});
submit_feedback(false, likert_vote());
});
$('.rate_freeform_area', element).change(function(eventObject) {
var freeform = eventObject.currentTarget.value;
Logger.log("edx.ratexblock.freeform_changed", {"freeform":freeform});
Logger.log("edx.ratexblock.freeform_changed", {"freeform":feedback()});
});
}
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