var student_answers = {}; var comments = {missing:"Missing answer", correct:"Correct", incorrect:"Incorrect"}; var student_id = "{{{STUDENT_ID}}}"; var page_id = "{{{PAGE_ID}}}";
Writing a project
$('#navbar-header').append("
The questionr
package allows one to write projects containing
questions that can be self grading or graded by an instructor. The
projects display as one long web page with the possibility of a top
navigation bar to aid in navigation.
Self-grading questions can be written to
have a hint
give comments on incorrect answers
have a badge to record the number of tries a student makes per question.
The markup is done using R-markdown
. The package requires some minimal header and footer content. The most minimal would be to put the following lines in an AsIs
header:
```{r echo=FALSE, results="asis"} ## Nothing to see here ... move along require("questionr", quietly=TRUE) page <- questionr:::Page$new() cat(page$write_header()) ```
and have the following minimal footer:
`r I(page$write_footer())`
Pages are prettier with navigation, but that is not required.
$('#navbar-header').append("
There are several types of questions available:
The radio_choice
style allows a choice of one item from a list of
values. The basic markup would look like:
Pick a state:
`r I(page$radio_choice(state.name[1:4], "Alaska"))` `r I(page$radio_choice(state.name[1:4], "Alaska"))`
which gives:
That's a little minimal. One can add a new problem indicator, a hint and comments on incorrect answers. For example:
`r I(page$new_problem())` Pick a really large state: ```{r echo=FALSE, results="asis"} cat(page$radio_choice(state.name[1:4], "Alaska", hint="North to ...", comment=list("Alabama"="Nope, that's Sweet home Alabama", "Arizona"="Nope, the Grand Canyon State", "Arkansas"="Nope that's the Natural State"), linebreak=TRUE)) ```
This yields
Pick a really large state:
Alabama Alaska Arizona Arkansas $('#prob_2').popover({title:'hint',content:'North to the future...'});The above example shows many things:
the new_problem
call will create a badge which records the number of tries and has a space for comments to be displayed. This is the default behaviour, but can be modified by calling with add_badge=FALSE
. This default to the value of instant_feedback
given to the page
constructor.
The hint
argument allows one to give the students hints that popup on mouse overs
The comment
argument takes a named list where the names come from the incorrect answers and the values specify a comment.
The problems are marked up inline wrapped with I()
or in a block
wrapped in cat()
. Maybe this can be removed through some fancy
knitr
options, but it hasn't yet been done.
A checkgroup allows the student to select one or more from a set of values. The basic markup looks like:
`r I(page$checkgroup_choice(state.name[1:4], c("Alabama", "Alaska")))`
yielding:
Alabama Alaska Arizona ArkansasComments are specified using values combined with "::". So for example
comments <- list("Alabama::Arkansas"="Nope not right",
"Alabama"="Nope, one more")
The other combinations will get the default:
A typeahead choice forces the user to type in their answers, but allows them to narrow the list of potential answers. It is useful when there can be many.
For example, to select one of the 50 states:
`r I(page$typeahead_choice(state.name, "Alaska", hint="North to the future"))`
Enter a state:
$('#prob_5').typeahead({source:[ "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming" ]}); $('#prob_5').popover({title:'hint',content:'North to the future'});The combobox_choice
gives the student a combobox to choose an item from. It is very similar to a radio choice, but manages screen space differently.
`r I(page$new_problem())` Pick a really large state: ```{r echo=FALSE, results="asis"} cat(page$combobox_choice(state.name[1:4], "Alaska", hint="North to the future...", comment=list("Alabama"="Nope, that's Sweet home Alabama", "Arizona"="Nope, the Grand Canyon State", "Arkansas"="Nope, that's the Natural State"))) ```
Pick a really large state: Select one ... Alabama Alaska Arizona Arkansas
$('#prob_6').popover({title:'hint',content:'North to the future...'});A numeric choice allows the student to enter a number. To work around rounding issues, the problem can be set with a min and maximum allowable value. The comment list has names less
and more
. Here is a way to get $\pi$:
`r I(page$new_problem())` What is $\pi$? ```{r echo=FALSE, results="asis"} cat(page$numeric_choice(3.1, 3.2, comment=list(more="Too much", less="Too little"))) ```
What is $\pi$?
The free_choice
option allows a student to enter in any text into a text box. The only option is a hint. These problems are not graded automatically, rather by the teacher of a section. While grading the text can be run through R-markdown.
```{r echo=FALSE, results="asis"} cat(page$new_problem()) cat(page$write("Well, how do you feel?")) cat(page$free_choice("Go ahead, enter whatever you want...")) ```
Well, how do you feel?Well, how do you feel?
$('#prob_8').popover({title:'hint',content:'Go ahead, enter whatever you want...'}); $('#navbar-header').append("
Problems can be radomized. Each student has their own random seed, and each project is generated with that seed. So different students will (likely) get different projects, but the same student should always get the same one. With this, problems can be randomized. For example, this problem asks a student to estimate the mean from a histogram:
```{r echo=FALSE, results="asis"} mu <- sample(1:10, 1); sigma=sample(1:3, 1) x <- rnorm(100, mu, sigma) cat(page$new_problem()) hist(x) page$write("Guess the mean from the histogram") cat(page$numeric_choice(mu-sigma/2, mu + sigma/2)) ```
Guess the mean from the histogramGuess the mean from the histogram
$('#navbar-header').append("
The basic page has no navigation features. Adding them is easy. We create an nav
object in the header with:
``` {r echo=FALSE, results="asis"} ## Nothing to see here ... move along require("questionr", quietly=TRUE) page <- questionr:::Page$new() nav <- questionr:::NavBar$new() cat(page$write_header()) ```
Then when we want to add navigation (which consists of an item in the menu bar and a heading in the file, we use the add
method:
`r I(nav$add("Some navigation"))`
To finish navigation we call the write_footer
method. This is usually just part of the footer along with a call to the write_footer
method of the page object.
One can add a notebook with tabs to compress the necessary vertical space. This is useful for grouping related items together in a question, such as diagnostic graphics. The tab container is made with the Tabs
reference class and new pages added with the add
method. All tabs are wrapped in a write_header
and write_footer
. For example, this code:
```{r echo=FALSE, results="asis"} tabs <- questionr:::Tabs$new() cat(tabs$write_header()) x <- rnorm(100) ``` `r I(tabs$add("Summary"))` ```{r} summary(x) ``` `r I(tabs$add("boxplot"))` ```{r} boxplot(x) ``` `r I(tabs$write_footer())`
makes these tabs:
summary(x)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -2.290 -0.902 0.156 0.061 0.911 3.130
boxplot(x)
Once knit, pages are standalone, self grading web pages. The grading can happen instantly and the badges and comments give the necessary feedback. This is the default behaviour. For such pages, the HTML is self contained and can be hosted anywhere. For exampe Rpubs.
Pages can also be shown within the system provided by this package. For such use, one should add buttons near the bottom. The call for this is:
`r I(page$grade_button())`
To deploy these pages, a teacher must upload them into a class or section as projects.
$('body').css('margin', '40px 10px'); //$('body').attr('data-offset','40'); //$('body').attr('data-target','#subnav'); $('body').attr('data-spy','scroll'); //$('[data-spy="scroll"]').each(function () { // var $spy = $(this).scrollspy('refresh') //}); var tmp = $(".nav-tabs") $.each(tmp, function(key, value) { $("#" + value.id + " a:first").tab("show") }); $("#navbar").scrollspy(); $("body").attr("data-spy", "scroll"); $("[data-spy=\'scroll\']").each(function () { var $spy = $(this).scrollspy("refresh") }); function comment_default(grade, stud_ans, comment, def) { var cmt = ""; if(grade == 100) { cmt = def.correct; } else if(typeof(comment) != "undefined") { if(typeof(comment[stud_ans]) != "undefined") { cmt = comment[stud_ans]; } else { cmt = def.incorrect; } } else { cmt = def.incorrect; } return cmt; }; function comment_checkgroup(grade, stud_ans, comment, def) { var tmp = []; $.each(stud_ans, function(key, value) {if(value !== null) tmp.push(value)}); return comment_default(grade, tmp.sort().join("::"), comment, def); }; function comment_numeric(grade, stud_ans, value, comment, def) { var cmt = ""; if(grade == 100) { cmt = def.correct; } else if(typeof(comment) != "undefined") { if(stud_ans < value[0]) { if(typeof(comment.less) != "undefined") { cmt = comment.less } else { cmt = def.incorrect } } else if(stud_ans > value[1]) { if(typeof(comment.more) != "undefined") { cmt = comment.more } else { cmt = def.incorrect } } } else { cmt = def.incorrect; } return cmt; } function grade_radio(ans, value) {return( ans == value ? 100 : 0) }; function grade_checkboxgroup(ans, value) { var out=[]; $.each(ans, function(key, value) { if(value != null) { out.push(value) }}); if(out.length != value.length) { return(0) }; out = out.sort(); var value = value.sort() for(var i=0; i < out.length; i++) { if(out[i] != value[i]) { return(0) } } return(100) }; function grade_typeahead(ans, value) { return( (ans == value) ? 100 : 0 )}; function grade_combo(ans, value) { return( (ans == value) ? 100 : 0) }; function grade_numeric(ans, value) { return( (ans >= value[0] && ans <= value[1]) ? 100 : 0) }; function submit_work(status) { $.ajax({ url:"/set_answers", type:"POST", data: { answers:JSON.stringify(student_answers), status:status, project_id:page_id }, success:function(data) { window.location.replace(""); } }); }; function set_radio(id, value) { $("#" + id + " [value=" + value + "]").attr("checked", true); }; function set_checkboxgroup(id, value) { $("#" + id + " [type=checkbox]").attr("checked", false); $.each(value, function(idx, val) { $("#" + id + " [value=" + val + "]").attr("checked", true) }) }; function set_typeahead(id, value) { $("#" + id).val(value) }; function set_combo(id, value) { if(value.length > 0) { $("#" + id + " [value=" + value + "]").attr("selected", true) } else { $("#" + id)[0].selectedIndex=0; } }; function set_numeric(id, value) { $("#" + id).val(value) }; function set_free(id, value) { $("#" + id).val(value); }; function set_answer(o) { var id = o.problem; var value = o.answer; var type = o.type if(type == "radio") { set_radio(id, value) } else if(type == "checkbox") { set_checkboxgroup(id, value) } else if(type == "typeahead") { set_typeahead(id, value) } else if(type == "combo") { set_combo(id, value) } else if(type == "numeric") { set_numeric(id, value) } else if(type == "free") { set_free(id, value) } }; function set_answers(status, stud_ans) { $.each(stud_ans, function(key, value) { set_answer(value); if(typeof(value.comment) != "undefined") { var cmt = 'Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.