@@ -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
0 commit comments