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

Commit 646d617

Browse files
committed
Merge branch 'master' of github.com:riknos314/RunestoneComponents
bringing up to date
2 parents 864fc2c + 38a41eb commit 646d617

File tree

22 files changed

+2238
-1255
lines changed

22 files changed

+2238
-1255
lines changed

runestone/clickableArea/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<h2>Clickable Area</h2>
2+
3+
```html
4+
<pre data-component="clickablearea" id="clickable1">
5+
<span data-question>Click on all variable assignment statements</span><span data-feedback>Remember, variable assignment statements usually involve the operator '='.</span>def main():
6+
<span data-incorrect>print("Hello world")</span>
7+
<span data-correct>x = 4</span>
8+
for i in range(5):
9+
<span data-correct>y = i</span>
10+
print(y)
11+
<span data-incorrect>return 0</span>
12+
</pre>
13+
```
14+
Here the <code>pre</code> tag represents the entire component to be rendered.
15+
Each area that the author would like to be clickable is wrapped in a <code>span</code> tag that has the <code>data-correct</code> or <code>data-incorrect</code> attribute.
16+
After specifying the data-component, question and feedback (optional), the author can start his/her block of code, indented as he/he would like, using <code>span</code> elements to identify the clickable parts of the code.
17+
18+
Option spec:
19+
<ul>
20+
<li><code>data-component="clickablearea"</code> identifies this as a clickable area component</li>
21+
<li><code>id</code> Must be unique in the document</li>
22+
<li><code>data-question</code> Identifies a <code>span</code> element that contains the question associated with the component</li>
23+
<li><code>data-feedback</code> Optional--identifies a <code>span</code> that contains the feedback that is displayed when someone answers incorrectly</li>
24+
<li><code>data-correct</code> Identifies a correct clickable area</li>
25+
<li><code>data-incorrect</code> Identifies an incorrect clickable area</li>
26+
</ul>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright (C) 2011 Bradley N. Miller
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
#
16+
17+
__author__ = 'isaiahmayerchak'
18+
19+
from docutils import nodes
20+
from docutils.parsers.rst import directives
21+
from docutils.parsers.rst import Directive
22+
23+
def setup(app):
24+
app.add_directive('clickablearea',ClickableArea)
25+
app.add_javascript('clickable.js')
26+
app.add_stylesheet('clickable.css')
27+
28+
app.add_node(ClickableAreaNode, html=(visit_ca_node, depart_ca_node))
29+
30+
31+
TEMPLATE = """
32+
<pre data-component="clickablearea" id="%(divid)s">
33+
<span data-question>%(question)s</span>%(feedback)s%(clickcode)s
34+
</pre>
35+
"""
36+
37+
38+
class ClickableAreaNode(nodes.General, nodes.Element):
39+
def __init__(self,content):
40+
"""
41+
Arguments:
42+
- `self`:
43+
- `content`:
44+
"""
45+
super(ClickableAreaNode,self).__init__()
46+
self.ca_options = content
47+
48+
# self for these functions is an instance of the writer class. For example
49+
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
50+
# The node that is passed as a parameter is an instance of our node class.
51+
def visit_ca_node(self,node):
52+
res = TEMPLATE
53+
54+
if "feedback" in node.ca_options:
55+
node.ca_options["feedback"] = "<span data-feedback>" + node.ca_options["feedback"] + "</span>"
56+
else:
57+
node.ca_options["feedback"] = ""
58+
59+
res = res % node.ca_options
60+
61+
self.body.append(res)
62+
63+
def depart_ca_node(self,node):
64+
pass
65+
66+
67+
class ClickableArea(Directive):
68+
required_arguments = 1
69+
optional_arguments = 0
70+
has_content = True
71+
option_spec = {"question":directives.unchanged,
72+
"feedback":directives.unchanged
73+
}
74+
75+
def run(self):
76+
"""
77+
process the multiplechoice directive and generate html for output.
78+
:param self:
79+
:return:
80+
.. clickablearea:: identifier
81+
:question: Question text
82+
83+
--Content--
84+
"""
85+
self.options['divid'] = self.arguments[0]
86+
87+
if self.content:
88+
source = "\n".join(self.content)
89+
else:
90+
source = '\n'
91+
source = source.replace(":click-correct:", "<span data-correct>")
92+
source = source.replace(":click-incorrect:", "<span data-incorrect>")
93+
source = source.replace(":endclick:", "</span>")
94+
self.options['clickcode'] = source
95+
96+
clickNode = ClickableAreaNode(self.options)
97+
clickNode.template_start = TEMPLATE
98+
99+
return [clickNode]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.clickable {
2+
cursor: pointer;
3+
}
4+
5+
.clickable-clicked {
6+
background-color: yellow;
7+
}
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*==========================================
2+
======= Master clickable.js ========
3+
============================================
4+
=== This file contains the JS for the ===
5+
=== Runestone clickable area component. ===
6+
============================================
7+
=== Created by ===
8+
=== Isaiah Mayerchak ===
9+
=== 7/1/15 ===
10+
==========================================*/
11+
function RunestoneBase () { // Basic parent stuff
12+
13+
}
14+
RunestoneBase.prototype.logBookEvent = function (info) {
15+
console.log("logging event " + this.divid);
16+
};
17+
RunestoneBase.prototype.logRunEvent = function (info) {
18+
console.log("running " + this.divid);
19+
};
20+
21+
var CAList = {}; // Dictionary that contains all instances of ClickableArea objects
22+
23+
24+
function ClickableArea (opts) {
25+
if (opts) {
26+
this.init(opts);
27+
}
28+
}
29+
30+
ClickableArea.prototype = new RunestoneBase();
31+
32+
/*=============================================
33+
== Initialize basic ClickableArea attributes ==
34+
=============================================*/
35+
ClickableArea.prototype.init = function (opts) {
36+
RunestoneBase.apply(this, arguments);
37+
var orig = opts.orig; // entire <pre> element that will be replaced by new HTML
38+
this.origElem = orig;
39+
this.divid = orig.id;
40+
41+
this.correctArray = []; // holds the IDs of all correct clickable span elements, used for eval
42+
this.incorrectArray = []; // holds IDs of all incorrect clickable span elements, used for eval
43+
44+
this.getQuestion();
45+
this.getFeedback();
46+
this.renderNewElements();
47+
};
48+
49+
/*===========================
50+
== Update basic attributes ==
51+
===========================*/
52+
53+
ClickableArea.prototype.getQuestion = function () {
54+
for (var i = 0; i < this.origElem.childNodes.length; i++) {
55+
if ($(this.origElem.childNodes[i]).is("[data-question]")) {
56+
this.question = this.origElem.childNodes[i];
57+
break;
58+
}
59+
}
60+
};
61+
62+
ClickableArea.prototype.getFeedback = function () {
63+
this.feedback = "";
64+
for (var i = 0; i < this.origElem.childNodes.length; i++) {
65+
if ($(this.origElem.childNodes[i]).is("[data-feedback]")) {
66+
this.feedback = this.origElem.childNodes[i];
67+
}
68+
}
69+
if (this.feedback !== "") { // Get the feedback element out of the <pre> if the user has defined feedback
70+
$(this.feedback).remove();
71+
this.feedback = this.feedback.innerHTML;
72+
}
73+
};
74+
75+
/*============================================
76+
== Check local storage and replace old HTML ==
77+
== with our new elements that don't have ==
78+
== data-correct/data-incorrect attributes ==
79+
============================================*/
80+
81+
ClickableArea.prototype.renderNewElements = function () {
82+
this.containerDiv = document.createElement("div");
83+
this.containerDiv.appendChild(this.question);
84+
$(this.containerDiv).addClass("alert alert-warning");
85+
86+
this.newPre = document.createElement("pre");
87+
this.newPre.innerHTML = $(this.origElem).html();
88+
this.containerDiv.appendChild(this.newPre);
89+
90+
this.checkLocalStorage();
91+
this.createButtons();
92+
this.createFeedbackDiv();
93+
94+
$(this.origElem).replaceWith(this.containerDiv);
95+
96+
};
97+
98+
ClickableArea.prototype.checkLocalStorage = function () {
99+
this.hasStoredAnswers = false;
100+
var len = localStorage.length;
101+
if (len > 0) {
102+
var ex = localStorage.getItem(eBookConfig.email + ":" + this.divid + "-given");
103+
if (ex !== null) {
104+
this.hasStoredAnswers = true;
105+
this.clickedIndexArray = ex.split(";");
106+
}
107+
}
108+
this.replaceSpanElements();
109+
};
110+
111+
ClickableArea.prototype.replaceSpanElements = function () {
112+
this.clickableArray = [];
113+
this.clickIndex = 0; // Index of this.clickedIndexArray that we're checking against
114+
this.clickableCounter = 0; // Index of the current clickable <span>
115+
for (var i = 0; i < this.newPre.childNodes.length; i++) {
116+
if ($(this.newPre.childNodes[i]).is("[data-correct]") || $(this.newPre.childNodes[i]).is("[data-incorrect]")) {
117+
118+
var replaceSpan = document.createElement("span"); // our new <span> that doesn't have the obvious data-correct/data-incorrect attribute
119+
replaceSpan.innerHTML = this.newPre.childNodes[i].innerHTML;
120+
$(replaceSpan).addClass("clickable");
121+
122+
if (this.hasStoredAnswers) { // Check if the span we're about to append to the pre was in local storage as clicked via its index
123+
if (this.clickedIndexArray[this.clickIndex].toString() === this.clickableCounter.toString()) {
124+
$(replaceSpan).addClass("clickable-clicked");
125+
this.clickIndex++;
126+
if (this.clickIndex === this.clickedIndexArray.length) { // Stop checking this if the index array is used up
127+
this.hasStoredAnswers = false;
128+
}
129+
}
130+
}
131+
replaceSpan.onclick = function () {
132+
if ($(this).hasClass("clickable-clicked")) {
133+
$(this).removeClass("clickable-clicked");
134+
} else {
135+
$(this).addClass("clickable-clicked");
136+
}
137+
};
138+
139+
if ($(this.newPre.childNodes[i]).is("[data-correct]")) {
140+
this.correctArray.push(replaceSpan);
141+
} else {
142+
this.incorrectArray.push(replaceSpan);
143+
}
144+
this.clickableArray.push(replaceSpan);
145+
$(this.newPre.childNodes[i]).replaceWith(replaceSpan);
146+
this.clickableCounter++;
147+
}
148+
}
149+
};
150+
151+
ClickableArea.prototype.createButtons = function () {
152+
this.submitButton = document.createElement("button"); // Check me button
153+
this.submitButton.textContent = "Check Me";
154+
$(this.submitButton).attr({
155+
"class": "btn btn-success",
156+
"name": "do answer"
157+
});
158+
159+
this.submitButton.onclick = function () {
160+
this.clickableEval();
161+
}.bind(this);
162+
163+
this.containerDiv.appendChild(this.submitButton);
164+
};
165+
166+
ClickableArea.prototype.createFeedbackDiv = function () {
167+
this.feedBackDiv = document.createElement("div");
168+
this.containerDiv.appendChild(document.createElement("br"));
169+
this.containerDiv.appendChild(this.feedBackDiv);
170+
};
171+
172+
/*========================================
173+
== Evaluation and setting local storage ==
174+
========================================*/
175+
176+
ClickableArea.prototype.clickableEval = function () {
177+
// Evaluation is done by iterating over the correct/incorrect arrays and checking by class
178+
this.setLocalStorage();
179+
this.correct = true;
180+
this.correctNum = 0;
181+
this.incorrectNum = 0;
182+
for (var i = 0; i < this.correctArray.length; i++) {
183+
if (!$(this.correctArray[i]).hasClass("clickable-clicked")) {
184+
this.correct = false;
185+
} else {
186+
this.correctNum++;
187+
}
188+
}
189+
for (var i = 0; i < this.incorrectArray.length; i++) {
190+
if ($(this.incorrectArray[i]).hasClass("clickable-clicked")) {
191+
this.correct = false;
192+
this.incorrectNum++;
193+
}
194+
}
195+
196+
this.renderFeedback();
197+
};
198+
199+
ClickableArea.prototype.setLocalStorage = function () {
200+
// Array of the indices of clicked span elements is passed to local storage
201+
this.givenIndexArray = [];
202+
for (var i = 0; i < this.clickableArray.length; i++) {
203+
if ($(this.clickableArray[i]).hasClass("clickable-clicked")) {
204+
this.givenIndexArray.push(i);
205+
}
206+
}
207+
localStorage.setItem(eBookConfig.email + ":" + this.divid + "-given", this.givenIndexArray.join(";"));
208+
};
209+
210+
ClickableArea.prototype.renderFeedback = function () {
211+
212+
if (this.correct) {
213+
$(this.feedBackDiv).html("You are Correct!");
214+
$(this.feedBackDiv).attr("class", "alert alert-success");
215+
216+
} else {
217+
$(this.feedBackDiv).html("Incorrect. You clicked on " + this.correctNum + " of the " + this.correctArray.length.toString() + " correct elements and " + this.incorrectNum + " of the " + this.incorrectArray.length.toString() + " incorrect elements. " + this.feedback);
218+
219+
$(this.feedBackDiv).attr("class", "alert alert-danger");
220+
}
221+
};
222+
223+
/*=================================
224+
== Find the custom HTML tags and ==
225+
== execute our code on them ==
226+
=================================*/
227+
$(document).ready(function () {
228+
$("[data-component=clickablearea]").each(function (index) {
229+
CAList[this.id] = new ClickableArea({"orig": this});
230+
});
231+
232+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
function RunestoneBase () { // Basic parent stuff
2+
3+
}
4+
5+
RunestoneBase.prototype.logBookEvent = function (info) {
6+
console.log("logging event " + this.divid);
7+
};
8+
9+
RunestoneBase.prototype.logRunEvent = function (info) {
10+
console.log("running " + this.divid);
11+
};

0 commit comments

Comments
 (0)