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

Commit f216f9f

Browse files
committed
fix conflict with origin
2 parents 5d717f9 + 4057bfe commit f216f9f

3 files changed

Lines changed: 180 additions & 43 deletions

File tree

runestone/common/project_template/_templates/plugin_layouts/sphinx_bootstrap/layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,4 +388,4 @@
388388

389389
<script> runestoneComponents.getSwitch(); </script>
390390

391-
{% endblock %}
391+
{% endblock %}

runestone/selectquestion/js/selectone.js

Lines changed: 158 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export default class SelectOne extends RunestoneBase {
3939
this.selector_id = $(opts.orig).first().attr("id");
4040
this.primaryOnly = $(opts.orig).data("primary");
4141
this.ABExperiment = $(opts.orig).data("ab");
42-
this.toggle = $(opts.orig).data("toggle");
42+
this.toggleOptions = $(opts.orig).data("toggleoptions");
43+
this.toggleLabels = $(opts.orig).data("togglelabels");
4344
opts.orig.id = this.selector_id;
4445
}
4546
/**
@@ -81,8 +82,11 @@ export default class SelectOne extends RunestoneBase {
8182
if (this.timedWrapper) {
8283
data.timedWrapper = this.timedWrapper;
8384
}
84-
if (this.toggle) {
85-
data.toggle = this.toggle;
85+
if (this.toggleOptions) {
86+
data.toggleOptions = this.toggleOptions;
87+
}
88+
if (this.toggleLabels) {
89+
data.toggleLabels = this.toggleLabels;
8690
}
8791
let opts = this.origOpts;
8892
let selectorId = this.selector_id;
@@ -121,14 +125,25 @@ export default class SelectOne extends RunestoneBase {
121125
self.containerDiv = res.question.containerDiv;
122126
self.realComponent.selectorId = selectorId;
123127
} else {
124-
///////////////////////////
125-
if (data.toggle) {
128+
if (data.toggleOptions) {
129+
var toggleLabels = data.toggleLabels.replace("togglelabels:", "").trim();
130+
if (toggleLabels) {
131+
toggleLabels = toggleLabels.split(",");
132+
for (var t = 0; t < toggleLabels.length; t++) {
133+
toggleLabels[t] = toggleLabels[t].trim();
134+
}
135+
}
126136
var toggleQuestions = this.questions.split(", ");
127137
var toggleUI = "";
138+
// check so that only the first toggle select question on the assignments page has a preview panel created, then all toggle select previews use this same panel
128139
if (!document.getElementById("component-preview")) {
129140
toggleUI +=
130-
'<div id="component-preview" class="col-md-6 toggle-preview" style="z-index: 999;"></div>';
141+
'<div id="component-preview" class="col-md-6 toggle-preview" style="z-index: 999;">' +
142+
'<div id="toggle-buttons"></div>' +
143+
'<div id="toggle-preview"></div>' +
144+
'</div>';
131145
}
146+
// dropdown menu containing the question options
132147
toggleUI +=
133148
'<label for="' +
134149
selectorId +
@@ -139,6 +154,7 @@ export default class SelectOne extends RunestoneBase {
139154
var toggleQuestionHTMLSrc;
140155
var toggleQuestionSubstring;
141156
var toggleQuestionType;
157+
var toggleQuestionTypes = [];
142158
for (i = 0; i < toggleQuestions.length; i++) {
143159
toggleQuestionHTMLSrc = await this.getToggleSrc(
144160
toggleQuestions[i]
@@ -147,13 +163,13 @@ export default class SelectOne extends RunestoneBase {
147163
'data-component="'
148164
)[1];
149165
switch (
150-
toggleQuestionSubstring.substring(
166+
toggleQuestionSubstring.slice(
151167
0,
152168
toggleQuestionSubstring.indexOf('"')
153169
)
154170
) {
155171
case "activecode":
156-
toggleQuestionType = "Active Code";
172+
toggleQuestionType = "Active Write Code";
157173
break;
158174
case "clickablearea":
159175
toggleQuestionType = "Clickable Area";
@@ -168,20 +184,36 @@ export default class SelectOne extends RunestoneBase {
168184
toggleQuestionType = "Multiple Choice";
169185
break;
170186
case "parsons":
171-
toggleQuestionType = "Parsons";
187+
toggleQuestionType = "Parsons Mixed-Up Code";
172188
break;
173189
case "shortanswer":
174190
toggleQuestionType = "Short Answer";
175191
break;
176192
}
193+
toggleQuestionTypes[i] = toggleQuestionType;
177194
toggleUI +=
178195
'<option value="' +
179196
toggleQuestions[i] +
180-
'">' +
181-
toggleQuestionType +
197+
'">';
198+
if (toggleLabels) {
199+
if (toggleLabels[i]) {
200+
toggleUI += toggleLabels[i];
201+
}
202+
else {
203+
toggleUI += toggleQuestionType +
204+
" - " +
205+
toggleQuestions[i];
206+
}
207+
}
208+
else {
209+
toggleUI += toggleQuestionType +
182210
" - " +
183-
toggleQuestions[i] +
184-
"</option>";
211+
toggleQuestions[i];
212+
}
213+
if ((i == 0) && (data.toggleOptions.includes("lock"))) {
214+
toggleUI += " (only this question will be graded)";
215+
}
216+
toggleUI += "</option>";
185217
}
186218
toggleUI +=
187219
'</select><div id="' +
@@ -191,14 +223,12 @@ export default class SelectOne extends RunestoneBase {
191223
toggleFirstID = toggleFirstID.split('"')[0];
192224
htmlsrc = toggleUI + htmlsrc + "</div>";
193225
}
194-
///////////////////////////
195226
// just render this component on the page in its usual place
196227
await renderRunestoneComponent(htmlsrc, selectorId, {
197228
selector_id: selectorId,
198229
useRunestoneServices: true,
199230
});
200-
///////////////////////////
201-
if (data.toggle) {
231+
if (data.toggleOptions) {
202232
$("#component-preview").hide();
203233
var toggleQuestionSelect = document.getElementById(
204234
selectorId + "-toggleQuestion"
@@ -212,23 +242,29 @@ export default class SelectOne extends RunestoneBase {
212242
"toggle_current",
213243
toggleFirstID
214244
);
245+
$("#" + selectorId).data(
246+
"toggle_current_type",
247+
toggleQuestionTypes[0]
248+
);
215249
break;
216250
}
217251
}
218252
toggleQuestionSelect.addEventListener(
219253
"change",
220254
async function () {
221255
await this.togglePreview(
222-
toggleQuestionSelect.parentElement.id
256+
toggleQuestionSelect.parentElement.id,
257+
data.toggleOptions,
258+
toggleQuestionTypes
223259
);
224260
}.bind(this)
225261
);
226262
}
227-
///////////////////////////
228263
}
229264
return response;
230265
}
231266

267+
// retrieve html source of a question, for use in various toggle functionalities
232268
async getToggleSrc(toggleQuestionID) {
233269
let request = new Request(
234270
"/runestone/admin/htmlsrc?acid=" + toggleQuestionID,
@@ -241,47 +277,71 @@ export default class SelectOne extends RunestoneBase {
241277
return htmlsrc;
242278
}
243279

244-
async togglePreview(parentID) {
280+
// on changing the value of toggle select dropdown, render selected question in preview panel, add appropriate buttons, then make preview panel visible
281+
async togglePreview(parentID, toggleOptions, toggleQuestionTypes) {
282+
$("#toggle-buttons").html("");
245283
var parentDiv = document.getElementById(parentID);
246284
var toggleQuestionSelect = parentDiv.getElementsByTagName("select")[0];
247285
var selectedQuestion =
248286
toggleQuestionSelect.options[toggleQuestionSelect.selectedIndex]
249287
.value;
250288
var htmlsrc = await this.getToggleSrc(selectedQuestion);
251-
let res = renderRunestoneComponent(htmlsrc, "component-preview", {
252-
selector_id: "component-preview",
289+
renderRunestoneComponent(htmlsrc, "toggle-preview", {
290+
selector_id: "toggle-preview",
253291
useRunestoneServices: true,
254292
});
255-
// let pd = document.getElementById(preview_div);
256-
// pd.appendChild(renderGradingComponents(sid, selectedQuestion));
257293

294+
// add "Close Preview" button to the preview panel
258295
let closeButton = document.createElement("button");
259296
$(closeButton).text("Close Preview");
260297
$(closeButton).addClass("btn btn-default");
261298
$(closeButton).click(function (event) {
262-
$("#component-preview").html("");
299+
$("#toggle-preview").html("");
263300
toggleQuestionSelect.value = $("#" + parentID).data(
264301
"toggle_current"
265302
);
266303
$("#component-preview").hide();
267304
});
268-
$("#component-preview").append(closeButton);
305+
$("#toggle-buttons").append(closeButton);
306+
307+
// if "lock" is not in toggle options, then allow adding more buttons to the preview panel
308+
if (!(toggleOptions.includes("lock"))) {
309+
let setButton = document.createElement("button");
310+
$(setButton).text("Select this Problem");
311+
$(setButton).addClass("btn btn-primary");
312+
$(setButton).click(
313+
async function () {
314+
await this.toggleSet(parentID, selectedQuestion, htmlsrc, toggleQuestionTypes);
315+
$("#component-preview").hide();
316+
}.bind(this)
317+
);
318+
$("#toggle-buttons").append(setButton);
319+
320+
// if "transfer" in toggle options, and if current question type is Parsons and selected question type is active code, then add "Transfer" button to preview panel
321+
if (toggleOptions.includes("transfer")) {
322+
var currentType = $("#" + parentID).data("toggle_current_type");
323+
var selectedType = toggleQuestionTypes[toggleQuestionSelect.selectedIndex];
324+
if ((currentType == "Parsons Mixed-Up Code") && (selectedType == "Active Write Code")) {
325+
let transferButton = document.createElement("button");
326+
$(transferButton).text("Transfer Response");
327+
$(transferButton).addClass("btn btn-primary");
328+
$(transferButton).click(
329+
async function () {
330+
await this.toggleTransfer(parentID, selectedQuestion, htmlsrc, toggleQuestionTypes);
331+
}.bind(this)
332+
);
333+
$("#toggle-buttons").append(transferButton);
334+
}
335+
}
336+
}
269337

270-
let setButton = document.createElement("button");
271-
$(setButton).text("Select this Problem");
272-
$(setButton).addClass("btn btn-primary");
273-
$(setButton).click(
274-
async function () {
275-
await this.toggleSet(parentID, selectedQuestion, htmlsrc);
276-
$("#component-preview").hide();
277-
}.bind(this)
278-
);
279-
$("#component-preview").append(setButton);
280338
$("#component-preview").show();
281339
}
282340

283-
async toggleSet(parentID, selectedQuestion, htmlsrc) {
341+
// on clicking "Select this Problem" button, close preview panel, replace current question in assignments page with selected question, and send request to update grading database
342+
async toggleSet(parentID, selectedQuestion, htmlsrc, toggleQuestionTypes) {
284343
var selectorId = parentID + "-toggleSelectedQuestion";
344+
var toggleQuestionSelect = document.getElementById(parentID).getElementsByTagName("select")[0];
285345
document.getElementById(selectorId).innerHTML = ""; // need to check whether this is even necessary
286346
await renderRunestoneComponent(htmlsrc, selectorId, {
287347
selector_id: selectorId,
@@ -295,8 +355,69 @@ export default class SelectOne extends RunestoneBase {
295355
{}
296356
);
297357
await fetch(request);
298-
$("#component-preview").html("");
358+
$("#toggle-preview").html("");
299359
$("#" + parentID).data("toggle_current", selectedQuestion);
360+
$("#" + parentID).data("toggle_current_type", toggleQuestionTypes[toggleQuestionSelect.selectedIndex]);
361+
}
362+
363+
// on clicking "Transfer" button, extract the current text and indentation of the Parsons blocks in the answer space, then paste that into the selected active code question
364+
async toggleTransfer(parentID, selectedQuestion, htmlsrc, toggleQuestionTypes) {
365+
// retrieve all Parsons lines within the answer space and loop through this list
366+
var currentParsons = document.getElementById(parentID + "-toggleSelectedQuestion").querySelectorAll("div[class^='answer']")[0].getElementsByClassName("prettyprint lang-py");
367+
var currentParsonsClass;
368+
var currentBlockIndent;
369+
var indentCount
370+
var indent;
371+
var parsonsLine;
372+
var parsonsLines = ``;
373+
var count;
374+
for (var p = 0; p < currentParsons.length; p++) {
375+
indentCount = 0;
376+
indent = "";
377+
// for Parsons blocks that have built-in indentation in their lines
378+
currentParsonsClass = currentParsons[p].classList[2];
379+
if (currentParsonsClass) {
380+
if (currentParsonsClass.includes("indent")) {
381+
indentCount = parseInt(indentCount) + parseInt(currentParsonsClass.slice(6,currentParsonsClass.length));
382+
}
383+
}
384+
// for Parsons answer spaces with vertical lines that allow student to define their own line indentation
385+
currentBlockIndent = currentParsons[p].parentElement.parentElement.style.left;
386+
if (currentBlockIndent) {
387+
indentCount = parseInt(indentCount) + parseInt(currentBlockIndent.slice(0,currentBlockIndent.indexOf("px")) / 30);
388+
}
389+
for (var d = 0; d < indentCount; d++) {
390+
indent += " ";
391+
}
392+
// retrieve each text snippet of each Parsons line and loop through this list
393+
parsonsLine = currentParsons[p].getElementsByTagName("span");
394+
count = 0;
395+
for (var l = 0; l < parsonsLine.length; l++) {
396+
if (parsonsLine[l].childNodes[0].nodeName == "#text") { // Parsons blocks have differing amounts of hierarchy levels (spans within spans)
397+
if ((p == 0) && (count == 0)) { // need different check than l == 0 because the l numbering doesn't align with location within line due to inconsistent span heirarchy
398+
parsonsLines += indent + parsonsLine[l].innerHTML;
399+
count++;
400+
}
401+
else if (count != 0) {
402+
parsonsLines += parsonsLine[l].innerHTML;
403+
count++;
404+
}
405+
else {
406+
parsonsLines = parsonsLines + `
407+
` + indent + parsonsLine[l].innerHTML;
408+
parsonsLines = parsonsLines.replace(" ", "");
409+
count++;
410+
}
411+
}
412+
}
413+
}
414+
// replace all existing code within selected active code question with extracted Parsons text
415+
var htmlsrcFormer = htmlsrc.slice(0, htmlsrc.indexOf("<textarea") + htmlsrc.split("<textarea")[1].indexOf(">") + 10);
416+
var htmlsrcLatter = htmlsrc.slice(htmlsrc.indexOf("</textarea>"), htmlsrc.length);
417+
htmlsrc = htmlsrcFormer + parsonsLines + htmlsrcLatter;
418+
419+
await this.toggleSet(parentID, selectedQuestion, htmlsrc, toggleQuestionTypes);
420+
$("#component-preview").hide();
300421
}
301422
}
302423

runestone/selectquestion/selectone.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
TEMPLATE = """
5050
<div class="runestone alert alert-warning sqcontainer">
51-
<div data-component="selectquestion" id={component_id} {selector} {points} {proficiency} {min_difficulty} {max_difficulty} {autogradable} {not_seen_ever} {primary} {AB} {toggle}>
51+
<div data-component="selectquestion" id={component_id} {selector} {points} {proficiency} {min_difficulty} {max_difficulty} {autogradable} {not_seen_ever} {primary} {AB} {toggle_options} {toggle_labels}>
5252
<p>Loading ...</p>
5353
</div>
5454
</div>
@@ -72,6 +72,7 @@ class SelectQuestion(RunestoneIdDirective):
7272
:max_difficulty: maximum difficulty level
7373
:ab: experiment_name
7474
:toggle: allow student to choose which question to answer from the given list, with first question in fromid list being rendered first
75+
:togglelabels: relabel each question in the fromid list according to input here; blank will revert to a default label
7576
7677
Difficulty is measured in one of two ways. For things like multiple choice and
7778
fill in the blank, we can use the % of students that get the answer correct on
@@ -94,7 +95,8 @@ class SelectQuestion(RunestoneIdDirective):
9495
"not_seen_ever": directives.flag,
9596
"primary": directives.flag,
9697
"ab": directives.unchanged,
97-
"toggle": directives.flag,
98+
"toggle": directives.unchanged,
99+
"togglelabels": directives.unchanged,
98100
}
99101
)
100102

@@ -182,10 +184,24 @@ def run(self):
182184
else:
183185
self.options["AB"] = ""
184186

185-
if "toggle" in self.options:
186-
self.options["toggle"] = "data-toggle=true"
187+
if ("toggle" in self.options) or ("togglelabels" in self.options):
188+
self.options[
189+
"toggle_options"
190+
] = "data-toggleoptions=\"toggle\""
191+
self.options[
192+
"toggle_labels"
193+
] = "data-togglelabels=\"togglelabels:\""
194+
if "toggle" in self.options:
195+
self.options[
196+
"toggle_options"
197+
] = "data-toggleoptions=\"toggle, " + f"{self.options['toggle']}" + "\""
198+
if "togglelabels" in self.options:
199+
self.options[
200+
"toggle_labels"
201+
] = "data-togglelabels=\"togglelabels: " + f"{self.options['togglelabels']}" + "\""
187202
else:
188-
self.options["toggle"] = ""
203+
self.options["toggle_options"] = ""
204+
self.options["toggle_labels"] = ""
189205

190206
maybeAddToAssignment(self)
191207

0 commit comments

Comments
 (0)