Learnosity Custom Question types allow you to create your own unique Questions, giving you full control over the rendering of the response area, the user interaction with the Question, and the scoring of the response.
A custom Question is defined as any other Question type, by passing a JSON object to the Questions API. In the Question JSON, you provide URLs to JavaScript files containing the program logic that will drive your Question and its scoring.
Initialization: Attributes for Custom Questions
The first step is to initialise your custom Question via Questions API. Please refer to the table of supported JSON attributes below.
Important: once a session of custom Question type is saved or submitted for server-side processing (such as back-end scoring) Learnosity can only access custom Question attributes supplied in the initialization JSON (see table below). All other attributes added to the Question object after initialization (for example, dynamically added attributes) will be ignored on the server side.
Table 1: Supported attributes for custom Questions
Attribute Name | Mandatory | Value | Comment |
---|---|---|---|
response_id | Yes | Type: string | Used in the page where custom Question will be rendered as a part of the class name "question-response_id " of span element (HTML hook), for example:<span class="learnosity-response question-response_id"></span>
|
type | Yes | Type: string | For the custom Question type, it should be exactly “custom” (case-sensitive). |
js | Yes | Type: object Type: string |
An object containing URLs to JavaScript files which define AMD modules for the Question and the Scorer. Note: Disabling autoscoring To disable autoscoring, do not supply a js.scorer attribute. In this case,
|
css | No | Type: string | A URL to a CSS file containing styles for the Question. |
valid_response | Yes | Type: string | The valid response for your custom Question. You must pass this attribute even if it is not used in your scoring logic — just pass it as an empty string, for example: (“valid_response”: “”) Important: If not provided, server-side scoring will not be triggered. |
<any custom attribute> | No | Type: string / Type: number / Type: boolean |
Depending on your custom scoring logic, these attributes’ values may or may not be accessible on the back-end (for Learnosity server-side scoring, for example) once the session is saved or submitted. Important: All attributes dynamically added to the custom Question object after its initialization will be ignored on the server-side. |
For more information on supported attributes, please visit the custom attributes documentation.
Example: Custom Question Initialization Attributes (JSON)
customQuestionJson = {
"response_id": "custom-shorttext-response-1",
"type": "custom",
"js": {
"question": "//demos.learnosity.com/usecases/customquestions/custom_shorttext_q.js",
"scorer": "//demos.learnosity.com/usecases/customquestions/custom_shorttext_s.js"
},
"css": "//demos.learnosity.com/usecases/customquestions/custom_shorttext.css",
"stimulus": "What is the capital of Australia?",
"valid_response": "Canberra",
"score": 1
};
Code example 1: JSON attributes for custom Question initialization, you can find an example in the assessment.php file in the Custom Question Skeleton, which is the questions field in $request variable
The Initialization Object When Calling Questions API
Questions API will instantiate your Question by calling a constructor, passing in two objects. The first object, init
contains the following properties:
Table 2: initialization properties for the init object
Property Name | Comment |
---|---|
state | The state Questions API was initialised in (initial , resume , or review ). |
question | The Question object to be rendered. |
response | Previously saved response data for this response ID. This should be placed in the response area when in resume or review state. |
$el | A jQuery selection containing the DOM element for your Question's response area. This is where you will render the Question. |
events | A Backbone.js Events object. You must use this to communicate with Questions API (see below). |
getFacade | A function that returns a facade object containing the public methods of the current Question instance. Add or override public methods on the facade object to make those available on instances of the custom Question object exposed by Questions API, for example:questionsApp.question("custom-question-1").disable() ). |
getI18nLabel | A function that returns the internationalization (i18n) text of the provided key. For example:this.initOptions.getI18nLabel('checkAnswer'); // return "Check Answer" text
|
questionsApiVersion | A string containing the version number of the current instance of Questions API. |
The second object, lrnUtils
is a facade containing the following:
renderComponent(String componentName, Element hook) |
A function used to render the given componentName at the specifed element. Used to render Learnosity's provided components like the Check Answer button or the suggested answer list component. The function returns a promise that resolves to a facade containing public methods available on the component. The promise resolves when the component has finished rendering. See the section on custom Question components, below. |
Rendering The Question
After initialisation, you can use the returned properties to render your application.
Your JavaScript module must render the HTML for the Question inside the provided element. When this is complete, you must notify Questions API that the Question is ready. This example renders a simple text input, and then triggers the ready
event:
const { el, init } = this;
el.html("<input type=\"text\" />");
init.events.trigger("ready");
Code example 12: notifying Questions API that the Question is ready. An example can be found in Custom Question Skeleton
Questions API will trigger an error event if your Question does not call the ready
event within an acceptable timeframe.
Saving Responses
Your JavaScript module must notify Questions API when the user enters or changes a response to the Question, by triggering a changed
event containing the response data. This example uses jQuery's change()
method to listen for changes to the text input:
var $response = $("input", init.$el);
$response.change(function (event) {
init.events.trigger("changed", event.currentTarget.value);
});
handleEvents() {
const { events } = this;
events.trigger('changed', responses);
events.on('validate', options => {
// do something
});
}
//after render and register events handler
this.render().then(() => {
this.handleEvents();
})
Code example 14: register events handler. An example can be found in Custom Question Skeleton
You can also save an object as a response:
init.events.trigger("changed", {"attr1": value1, "attr2": value2});
Code example 15: saving an object as a response
This may be required when your answer has several data points that must be returned (for example, graph coordinates with X and Y values, or a record of the student’s actions).
This ensures responses can be saved as part of the session, or retrieved locally via Questions API public methods, e.g. getResponses()
.
Reset Response
Custom Questions do not support resetResponse()
. If you want to achieve reset response functionality with a custom Question, you can add support for this to your Question by triggering a resetResponse
event and implementing a reset for other states.
An example can be found in the Custom Question Skeleton.
Validation UI
All Learnosity Question types have a validation UI to give feedback to the user for valid or invalid responses. This feedback can be triggered via Questions API public methods e.g. validateQuestions(). You can add support for this to your Question by listening for a validate
event. The parameters for this are listed in the table below.
Table 3: parameters for the validate
event handler
Parameter Name | Parameter Value | Comment |
---|---|---|
options | Objectoptions = { The value depends on the initialization object passed to Questions API. Default value is true. |
The showCorrectAnswers flag has different behaviors based on the state provided.More information: showCorrectAnswers Documentation |
init.events.on("validate", function (options) { // options object containing "showCorrectAnswers" to tell developer if he/she should display correct answers to user
var result = init.getFacade.isValid(); // Use facade.isValid(true) to get the detailed report
this.clearValidationUI();
this.showValidationUI(result);
if (!result && options.showCorrectAnswers) {
this.showCorrectAnswers();
}
}.bind(this));
Code example 22: the validate event handler
Important Handler code for the
validate
event should be provided before the ready
event is sent off.Additionally, apply the following Learnosity class names to the relevant elements of the Question to have your validation UI styled according to Learnosity's standard look and feel.
this.$el
.find(".input-wrapper")
// Add "lrn_response_index_visible" class if you want to display the index of current response
.addClass("lrn_response_index_visible")
// Add this class to display default Learnosity correct, incorrect style
.addClass(isCorrect ? "lrn_correct" : "lrn_incorrect")
// After adding the class "lrn_response_index_visible", you then can inject the response index element
.prepend("<span class="lrn_responseIndex"><span>1</span></span>")
// Add this element if you want to display to corresponding validation (cross, tick) icon
.append("<span class="lrn_validation_icon"/>");
Code example 23: supported class names
Questions API States
The validate
event is triggered for all supported states of Questions API (initial, resume, review). Therefore, its event handler is the best place to accommodate your code to manage custom Question in different states.
If you would like to use your Question in resume
or review
states, you should check for an existing response value when rendering your Question. In review
state, you should also show your Question's validation UI.
For instance:
init.events.on("validate", function (options) {
if (init.response) {
$response.val(init.response);
}
if (init.state == "review") {
// show validation UI
}
}
Code example 24: checking for an existing response value in review mode
Custom Question Components
Custom Question components are a way to include common Learnosity controls and UI elements into a custom Question with standard Learnosity styling and behaviour.
Use the lrnUtils
object passed to the custom Question's constructor to inject components into the DOM elements of your Question. Simply call lrnUtils.renderComponent()
, passing the string name of the desired component and the DOM element where it should be placed. The function returns a promise, which resolves to a facade of public methods that can be called on the component.
See the following examples for each of the available components.
CheckAnswerButton
Renders the Check Answer button. On click, the button validates the current custom Question. This button will be invisible during review
state.
Available methods:
remove()
this.$el.append("<div data-lrn-component=\"suggestedAnswersList\"/>");
this.lrnUtils.renderComponent("CheckAnswerButton", this.$el.find("[data-lrn-component=\"checkAnswer\"]").get(0));
Code example 25: rendering the `Check Answer` button
SuggestedAnswersList
Renders the Suggested Answers List component. This component will only be visible if the activity's showCorrectAnswers is set to "true"
.
Available methods:
setAnswers()
reset()
remove()
var self = this;
this.lrnUtils.renderComponent("SuggestedAnswersList",
this.$el.find("[data-lrn-component=\"suggestedAnswersList\"]").get(0))
.then(function (component) {
self.suggestedAnswersList = component;
// Pass in string to display correct answer list without the index
// self.suggestedAnswersList.setAnswers(correctAnswerText);
// Pass in an array of object which contains index and label to render a list of suggested answers
self.suggestedAnswersList.setAnswers([{
index: 0,
label: correctAnswerText
}]);
});
Code example 26: rendering the correct answers
Scoring
To implement scoring for your Question, your need to provide a separate JavaScript module that defines a scorer function. Questions API will call this function, passing it the Question object and the response data for the Question. The response data comes directly from the changed
events triggered by your Question.
You must define methods on the prototype of the scorer function according to our scoring interface, as shown in the example below. The object returned by your module must define a Scorer
property containing the scorer function. You can override the functions in custom-question-skeleton/src/scorer/index.js to implement your scoring logic if you are using the Custom Question Skelton to build your own custom question.
Implementing a scorer will allow responses for the Question to be evaluated by Questions API public methods, such as getScores().
Server-side variables for the Scorer function
The Scorer function, (which executes on the server-side) takes question and response variables like so:Scorer(question, response)
. Scoring for this is executed remotely, so only certain variables are available in that environment.The question variable
The question variable is the object (containing JSON attributes) which is passed to initialize Questions API (refer to Table 1: supported attributes for Custom Questions).For example, if you want to use the
valid_response
attribute you would call it in the form question.valid_response
.Only the attributes which are passed at initialization time will be processed for server-side scoring.
Important Any attributes dynamically added to the Question object after Questions API has been initialized successfully will be ignored during server side scoring.
The response variable
The response variable is the value attribute retrieved via Questions API public methods, such asgetResponses()
.For example, calling
questionsApp.getResponses()
in the browser console returns this object:response_id:
apiVersion:"v2.119.0"
type:"object"
value:
attribute1:"value1"
attribute2:"value2"
Code example 19: the object returned by questionsApp.getResponses()
Therefore, the response is equal to the value
attribute of response_id above.
The example below shows how to access attribute1
of value
within the score function.
function Scorer(question, response) {
this.question = question;
this.response = response;
}
Scorer.prototype.score = function () {
return this.response.attribute1;
};
Code example 20: accessing 'attribute1' within the score function
Public methods required by Learnosity:
Method | Returns | Comment |
---|---|---|
isValid() | boolean | Check if the current question's response is valid or not |
validateIndividualResponses() | object | Returns an object displaying the validation state of each individual item inside the stored response For example: The student response value is: { min: 10, max: 20 } and our correct answer is { min: 10, max: 30 } Then we expect the result of this validateIndividualResponses will be: { min: true, max: false } |
score() | number | Returns the score of the stored response |
maxScore() | number | Returns the possible max score of the stored response |
canValidateResponse() | boolean | Check if the current question is scorable or not. For example:
|
Testing Scoring Functions on the Server-side: score and maxScore
On the server-side, the score and maxScore functions are called once the session is saved or submitted. To test these functions, you can use the Data API online scoring endpoint which uses the same logic as the back-end scoring.
Screenshot: Data API online scoring endpoint demo page
To test your scoring functions:
1. Visit https://demos.learnosity.com/analytics/data/index.php then scroll to the Scoring section at the bottom of the page.
2. On the questions field on that page, paste in the JSON object for your custom Question(s).
[{
"response_id": "custom-shorttext-response-1",
"type": "custom",
"js": {
"question":"//docs.learnosity.com/demos/products/questionsapi/questiontypes/custom_shorttext.js",
"scorer": "//docs.learnosity.com/demos/products/questionsapi/questiontypes/custom_shorttext_scorer.js"
},
"css": "//docs.learnosity.com/demos/products/questionsapi/questiontypes/custom_shorttext.css",
"stimulus": "What is the capital of Australia?",
"valid_response": "Canberra",
"score": 1
}]
Code example 15: the JSON object for a custom Question
3. On the responses field on that page, paste in the JSON object you can retrieve locally via Questions API public methods, for example, getResponses()
. For instance, call questionsApp.getResponses()
in the browser console to get the following:
response_id:
apiVersion: "v2.119.0"
type: "object"
value: "lesdafsdf"
Code example 16: returned data from calling questionsApp.getResponses() in the browser console
Therefore, testing the responses object will look like the following:
{
"custom-shorttext-response-1": {
"value": "Canberra",
"type": "object",
"apiVersion": "v2.119.0"
}
}
Code example 17: testing the responses object
4. Click Submit to send the request and analyse the result data on the Response tab (you will be switched there automatically). The result data will look like the following:
{
"meta": {
"status": true,
"timestamp": 1529371443,
"records": 1
},
"data": [
{
"response_id": "custom-shorttext-response-1",
"type": "custom",
"automarkable": true,
"is_valid": true,
"score": 1,
"max_score": 1,
"attempted": true,
"error": null
}
]
}
Code example 18: result data from the Data API endpoint
5. Check that data[].score
and data[].maxScore
are available (not null) and have correct values. Doing this, you can iterate over your server-side scoring, until your score
and maxScore
are returning the expected results. Then, you can move this working model into your JavaScript program.
Advanced use cases
Mathcore auto-grading module
You can use this module (Mathcore) to validate math responses (as LaTeX) against the defined specification.
The passed value can be any valid LaTeX string. The specification, on the other hand, is a complex object. We encourage you to build it using the user interface of the Math Formula Question type in Question Editor (see the Question Editor demo pages). This way you can quickly learn which methods and options are available. To do so, simply use the validation tab to build and Source button to preview specification JSON.
For more information, see the full documentation for the Mathcore auto-grading library.
See the Mathcore auto-grading documentationPass extra information into custom questions through init_options
In some scenarios, you need the clients using your custom questions/features to pass certain information like authentication tokens. This information will be stored inside the initOptions custom_widget_options
and you can retrieve those information through the public method init.getCustomWidgetOptions(key: string)
.
You are required to let your clients know what key
you expect them to set in the initOptions custom_widget_options
.
// Example of initOptions with custom_widget_options
const initOptions = {
custom_widget_options: {
custom_short_text: {
authenticationToken: '1234-abcd'
}
...
}
};
const questionsApp = LearnosityApp.init(initOptions);
// Your Custom Question Code
export default class Question {
constructor(init) {
const authenticationToken = init
.getCustomWidgetOptions('custom_short_text')
.authenticationToken;
}
//....
}
Code example 8: Retrieving custom_widget_options
Demo
Here is a demo that shows an example custom implementation of the Short Text Question type. You can rewrite the Question JSON to define your own custom Questions.
Go to demo