Authoring Custom Questions & Features

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.

This page describes how to set up default schemas rules as well as the desired editor layout for your custom Question or Feature with Learnosity Question Editor API.

Overview

In order to build a Custom Question / Feature, you need to do the following:

  1. Define a schema for the Question Editor form (JSON data),
  2. Provide a corresponding HTML layout, which contains the hooks which Question Editor uses to insert the schema attributes (HTML file),
  3. Provide the custom question logic (JS file) which contains logic to parse the Question JSON and render the Question, and
  4. Provide the custom scoring logic (JS file) which contains logic to score the learner's response.

In this article, we'll look into some different examples for the schema and the HTML layout (points one and two above) - covering a simple static form, a dynamic example and a more advanced dynamic example.

Please refer to Creating Custom Questions for information on the custom Question and scoring logic (points 3 and 4 above).

Custom Question / Feature Schemas

In order for the Question Editor to understand what desired input types you want to use when authoring your custom Question or Feature, you need to define the schema for each input.

The rule will be inserted through the custom_question_types and custom_feature_types initialization options.

Each entry in the array will define some properties including the JavaScript and the layout, see more information on the docs pages linked above.

The schemas are defined in the property editor_schema.

Static attributes

Static forms are easily achievable using the schemas rules mentioned above. To see an example of this, along with the schema and the layout, please see our demo for the custom Question, using a box & whisker chart.

Dynamic attributes

It is possible to create dynamic forms using conditional_attributes. That is, you can have attributes which only appear when another attributes value is a specific value.

If you want to have nested conditionals, that is, attributes which are shown based on the value if another attribute, which itself is shown based on the attribute of another, then the properties must be nested themselves.

For single level conditionals the output JSON can remain flat, but if if you have nested, then the JSON will have nested attributes.

To understand further, see the following two examples.

Basic Example

In this example, we have a dropdown menu to select types of triangle. When one is selected, different fields show based on that value. For example, for equilateral, as all sides are the same length, there's just one field for "Side length" which appears. Whereas for scalene, as all three sides are different lengths, three fields (array) are shown. See the video below which demonstrates this, and below that are the code examples for the schema, HTML and output JSON.


Video 1: basic dynamic form

 

Schema data

{
    "editor_schema": {
        "hidden_question": false,
        "attributes": {
            "stimulus": {
                "type": "string",
                "name": "Stimulus",
                "description": "The question for the student to answer",
                "required": true,
                "element": "editor",
                "default": "Calculate the area of the shape below"
            },
            "triangle_type": {
                "type": "string",
                "name": "Triangle Type",
                "description": "Select a type of triangle.",
                "required": true,
                "element": "select",
                "options": [{
                    "value": "equilateral",
                    "label": "Equilateral"
                }, {
                    "value": "isosceles",
                    "label": "Isosceles"
                }, {
                    "value": "scalene",
                    "label": "Scalene"
                }, {
                    "value": "right",
                    "label": "Right Angled"
                }],
                "default": "equilateral"
            },
            "color": {
                "type": "string",
                "name": "Color",
                "description": "Fill color of the shape.",
                "required": true,
                "element": "colorPicker",
                "default": "#0f3d6d"
            }
        },
        "conditional_attributes": {
            "attribute_key": "triangle_type",
            "conditions": [{
                "value": "equilateral",
                "attributes": {
                    "side_length": {
                        "type": "number",
                        "name": "Side Length (cm)",
                        "description": "Length of the side of the triangle.",
                        "required": true
                    }
                }
            }, {
                "value": "isosceles",
                "attributes": {
                    "base": {
                        "type": "number",
                        "name": "Base",
                        "description": "Base length of the triangle.",
                        "required": true
                    },
                    "height": {
                        "type": "number",
                        "name": "Side Length",
                        "description": "Length of the equal sides of the triangle.",
                        "required": true
                    }
                }
            }, {
                "value": "scalene",
                "attributes": {
                    "side_lengths": {
                        "items": {
                            "type": "number",
                            "name": "Side length "
                        },
                        "name": "Side Lengths",
                        "description": "Lengths of the sides of the triangle.",
                        "required": true,
                        "type": "array",
                        "count": {
                            "minimum": 3,
                            "maximum": 3
                        }
                    }
                }
            }, {
                "value": "right",
                "attributes": {
                    "base": {
                        "type": "number",
                        "name": "Base",
                        "description": "Base length of the triangle.",
                        "required": true
                    },
                    "height": {
                        "type": "number",
                        "name": "Height",
                        "description": "Height of the triangle.",
                        "required": true
                    }
                }
            }]
        }
    }
}

Source code example 1: the schema data

 

HTML layout

<div class="lrn-qe-edit-form">
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="stimulus"></span><span data-lrn-qe-input="stimulus"></span>
    </div>
    <!-- triangle -->
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="triangle_type"></span><span data-lrn-qe-input="triangle_type"></span>
    </div>
    <!-- equilateral -->
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="side_length"></span><span data-lrn-qe-input="side_length"></span>
    </div>
    <!-- isosceles -->
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="base"></span><span data-lrn-qe-input="base"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper><span data-lrn-qe-label="height"></span>
        <span data-lrn-qe-input="height"></span>
    </div>
    <!-- scalene -->
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <div data-lrn-qe-loop="side_lengths[*]">
            <div data-lrn-qe-label="side_lengths[*]"></div>
            <div data-lrn-qe-input="side_lengths[*]"></div>
        </div>
    </div>
    <!-- color -->
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="color"></span><span data-lrn-qe-input="color"></span>
    </div>
</div>

Source code example 2: the HTML layout

 

Example JSON (for a scalene triangle)

{
    "type": "custom",
    "stimulus": "Calculate the area of the shape below",
    "js": {
        "question": "shapes_q.js",
        "scorer": "shapes_s.js"
    },
    "valid_response": 1.9843,
    "score": 1,
    "custom_type": "custom",
    "css": "custom.css",
    "version": "v1.0.0",
    "triangle_type": "scalene",
    "color": "#0f3d6d",
    "side_lengths": [2, 3, 2]
}

Source code example 3: the JSON source for the scalene example

 

Advanced Example

In this example, we have a select dropdown where the shape dimensions is picked first (2D/3D), then based on that another drop-down menu shows different classifications of shapes (Triangle, Quadrilateral, etc.), and then based on that different shape types are shown (Square, Scalene, and so on).
See the video below which demonstrates this, and below that are the code examples for the schema, HTML and output JSON.


Video 2: advanced dynamic form

 

Schema data

{
    "editor_schema": {
        "hidden_question": false,
        "attributes": {
            "stimulus": {
                "type": "string",
                "name": "Stimulus",
                "description": "The question for the student to answer",
                "required": true,
                "element": "editor",
                "default": "Calculate the [area/volume] of the shape below"
            },
            "dimensions": {
                "type": "string",
                "name": "Shape Dimensions",
                "description": "Select the type of shape.",
                "required": true,
                "element": "select",
                "options": [{
                    "value": "2d",
                    "label": "2D"
                }, {
                    "value": "3d",
                    "label": "3D"
                }],
                "default": "3d"
            },
            "color": {
                "type": "string",
                "name": "Color",
                "description": "Fill color of the shape.",
                "required": true,
                "element": "colorPicker",
                "default": "#0cb0d8"
            }
        },
        "conditional_attributes": {
            "attribute_key": "dimensions",
            "conditions": [{
                "value": "2d",
                "attributes": {
                    "2d": {
                        "type": "object",
                        "attributes": {
                            "shape": {
                                "type": "string",
                                "name": "2D Shape",
                                "description": "Select a 2D shape.",
                                "required": true,
                                "element": "select",
                                "options": [{
                                    "value": "quadrilateral",
                                    "label": "Quadrilateral"
                                }, {
                                    "value": "triangle",
                                    "label": "Triangle"
                                }, {
                                    "value": "polygon",
                                    "label": "Polygon"
                                }],
                                "default": "triangle"
                            }
                        },
                        "conditional_attributes": {
                            "attribute_key": "shape",
                            "conditions": [{
                                "value": "quadrilateral",
                                "attributes": {
                                    "quadrilateral": {
                                        "type": "object",
                                        "attributes": {
                                            "type": {
                                                "type": "string",
                                                "name": "Quadrilateral Type",
                                                "description": "Select a type of quadrilateral.",
                                                "required": true,
                                                "element": "select",
                                                "options": [{
                                                    "value": "square",
                                                    "label": "Square"
                                                }, {
                                                    "value": "rectangle",
                                                    "label": "Rectangle"
                                                }, {
                                                    "value": "rhombus",
                                                    "label": "Rhombus"
                                                }],
                                                "default": "square"
                                            }
                                        },
                                        "conditional_attributes": {
                                            "attribute_key": "type",
                                            "conditions": [{
                                                "value": "square",
                                                "attributes": {
                                                    "square_options": {
                                                        "type": "object",
                                                        "name": "Square Options",
                                                        "description": "Options for squares.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length",
                                                                "description": "Length of the side of the square.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "rectangle",
                                                "attributes": {
                                                    "rectangle_options": {
                                                        "type": "object",
                                                        "name": "Rectangle Options",
                                                        "description": "Options for rectangles.",
                                                        "required": true,
                                                        "attributes": {
                                                            "length": {
                                                                "type": "number",
                                                                "name": "Length",
                                                                "description": "Length of the rectangle.",
                                                                "required": true
                                                            },
                                                            "width": {
                                                                "type": "number",
                                                                "name": "Width",
                                                                "description": "Width of the rectangle.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "rhombus",
                                                "attributes": {
                                                    "rhombus_options": {
                                                        "type": "object",
                                                        "name": "Rhombus Options",
                                                        "description": "Options for rhombuses.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length",
                                                                "description": "Length of the side of the rhombus.",
                                                                "required": true
                                                            },
                                                            "angle": {
                                                                "type": "number",
                                                                "name": "Angle",
                                                                "description": "Angle of the rhombus.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }]
                                        }
                                    }
                                }
                            }, {
                                "value": "triangle",
                                "attributes": {
                                    "triangle": {
                                        "type": "object",
                                        "attributes": {
                                            "type": {
                                                "type": "string",
                                                "name": "Triangle Type",
                                                "description": "Select a type of triangle.",
                                                "required": true,
                                                "element": "select",
                                                "options": [{
                                                    "value": "equilateral",
                                                    "label": "Equilateral"
                                                }, {
                                                    "value": "isosceles",
                                                    "label": "Isosceles"
                                                }, {
                                                    "value": "scalene",
                                                    "label": "Scalene"
                                                }, {
                                                    "value": "right",
                                                    "label": "Right Angled"
                                                }],
                                                "default": "equilateral"
                                            }
                                        },
                                        "conditional_attributes": {
                                            "attribute_key": "type",
                                            "conditions": [{
                                                "value": "equilateral",
                                                "attributes": {
                                                    "equilateral_options": {
                                                        "type": "object",
                                                        "name": "Equilateral Options",
                                                        "description": "Options for equilateral triangles.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length (cm)",
                                                                "description": "Length of the side of the triangle.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "isosceles",
                                                "attributes": {
                                                    "isosceles_options": {
                                                        "type": "object",
                                                        "name": "Isosceles Options",
                                                        "description": "Options for isosceles triangles.",
                                                        "required": true,
                                                        "attributes": {
                                                            "base": {
                                                                "type": "number",
                                                                "name": "Base",
                                                                "description": "Base length of the triangle.",
                                                                "required": true
                                                            },
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length",
                                                                "description": "Length of the equal sides of the triangle.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "scalene",
                                                "attributes": {
                                                    "scalene_options": {
                                                        "type": "object",
                                                        "name": "Scalene Options",
                                                        "description": "Options for scalene triangles.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_lengths": {
                                                                "items": {
                                                                    "type": "number",
                                                                    "name": "Side length "
                                                                },
                                                                "name": "Side Lengths",
                                                                "description": "Lengths of the sides of the triangle.",
                                                                "required": false,
                                                                "type": "array",
                                                                "count": {
                                                                    "minimum": 3,
                                                                    "maximum": 3
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "right",
                                                "attributes": {
                                                    "right_options": {
                                                        "type": "object",
                                                        "name": "Right Options",
                                                        "description": "Options for right triangles.",
                                                        "required": true,
                                                        "attributes": {
                                                            "base": {
                                                                "type": "number",
                                                                "name": "Base",
                                                                "description": "Base length of the triangle.",
                                                                "required": true
                                                            },
                                                            "height": {
                                                                "type": "number",
                                                                "name": "Height",
                                                                "description": "Height of the triangle.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }]
                                        }
                                    }
                                }
                            }, {
                                "value": "polygon",
                                "attributes": {
                                    "type": "object",
                                    "polygon": {
                                        "attributes": {
                                            "type": {
                                                "type": "string",
                                                "name": "Polygon Type",
                                                "description": "Select a type of polygon.",
                                                "required": true,
                                                "element": "select",
                                                "options": [{
                                                    "value": "pentagon",
                                                    "label": "Pentagon"
                                                }, {
                                                    "value": "hexagon",
                                                    "label": "Hexagon"
                                                }, {
                                                    "value": "octagon",
                                                    "label": "Octagon"
                                                }],
                                                "default": "pentagon"
                                            }
                                        },
                                        "conditional_attributes": {
                                            "attribute_key": "type",
                                            "conditions": [{
                                                "value": "pentagon",
                                                "attributes": {
                                                    "pentagon_options": {
                                                        "type": "object",
                                                        "name": "Pentagon Options",
                                                        "description": "Options for pentagons.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length",
                                                                "description": "Length of the side of the pentagon.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "hexagon",
                                                "attributes": {
                                                    "hexagon_options": {
                                                        "type": "object",
                                                        "name": "Hexagon Options",
                                                        "description": "Options for hexagons.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length",
                                                                "description": "Length of the side of the hexagon.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }, {
                                                "value": "octagon",
                                                "attributes": {
                                                    "octagon_options": {
                                                        "type": "object",
                                                        "name": "Octagon Options",
                                                        "description": "Options for octagons.",
                                                        "required": true,
                                                        "attributes": {
                                                            "side_length": {
                                                                "type": "number",
                                                                "name": "Side Length",
                                                                "description": "Length of the side of the octagon.",
                                                                "required": true
                                                            }
                                                        }
                                                    }
                                                }
                                            }]
                                        }
                                    }
                                }
                            }]
                        }
                    }
                }
            }, {
                "value": "3d",
                "attributes": {
                    "3d": {
                        "type": "object",
                        "attributes": {
                            "shape": {
                                "type": "string",
                                "name": "3D Shape",
                                "description": "Select a 3D shape.",
                                "required": true,
                                "element": "select",
                                "options": [{
                                    "value": "polyhedron",
                                    "label": "Polyhedron"
                                }, {
                                    "value": "sphere",
                                    "label": "Sphere"
                                }, {
                                    "value": "cylinder",
                                    "label": "Cylinder"
                                }, {
                                    "value": "cone",
                                    "label": "Cone"
                                }],
                                "default": "polyhedron"
                            }
                        },
                        "conditional_attributes": {
                            "attribute_key": "shape",
                            "conditions": [{
                                "value": "polyhedron",
                                "attributes": {}
                            }]
                        }
                    }
                }
            }]
        }
    }
}

Source code example 4: schema data for the advanced example

 

HTML layout (not all included for brevity)

<div class="lrn-qe-edit-form">
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="stimulus"></span>
        <span data-lrn-qe-input="stimulus"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
      	<span data-lrn-qe-label="dimensions"></span>
      	<span data-lrn-qe-input="dimensions"></span>
  	</div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
      	<span data-lrn-qe-label="2d.shape"></span>
      	<span data-lrn-qe-input="2d.shape"></span>
  	</div>
    <!-- 2D --><!-- quadrilateral -->
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="2d.quadrilateral.type"></span>
        <span data-lrn-qe-input="2d.quadrilateral.type"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="2d.quadrilateral.square_options.side_length"></span>
        <span data-lrn-qe-input="2d.quadrilateral.square_options.side_length"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="2d.quadrilateral.square_options.color"></span>
        <span data-lrn-qe-input="2d.quadrilateral.square_options.color"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="2d.quadrilateral.rectangle_options.length"></span>
        <span data-lrn-qe-input="2d.quadrilateral.rectangle_options.length"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="2d.quadrilateral.rectangle_options.width"></span>
        <span data-lrn-qe-input="2d.quadrilateral.rectangle_options.width"></span>
    </div>
    <div class="lrn-qe-col-lg-12" data-lrn-qe-layout-wrapper>
        <span data-lrn-qe-label="2d.quadrilateral.rectangle_options.color"></span>
        <span data-lrn-qe-input="2d.quadrilateral.rectangle_options.color"></span>
    </div>
    ....
</div>

Source code example 5: HTML layout for the advanced example

 

JSON source (for the 3D -> polyhedron -> cube)

{
    "type": "custom",
    "js": {
        "question": "shapes_q.js",
        "scorer": "shapes_s.js"
    },
    "custom_type": "custom",
    "css": "custom.css",
    "version": "v1.0.0",
    "dimensions": "3d",
    "color": "#0cb0d8",
    "3d": {
        "polyhedron": {
            "cube_options": {
                "edge_length": 3
            }
        }
    }
}

Source code example 6: JSON for the 3D polyhedron cube

 

JSON source (for the 2D -> polyhedron -> cube)

{
    "type": "custom",
    "stimulus": "Calculate the [area/volume] of the shape below",
    "js": {
        "question": "shapes_q.js",
        "scorer": "shapes_s.js"
    },
    "custom_type": "custom",
    "css": "custom.css",
    "version": "v1.0.0",
    "dimensions": "2d",
    "color": "#0cb0d8",
    "2d": {
        "shape": "quadrilateral",
        "quadrilateral": {
            "type": "rectangle",
            "rectangle_options": {
                "length": 1,
                "width": 4
            }
        }
    }
}

Source code example 7: JSON for the 2D polyhedron cube

 

How to Create Custom Question / Feature Custom Tile Items

Once the custom Question / Feature is defined through custom_question_types and custom_feature_types, we can now reference those custom Questions / Features in question_type_templates through their custom_type unique IDs.

 

Defining Custom Question / Feature Tile Items

const initOptions = {
    custom_question_types: [{
        "custom_type": "custom_short_text",
        "type": "custom",
        "name": "Custom Shorttext (DRAFT)",
        "js": {
            "question": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext_q.js",
            "scorer": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext_s.js"
        },
        "css": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext.css",
        "editor_layout": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext.html",
        "version": "v0.1.0",
        "editor_schema": {
            "hidden_question": false,
            "properties": {
                "valid_response": {
                    "type": "string",
                    "name": "Valid response",
                    "description": "Correct answer for the question.",
                    "required": false
                },
                "score": {
                    "type": "number",
                    "name": "Score",
                    "description": "Score for a correct answer.",
                    "required": false,
                    "default": 0
                }
            }
        }
    }],

    custom_feature_types: [{
        "custom_type": "name_of_custom_type",
        "name": "Display Name of Custom Feature",
        "js": "//[your.domain]/myfeaturescript.js",
        "css": "//[your.domain]/myfeaturestyles.css",
        "version": "1.0.0",
        "editor_layout": "//[your.domain]/myquestioneditorlayout.html",
        "editor_schema": {
            "hidden_question": false,
            "properties": {
                "placeholder_text": {
                    "type": "string",
                    "required": false
                }
            }
        }
    }],

    question_type_templates: {
        custom_short_text: [{
            "name": "Custom Question - Short text",
            "description": "A custom question type - short text",
            "group_reference": "other",
            "image": "custom_image_link_of_your_custom_question_tile_item",
            "defaults": {
                "type": "custom",
                "js": {
                    "question": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext_q.js",
                    "scorer": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext_s.js"
                },
                "css": "//demos.learnosity.com/casestudies/customquestions/custom_shorttext.css",
                "valid_response": "",
                "score": 1
            }
        }],
        custom_text_input_feature: [{
            "name": "Custom Feature - Simple textinput",
            "description": "A custom feature type - simple textinput",
            "image": "custom_image_link_of_your_custom_feature_tile_item",
            "defaults": {
                "type": "customfeature",
                "js": "//docs.learnosity.com/demos/products/questionsapi/customfeatures/textinput/index.js",
                "css": "//docs.learnosity.com/demos/products/questionsapi/customfeatures/textinput/index.css",
                "placeholder": "Default placeholder"
            }
        }]
    }
};

Source code example 8: Defining Custom Question / Feature Tile Items

 

Was this article helpful?

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