1313# You should have received a copy of the GNU General Public License
1414# along with this program. If not, see <http://www.gnu.org/licenses/>.
1515#
16- __author__ = 'isaiahmayerchak '
16+ __author__ = 'hewner '
1717
1818import re
1919from docutils import nodes
@@ -28,44 +28,17 @@ def setup(app):
2828 app .add_stylesheet ('parsons.css' )
2929 app .add_stylesheet ('lib/prettify.css' )
3030
31- app .add_node (ParsonsNode , html = (visit_prs_node , depart_prs_node ))
32-
3331 # includes parsons specific javascript headers
3432 # parsons-noconflict reverts underscore and
3533 # jquery to their original versions
34+ app .add_javascript ('lib/jquery.min.js' )
35+ app .add_javascript ('lib/jquery-ui.min.js' )
3636 app .add_javascript ('lib/prettify.js' )
3737 app .add_javascript ('lib/underscore-min.js' )
3838 app .add_javascript ('lib/lis.js' )
3939 app .add_javascript ('parsons.js' )
40- app .add_javascript ('parsonsaux.js' )
41-
42-
43- TEMPLATE = '''
44- <pre data-component="parsons" id="%(divid)s">
45- <span data-question>%(qnumber)s: %(instructions)s</span>%(code)s</pre>
46- '''
47-
48- class ParsonsNode (nodes .General , nodes .Element ):
49- def __init__ (self ,content ):
50- """
51- Arguments:
52- - `self`:
53- - `content`:
54- """
55- super (ParsonsNode ,self ).__init__ ()
56- self .prs_options = content
57-
58- def visit_prs_node (self ,node ):
59- res = TEMPLATE % node .prs_options
60-
61- self .body .append (res )
40+ app .add_javascript ('parsons-noconflict.js' )
6241
63- def depart_prs_node (self ,node ):
64- ''' This is called at the start of processing an datafile node. If parsons had recursive nodes
65- etc and did not want to do all of the processing in visit_prs_node any finishing touches could be
66- added here.
67- '''
68- pass
6942
7043class ParsonsProblem (Assessment ):
7144 required_arguments = 1
@@ -76,13 +49,18 @@ class ParsonsProblem(Assessment):
7649
7750 def run (self ):
7851 """
79- Instructions for solving the problem should be written and then a line with -----
52+
53+ Instructions for solving the problem should be written and then a line with -----
8054 signals the beginning of the code. If you want more than one line in a single
8155 code block, seperate your code blocks with =====.
56+
8257 Both the instructions sections and code blocks are optional. If you don't include any
8358 =====, the code will assume you want each line to be its own code block.
59+
8460Example:
61+
8562.. parsonsprob:: unqiue_problem_id_here
63+
8664 Solve my really cool parsons problem...if you can.
8765 -----
8866 def findmax(alist):
@@ -98,24 +76,173 @@ def findmax(alist):
9876 curmax = item
9977 =====
10078 return curmax
79+
80+
10181 """
102- self .options ['divid' ] = self .arguments [0 ]
103- self .options ['qnumber' ] = self .getNumber ()
104- self .options ['instructions' ] = ""
105- self .options ['code' ] = self .content
82+
83+ template_values = {}
84+ template_values ['qnumber' ] = self .getNumber ()
85+ template_values ['unique_id' ] = self .lineno
86+ template_values ['instructions' ] = ""
87+ code = self .content
88+
10689 if '-----' in self .content :
10790 index = self .content .index ('-----' )
108- self . options ['instructions' ] = "\n " .join (self .content [:index ])
109- self . options [ ' code' ] = self .content [index + 1 :]
91+ template_values ['instructions' ] = "\n " .join (self .content [:index ])
92+ code = self .content [index + 1 :]
11093
111- if '=====' in self .options ['code' ]:
112- self .options ['code' ] = "\n " .join (self .options ['code' ])
113-
114- self .options ['code' ] = self .options ['code' ].replace ('=====' , '---' )
94+ if '=====' in code :
95+ template_values ['code' ] = self .parse_multiline_parsons (code );
11596 else :
116- self .options ['code' ] = "\n " .join (self .options ['code' ])
117-
118- self .options ['divid' ] = self .arguments [0 ]
97+ template_values ['code' ] = "\n " .join (code )
98+
99+ template_values ['divid' ] = self .arguments [0 ]
100+
101+ TEMPLATE = '''
102+ <div class='parsons alert alert-warning' id="parsons-%(unique_id)s">
103+ <div class="parsons-text">%(qnumber)s: %(instructions)s<br /><br /></div>
104+ <div style="clear:left;"></div>
105+ <div id="parsons-orig-%(unique_id)s" style="display:none;">%(code)s</div>
106+ <div class="sortable-code-container">
107+ <div id="parsons-sortableTrash-%(unique_id)s" class="sortable-code"></div>
108+ <div id="parsons-sortableCode-%(unique_id)s" class="sortable-code"></div>
109+ <div style="clear:left;"></div>
110+ </div>
111+ <div class="parsons-controls">
112+ <input type="button" class='btn btn-success' id="checkMe%(unique_id)s" value="Check Me"/>
113+ <input type="button" class='btn btn-default' id="reset%(unique_id)s" value="Reset"/>
114+ <div id="parsons-message-%(unique_id)s"></div>
115+ </div>
116+ </div>
117+
118+ <script>
119+ $pjQ(document).ready(function(){
120+ var rb = new RunestoneBase();
121+ $("#parsons-%(unique_id)s").not(".sortable-code").not(".parsons-controls").on("click", function(){
122+ $('html, body').animate({
123+ scrollTop: ($("#parsons-%(unique_id)s").offset().top - 50)
124+ }, 700);
125+ }).find(".sortable-code, .parsons-controls").click(function(e) {
126+ return false;
127+ });
128+ var msgBox = $("#parsons-message-%(unique_id)s");
129+ msgBox.hide();
130+ var displayErrors = function (fb) {
131+ if(fb.errors.length > 0) {
132+ var hash = pp_%(unique_id)s.getHash("#ul-parsons-sortableCode-%(unique_id)s");
133+ msgBox.fadeIn(500);
134+ msgBox.attr('class','alert alert-danger');
135+ msgBox.html(fb.errors[0]);
136+ rb.logBookEvent({'event':'parsons', 'act':hash, 'div_id':'%(divid)s'});
137+
138+ } else {
139+ rb.logBookEvent({'event':'parsons', 'act':'yes', 'div_id':'%(divid)s'});
140+ msgBox.fadeIn(100);
141+ msgBox.attr('class','alert alert-success');
142+ msgBox.html("Perfect!")
143+ }
144+
145+ }
146+
147+ $(window).load(function() {
148+ // set min width and height
149+ var sortableul = $("#ul-parsons-sortableCode-%(unique_id)s");
150+ var trashul = $("#ul-parsons-sortableTrash-%(unique_id)s");
151+ var sortableHeight = sortableul.height();
152+ var sortableWidth = sortableul.width();
153+ var trashWidth = trashul.width();
154+ var trashHeight = trashul.height();
155+ var minHeight = Math.max(trashHeight,sortableHeight);
156+ var minWidth = Math.max(trashWidth, sortableWidth);
157+ trashul.css("min-height",minHeight + "px");
158+ sortableul.css("min-height",minHeight + "px");
159+ sortableul.height(minHeight);
160+ trashul.css("min-width",minWidth + "px");
161+ sortableul.css("min-width",minWidth + "px");
162+ });
163+
164+
165+ var pp_%(unique_id)s = new ParsonsWidget({
166+ 'sortableId': 'parsons-sortableCode-%(unique_id)s',
167+ 'trashId': 'parsons-sortableTrash-%(unique_id)s',
168+ 'max_wrong_lines': 1,
169+ 'solution_label': 'Drop blocks here',
170+ 'feedback_cb' : displayErrors
171+ });
172+ pp_%(unique_id)s.init($pjQ("#parsons-orig-%(unique_id)s").text());
173+ pp_%(unique_id)s.shuffleLines();
174+
175+ if(localStorage.getItem('%(divid)s') && localStorage.getItem('%(divid)s-trash')) {
176+ try {
177+ var solution = localStorage.getItem('%(divid)s');
178+ var trash = localStorage.getItem('%(divid)s-trash');
179+ pp_%(unique_id)s.createHTMLFromHashes(solution,trash);
180+ pp_%(unique_id)s.getFeedback();
181+ } catch(err) {
182+ var text = "An error occured restoring old %(divid)s state. Error: ";
183+ console.log(text + err.message);
184+ }
185+
186+ }
187+ $pjQ("#reset%(unique_id)s").click(function(event){
188+ event.preventDefault();
189+ pp_%(unique_id)s.shuffleLines();
190+
191+ // set min width and height
192+ var sortableul = $("#ul-parsons-sortableCode-%(unique_id)s");
193+ var trashul = $("#ul-parsons-sortableTrash-%(unique_id)s");
194+ var sortableHeight = sortableul.height();
195+ var sortableWidth = sortableul.width();
196+ var trashWidth = trashul.width();
197+ var trashHeight = trashul.height();
198+ var minHeight = Math.max(trashHeight,sortableHeight);
199+ var minWidth = Math.max(trashWidth, sortableWidth);
200+ trashul.css("min-height",minHeight + "px");
201+ sortableul.css("min-height",minHeight + "px");
202+ trashul.css("min-width",minWidth + "px");
203+ sortableul.css("min-width",minWidth + "px");
204+ msgBox.hide();
205+ });
206+
207+ $pjQ("#checkMe%(unique_id)s").click(function(event){
208+ event.preventDefault();
209+ var hash = pp_%(unique_id)s.getHash("#ul-parsons-sortableCode-%(unique_id)s");
210+ localStorage.setItem('%(divid)s',hash);
211+ hash = pp_%(unique_id)s.getHash("#ul-parsons-sortableTrash-%(unique_id)s");
212+ localStorage.setItem('%(divid)s-trash',hash);
213+
214+ pp_%(unique_id)s.getFeedback();
215+ msgBox.fadeIn(100);
216+
217+ });
218+
219+ });
220+
221+
222+ </script>
223+
224+ '''
119225
120226 self .assert_has_content ()
121- return [ParsonsNode (self .options )]
227+ return [nodes .raw ('' , TEMPLATE % template_values , format = 'html' )]
228+
229+ def parse_multiline_parsons (self , lines ):
230+ current_block = []
231+ results = []
232+ for line in lines :
233+ if (line == '=====' ):
234+ results .append (self .convert_leading_whitespace_for_block (current_block ))
235+ current_block = []
236+ else :
237+ current_block .append (line )
238+ results .append (self .convert_leading_whitespace_for_block (current_block ))
239+ return "\n " .join (results )
240+
241+ def convert_leading_whitespace_for_block (self , block ):
242+ whitespaceMatcher = re .compile ("^\s*" )
243+ initialWhitespace = whitespaceMatcher .match (block [0 ]).end ()
244+ result = block [0 ]
245+ for line in block [1 :]:
246+ result += '\\ n' # not a line break...the literal characters \n
247+ result += line [initialWhitespace :]
248+ return result
0 commit comments