Taxonomy Icon

Cloud

Learning objectives

This tutorial shows you how to use the Learning Tools Interoperability standard so that you can write web applications that integrate with online training platforms as learning modules. This is the most flexible way to write learning modules.

Prerequisites

To build the application you need:

View the application

View the source code

Estimated time

Completing this tutorial should take about one hour.

Introduction

The cheapest, most user-friendly way to provide training to employees and customers is by using an online training platform, such as Moodle. Online training platforms allow for training that is available 24×7 on demand when the learner needs it. They only need to be produced once and can then be available for as long as they are useful.

However, there is one drawback to learning platforms. As a type of content management system (CMS), they are restricted to whatever types of content the platform and plug-in authors think is useful. If you want to do something out of the ordinary, this can be a problem.

I work in UberKnowledge, an innovative training content provider with an emphasis on short, directed online training. The instructional design for some of our classes calls for question-based learning. Our target audience is adult learners with widely differing levels of knowledge, so instead of wasting their time teaching them what they already know, we have them answer questions: If they get the answer to a particular question right, then we know they understand the material; if they don’t, an area opens up with materials to teach them the answer.

Our online training platform is Moodle-based and does not support this functionality. It has an online quiz function, but as it is designed for graded quizzes it doesn’t show the answers until the quiz is submitted. To be able to design a system that will implement exactly what we need, we had to write our own web application. To tie it to our Moodle, we needed the Learning Tools Interoperability (LTI) Standard.

Architecture

There are three entities involved in LTI:

  • Provider — Provides the training module; this tutorial focuses on writing the provider
  • Consumer — Requests the module, for example Moodle
  • Browser — The user’s browser that displays the module

Figure 1

Here is the process:

  1. The consumer identifies the need for the external module and sends the browser a form that posts automatically (using JavaScript) with the required information. This includes authentication, the student’s identity, and a URL for the provider to inform the consumer of the student’s grade.
  2. The browser receives the form and submits it to the provider.
  3. The provider responds with the module to display.
  4. After some additional communication, the provider may contact the consumer with a grade for the student.

Steps

1. Create a simple module provider

In this step, you create a simple module provider using Node.js on the IBM Cloud. To see my instance of the application (LTI consumer and LTI provider), go to https://ori-test.moodlecloud.com/login, create an account for yourself, and register for the LTI Demo class. To create your own instance, create the application and development environment and then use this file for the app.js and this file for the package.json and start the application.

Moodle instance

If you already have a Moodle instance, you can skip this section. If not, here are the instructions to create a free one on MoodleCloud and create a course for it.

  1. Sign up for a MoodleCloud account. You need a cell phone number for an SMS verification.
  2. Click the gear icon and then Turn editing on.
  3. Click Add a new course.
  4. Enter these parameters:
Parameter Value
Course full name LTI demo
Course short name LTI demo
Course end date enable Clear (so it is disabled)
  1. Click Save and display.
  2. The default course has four topics. Click Edit > Delete topic on three of them.

Add the module to a course

To add the module as a course activity, follow these instructions:

  1. Open a course in the editor (if you followed the instructions above to create a MoodleCloud class, you are already there).
  2. Click Add an activity or resource.
  3. Select External tool and click Add.
  4. Click Show more and enter these parameters:
Parameter Value
Activity name Simple module
Tool URL and secure tool URL https:///module_1. To use the sample application, enter https://lti-sample.mybluemix.net/module_1.
Consumer key top
Shared secret secret
Customer parameters testParam=testVal
param2=some value
  1. Click Save and display. The result should look similar to this screen capture:

    Figure 2

  2. The result is the parameters that Moodle provides to the module, in JSON format. You can use a JSON formatter to make the parameters more readable. As you can see, the custom parameters are prefixed with custom_.

    Figure 3

Here is a detailed explanation of the relevant lines in app.js:

These are the two packages you need. The uuid4 package is used for session IDs. The ims-lti package is the one that actually implements the LTI standard for you.

var uuid = require("uuid4");
var lti = require("ims-lti");

The LTI Provider library expects connections to be HTTPS for security. However, in the IBM Cloud’s Platform as a Service (PaaS) service the connections from the outside are terminated by IBM DataPower Gateways. The connection that the application receives is HTTP, without any encryption. By enabling trust proxy, you tell the application to trust that the proxy handles security.

// create a new express server
var app = express();
app.enable('trust proxy');

This associative array contains the session information for different sessions.

var sessions = {};

LTI uses the POST HTTP verb, which means there are additional fields after the HTTP header that need to be parsed. Here’s how you parse them.

app.post("*", require("body-parser").urlencoded({extended: true}));

This call actually implements the module.

app.post("/module_1", (req, res) => {

The first step is to create an LTI provider. This provider understands the LTI request and has all the necessary parameters. It also has the methods for using that data.

        var moodleData = new lti.Provider("top", "secret");

This call provides moodleData with the request and checks if it is valid.

        moodleData.valid_request(req, (err, isValid) => {

If the call isn’t valid, an error message is returned:

                if (!isValid) {
                        res.send("Invalid request: " + err);
                        return ;
                }

If you’ve gotten to this point, it means the module has been invoked correctly; if so, create a random unique session ID and store the moodleData object under that session. That way, you can have the browser respond with the session ID and you’ll know what data to use.

                var sessionID = uuid();
                sessions[sessionID] = moodleData;

Normally you’d send HTML for a learning objective here, however, to show what information is available to the module writer, this module returns that information in JSON format.

                res.send(moodleData.body);
        });   // moodleDate.valid_request

});       // app.post("/module_1");

The rest of the program is standard IBM Cloud Node.js boilerplate that’s used to get the port number and start the HTTP server.

2. Create a module with actual information

To create a useful module, create an HTML file, read it in the application, add the parameters it needs to have, and send it as the response.

To see an example, update the IBM Cloud application: Upload this file as mod2.html, and replace app.js with this file. Restart the application.

In the Moodle, follow these directions:

  1. Go to the test class.
  2. Click the gear icon and then Turn editing on.
  3. In the row with simple module, click Edit > Duplicate.
  4. Edit the settings for the new version of simple module to modify these parameters:
Parameter New Value
Activity name Module with HTML
Tool URL and secure tool URL Replace module_1 with module_2
  1. View the module. It should look similar to this:

    Figure 4

Here are the new parts in app.js:

The fs package is the Node.js API for the file system. The fs.readFileSync function reads the file and does not return until the file is read. That is normally a bad idea in Node.js, but in this case it happens only when the application starts. Using the synchronous function allows you to avoid handling a case where someone attempts to get the module before the file is ready.

var fs = require('fs');
var mod2File = fs.readFileSync("mod2.html", "utf8");

You’ll use a different URL here to serve a different module. This is one way to distinguish between different modules in the same application; the other way is to use parameters, such as custom_testParam in the first module.

app.post("/module_2", (req, res) => {

The lines that create the Moodle provider, validate the request, and deal with the session ID are the same as in the previous module.

The HTML file you send to the browser can’t be static because it needs to include whatever parameters affect the module (in this case, the user name). Also, if you need to report a grade, as you would in the next example, you need the session ID. One way to do this is to replace a string inside the HTML with the parameters you need to send.

                var sendMe =
                        mod2File.toString().replace("//PARAMS**GO**HERE",

In JavaScript, you can use backticks to specify a template literal. Template literals can span multiple lines and include the other two-string delimiters (double and single quotes). The syntax ${<expression>} has a special meaning — the expression is evaluated and the value placed in the string. These features make it easy to produce JavaScript code that declares a constant with the parameter values you need.

                `
                        const params = {
                                sessionID: "${sessionID}",
                                user: "${moodleData.body.ext_user_username}"
                        };
                `);

Here you set the content type to HTML, and send the file to the user’s browser to be displayed inside the Moodle window.

                res.setHeader("Content-Type", "text/html");
                res.send(sendMe);
        });   // moodleDate.valid_request

});       // app.post("/module_2");

Most of mod2.html is standard HTML, but two parts of it are somewhat unusual. First, in the head section of the HTML you have a script with the string that app.js replaces with the parameter value:

                <script>
                //PARAMS**GO**HERE
                </script>

Then, in the body, you can put scripts that use document.write to put the parameters that you got from the LTI provider (the app.js code running on the IBM Cloud).

                <h2>Module</h2>
                <script>
                        document.write(`Hello ${params.user}`);
                </script>

3. Report grades to the LTI consumer

If your module is some kind of test or assignment, it needs to report the student’s grade back to the LTI consumer. To see this in action, upload this file as mod3.html, and replace app.js with this file. In Moodle, duplicate the previous module and in the URLs replace module_2 with module_3. The module should look like this:

Figure 5

And after you submit a grade, it should look like this:

Figure 6

If you click Grades in the sidebar, you should see your grade in Moodle:

Figure 7

Note: If you are using your own Moodle instance, you might have to create a new user for your grade to register. As the creator of the class you are an administrator, so you can see all of the modules but you don’t get grades.

The call that serves /module_3 is nearly identical to that of /module_2. The only difference is the file served (mod3.html instead of mod2.html). The mod3.html file has a few parts that are different from the previous example.

The following line defines a range input field, which lets students select their grade. Of course, in a real module it is more likely to be a test with students submitting their answers and the module calculating the grade based on those answers.

<input id="grade" type="range" min="0" max="100">

This button lets the student submit the grade. It calculates a path and then goes there.

<button type="button" onClick="
        var path = `/grade/${params.sessionID}/${grade.value}`;
document.location = path;
">Submit grade</button>

To accept grade reports, app.js has this handler call. Notice that the second and third path components are preceded by a colon (:). This declares them to be path parameters, matching any string that is a legitimate path component.

app.get("/grade/:sessionID/:grade", (req, res) => {

The path parameters are available in the req.params associative array. The session ID lets you get the Moodle session object.

        const session = sessions[req.params.sessionID];
        var grade = req.params.grade;
        var resp;

        ...

The Moodle session object has a method to update the result, the grade for the activity. The grade is on a scale of 0 to 1, so you divide the grade you got by 100.

        session.outcome_service.send_replace_result(grade/100,

And here is the callback that’s called after the update is done:

   (err, isValid) => {
                if (!isValid)
                        resp += `<br/>Update failed ${err}`;

                res.send(resp);
        });

});    // app.get("/grade...")

Summary

You now have the necessary knowledge and skills to create Learning Tools Interoperability (LTI) modules for Moodle and related systems on Node.js, and execute them through the IBM Cloud. Take what you’ve learned here and start creating online training apps that meet the needs of your employees and customers.