Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
problem-builder
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
OpenEdx
problem-builder
Commits
a4f9ec66
Commit
a4f9ec66
authored
Jul 30, 2014
by
Xavier Antoviaque
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #49 from aboudreault/mentoring-assessment-mode
Mentoring assessment mode
parents
31042d9c
1105c899
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
654 additions
and
164 deletions
+654
-164
README.md
+34
-7
mentoring/answer.py
+8
-5
mentoring/html.py
+4
-1
mentoring/light_children.py
+12
-0
mentoring/mcq.py
+1
-0
mentoring/mentoring.py
+142
-5
mentoring/mrq.py
+1
-0
mentoring/public/css/mentoring.css
+13
-0
mentoring/public/js/answer.js
+6
-2
mentoring/public/js/mentoring.js
+51
-135
mentoring/public/js/mentoring_assessment_view.js
+193
-0
mentoring/public/js/mentoring_standard_view.js
+125
-0
mentoring/public/js/questionnaire.js
+10
-0
mentoring/questionnaire.py
+5
-3
mentoring/table.py
+4
-4
mentoring/templates/html/mentoring.html
+20
-1
mentoring/templates/html/mentoring_grade.html
+14
-0
mentoring/templates/xml/mentoring_default.xml
+1
-1
mentoring/utils.py
+10
-0
No files found.
README.md
View file @
a4f9ec66
...
...
@@ -19,12 +19,12 @@ Examples
First XBlock instance:
```
xml
<mentoring
url_name=
"goal_definition"
followed_by=
"getting_feedback"
>
<mentoring
url_name=
"goal_definition"
followed_by=
"getting_feedback"
weight=
"20"
>
<html>
<p>
What is your goal?
</p>
</html>
<answer
name=
"goal"
/>
<answer
name=
"goal"
weight=
"10"
/>
</mentoring>
```
...
...
@@ -40,15 +40,18 @@ Second XBlock instance:
<html>
<p>
Ask feedback from friends about this goal - what did they think?
</p>
</html>
<answer
name=
"goal_feedback"
/>
<answer
name=
"goal_feedback"
weight=
"5"
/>
</mentoring>
```
You can specify the weight of a free form answer. It will be considered during the
grade/score computation.
### Self-assessment MCQs
```
xml
<mentoring
url_name=
"mcq_1"
enforce_dependency=
"false"
>
<mcq
name=
"mcq_1_1"
type=
"choices"
>
<mcq
name=
"mcq_1_1"
type=
"choices"
weight=
"10"
>
<question>
Do you like this MCQ?
</question>
<choice
value=
"yes"
>
Yes
</choice>
<choice
value=
"maybenot"
>
Maybe not
</choice>
...
...
@@ -59,7 +62,7 @@ Second XBlock instance:
<tip
reject=
"understand"
><html><div
id=
"test-custom-html"
>
Really?
</div></html></tip>
</mcq>
<mcq
name=
"mcq_1_2"
type=
"rating"
low=
"Not good at all"
high=
"Extremely good"
>
<mcq
name=
"mcq_1_2"
type=
"rating"
low=
"Not good at all"
high=
"Extremely good"
weight=
"5"
>
<question>
How much do you rate this MCQ?
</question>
<choice
value=
"notwant"
>
I don't want to rate it
</choice>
...
...
@@ -78,10 +81,13 @@ Second XBlock instance:
</mentoring>
```
You can specify the weight of a self-assessment MCQ. It will be considered during the
grade/score computation.
### Self-assessment MRQs
```
xml
<mentoring
url_name=
"m
c
q_1"
enforce_dependency=
"false"
>
<mrq
name=
"mrq_1_1"
type=
"choices"
hide_results=
"true"
>
<mentoring
url_name=
"m
r
q_1"
enforce_dependency=
"false"
>
<mrq
name=
"mrq_1_1"
type=
"choices"
hide_results=
"true"
weight=
"10"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
...
...
@@ -104,6 +110,9 @@ Second XBlock instance:
</mentoring>
```
You can specify the weight of a self-assessment MRQ. It will be considered during the
grade/score computation.
### Tables
```
xml
...
...
@@ -128,6 +137,24 @@ Second XBlock instance:
</vertical>
```
### Modes
There are 2 mentoring modes available:
*
standard: Traditional mentoring. All questions are displayed in the page and submitted at the
same time. The student get some tips and feedback about their answers. (default mode)
*
assessment: Questions are displayed and submitted one after one. The student dont get tips or
feedback but only know if their answer was correct. Assessment mode comes with a default
max_attempts of 2.
To set the
*assessment*
mode, set the mode attribute in the settings:
```
xml
<mentoring
url_name=
"mentoring_1"
mode=
"assesment"
>
...
</mentoring>
```
### Maximum Attempts
You can set the number of maximum attempts for the unit completion, as well as
...
...
mentoring/answer.py
View file @
a4f9ec66
...
...
@@ -29,9 +29,9 @@ from lazy import lazy
from
xblock.fragment
import
Fragment
from
.light_children
import
LightChild
,
Boolean
,
Scope
,
String
,
Integer
from
.light_children
import
LightChild
,
Boolean
,
Scope
,
String
,
Integer
,
Float
from
.models
import
Answer
from
.utils
import
render_template
,
serialize_opaque_key
from
.utils
import
render_
js_
template
,
serialize_opaque_key
# Globals ###########################################################
...
...
@@ -53,6 +53,8 @@ class AnswerBlock(LightChild):
default
=
None
,
scope
=
Scope
.
content
)
min_characters
=
Integer
(
help
=
"Minimum number of characters allowed for the answer"
,
default
=
0
,
scope
=
Scope
.
content
)
weight
=
Float
(
help
=
"Defines the maximum total grade of the light child block."
,
default
=
1
,
scope
=
Scope
.
content
,
enforce_type
=
True
)
@lazy
def
student_input
(
self
):
...
...
@@ -74,11 +76,11 @@ class AnswerBlock(LightChild):
def
mentoring_view
(
self
,
context
=
None
):
if
not
self
.
read_only
:
html
=
render_template
(
'templates/html/answer_editable.html'
,
{
html
=
render_
js_
template
(
'templates/html/answer_editable.html'
,
{
'self'
:
self
,
})
else
:
html
=
render_template
(
'templates/html/answer_read_only.html'
,
{
html
=
render_
js_
template
(
'templates/html/answer_read_only.html'
,
{
'self'
:
self
,
})
...
...
@@ -90,7 +92,7 @@ class AnswerBlock(LightChild):
return
fragment
def
mentoring_table_view
(
self
,
context
=
None
):
html
=
render_template
(
'templates/html/answer_table.html'
,
{
html
=
render_
js_
template
(
'templates/html/answer_table.html'
,
{
'self'
:
self
,
})
fragment
=
Fragment
(
html
)
...
...
@@ -104,6 +106,7 @@ class AnswerBlock(LightChild):
return
{
'student_input'
:
self
.
student_input
,
'completed'
:
self
.
completed
,
'weight'
:
self
.
weight
,
'score'
:
1
if
self
.
completed
else
0
,
}
...
...
mentoring/html.py
View file @
a4f9ec66
...
...
@@ -56,7 +56,10 @@ class HTMLBlock(LightChild):
return
block
def
student_view
(
self
,
context
=
None
):
return
Fragment
(
self
.
content
)
return
Fragment
(
u"<script type='text/template' id='{}'>
\n
{}
\n
</script>"
.
format
(
'light-child-template'
,
self
.
content
))
def
mentoring_view
(
self
,
context
=
None
):
return
self
.
student_view
(
context
)
...
...
mentoring/light_children.py
View file @
a4f9ec66
...
...
@@ -365,6 +365,18 @@ class Boolean(LightChildField):
self
.
data
[
instance
]
=
value
class
Float
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
Float
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
default
=
kwargs
.
get
(
'default'
,
0
)
def
__set__
(
self
,
instance
,
value
):
try
:
self
.
data
[
instance
]
=
float
(
value
)
except
(
TypeError
,
ValueError
):
# not an integer
self
.
data
[
instance
]
=
0
class
List
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
List
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
...
...
mentoring/mcq.py
View file @
a4f9ec66
...
...
@@ -81,6 +81,7 @@ class MCQBlock(QuestionnaireAbstractBlock):
'submission'
:
submission
,
'completed'
:
completed
,
'tips'
:
tips
,
'weight'
:
self
.
weight
,
'score'
:
1
if
completed
else
0
,
}
log
.
debug
(
u'MCQ submission result:
%
s'
,
result
)
...
...
mentoring/mentoring.py
View file @
a4f9ec66
...
...
@@ -30,11 +30,12 @@ from lxml import etree
from
StringIO
import
StringIO
from
xblock.core
import
XBlock
from
xblock.fields
import
Boolean
,
Scope
,
String
,
Integer
,
Float
from
xblock.fields
import
Boolean
,
Scope
,
String
,
Integer
,
Float
,
List
from
xblock.fragment
import
Fragment
from
.light_children
import
XBlockWithLightChildren
from
.title
import
TitleBlock
from
.html
import
HTMLBlock
from
.message
import
MentoringMessageBlock
from
.utils
import
get_scenarios_from_path
,
load_resource
,
render_template
...
...
@@ -75,9 +76,40 @@ class MentoringBlock(XBlockWithLightChildren):
default
=
0
,
scope
=
Scope
.
user_state
,
enforce_type
=
True
)
max_attempts
=
Integer
(
help
=
"Number of max attempts for this questions"
,
default
=
0
,
scope
=
Scope
.
content
,
enforce_type
=
True
)
mode
=
String
(
help
=
"Mode of the mentoring. 'standard' or 'accessment'"
,
default
=
'standard'
,
scope
=
Scope
.
content
)
step
=
Integer
(
help
=
"Keep track of the student assessment progress."
,
default
=
0
,
scope
=
Scope
.
user_state
,
enforce_type
=
True
)
student_results
=
List
(
help
=
"Store results of student choices."
,
default
=
[],
scope
=
Scope
.
user_state
)
icon_class
=
'problem'
has_score
=
True
MENTORING_MODES
=
(
'standard'
,
'assessment'
)
@property
def
is_assessment
(
self
):
return
self
.
mode
==
'assessment'
@property
def
steps
(
self
):
return
[
child
for
child
in
self
.
get_children_objects
()
if
not
isinstance
(
child
,
(
HTMLBlock
,
TitleBlock
,
MentoringMessageBlock
))]
@property
def
score
(
self
):
"""Compute the student score taking into account the light child weight."""
total_child_weight
=
sum
(
float
(
step
.
weight
)
for
step
in
self
.
steps
)
if
total_child_weight
==
0
:
return
(
0
,
0
,
0
)
score
=
sum
(
r
[
1
][
'score'
]
*
r
[
1
][
'weight'
]
\
for
r
in
self
.
student_results
)
/
total_child_weight
correct
=
sum
(
1
for
r
in
self
.
student_results
if
r
[
1
][
'completed'
]
==
True
)
incorrect
=
sum
(
1
for
r
in
self
.
student_results
if
r
[
1
][
'completed'
]
==
False
)
return
(
score
,
float
(
'
%0.2
f'
%
(
score
*
100
,)),
correct
,
incorrect
)
def
student_view
(
self
,
context
):
fragment
,
named_children
=
self
.
get_children_fragment
(
context
,
view_name
=
'mentoring_view'
,
...
...
@@ -92,8 +124,18 @@ class MentoringBlock(XBlockWithLightChildren):
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/css/mentoring.css'
))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/vendor/underscore-min.js'
))
if
self
.
is_assessment
:
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/mentoring_assessment_view.js'
)
)
else
:
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/mentoring_standard_view.js'
)
)
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/mentoring.js'
))
fragment
.
add_resource
(
load_resource
(
'templates/html/mentoring_attempts.html'
),
"text/html"
)
fragment
.
add_resource
(
load_resource
(
'templates/html/mentoring_grade.html'
),
"text/html"
)
fragment
.
initialize_js
(
'MentoringBlock'
)
...
...
@@ -129,6 +171,9 @@ class MentoringBlock(XBlockWithLightChildren):
log
.
info
(
u'Received submissions: {}'
.
format
(
submissions
))
self
.
attempted
=
True
if
self
.
is_assessment
:
return
self
.
handleAssessmentSubmit
(
submissions
,
suffix
)
submit_results
=
[]
completed
=
True
for
child
in
self
.
get_children_objects
():
...
...
@@ -163,9 +208,15 @@ class MentoringBlock(XBlockWithLightChildren):
# Once it was completed, lock score
if
not
self
.
completed
:
score
=
sum
(
r
[
1
][
'score'
]
for
r
in
submit_results
)
/
float
(
len
(
submit_results
))
# save user score and results
while
self
.
student_results
:
self
.
student_results
.
pop
()
for
result
in
submit_results
:
self
.
student_results
.
append
(
result
)
(
raw_score
,
score
,
correct
,
incorrect
)
=
self
.
score
self
.
runtime
.
publish
(
self
,
'grade'
,
{
'value'
:
score
,
'value'
:
raw_
score
,
'max_value'
:
1
,
})
...
...
@@ -183,6 +234,77 @@ class MentoringBlock(XBlockWithLightChildren):
'num_attempts'
:
self
.
num_attempts
}
def
handleAssessmentSubmit
(
self
,
submissions
,
suffix
):
completed
=
False
step
=
0
children
=
[
child
for
child
in
self
.
get_children_objects
()
\
if
not
isinstance
(
child
,
TitleBlock
)]
for
child
in
children
:
if
child
.
name
and
child
.
name
in
submissions
:
submission
=
submissions
[
child
.
name
]
# Assessment mode doesn't allow to modify answers
# This will get the student back at the step he should be
step
=
children
.
index
(
child
)
if
self
.
step
>
step
or
self
.
max_attempts_reached
:
step
=
self
.
step
completed
=
False
break
self
.
step
=
step
+
1
child_result
=
child
.
submit
(
submission
)
if
'tips'
in
child_result
:
del
child_result
[
'tips'
]
self
.
student_results
.
append
([
child
.
name
,
child_result
])
child
.
save
()
completed
=
child_result
[
'completed'
]
(
raw_score
,
score
,
correct
,
incorrect
)
=
self
.
score
if
step
==
len
(
self
.
steps
):
log
.
info
(
u'Last assessment step submitted: {}'
.
format
(
submissions
))
if
not
self
.
max_attempts_reached
:
self
.
runtime
.
publish
(
self
,
'grade'
,
{
'value'
:
raw_score
,
'max_value'
:
1
,
})
self
.
num_attempts
+=
1
self
.
completed
=
True
return
{
'completed'
:
completed
,
'attempted'
:
self
.
attempted
,
'max_attempts'
:
self
.
max_attempts
,
'num_attempts'
:
self
.
num_attempts
,
'step'
:
self
.
step
,
'score'
:
score
,
'correct_answer'
:
correct
,
'incorrect_answer'
:
incorrect
}
@XBlock.json_handler
def
try_again
(
self
,
data
,
suffix
=
''
):
if
self
.
max_attempts_reached
:
return
{
'result'
:
'error'
,
'message'
:
'max attempts reached'
}
# reset
self
.
step
=
0
self
.
completed
=
False
while
self
.
student_results
:
self
.
student_results
.
pop
()
return
{
'result'
:
'success'
}
@property
def
max_attempts_reached
(
self
):
return
self
.
max_attempts
>
0
and
self
.
num_attempts
>=
self
.
max_attempts
...
...
@@ -222,19 +344,34 @@ class MentoringBlock(XBlockWithLightChildren):
def
studio_submit
(
self
,
submissions
,
suffix
=
''
):
log
.
info
(
u'Received studio submissions: {}'
.
format
(
submissions
))
success
=
True
xml_content
=
submissions
[
'xml_content'
]
try
:
etree
.
parse
(
StringIO
(
xml_content
))
content
=
etree
.
parse
(
StringIO
(
xml_content
))
except
etree
.
XMLSyntaxError
as
e
:
response
=
{
'result'
:
'error'
,
'message'
:
e
.
message
}
success
=
False
else
:
root
=
content
.
getroot
()
if
'mode'
in
root
.
attrib
:
if
root
.
attrib
[
'mode'
]
not
in
self
.
MENTORING_MODES
:
response
=
{
'result'
:
'error'
,
'message'
:
"Invalid mentoring mode: should be 'standard' or 'assessment'"
}
success
=
False
elif
root
.
attrib
[
'mode'
]
==
'assessment'
and
'max_attempts'
not
in
root
.
attrib
:
# assessment has a default of 2 max_attempts
root
.
attrib
[
'max_attempts'
]
=
'2'
if
success
:
response
=
{
'result'
:
'success'
,
}
self
.
xml_content
=
xml_content
self
.
xml_content
=
etree
.
tostring
(
content
,
pretty_print
=
True
)
log
.
debug
(
u'Response from Studio: {}'
.
format
(
response
))
return
response
...
...
mentoring/mrq.py
View file @
a4f9ec66
...
...
@@ -82,6 +82,7 @@ class MRQBlock(QuestionnaireAbstractBlock):
'completed'
:
completed
,
'choices'
:
results
,
'message'
:
self
.
message
,
'weight'
:
self
.
weight
,
'score'
:
sum
(
1.0
for
r
in
results
if
r
[
'completed'
])
/
len
(
results
)
}
...
...
mentoring/public/css/mentoring.css
View file @
a4f9ec66
...
...
@@ -53,6 +53,10 @@
margin-top
:
20px
;
}
.mentoring
.submit
input
{
display
:
none
;
}
.mentoring
.attempts
{
margin-left
:
10px
;
display
:
inline-block
;
...
...
@@ -86,6 +90,15 @@
height
:
33.33px
;
}
.mentoring
.assessment-checkmark
{
margin-right
:
10px
;
}
.mentoring
.grade
.checkmark-incorrect
{
margin-left
:
10px
;
margin-right
:
20px
;
}
.mentoring
input
[
type
=
button
],
.mentoring
input
[
type
=
button
]
:focus
{
background-color
:
#3384ca
;
...
...
mentoring/public/js/answer.js
View file @
a4f9ec66
function
AnswerBlock
(
runtime
,
element
)
{
return
{
mode
:
null
,
init
:
function
(
options
)
{
// register the child validator
$
(
':input'
,
element
).
on
(
'keyup'
,
options
.
onChange
);
this
.
mode
=
options
.
mode
;
var
checkmark
=
$
(
'.answer-checkmark'
,
element
);
var
completed
=
$
(
'.xblock-answer'
,
element
).
data
(
'completed'
);
if
(
completed
===
'True'
)
{
if
(
completed
===
'True'
&&
this
.
mode
===
'standard'
)
{
checkmark
.
addClass
(
'checkmark-correct icon-ok fa-check'
);
}
},
...
...
@@ -17,6 +18,9 @@ function AnswerBlock(runtime, element) {
},
handleSubmit
:
function
(
result
)
{
if
(
this
.
mode
===
'assessment'
)
return
;
var
checkmark
=
$
(
'.answer-checkmark'
,
element
);
$
(
element
).
find
(
'.message'
).
text
((
result
||
{}).
error
||
''
);
...
...
mentoring/public/js/mentoring.js
View file @
a4f9ec66
function
MentoringBlock
(
runtime
,
element
)
{
var
attemptsTemplate
=
_
.
template
(
$
(
'#xblock-attempts-template'
).
html
());
var
children
;
// Keep track of children. A Child need a single object scope for its data.
var
submitXHR
;
function
renderAttempts
()
{
var
data
=
$
(
'.attempts'
,
element
).
data
();
$
(
'.attempts'
,
element
).
html
(
attemptsTemplate
(
data
));
}
function
renderDependency
()
{
var
warning_dom
=
$
(
'.missing-dependency'
,
element
),
data
=
warning_dom
.
data
();
if
(
data
.
missing
===
'True'
)
{
warning_dom
.
show
();
}
}
var
data
=
$
(
'.mentoring'
,
element
).
data
();
var
children_dom
=
[];
// Keep track of children. A Child need a single object scope for its data.
var
children
=
[];
var
step
=
data
.
step
;
function
callIfExists
(
obj
,
fn
)
{
if
(
typeof
obj
!==
'undefined'
&&
typeof
obj
[
fn
]
==
'function'
)
{
...
...
@@ -25,155 +13,83 @@ function MentoringBlock(runtime, element) {
}
}
function
handleSubmitResults
(
results
)
{
messagesDOM
.
empty
().
hide
();
$
.
each
(
results
.
submitResults
||
[],
function
(
index
,
submitResult
)
{
var
input
=
submitResult
[
0
],
result
=
submitResult
[
1
],
child
=
getChildByName
(
element
,
input
);
var
options
=
{
max_attempts
:
results
.
max_attempts
,
num_attempts
:
results
.
num_attempts
function
renderAttempts
()
{
var
data
=
$
(
'.attempts'
,
element
).
data
();
$
(
'.attempts'
,
element
).
html
(
attemptsTemplate
(
data
));
}
callIfExists
(
child
,
'handleSubmit'
,
result
,
options
);
});
$
(
'.attempts'
,
element
).
data
(
'max_attempts'
,
results
.
max_attempts
);
$
(
'.attempts'
,
element
).
data
(
'num_attempts'
,
results
.
num_attempts
);
renderAttempts
();
function
renderDependency
()
{
var
warning_dom
=
$
(
'.missing-dependency'
,
element
),
data
=
warning_dom
.
data
();
// Messages should only be displayed upon hitting 'submit', not on page reload
messagesDOM
.
append
(
results
.
message
);
if
(
messagesDOM
.
html
().
trim
())
{
messagesDOM
.
prepend
(
'<div class="title1">Feedback</div>'
);
messagesDOM
.
show
();
if
(
data
.
missing
===
'True'
)
{
warning_dom
.
show
();
}
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
}
function
getChildren
(
element
)
{
if
(
!
_
.
isUndefined
(
children
))
return
children
;
function
readChildren
(
element
)
{
var
doms
=
$
(
'.xblock-light-child'
,
element
);
var
children_dom
=
$
(
'.xblock-light-child'
,
element
);
children
=
[];
$
.
each
(
children_dom
,
function
(
index
,
child_dom
)
{
$
.
each
(
doms
,
function
(
index
,
child_dom
)
{
var
child_type
=
$
(
child_dom
).
attr
(
'data-type'
),
child
=
window
[
child_type
];
children_dom
.
push
(
child_dom
);
children
.
push
(
child
);
if
(
typeof
child
!==
'undefined'
)
{
child
=
child
(
runtime
,
child_dom
);
child
.
name
=
$
(
child_dom
).
attr
(
'name'
);
children
.
push
(
child
)
;
children
[
children
.
length
-
1
]
=
child
;
}
});
return
children
;
}
function
getChildByName
(
element
,
name
)
{
var
children
=
getChildren
(
element
);
/* Init and display a child. */
function
displayChild
(
index
,
options
)
{
var
options
=
options
||
{};
options
.
mode
=
data
.
mode
;
if
(
index
>=
children
.
length
)
return
children
.
length
;
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
var
child
=
children
[
i
];
if
(
child
.
name
===
name
)
{
var
template
=
$
(
'#light-child-template'
,
children_dom
[
index
]).
html
();
$
(
children_dom
[
index
]).
append
(
template
);
$
(
children_dom
[
index
]).
show
();
var
child
=
children
[
index
];
callIfExists
(
child
,
'init'
,
options
);
return
child
;
}
}
}
function
submit
()
{
var
success
=
true
;
var
data
=
{};
var
children
=
getChildren
(
element
);
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
var
child
=
children
[
i
];
if
(
child
.
name
!==
undefined
)
{
data
[
child
.
name
]
=
callIfExists
(
child
,
'submit'
);
}
}
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'submit'
);
if
(
submitXHR
)
{
submitXHR
.
abort
();
}
submitXHR
=
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
data
)).
success
(
handleSubmitResults
);
}
function
clearResults
()
{
messagesDOM
.
empty
().
hide
();
var
children
=
getChildren
(
element
);
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
callIfExists
(
children
[
i
],
'clearResult'
);
}
}
function
onChange
()
{
clearResults
();
validateXBlock
();
}
function
initXBlock
()
{
messagesDOM
=
$
(
element
).
find
(
'.messages'
);
submitDOM
=
$
(
element
).
find
(
'.submit .input-main'
);
submitDOM
.
bind
(
'click'
,
submit
);
// init children (especially mrq blocks)
var
children
=
getChildren
(
element
);
var
options
=
{
onChange
:
onChange
};
_
.
each
(
children
,
function
(
child
)
{
callIfExists
(
child
,
'init'
,
options
);
function
displayChildren
(
options
)
{
$
.
each
(
children_dom
,
function
(
index
)
{
displayChild
(
index
,
options
);
});
renderAttempts
();
renderDependency
();
validateXBlock
();
}
function
handleRefreshResults
(
results
)
{
$
(
element
).
html
(
results
.
html
);
initXBlock
();
}
function
refreshXBlock
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'view'
);
$
.
post
(
handlerUrl
,
'{}'
).
success
(
handleRefreshResults
);
}
// validate all children
function
validateXBlock
()
{
var
is_valid
=
true
;
var
data
=
$
(
'.attempts'
,
element
).
data
();
var
children
=
getChildren
(
element
);
if
((
data
.
max_attempts
>
0
)
&&
(
data
.
num_attempts
>=
data
.
max_attempts
))
{
is_valid
=
false
;
}
else
{
function
getChildByName
(
element
,
name
)
{
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
var
child
=
children
[
i
];
if
(
child
.
name
!==
undefined
)
{
var
child_validation
=
callIfExists
(
child
,
'validate'
);
if
(
_
.
isBoolean
(
child_validation
))
{
is_valid
=
is_valid
&&
child_validation
;
}
if
(
child
&&
child
.
name
===
name
)
{
return
child
;
}
}
}
if
(
!
is_valid
)
{
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
var
mentoring
=
{
callIfExists
:
callIfExists
,
renderAttempts
:
renderAttempts
,
renderDependency
:
renderDependency
,
readChildren
:
readChildren
,
children_dom
:
children_dom
,
children
:
children
,
displayChild
:
displayChild
,
displayChildren
:
displayChildren
,
getChildByName
:
getChildByName
,
step
:
step
}
else
{
submitDOM
.
removeAttr
(
"disabled"
);
if
(
data
.
mode
===
'standard'
)
{
MentoringStandardView
(
runtime
,
element
,
mentoring
);
}
else
if
(
data
.
mode
===
'assessment'
)
{
MentoringAssessmentView
(
runtime
,
element
,
mentoring
);
}
// We need to manually refresh, XBlocks are currently loaded together with the section
refreshXBlock
(
element
);
}
mentoring/public/js/mentoring_assessment_view.js
0 → 100644
View file @
a4f9ec66
function
MentoringAssessmentView
(
runtime
,
element
,
mentoring
)
{
var
gradeTemplate
=
_
.
template
(
$
(
'#xblock-grade-template'
).
html
());
var
submitDOM
,
nextDOM
,
reviewDOM
,
tryAgainDOM
;
var
submitXHR
;
var
checkmark
;
var
active_child
;
var
callIfExists
=
mentoring
.
callIfExists
;
function
cleanAll
()
{
// clean checkmark state
checkmark
.
removeClass
(
'checkmark-correct icon-ok fa-check'
);
checkmark
.
removeClass
(
'checkmark-incorrect icon-exclamation fa-exclamation'
);
/* hide all children */
$
(
':nth-child(2)'
,
mentoring
.
children_dom
).
remove
();
$
(
'.grade'
).
html
(
''
);
$
(
'.attempts'
).
html
(
''
);
}
function
renderGrade
()
{
var
data
=
$
(
'.grade'
,
element
).
data
();
cleanAll
();
$
(
'.grade'
,
element
).
html
(
gradeTemplate
(
data
));
reviewDOM
.
hide
()
submitDOM
.
hide
()
nextDOM
.
hide
();
tryAgainDOM
.
show
();
var
attempts_data
=
$
(
'.attempts'
,
element
).
data
();
if
(
attempts_data
.
num_attempts
>=
attempts_data
.
max_attempts
)
{
tryAgainDOM
.
attr
(
"disabled"
,
"disabled"
);
}
else
{
tryAgainDOM
.
removeAttr
(
"disabled"
);
}
mentoring
.
renderAttempts
();
}
function
handleTryAgain
(
result
)
{
if
(
result
.
result
!==
'success'
)
return
;
active_child
=
0
;
displayNextChild
();
tryAgainDOM
.
hide
();
submitDOM
.
show
().
removeAttr
(
'disabled'
);
nextDOM
.
show
();
}
function
tryAgain
()
{
var
success
=
true
;
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'try_again'
);
if
(
submitXHR
)
{
submitXHR
.
abort
();
}
submitXHR
=
$
.
post
(
handlerUrl
,
JSON
.
stringify
({})).
success
(
handleTryAgain
);
}
function
initXBlockView
()
{
submitDOM
=
$
(
element
).
find
(
'.submit .input-main'
);
nextDOM
=
$
(
element
).
find
(
'.submit .input-next'
);
reviewDOM
=
$
(
element
).
find
(
'.submit .input-review'
);
tryAgainDOM
=
$
(
element
).
find
(
'.submit .input-try-again'
);
checkmark
=
$
(
'.assessment-checkmark'
,
element
);
submitDOM
.
show
();
submitDOM
.
bind
(
'click'
,
submit
);
nextDOM
.
bind
(
'click'
,
displayNextChild
);
nextDOM
.
show
();
reviewDOM
.
bind
(
'click'
,
renderGrade
);
tryAgainDOM
.
bind
(
'click'
,
tryAgain
);
active_child
=
mentoring
.
step
-
1
;
mentoring
.
readChildren
();
displayNextChild
();
mentoring
.
renderDependency
();
}
function
isLastChild
()
{
return
(
active_child
==
mentoring
.
children
.
length
-
1
);
}
function
isDone
()
{
return
(
active_child
==
mentoring
.
children
.
length
);
}
function
displayNextChild
()
{
var
options
=
{
onChange
:
onChange
};
cleanAll
();
// find the next real child block to display. HTMLBlock are always displayed
++
active_child
;
while
(
1
)
{
var
child
=
mentoring
.
displayChild
(
active_child
,
options
);
if
((
typeof
child
!==
'undefined'
)
||
active_child
==
mentoring
.
children
.
length
-
1
)
break
;
++
active_child
;
}
if
(
isDone
())
renderGrade
();
nextDOM
.
attr
(
'disabled'
,
'disabled'
);
reviewDOM
.
attr
(
'disabled'
,
'disabled'
);
validateXBlock
();
}
function
onChange
()
{
validateXBlock
();
}
function
handleSubmitResults
(
result
)
{
$
(
'.grade'
,
element
).
data
(
'score'
,
result
.
score
);
$
(
'.grade'
,
element
).
data
(
'correct_answer'
,
result
.
correct_answer
);
$
(
'.grade'
,
element
).
data
(
'incorrect_answer'
,
result
.
incorrect_answer
);
$
(
'.grade'
,
element
).
data
(
'max_attempts'
,
result
.
max_attempts
);
$
(
'.grade'
,
element
).
data
(
'num_attempts'
,
result
.
num_attempts
);
$
(
'.attempts'
,
element
).
data
(
'max_attempts'
,
result
.
max_attempts
);
$
(
'.attempts'
,
element
).
data
(
'num_attempts'
,
result
.
num_attempts
);
if
(
result
.
completed
)
{
checkmark
.
addClass
(
'checkmark-correct icon-ok fa-check'
);
}
else
{
checkmark
.
addClass
(
'checkmark-incorrect icon-exclamation fa-exclamation'
);
}
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
/* Something went wrong with student submission, denied next question */
if
(
result
.
step
!=
active_child
+
1
)
{
active_child
=
result
.
step
-
1
;
displayNextChild
();
}
else
{
nextDOM
.
removeAttr
(
"disabled"
);
reviewDOM
.
removeAttr
(
"disabled"
);
}
}
function
submit
()
{
var
success
=
true
;
var
data
=
{};
var
child
=
mentoring
.
children
[
active_child
];
if
(
child
&&
child
.
name
!==
undefined
)
{
data
[
child
.
name
]
=
callIfExists
(
child
,
'submit'
);
}
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'submit'
);
if
(
submitXHR
)
{
submitXHR
.
abort
();
}
submitXHR
=
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
data
)).
success
(
handleSubmitResults
);
}
function
validateXBlock
()
{
var
is_valid
=
true
;
var
data
=
$
(
'.attempts'
,
element
).
data
();
var
children
=
mentoring
.
children
;
// if ((data.max_attempts > 0) && (data.num_attempts >= data.max_attempts)) {
// is_valid = false;
// }
var
child
=
mentoring
.
children
[
active_child
];
if
(
child
&&
child
.
name
!==
undefined
)
{
var
child_validation
=
callIfExists
(
child
,
'validate'
);
if
(
_
.
isBoolean
(
child_validation
))
{
is_valid
=
is_valid
&&
child_validation
;
}
}
if
(
!
is_valid
)
{
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
}
else
{
submitDOM
.
removeAttr
(
"disabled"
);
}
if
(
isLastChild
())
{
nextDOM
.
hide
()
reviewDOM
.
show
();
}
}
initXBlockView
();
}
mentoring/public/js/mentoring_standard_view.js
0 → 100644
View file @
a4f9ec66
function
MentoringStandardView
(
runtime
,
element
,
mentoring
)
{
var
submitXHR
;
var
callIfExists
=
mentoring
.
callIfExists
;
function
handleSubmitResults
(
results
)
{
messagesDOM
.
empty
().
hide
();
$
.
each
(
results
.
submitResults
||
[],
function
(
index
,
submitResult
)
{
var
input
=
submitResult
[
0
],
result
=
submitResult
[
1
],
child
=
mentoring
.
getChildByName
(
element
,
input
);
var
options
=
{
max_attempts
:
results
.
max_attempts
,
num_attempts
:
results
.
num_attempts
}
callIfExists
(
child
,
'handleSubmit'
,
result
,
options
);
});
$
(
'.attempts'
,
element
).
data
(
'max_attempts'
,
results
.
max_attempts
);
$
(
'.attempts'
,
element
).
data
(
'num_attempts'
,
results
.
num_attempts
);
mentoring
.
renderAttempts
();
// Messages should only be displayed upon hitting 'submit', not on page reload
messagesDOM
.
append
(
results
.
message
);
if
(
messagesDOM
.
html
().
trim
())
{
messagesDOM
.
prepend
(
'<div class="title1">Feedback</div>'
);
messagesDOM
.
show
();
}
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
}
function
submit
()
{
var
success
=
true
;
var
data
=
{};
var
children
=
mentoring
.
children
;
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
var
child
=
children
[
i
];
if
(
child
&&
child
.
name
!==
undefined
)
{
data
[
child
.
name
]
=
callIfExists
(
child
,
'submit'
);
}
}
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'submit'
);
if
(
submitXHR
)
{
submitXHR
.
abort
();
}
submitXHR
=
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
data
)).
success
(
handleSubmitResults
);
}
function
clearResults
()
{
messagesDOM
.
empty
().
hide
();
var
children
=
mentoring
.
children
;
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
callIfExists
(
children
[
i
],
'clearResult'
);
}
}
function
onChange
()
{
clearResults
();
validateXBlock
();
}
function
initXBlockView
()
{
messagesDOM
=
$
(
element
).
find
(
'.messages'
);
submitDOM
=
$
(
element
).
find
(
'.submit .input-main'
);
submitDOM
.
bind
(
'click'
,
submit
);
submitDOM
.
show
();
var
options
=
{
onChange
:
onChange
};
mentoring
.
displayChildren
(
options
);
mentoring
.
renderAttempts
();
mentoring
.
renderDependency
();
validateXBlock
();
}
function
handleRefreshResults
(
results
)
{
$
(
element
).
html
(
results
.
html
);
mentoring
.
readChildren
();
initXBlockView
();
}
function
refreshXBlock
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'view'
);
$
.
post
(
handlerUrl
,
'{}'
).
success
(
handleRefreshResults
);
}
// validate all children
function
validateXBlock
()
{
var
is_valid
=
true
;
var
data
=
$
(
'.attempts'
,
element
).
data
();
var
children
=
mentoring
.
children
;
if
((
data
.
max_attempts
>
0
)
&&
(
data
.
num_attempts
>=
data
.
max_attempts
))
{
is_valid
=
false
;
}
else
{
for
(
var
i
=
0
;
i
<
children
.
length
;
i
++
)
{
var
child
=
children
[
i
];
if
(
child
&&
child
.
name
!==
undefined
)
{
var
child_validation
=
callIfExists
(
child
,
'validate'
);
if
(
_
.
isBoolean
(
child_validation
))
{
is_valid
=
is_valid
&&
child_validation
;
}
}
}
}
if
(
!
is_valid
)
{
submitDOM
.
attr
(
'disabled'
,
'disabled'
);
}
else
{
submitDOM
.
removeAttr
(
"disabled"
);
}
}
// We need to manually refresh, XBlocks are currently loaded together with the section
refreshXBlock
(
element
);
}
mentoring/public/js/questionnaire.js
View file @
a4f9ec66
...
...
@@ -52,7 +52,9 @@ function MessageView(element) {
function
MCQBlock
(
runtime
,
element
)
{
return
{
mode
:
null
,
init
:
function
(
options
)
{
this
.
mode
=
options
.
mode
;
$
(
'input[type=radio]'
,
element
).
on
(
'change'
,
options
.
onChange
);
},
...
...
@@ -67,6 +69,9 @@ function MCQBlock(runtime, element) {
},
handleSubmit
:
function
(
result
)
{
if
(
this
.
mode
===
'assessment'
)
return
;
var
messageView
=
MessageView
(
element
);
messageView
.
clearResult
();
...
...
@@ -132,7 +137,9 @@ function MCQBlock(runtime, element) {
function
MRQBlock
(
runtime
,
element
)
{
return
{
mode
:
null
,
init
:
function
(
options
)
{
this
.
mode
=
options
.
mode
;
$
(
'input[type=checkbox]'
,
element
).
on
(
'change'
,
options
.
onChange
);
},
...
...
@@ -147,6 +154,9 @@ function MRQBlock(runtime, element) {
},
handleSubmit
:
function
(
result
,
options
)
{
if
(
this
.
mode
===
'assessment'
)
return
;
var
messageView
=
MessageView
(
element
);
if
(
result
.
message
)
{
...
...
mentoring/questionnaire.py
View file @
a4f9ec66
...
...
@@ -28,9 +28,9 @@ import logging
from
xblock.fragment
import
Fragment
from
.choice
import
ChoiceBlock
from
.light_children
import
LightChild
,
Scope
,
String
from
.light_children
import
LightChild
,
Scope
,
String
,
Float
from
.tip
import
TipBlock
from
.utils
import
render_template
from
.utils
import
render_template
,
render_js_template
# Globals ###########################################################
...
...
@@ -51,6 +51,8 @@ class QuestionnaireAbstractBlock(LightChild):
type
=
String
(
help
=
"Type of questionnaire"
,
scope
=
Scope
.
content
,
default
=
"choices"
)
question
=
String
(
help
=
"Question to ask the student"
,
scope
=
Scope
.
content
,
default
=
""
)
message
=
String
(
help
=
"General feedback provided when submiting"
,
scope
=
Scope
.
content
,
default
=
""
)
weight
=
Float
(
help
=
"Defines the maximum total grade of the light child block."
,
default
=
1
,
scope
=
Scope
.
content
,
enforce_type
=
True
)
valid_types
=
(
'choices'
)
...
...
@@ -77,7 +79,7 @@ class QuestionnaireAbstractBlock(LightChild):
raise
ValueError
,
u'Invalid value for {}.type: `{}`'
.
format
(
name
,
self
.
type
)
template_path
=
'templates/html/{}_{}.html'
.
format
(
name
.
lower
(),
self
.
type
)
html
=
render_template
(
template_path
,
{
html
=
render_
js_
template
(
template_path
,
{
'self'
:
self
,
'custom_choices'
:
self
.
custom_choices
})
...
...
mentoring/table.py
View file @
a4f9ec66
...
...
@@ -29,7 +29,7 @@ import logging
from
xblock.fields
import
Scope
from
.light_children
import
LightChild
,
String
from
.utils
import
load_resource
,
render_template
from
.utils
import
load_resource
,
render_
js_
template
# Globals ###########################################################
...
...
@@ -66,7 +66,7 @@ class MentoringTableBlock(LightChild):
else
:
raise
fragment
.
add_content
(
render_template
(
'templates/html/mentoring-table.html'
,
{
fragment
.
add_content
(
render_
js_
template
(
'templates/html/mentoring-table.html'
,
{
'self'
:
self
,
'columns_frags'
:
columns_frags
,
'header_frags'
:
header_frags
,
...
...
@@ -102,7 +102,7 @@ class MentoringTableColumnBlock(LightChild):
fragment
,
named_children
=
self
.
get_children_fragment
(
context
,
view_name
=
'mentoring_table_view'
,
not_instance_of
=
MentoringTableColumnHeaderBlock
)
fragment
.
add_content
(
render_template
(
'templates/html/mentoring-table-column.html'
,
{
fragment
.
add_content
(
render_
js_
template
(
'templates/html/mentoring-table-column.html'
,
{
'self'
:
self
,
'named_children'
:
named_children
,
}))
...
...
@@ -115,7 +115,7 @@ class MentoringTableColumnBlock(LightChild):
fragment
,
named_children
=
self
.
get_children_fragment
(
context
,
view_name
=
'mentoring_table_header_view'
,
instance_of
=
MentoringTableColumnHeaderBlock
)
fragment
.
add_content
(
render_template
(
'templates/html/mentoring-table-header.html'
,
{
fragment
.
add_content
(
render_
js_
template
(
'templates/html/mentoring-table-header.html'
,
{
'self'
:
self
,
'named_children'
:
named_children
,
}))
...
...
mentoring/templates/html/mentoring.html
View file @
a4f9ec66
<div
class=
"mentoring"
>
<div
class=
"mentoring"
data-mode=
"{{ self.mode }}"
data-step=
"{{ self.step }}"
>
<div
class=
"missing-dependency warning"
data-missing=
"{{ self.has_missing_dependency }}"
>
You need to complete
<a
href=
"{{ missing_dependency_url }}"
>
the previous step
</a>
before
attempting this step.
...
...
@@ -13,8 +13,27 @@
{{c.body_html|safe}}
{% endfor %}
{% if self.display_submit %}
<div
class=
"grade"
data-score=
"{{ self.score.1 }}"
data-correct_answer=
"{{ self.score.2 }}"
data-incorrect_answer=
"{{ self.score.3 }}"
data-max_attempts=
"{{ self.max_attempts }}"
data-num_attempts=
"{{ self.num_attempts }}"
>
</div>
<div
class=
"submit"
>
{% if self.mode == 'assessment' %}
<span
class=
"assessment-checkmark icon-2x"
></span>
{% endif %}
<input
type=
"button"
class=
"input-main"
value=
"Submit"
disabled=
"disabled"
></input>
{% if self.mode == 'assessment' %}
<input
type=
"button"
class=
"input-next"
value=
"Next Question"
disabled=
"disabled"
></input>
<input
type=
"button"
class=
"input-review"
value=
"Review grade"
disabled=
"disabled"
></input>
<input
type=
"button"
class=
"input-try-again"
value=
"Try again"
disabled=
"disabled"
></input>
{% endif %}
<div
class=
"attempts"
data-max_attempts=
"{{ self.max_attempts }}"
data-num_attempts=
"{{ self.num_attempts }}"
></div>
</div>
{% endif %}
...
...
mentoring/templates/html/mentoring_grade.html
0 → 100644
View file @
a4f9ec66
<script
type=
"text/template"
id=
"xblock-grade-template"
>
<%
if
(
_
.
isNumber
(
max_attempts
)
&&
max_attempts
>
0
&&
num_attempts
>=
max_attempts
)
{{
%>
<
p
>
Note
:
you
have
used
all
attempts
.
Continue
to
the
next
unit
.
<
/p
>
<%
}}
else
{{
%>
<
p
>
Note
:
if
you
retake
this
assessment
,
only
your
final
score
counts
.
<
/p
>
<%
}}
%>
<
h2
>
You
scored
<%=
score
%>%
on
this
assessment
.
<
/h2
>
<
hr
/>
<
span
class
=
"assessment-checkmark icon-2x checkmark-correct icon-ok fa-check"
><
/span
>
<
p
>
You
answered
<%=
correct_answer
%>
questions
correctly
.
<
/p
>
<
span
class
=
"assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa-exclamation"
><
/span
>
<
p
>
You
answered
<%=
incorrect_answer
%>
questions
incorrectly
.
<
/p
>
</script>
mentoring/templates/xml/mentoring_default.xml
View file @
a4f9ec66
<mentoring
url_name=
"{{ url_name }}"
display_name=
"Nav tooltip title"
weight=
"1"
>
<mentoring
url_name=
"{{ url_name }}"
display_name=
"Nav tooltip title"
weight=
"1"
mode=
"standard"
>
<title>
Default Title
</title>
<html>
<p>
What is your goal?
</p>
...
...
mentoring/utils.py
View file @
a4f9ec66
...
...
@@ -57,6 +57,16 @@ def render_template(template_path, context={}):
return
template
.
render
(
Context
(
context
))
def
render_js_template
(
template_path
,
context
=
{},
id
=
'light-child-template'
):
"""
Render a js template.
"""
return
u"<script type='text/template' id='{}'>
\n
{}
\n
</script>"
.
format
(
id
,
render_template
(
template_path
,
context
)
)
def
list2csv
(
row
):
"""
Convert a list to a CSV string (single row)
...
...
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