-
Notifications
You must be signed in to change notification settings - Fork 40
How to interact with the SCORM API from the React application
This guide explains how to communicate and interact with the SCORM API from the React application developed using the RESCORM boilerplate. This guide does not explain SCORM. If you are interested in learning SCORM, you have the official documentation of SCORM 1.2 available here and the official documentation of SCORM 2004 available here.
Once the application is loaded by the web browser, it will try to find a JavaScript SCORM API provided by the environment (typically a Learning Management System like Moodle). The search can have three possible results: a SCORM 2004 API is found, a SCORM 1.2 API is found, or no API is found. If an API is found, the connection will be automatically established.
Although is not necessary, since the application handles all possible failures related to the SCORM connection, it's possible for a React component to check whether the application is connected to a SCORM-compliant environment as follows:
import * as SCORM_WRAPPER from '../vendors/SCORM_API_Wrapper.js';
[...]
if(SCORM_WRAPPER.isConnected()){
//The app is connected to a SCORM-compliant environment.
} else {
//The app is not connected.
}
If the adaptive key of the configuration file is true (which is its default value), the React application will wait for the SCORM connection to be resolved (succesfully or unsuccessfully) before loading its main content (i.e. the Quiz in the provided example). Otherwise, the application will load the content before the connection to SCORM be attempted.
From the React application, it is possible to report the following data to the SCORM-compliant environment through the SCORM API:
- progress_measure: It is a measure of the progress the learner has made towards completing the application (i.e. the educational resource). This parameter can take a value from 0 to 1. If the progress_measure is higher or equal than the completion_threshold defined in the configuration file, the completion status reported by SCORM will be "completed". If the progress_measure is lower than the completion_threshold but higher than the completion_attempt_threshold defined in the configuration file, the completion status will be "incomplete". Otherwise, the completion status will be "not attempted".
- score: it is the learner score or grade for the application (i.e. for the educational resource). This parameter can take a value from 0 to 1. If the score is higher or equal than the score_threshold defined in the configuration file, the success status reported by SCORM will be "passed". Otherwise, the success status will be "failed".
In order to report a progress_measure or a score through the SCORM API, the React application must use objectives. An objective represents an activity or a milestone of the application whose accomplishment will cause a progress_measure and/or score to be reported. Objectives are created using the "Objective" constructor provided by the "/vendors/Utils.js" file, and have the following fields:
Filed Name | Description |
---|---|
id | Unique identifier. It can be a number or a string. Default value is an automatically generated number. |
accomplished | True if the objective has been accomplished, false otherwise. Default value is false. |
progress_measure | The progress_measure that this objective represents with respect to the whole application. It should be a value from 0 to 1. Default value is 0. |
score | The score that this objective represents with respect to the whole application. It should be null or a value from 0 to 1. Default value is null. |
accomplished_score | The score achieved when the objective was accomplished. It should be null or a value higher than 0 but lower than score. Default value is null. |
The RESCORM boilerplate includes a working example of a Quiz in app/components/Quiz.jsx. This example illustrates how to use and manage objectives.
Firstly, the objectives should be created and registrated in the application. It is recommended to do this in the componentDidMount function of the React component:
import { addObjectives } from './../reducers/actions';
[...]
componentDidMount(){
// Create objectives (One per question included in the quiz)
let objectives = [];
let nQuestions = this.state.quiz.questions.length;
for(let i = 0; i < nQuestions; i++){
objectives.push(new Utils.Objective({id:("Question" + (i+1)), progress_measure:(1/nQuestions), score:(1/nQuestions)}));
}
this.props.dispatch(addObjectives(objectives));
}
In this example, an objective is created for each question of the quiz. Each of these objectives has the same progress_measure and score. The sum of the progress measures of all objectives of an application should be 1. Similarly, if the application has at least one objective with a score, the sum of the scores of all objectives should be 1. Note that in order to succesfully create an objective it is only necessary to specify its identifier, progress measure and, optionally, its score.
Once an objective has been created and added to the application, it can be accessed as follows:
let objective = this.props.tracking.objectives[objectiveId];
Secondly, each time an objective is accomplished, the React application must dispatch the objectiveAccomplished action indicating which objective has been accomplished and, if that objective has a defined score, its accomplished_score. Next, it is shown as an example how the provided Quiz application dispatches the objectiveAccomplished actions when objectives are accomplished.
import { objectiveAccomplished } from './../reducers/actions';
[...]
onAnswerQuestion(){
// Calculate score
let nChoices = this.props.question.choices.length;
let correctAnswers = 0;
let incorrectAnswers = 0;
let blankAnswers = 0;
for(let i = 0; i < nChoices; i++){
let choice = this.props.question.choices[i];
if(this.state.selected_choices_ids.indexOf(choice.id) !== -1){
// Answered choice
if(choice.answer === true){
correctAnswers += 1;
} else {
incorrectAnswers += 1;
}
} else {
blankAnswers += 1;
}
}
let scorePercentage = Math.max(0, (correctAnswers - incorrectAnswers) /
this.props.question.choices.filter(function(c){return c.answer === true;}).length);
// Send data via SCORM
let objective = this.props.objective;
this.props.dispatch(objectiveAccomplished(objective.id, objective.score * scorePercentage));
// Mark question as answered
this.setState({answered:true});
}
The Quiz application creates a objective for each question of the quiz. Then, each time a question is answered by the user, it dispatches an objectiveAccomplished action in order to accomplish the objective associated to that question. The objectiveAccomplished action admits two parameters: objectiveId and accomplishedScore. The first parameter (objectiveId) is mandatory and should be the identifier of the objective to be accomplished. The second parameter (accomplishedScore) should be provided only if the objective has a defined score. It is recommended to calculate the accomplishedScore as a percentage of the score defined in the objective (as shown in the example).
It is possible to use an action created using the redux-thunk middleware for accomplishing objectives instead of using regular actions. Thereby, it will be able to dispatch other actions or execute any code after accomplishing an objective. In order to achieve this, it is necessary to import and use the objectiveAccomplishedThunk action as follows:
import { objectiveAccomplishedThunk } from './../reducers/actions';
[...]
onAnswerQuestion(){
[...]
// Send data via SCORM
let objective = this.props.objective;
this.props.dispatch(objectiveAccomplishedThunk(objective.id, objective.score * scorePercentage));
[...]
}
The RESCORM boilerplate also provides an action called resetObjectives that allows to reset all added objectives in order to restart the application. Next, it is shown as an example how the provided Quiz application uses this action to reset the quiz:
onResetQuiz(){
this.setState({current_question_index:1});
this.props.dispatch(resetObjectives());
}
Basically, first it resets its own state and then it dispatches the resetObjectives action.