Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 4e346cd

Browse files
authored
Merge pull request #1328 from amy21206/master
Adding block-based feedback and syntax highlighting for horizontal Parsons problems
2 parents 1730d2d + 588bc14 commit 4e346cd

File tree

10 files changed

+5851
-472
lines changed

10 files changed

+5851
-472
lines changed

runestone/hparsons/css/hljs-xcode.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runestone/hparsons/hparsons.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ def setup(app):
4646
TEMPLATE_END = """
4747
</div>
4848
<div class='hparsons'></div>
49-
<textarea data-lang="%(language)s"
49+
<textarea
50+
%(language)s
5051
%(optional)s
5152
%(dburl)s
52-
%(textentry)s
5353
%(reuse)s
5454
%(randomize)s
55+
%(blockanswer)s
5556
style="visibility: hidden;">
5657
%(initialsetting)s
5758
</textarea>
@@ -91,25 +92,21 @@ def depart_hp_html(self, node):
9192

9293

9394
class HParsonsDirective(RunestoneIdDirective):
94-
# only keep: language, autograde, dburl
9595
"""
9696
.. hparsons:: uniqueid
97-
:language: sql, regex
97+
:language: python, java, javscript, sql, html: only for highlighting purpose.
9898
:dburl: only for sql -- url to load database
9999
:randomize: randomize the order of horizontal parsons
100-
TODO: fix textentry
101100
:reuse: only for parsons -- make the blocks reusable
102-
:textentry: if you will use text entry instead of horizontal parsons
101+
:blockanswer: 0 1 2 3 # Provide answer for block-based feedback. Please note that the number of block start from 0. If not provided, will use execution based feedback.
103102
104103
Here is the problem description. It must ends with the tildes.
105104
Make sure you use the correct delimitier for each section below.
106105
~~~~
107106
--blocks--
108107
block 1
109108
block 2
110-
--explanations--
111-
explanations for block 1
112-
explanations for block 2
109+
block 3
113110
--unittest--
114111
assert 1,1 == world
115112
assert 0,1 == hello
@@ -124,9 +121,9 @@ class HParsonsDirective(RunestoneIdDirective):
124121
{
125122
"dburl": directives.unchanged,
126123
"language": directives.unchanged,
127-
"textentry": directives.flag,
128124
"reuse": directives.flag,
129125
"randomize": directives.flag,
126+
"blockanswer": directives.unchanged,
130127
}
131128
)
132129

@@ -135,10 +132,10 @@ def run(self):
135132

136133
env = self.state.document.settings.env
137134

138-
if "textentry" in self.options:
139-
self.options['textentry'] = ' data-textentry="true"'
135+
if "language" in self.options:
136+
self.options["language"] = "data-language='{}'".format(self.options["language"])
140137
else:
141-
self.options['textentry'] = ''
138+
self.options["language"] = ""
142139

143140
if "reuse" in self.options:
144141
self.options['reuse'] = ' data-reuse="true"'
@@ -150,6 +147,11 @@ def run(self):
150147
else:
151148
self.options['randomize'] = ''
152149

150+
if "blockanswer" in self.options:
151+
self.options["blockanswer"] = "data-blockanswer='{}'".format(self.options["blockanswer"])
152+
else:
153+
self.options['blockanswer'] = ''
154+
153155
explain_text = None
154156
if self.content:
155157
if "~~~~" in self.content:
@@ -165,10 +167,6 @@ def run(self):
165167

166168
self.options["initialsetting"] = source
167169

168-
# TODO: change this
169-
if "language" not in self.options:
170-
self.options["language"] = "python"
171-
172170
# SQL Options
173171
if "dburl" in self.options:
174172
self.options["dburl"] = "data-dburl='{}'".format(self.options["dburl"])
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import HParsonsFeedback from "./hparsonsFeedback";
2+
import BlockBasedGrader from "./blockGrader.js";
3+
import "../../parsons/js/parsons-i18n.en.js";
4+
import "../../parsons/js/parsons-i18n.pt-br.js";
5+
6+
export default class BlockFeedback extends HParsonsFeedback {
7+
createOutput() {
8+
// Block based grading output
9+
this.messageDiv = document.createElement("div");
10+
this.hparsons.outerDiv.appendChild(this.messageDiv);
11+
}
12+
customizeUI() {
13+
$(this.hparsons.runButton).text('Check Me');
14+
}
15+
16+
init() {
17+
this.checkCount = 0;
18+
this.solved = false;
19+
// TODO: not sure what is the best way to do this
20+
this.grader = new BlockBasedGrader();
21+
let solutionBlocks = [];
22+
for (let i = 0; i < this.hparsons.blockAnswer.length; ++i) {
23+
solutionBlocks.push(this.hparsons.originalBlocks[this.hparsons.blockAnswer[i]]);
24+
}
25+
this.solution = solutionBlocks;
26+
this.grader.solution = solutionBlocks;
27+
this.answerArea = this.hparsons.hparsonsInput.querySelector('.drop-area');
28+
}
29+
30+
// Called when check button clicked (block-based Feedback)
31+
async runButtonHandler() {
32+
this.checkCurrentAnswer();
33+
this.renderFeedback();
34+
}
35+
36+
// Used for block-based feedback
37+
checkCurrentAnswer() {
38+
if (!this.solved) {
39+
this.checkCount++;
40+
this.clearFeedback();
41+
this.grader.answer = this.hparsons.hparsonsInput.getParsonsTextArray();
42+
this.grade = this.grader.grade();
43+
if (this.grade == "correct") {
44+
$(this.hparsons.runButton).prop("disabled", true);
45+
this.solved = true;
46+
}
47+
}
48+
}
49+
50+
renderFeedback() {
51+
this.grade = this.grader.graderState;
52+
var feedbackArea;
53+
var answerArea = $(this.answerArea);
54+
feedbackArea = $(this.messageDiv);
55+
56+
if (this.grade === "correct") {
57+
answerArea.addClass("correct");
58+
feedbackArea.fadeIn(100);
59+
feedbackArea.attr("class", "alert alert-info");
60+
if (this.checkCount > 1) {
61+
feedbackArea.html(
62+
$.i18n("msg_parson_correct", this.checkCount)
63+
);
64+
} else {
65+
feedbackArea.html($.i18n("msg_parson_correct_first_try"));
66+
}
67+
this.checkCount = 0;
68+
}
69+
70+
if (this.grade === "incorrectTooShort") {
71+
// too little code
72+
answerArea.addClass("incorrect");
73+
feedbackArea.fadeIn(500);
74+
feedbackArea.attr("class", "alert alert-danger");
75+
feedbackArea.html($.i18n("msg_parson_too_short"));
76+
}
77+
78+
if (this.grade === "incorrectMoveBlocks") {
79+
var answerBlocks = this.answerArea.children;
80+
var inSolution = [];
81+
var inSolutionIndexes = [];
82+
var notInSolution = [];
83+
for (let i = 0; i < answerBlocks.length; i++) {
84+
var block = answerBlocks[i];
85+
var index = this.solution.indexOf(block.textContent);
86+
if (index == -1) {
87+
notInSolution.push(block);
88+
} else {
89+
inSolution.push(block);
90+
inSolutionIndexes.push(index);
91+
}
92+
}
93+
var lisIndexes = this.grader.inverseLISIndices(inSolutionIndexes);
94+
for (let i = 0; i < lisIndexes.length; i++) {
95+
notInSolution.push(inSolution[lisIndexes[i]]);
96+
}
97+
answerArea.addClass("incorrect");
98+
feedbackArea.fadeIn(500);
99+
feedbackArea.attr("class", "alert alert-danger");
100+
for (let i = 0; i < notInSolution.length; i++) {
101+
$(notInSolution[i]).addClass("incorrectPosition");
102+
}
103+
feedbackArea.html($.i18n("msg_parson_wrong_order"));
104+
}
105+
}
106+
107+
// Feedback UI for Block-based Feedback
108+
clearFeedback() {
109+
$(this.answerArea).removeClass("incorrect correct");
110+
var children = this.answerArea.childNodes;
111+
for (var i = 0; i < children.length; i++) {
112+
$(children[i]).removeClass(
113+
"correctPosition incorrectPosition"
114+
);
115+
}
116+
$(this.messageDiv).hide();
117+
}
118+
119+
reset() {
120+
if (this.solved) {
121+
this.checkCount = 0;
122+
$(this.hparsons.runButton).prop("disabled", false);
123+
this.solved = false;
124+
}
125+
this.clearFeedback();
126+
}
127+
128+
}

0 commit comments

Comments
 (0)