Implementing Interactive

LARA embeds interactives using iframe and provides LARA Interactive API. This API can be used e.g. to customize interactive, save state authored by teachers or save student progress. The whole communication between LARA and Interactive happens through iFramePhone which is a simple wrapper around postMessage API. Interactive which wants to use LARA Interactive API needs to use this library. Check its readme to see how to include it in your project (npm, RequireJS or via script tag). Once iframePhone library is available, Interactive needs to setup communication:

iframe-phone setup

var phone = iframePhone.getIFrameEndpoint();
phone.addListener('initInteractive', function (data) {
  // handle initInteractive call
});
phone.addListener('otherMessageType', function (data) {
  // handle otherMessageType call
});
// more handlers here

// IMPORTANT: Initialize connection after all message listeners are added!
phone.initialize();
// Inform LARA about supported features. Some of them require that
//  (e.g. state authoring, student progress saving):
phone.post('supportedFeatures', {
  apiVersion: 1,
  features: {
    someFeature: true
    otherFeature: true
  }
});

Handling the initInteractive message

This is one of the most important messages sent by LARA. It provides most of the information stored by LARA. It tells the interactive what is the current API version, mode (runtime, authoring or report), authoredState (state defined by author), interactiveState (state based on student work) and so on. Interactive should add initInteractive handler that reads values stored in data Object / hash:

phone.addListener('initInteractive', function (data) {
  // process data object, e.g.:
  initializeMode(data.mode);
  loadAuthoredState(data.authoredState);
  loadStudentWork(data.interactiveState);
});

Data passed to initInteractive

  • error - null if everything is okay, error message otherwise (e.g. data fetching might fail).
  • mode - "authoring", "runtime" or "report". Interactive can be displayed in one of those three modes, but it's up to interactive whether it supports authoring mode. Runtime is basic mode which is presented to students. Authoring mode can be used by authors to customize interactive. Report mode is used when embedding the interactive in a report.
  • authoredState [optional] - JSON that has been saved by interactive earlier in authoring mode using authoredState message (described below in other section). If it's not defined, it would be falsy value (null or empty string).
  • interactiveState [optional] - JSON that has been saved by interactive earlier in runtime mode using interactiveState message. More detail in Saving Student Progress section. Provided only in runtime mode. If it's not defined, it would be falsy value (null or empty string).
  • globalInteractiveState [optional] - JSON that has been saved by any interactive in the same activity earlier in runtime mode using globalInteractiveState message. Provided only in runtime mode. If it's not defined, it would be falsy value (null or empty string).
  • hasLinkedInteractive [optional] - boolean or undefined. If this is true then the author has linked this interactive to another interactive in the activity or sequence. This will be true even if the linked interactive has not saved state yet.
  • linkedState [optional] - JSON that has been saved by the linked interactive. If no state has been saved this will be falsy.
  • interactiveStateUrl [optional] - Fully qualified URL to access the interactive state externally. See Accessing Interactive State with HTTP for more information.
  • collaboratorUrls [optional] - Fully qualified URL to access the interactive state of each collaborator working with the current student. These URLs can be used to save a copy of the work into each collaborator's interactive state.
  • classInfoUrl - URL to request more information about the class of the student that is running the interactive. When run as a student in the portal this URL is of form: https://learn.staging.concord.org/api/v1/classes/[class_id] it provides information about the teachers and students of the class. As well as a class_hash that can be used to store info about the class in a external system (like Firebase). The URL is protected by cookie based authentication. withCredentials should be used to access it. TODO: what happens when the activity is run without a class? For example when it is run anonymously.
  • interactive - The value of this is
   {id: [id of interactive in LARA], name: [name of interactive in LARA]}
  • authInfo - The value of this is
   {provider: [domain of authentication provider],
    loggedIn: [whether the current user is logged in],
    email:    [email address for the current user]}

Interactive customization

If interactive wants to support customization, it should inform LARA about it using supportedFeatures message:

phone.post('supportedFeatures', {
  apiVersion: 1,
  features: {
    authoredState: true
    // + other supported features
  }
});

initInteractive message provides mode property. Interactive can display authoring interface when mode value is equal to "authoring". Every customization made by author should be sent back to LARA using authoredState message:

phone.post('authoredState', data);

The authoredState data should be a JSON compatible object, that is the only requirement. Next time the interactive is loaded either in authoring or runtime mode, initInteractive will provide this state to the interactive in authoredState property. Note that it's recommended that authoredState should be sent to LARA immediately after every change. LARA provides its own "Save" button that would actually save this state to the DB and "Reset" which would set it back to null.

Saving student progress

If interactive wants to support student progress saving, it should inform LARA about it using supportedFeatures message:

phone.post('supportedFeatures', {
  apiVersion: 1,
  features: {
    interactiveState: true
    // + other supported features
  }
});

Currently, LARA is not paying attention to this interactiveState supportedFeature, but it will do so in the future.

When this interactive is added to a LARA page the author needs to check the "save state" checkbox.

initInteractive provides mode property. "runtime" mode means that Interactive is viewed by student. Interactive can save student progress using interactiveState message:

phone.post('interactiveState', data);

Normally the interactiveState data should be a JSON compatible object, that is the only requirement. Next time the interactive is loaded in runtime mode, initInteractive will provide this state to the interactive in interactiveState property.

LARA will send the interactive a getInteractiveState periodically:

  • every 5 seconds
  • on window focus
  • iframe mouseout
  • when the student tries to leave the page containing the interactive

The interactive should add a listener for getInteractiveState. To be safe, remember to add the listener before calling phone.initialize();. Here is an example listener:

phone.addListener('getInteractiveState', function () {
  phone.post('interactiveState', interactiveState);
});

IMPORTANT When the student tries to leave the page with the interactive, the interactive needs to respond with an interactiveState message. LARA will not changes pages until it receives this message.

There is a special value "nochange", that can be sent as the interactiveState data. This tells LARA the interactiveState has not changed since the last time. This is useful when responding to getInteractiveState

Interactive aspectRatio

If interactive wants to tell LARA what aspect ratio use, it should inform LARA about it using supportedFeatures message:

phone.post('supportedFeatures', {
  apiVersion: 1,
  features: {
    aspectRatio: 1.67
    // + other supported features
  }
});

The LARA author needs to select "Default aspect ratio, or set by interactive" in the interactive authoring dialog box, otherwise this setting will be ignored. More info about the interactive sizing can be found in the LARA Authoring User Manual.

Custom learner URL

DEPRECATED This feature is related to student progress saving. Interactive can provide versioned URL to make sure that the given data format is always supported. Otherwise the interactive might be updated and not be able to open the old state format.

This is deprecated because it causes more problems than it is worth. We have wanted to remove support for old versions of interactives, and we've had to remember to go through all of the interactive states and these learner urls. Also the concept of saving a URL that works in this maner is confusing. Sometimes the interactive is loaded with the authored URL sometimes it is loaded with the saved URL. So a set of students or testers might see different versions of what seems like it should be the same interactive. Instead of using this approach to handle old state, the interactive should handle migrating its own old state. If migrating its own state isn't practical then please work with the LARA developers to do a batch migration on the server.

phone.post('setLearnerUrl', 'https://my.example.interactive/version/1');

Global interactive state

A few interactives can share one global state within a single LARA activity. E.g. you can let students work on the same model on different pages of the activity. Interactive can set global state using interactiveStateGlobal message:

phone.post('interactiveStateGlobal', globalState);

Once this message is received, the server immediately posts loadInteractiveGlobal to all interactives on the same page (except from the sender of the original save message). So, interactives can listen to this message:

phone.addListener('loadInteraciveGlobal', function (globalState) {
  // Process global state, e.g.:
  loadState(globalState);
});

If one of the interactives has sent interactiveStateGlobal message before another interactive is initialized then the global state will be provided in the initInteractive message.

NOTE Another way to communicate between two interactives is with Linked Interactives.

Logging

This message proxies communication from the interactive → LARA → Logging server. There is only one way communication between the interactive and LARA. The interactive is expected to post following messages using iframe phone:

phone.post('log', {action: 'actionName', data: {someValue: 1, otherValue: 2}});

LARA listens to these events only when logging is enabled (they will be ignored otherwise). When a log message is received, LARA issues a POST request to the Logging server. LARA uses provided action name and data, but also adds additional information to the event (context that might useful for researchers, e.g. user name, activity name, url, session ID, etc.).

Auth info

Interactive can ask LARA about the current users authentication information using getAuthInfo message. It has no payload data:

phone.post('getAuthInfo');

LARA responds using authInfo message:

phone.addListener('authInfo', function (info) {
  // ...
});

The payload is the object {provider: <string>, loggedIn: <boolean>, email: <string>} where provider and loggedIn are always set and email is only set if the user has an email address.

getExtendedSupport

This is a message sent by LARA to the interactive. The interactive can respond with a extendedSupport message. The data of the extendedSupport only supports one property reset. The value of reset should be boolean. If the interactive sends reset: true then LARA will show a button below the interactive for clearing the interactive state and reloading the interactive. If reset: false then LARA will not show this button. By default LARA will show this button.

NOTE this extended support message is out of date. It is the only way to hide the reset button, so it isn't deprecated. New options like this will be added to the supportedFeatures message described above.

Linked interactives

Each interactive in LARA can be linked with one other interactive in the same activity or sequence. A LARA author edits the main interactive and can add a reference to the linked interactive. Currently this reference is with an interactive id, but that referencing mechanism could change and it should not effect how the interactives are implemented.

When the main interactive is loaded it will be sent the initInteractive message just like normal. This message has 2 fields in related to linked interactives.

The hasLinkedInteractive field will be set to true if the author has setup the linking. This will be true even if the learner hasn't done any work on the linked interactive yet.

The linkedState field will contain the work the learner has done on the linked interactive. It will be falsy if the student hasn't done any work.

An example use case is an activity where a student is incrementally building a diagram. In interactive 1 the student fills out part of the diagram and then in interactive 2 the student is asked to add more to the diagram. Interactive 2 can load in the state from the interactive 1 and display the diagram the student created before. Any new changes made can be saved separately into interactive 2's state. The state of interactive 1 will be preserved.

The main interactive can use the hasLinkedInteractive field to tell the difference between being used independently in the activity or being linked to another interactive. In the diagram example above, if interactive 2 sees there is no linkedState, it should then check hasLinkedInteractive. In that case it could tell the student "you should complete the previous diagram before starting this one". If hasLinkedInteractive is falsy then the interactive knows it shouldn't show this message.

Accessing Interactive State with HTTP

TODO