Creating Custom Questions

Please see Getting Started with Custom Questions and Features before starting with your Custom Question project.

We recommend using the Custom Question Skeleton project to build your Custom Question.

Learn how to create a Custom Question type to use with Learnosity's Questions API.

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.

This restriction applies only to attributes added after initialization. Any attribute included in the initialization Question JSON, including native Learnosity attributes and custom attributes, is available to both the Question implementation and server-side scoring.

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. 
"js": { "question": "my_custom_question.js", "scorer": "my_custom_scorer.js" } 
OR 
A string containing URL to a JavaScript file which defines an AMD module for the Question, and optionally, Scorer. 
"js": "my_custom_combined_file.js" 
Important: For successful server-side scoring, the JavaScript files supplied must be accessible externally. 
Note: Disabling autoscoring 
To disable autoscoring, do not supply a js.scorer attribute. In this case, automarkable should return false and you will not see a scoring exception as a pop-up window. 
Note: scorer.js return values for automarkable 
If you don't use the canValidateResponse method, automarkable should return true. 
If you do use the canValidateResponse method, automarkable should return the canValidateResponse value.
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 Question attributes article.

Example: Custom Question Initialization Attributes (JSON)

const 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: 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

Using native Learnosity Question attributes

Custom Questions can reuse many of the same Question attributes that are available in native Learnosity Question types. The Custom Question schema inherits from the standard Learnosity Question schema, allowing you to use native attributes alongside your own custom attributes.

Examples of supported native attributes include:

  • stimulus
  • instructor_stimulus
  • instant_feedback
  • feedback_attempts
  • validation
  • is_math
  • math_renderer
  • ui_style
  • metadata

When defining these attributes, use the same attribute names and JSON structure as native Learnosity Question types.

Example

{
   "type": "custom",
   "stimulus": "What is the capital of Australia?",
   "instructor_stimulus": "Accept Canberra only.",
   "validation": {
       "valid_response": {
           "score": 1
       }
   }
}

Platform-managed and custom-rendered attributes

Some native attributes are automatically handled by the Learnosity platform, while others must be rendered by your Custom Question implementation.

Attribute Behavior
instructor_stimulus Automatically managed by the platform. Visibility is controlled by the Question initialization options.
instant_feedback Automatically enables the Check Answer button when supported.
feedback_attempts Automatically limits the number of Check Answer attempts.
validation Provides the validation structure, but your Custom Question scorer must implement the scoring logic.
stimulus Must be rendered by your Custom Question JavaScript. The platform does not automatically render the Question stimulus.

When implementing your Question, read these values from the Question JSON and render them as required by your Question UI.

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:

Method Comment
renderComponent(String componentName, Element hook) A function used to render the given componentName at the specified 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: 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.

Rendering native Question attributes

Although native Question attributes such as stimulus are available in the Question JSON, Learnosity does not automatically render them for Custom Questions. Your Question implementation is responsible for reading these values and rendering them within the Question UI.

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);
});

Code example: using jQuery's change() method to listen for changes to text input

Or you can register the events handler when Question is being initialized

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: 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: 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 learner's actions).

This ensures responses can be saved as part of the session, or retrieved locally via Questions API public methods, for example, 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, for example, 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 Object 
options = { showCorrectAnswers: value }
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. 
See showCorrectAnswers article.
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: 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: 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: 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: 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: 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 Skeleton 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.

Question attributes available to the scorer

The question object passed to the scorer contains the same Question JSON that was supplied during initialization.

This includes:

  • native Question attributes
  • custom Question attributes
  • author-defined values
  • validation configuration

Only attributes present in the initialization JSON are available during server-side scoring. Attributes added dynamically after initialization are ignored.

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 as getResponses().

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: 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: accessing "attribute1" within the score function

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 learner 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: 
If there is no valid response data set in the Question, this method should return false 
If this Question type is not scorable (like an essay or open ended question) then this will return false

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.

data-api-online-scoring-endpoint.png
Screenshot: Data API online scoring endpoint demo page

To test your scoring functions:

  • Visit https://demos.learnosity.com/analytics/data/index.php then scroll to the Scoring section at the bottom of the page.
  • 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":"//learnosity.github.io/docs-learnosity-apis/examples/custom_question_types/shorttext/custom_shorttext.js",
            "scorer": "//learnosity.github.io/docs-learnosity-apis/examples/custom_question_types/shorttext/custom_shorttext_scorer.js"
        },
        "css": "//learnosity.github.io/docs-learnosity-apis/examples/custom_question_types/shorttext/custom_shorttext.css",
        "stimulus": "What is the capital of Australia?",
        "valid_response": "Canberra",
        "score": 1
    }]

    Code example: the JSON object for a custom Question

  • 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: 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: testing the responses object

  • 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: result data from the Data API endpoint

  • 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 documentation.

Pass 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: Retrieving custom_widget_options

Error Handling

For more details, visit the Best practices for Error Handling in Custom Questions article.

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

Was this article helpful?

Did you arrive here by accident? If so, learn more about Learnosity.