Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xblock-poll
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
xblock-poll
Commits
7fc0f64f
Commit
7fc0f64f
authored
Jan 01, 2015
by
Jonathan Piacenti
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Presentation tweaks and scaffolding for Survey.
parent
0482f8a1
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
196 additions
and
76 deletions
+196
-76
poll/__init__.py
+1
-2
poll/poll.py
+105
-24
poll/public/handlebars/poll_results.handlebars
+2
-2
poll/public/handlebars/poll_studio.handlebars
+0
-0
poll/public/html/survey.html
+30
-0
poll/public/js/poll.js
+53
-41
setup.py
+3
-4
tests/integration/test_poll_functions.py
+2
-3
No files found.
poll/__init__.py
View file @
7fc0f64f
from
.poll
import
PollBlock
\ No newline at end of file
from
.poll
import
PollBlock
,
SurveyBlock
poll/poll.py
View file @
7fc0f64f
...
...
@@ -9,21 +9,45 @@ import pkg_resources
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
Dict
,
List
from
xblock.fragment
import
Fragment
from
xblockutils.publish_event
import
PublishEventMixin
from
xblockutils.resources
import
ResourceLoader
class
PollBlock
(
XBlock
):
class
ResourceMixin
(
object
):
loader
=
ResourceLoader
(
__name__
)
@staticmethod
def
resource_string
(
path
):
"""Handy helper for getting resources from our kit."""
data
=
pkg_resources
.
resource_string
(
__name__
,
path
)
return
data
.
decode
(
"utf8"
)
def
create_fragment
(
self
,
context
,
template
,
css
,
js
,
js_init
):
html
=
Template
(
self
.
resource_string
(
template
))
.
render
(
Context
(
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
(
css
))
frag
.
add_javascript
(
self
.
resource_string
(
js
))
frag
.
initialize_js
(
js_init
)
return
frag
class
PollBlock
(
XBlock
,
ResourceMixin
,
PublishEventMixin
):
"""
Poll XBlock. Allows a teacher to poll users, and presents the results so
far of the poll to the user when finished.
"""
display_name
=
String
(
default
=
'Poll'
)
question
=
String
(
default
=
'What is your favorite color?'
)
# This will be converted into an OrderedDict.
# Key, (Label, Image path)
answers
=
List
(
default
=
((
'R'
,
{
'label'
:
'Red'
,
'img'
:
None
}),
(
'B'
,
{
'label'
:
'Blue'
,
'img'
:
None
}),
(
'G'
,
{
'label'
:
'Green'
,
'img'
:
None
}),
(
'O'
,
{
'label'
:
'Other'
,
'img'
:
None
})),
scope
=
Scope
.
settings
,
help
=
"The
question
on this poll."
scope
=
Scope
.
settings
,
help
=
"The
answer options
on this poll."
)
feedback
=
String
(
default
=
''
,
help
=
"Text to display after the user votes."
)
tally
=
Dict
(
default
=
{
'R'
:
0
,
'B'
:
0
,
'G'
:
0
,
'O'
:
0
},
...
...
@@ -31,19 +55,14 @@ class PollBlock(XBlock):
help
=
"Total tally of answers from students."
)
choice
=
String
(
scope
=
Scope
.
user_state
,
help
=
"The student's answer"
)
loader
=
ResourceLoader
(
__name__
)
def
resource_string
(
self
,
path
):
"""Handy helper for getting resources from our kit."""
data
=
pkg_resources
.
resource_string
(
__name__
,
path
)
return
data
.
decode
(
"utf8"
)
@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
):
...
...
@@ -95,7 +114,7 @@ class PollBlock(XBlock):
for
answer
in
tally
:
try
:
answer
[
'percent'
]
=
int
(
answer
[
'count'
]
/
float
(
total
))
*
100
answer
[
'percent'
]
=
round
(
answer
[
'count'
]
/
float
(
total
)
*
100
)
if
answer
[
'key'
]
==
choice
:
answer
[
'choice'
]
=
True
except
ZeroDivisionError
:
...
...
@@ -121,18 +140,6 @@ class PollBlock(XBlock):
else
:
return
None
def
create_fragment
(
self
,
context
,
template
,
css
,
js
,
js_init
):
html
=
Template
(
self
.
resource_string
(
template
))
.
render
(
Context
(
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
(
css
))
frag
.
add_javascript
(
self
.
resource_string
(
js
))
frag
.
initialize_js
(
js_init
)
return
frag
def
student_view
(
self
,
context
=
None
):
"""
The primary view of the PollBlock, shown to students
...
...
@@ -141,7 +148,7 @@ class PollBlock(XBlock):
if
not
context
:
context
=
{}
js_template
=
self
.
resource_string
(
'/public/handlebars/results.handlebars'
)
'/public/handlebars/
poll_
results.handlebars'
)
choice
=
self
.
get_choice
()
...
...
@@ -160,7 +167,7 @@ class PollBlock(XBlock):
if
self
.
choice
:
detail
,
total
=
self
.
tally_detail
()
context
.
update
({
'tally'
:
detail
,
'total'
:
total
})
context
.
update
({
'tally'
:
detail
,
'total'
:
total
,
'plural'
:
total
>
1
})
return
self
.
create_fragment
(
context
,
"public/html/poll.html"
,
"public/css/poll.css"
,
...
...
@@ -275,6 +282,17 @@ class PollBlock(XBlock):
self
.
tally
[
choice
]
=
self
.
tally
.
get
(
choice
,
0
)
+
1
# Let the LMS know the user has answered the poll.
self
.
runtime
.
publish
(
self
,
'progress'
,
{})
self
.
runtime
.
publish
(
self
,
'grade'
,
{
'value'
:
1
,
'max_value'
:
1
,
})
self
.
publish_event_from_dict
(
'xblock.poll.submitted'
,
{
'choice'
:
self
.
choice
},
)
result
[
'success'
]
=
True
return
result
...
...
@@ -301,3 +319,66 @@ class PollBlock(XBlock):
</vertical_demo>
"""
),
]
class
SurveyBlock
(
XBlock
,
ResourceMixin
,
PublishEventMixin
):
display_name
=
String
(
default
=
'Survey'
)
answers
=
List
(
default
=
(
(
'Y'
,
{
'label'
:
'Yes'
,
'img'
:
None
}),
(
'N'
,
{
'label'
:
'No'
,
'img'
:
None
}),
(
'M'
,
{
'label'
:
'Maybe'
,
'img'
:
None
})),
scope
=
Scope
.
settings
,
help
=
"Answer choices for this Survey"
)
questions
=
Dict
(
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?'
},
scope
=
Scope
.
settings
,
help
=
"Questions for this Survey"
)
feedback
=
String
(
default
=
''
,
help
=
"Text to display after the user votes."
)
tally
=
Dict
(
default
=
{
'enjoy'
:
{
'Y'
:
0
,
'N'
:
0
,
'M'
:
0
},
'recommend'
:
{
'Y'
:
0
,
'N'
:
0
,
'M'
:
0
},
'learn'
:
{
'Y'
:
0
,
'N'
:
0
,
'M'
:
0
}},
scope
=
Scope
.
user_state_summary
,
help
=
"Total tally of answers from students."
)
choices
=
Dict
(
help
=
"The user's answers"
)
def
student_view
(
self
,
context
=
None
):
"""
The primary view of the PollBlock, shown to students
when viewing courses.
"""
if
not
context
:
context
=
{}
context
.
update
({
'choices'
:
self
.
choices
,
# Offset so choices will always be True.
'answers'
:
self
.
answers
,
'questions'
:
self
.
questions
,
# Mustache is treating an empty string as true.
'feedback'
:
markdown
(
self
.
feedback
)
or
False
,
# The SDK doesn't set url_name.
'url_name'
:
getattr
(
self
,
'url_name'
,
''
),
})
return
self
.
create_fragment
(
context
,
"public/html/survey.html"
,
"public/css/poll.css"
,
"public/js/poll.js"
,
"PollBlock"
)
@staticmethod
def
workbench_scenarios
():
"""
Canned scenarios for display in the workbench.
"""
return
[
(
"Default Survey"
,
"""
<vertical_demo>
<survey />
</vertical_demo>
"""
),
]
poll/public/handlebars/results.handlebars
→
poll/public/handlebars/
poll_
results.handlebars
View file @
7fc0f64f
<script
id=
"
results
"
type=
"text/html"
>
<script
id=
"
poll-results-template
"
type=
"text/html"
>
{{{
question
}}}
<
ul
class
=
"poll-answers-results"
>
{{#
each
tally
}}
...
...
@@ -31,7 +31,7 @@
{{/
each
}}
<
/ul
>
<
input
class
=
"input-main"
type
=
"button"
name
=
"poll-submit"
value
=
"Submit"
disabled
>
<
div
class
=
"poll-footnote"
>
Results
gathered
from
{{
total
}}
respondent
(
s
)
.
<
/div
>
<
div
class
=
"poll-footnote"
>
Results
gathered
from
{{
total
}}
respondent
{{#if
plural
}}
s
{{/if}}
.
<
/div
>
{{#if
feedback
}}
<
hr
/>
<
div
class
=
"poll-feedback"
>
...
...
poll/public/handlebars/studio.handlebars
→
poll/public/handlebars/
poll_
studio.handlebars
View file @
7fc0f64f
File moved
poll/public/html/survey.html
0 → 100644
View file @
7fc0f64f
<div
class=
"survey-block"
>
{# If no form is present, the Javascript will load the results instead. #}
{% if not choices %}
<form>
<table>
<thead>
<tr>
<th></th>
{% for answer, details in answers %}
<th>
{{details.label}}
</th>
{% endfor %}
</tr>
</thead>
{% for key, question in questions.items %}
<tr>
<td>
{{question}}
</td>
{% for answer, answer_details in answers %}
<td>
<input
type=
"radio"
name=
"{{key}}"
value=
"{{answer}}"
/>
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input
class=
"input-main"
type=
"button"
name=
"poll-submit"
value=
"Submit"
disabled
/>
</form>
{% endif %}
</div>
poll/public/js/poll.js
View file @
7fc0f64f
/* Javascript for PollBlock. */
function
PollBlock
(
runtime
,
element
)
{
var
voteUrl
=
runtime
.
handlerUrl
(
element
,
'vote'
);
var
tallyURL
=
runtime
.
handlerUrl
(
element
,
'get_results'
);
var
PollUtil
=
{
init
:
function
(
runtime
,
element
)
{
this
.
voteUrl
=
runtime
.
handlerUrl
(
element
,
'vote'
);
this
.
tallyURL
=
runtime
.
handlerUrl
(
element
,
'get_results'
);
this
.
element
=
element
;
this
.
runtime
=
runtime
;
this
.
submit
=
$
(
'input[type=button]'
,
element
);
this
.
resultsTemplate
=
Handlebars
.
compile
(
$
(
"#poll-results-template"
,
element
).
html
());
},
poll_init
:
function
(){
// If the submit button doesn't exist, the user has already
// selected a choice.
var
self
=
this
;
if
(
self
.
submit
.
length
)
{
var
radio
=
$
(
'input[name=choice]:checked'
,
self
.
element
);
self
.
submit
.
click
(
function
(
event
)
{
// Refresh.
radio
=
$
(
radio
.
selector
,
element
);
var
choice
=
radio
.
val
();
$
.
ajax
({
type
:
"POST"
,
url
:
self
.
voteUrl
,
data
:
JSON
.
stringify
({
"choice"
:
choice
}),
success
:
self
.
getResults
});
});
// If the user has refreshed the page, they may still have an answer
// selected and the submit button should be enabled.
var
answers
=
$
(
'input[type=radio]'
,
self
.
element
);
if
(
!
radio
.
val
())
{
answers
.
bind
(
"change.EnableSubmit"
,
self
.
enableSubmit
);
}
else
{
self
.
enableSubmit
();
}
}
else
{
self
.
getResults
({
'success'
:
true
});
}
},
var
submit
=
$
(
'input[type=button]'
,
element
);
var
resultsTemplate
=
Handlebars
.
compile
(
$
(
"#results"
,
element
).
html
());
function
getResults
(
data
)
{
getResults
:
function
(
data
)
{
var
self
=
this
;
if
(
!
data
[
'success'
])
{
alert
(
data
[
'errors'
].
join
(
'
\
n'
));
}
...
...
@@ -14,44 +50,21 @@ function PollBlock(runtime, element) {
// Semantically, this would be better as GET, but we can use helper
// functions with POST.
type
:
"POST"
,
url
:
tallyURL
,
url
:
self
.
tallyURL
,
data
:
JSON
.
stringify
({}),
success
:
function
(
data
)
{
$
(
'div.poll-block'
,
element
).
html
(
resultsTemplate
(
data
));
$
(
'div.poll-block'
,
self
.
element
).
html
(
self
.
resultsTemplate
(
data
));
}
})
}
},
function
enableSubmit
()
{
submit
.
removeAttr
(
"disabled"
);
answers
.
unbind
(
"change.EnableSubmit"
);
enableSubmit
:
function
()
{
this
.
submit
.
removeAttr
(
"disabled"
);
this
.
answers
.
unbind
(
"change.EnableSubmit"
);
}
};
// If the submit button doesn't exist, the user has already
// selected a choice.
if
(
submit
.
length
)
{
var
radio
=
$
(
'input[name=choice]:checked'
,
element
);
submit
.
click
(
function
(
event
)
{
// Refresh.
radio
=
$
(
radio
.
selector
,
element
);
var
choice
=
radio
.
val
();
$
.
ajax
({
type
:
"POST"
,
url
:
voteUrl
,
data
:
JSON
.
stringify
({
"choice"
:
choice
}),
success
:
getResults
});
});
// If the user has refreshed the page, they may still have an answer
// selected and the submit button should be enabled.
var
answers
=
$
(
'input[type=radio]'
,
element
);
if
(
!
radio
.
val
())
{
answers
.
bind
(
"change.EnableSubmit"
,
enableSubmit
);
}
else
{
enableSubmit
();
}
}
else
{
getResults
({
'success'
:
true
});
}
}
\ No newline at end of file
function
PollBlock
(
runtime
,
element
)
{
PollUtil
.
init
(
runtime
,
element
);
PollUtil
.
poll_init
();
}
setup.py
View file @
7fc0f64f
...
...
@@ -22,7 +22,7 @@ def package_data(pkg, roots):
setup
(
name
=
'xblock-poll'
,
version
=
'0.
1
'
,
version
=
'0.
2
'
,
description
=
'An XBlock for polling users.'
,
packages
=
[
'poll'
,
...
...
@@ -30,14 +30,14 @@ setup(
install_requires
=
[
'XBlock'
,
'markdown'
,
'bleach'
,
'xblock-utils'
,
],
dependency_links
=
[
'http://github.com/edx-solutions/xblock-utils/tarball/master#egg=xblock-utils'
],
entry_points
=
{
'xblock.v1'
:
[
'poll = poll:PollBlock'
,
'survey = poll:SurveyBlock'
,
]
},
package_data
=
package_data
(
"poll"
,
[
"static"
,
"public"
]),
)
\ No newline at end of file
)
tests/integration/test_poll_functions.py
View file @
7fc0f64f
...
...
@@ -58,7 +58,7 @@ class TestPollFunctions(PollBaseTest):
"Thank you
\n
for being a valued student."
)
self
.
assertEqual
(
self
.
browser
.
find_element_by_css_selector
(
'.poll-footnote'
)
.
text
,
'Results gathered from 100 respondent
(s)
.'
)
'Results gathered from 100 respondent
s
.'
)
self
.
assertFalse
(
self
.
browser
.
find_element_by_css_selector
(
'input[name=poll-submit]'
)
.
is_enabled
())
...
...
@@ -79,4 +79,4 @@ class TestPollFunctions(PollBaseTest):
self
.
wait_until_exists
(
'input[name=poll-submit]:disabled'
)
self
.
go_to_page
(
'Poll Functions'
)
self
.
assertFalse
(
self
.
get_submit
()
.
is_enabled
())
\ No newline at end of file
self
.
assertFalse
(
self
.
get_submit
()
.
is_enabled
())
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment