XAPIstatement
XAPIStatement (actor, verb, object)
A convenient JSON-compatible xAPI statement wrapper
All args are optional, but the statement may not be complete or valid
Can also pass an Agent IFI, Verb ID, and an Activity ID in lieu of these args
Arguments | ||
---|---|---|
actor | string |
The Agent or Group committing the action described by the statement optional |
verb | string |
The Verb for the action described by the statement optional |
object | string |
The receiver of the action. An Agent, Group, Activity, SubStatement, or StatementRef optional |
var stmt = new ADL.XAPIStatement(
'mailto:steve.vergenz.ctr@adlnet.gov',
'http://adlnet.gov/expapi/verbs/launched',
'http://vwf.adlnet.gov/xapi/virtual_world_sandbox'
);
>> {
"actor": {
"objectType": "Agent",
"mbox": "mailto:steve.vergenz.ctr@adlnet.gov" },
"verb": {
"id": "http://adlnet.gov/expapi/verbs/launched" },
"object": {
"objectType": "Activity",
"id": "http://vwf.adlnet.gov/xapi/virtual_world_sandbox" },
"result": {
"An optional property that represents a measured outcome related to the Statement in which it is included."}}
var XAPIStatement = function(actor, verb, object, result)
{
// initialize
// if first arg is an xapi statement, parse
if( actor && actor.actor && actor.verb && actor.object ){
var stmt = actor;
for(var i in stmt){
if(i != 'actor' && i != 'verb' && i != 'object')
this[i] = stmt[i];
}
actor = stmt.actor;
verb = stmt.verb;
object = stmt.object;
}
if(actor){
if( actor instanceof Agent )
this.actor = actor;
else if(actor.objectType === 'Agent' || !actor.objectType)
this.actor = new Agent(actor);
else if(actor.objectType === 'Group')
this.actor = new Group(actor);
}
else this.actor = null;
if(verb){
if( verb instanceof Verb )
this.verb = verb;
else
this.verb = new Verb(verb);
}
else this.verb = null;
// decide what kind of object was passed
if(object)
{
if( object.objectType === 'Activity' || !object.objectType ){
if( object instanceof Activity )
this.object = object;
else
this.object = new Activity(object);
}
else if( object.objectType === 'Agent' ){
if( object instanceof Agent )
this.object = object;
else
this.object = new Agent(object);
}
else if( object.objectType === 'Group' ){
if( object instanceof Group )
this.object = object;
else
this.object = new Group(object);
}
else if( object.objectType === 'StatementRef' ){
if( object instanceof StatementRef )
this.object = object;
else
this.object = new StatementRef(object);
}
else if( object.objectType === 'SubStatement' ){
if( object instanceof SubStatement )
this.object = object;
else
this.object = new SubStatement(object);
}
else this.object = null;
}
else this.object = null;
// add support for result object
if(result)
{
this.result = result;
}
this.generateId = function(){
this.id = ADL.ruuid();
};
};
XAPIStatement.prototype.toString = function(){
return this.actor.toString() + " " + this.verb.toString() + " " + this.object.toString() + " " + this.result.toString();
};
XAPIStatement.prototype.isValid = function(){
return this.actor && this.actor.isValid()
&& this.verb && this.verb.isValid()
&& this.object && this.object.isValid()
&& this.result && this.result.isValid();
};
XAPIStatement.prototype.generateRegistration = function(){
_getobj(this,'context').registration = ADL.ruuid();
};
XAPIStatement.prototype.addParentActivity = function(activity){
_getobj(this,'context.contextActivities.parent[]').push(new Activity(activity));
};
XAPIStatement.prototype.addGroupingActivity = function(activity){
_getobj(this,'context.contextActivities.grouping[]').push(new Activity(activity));
};
XAPIStatement.prototype.addOtherContextActivity = function(activity){
_getobj(this,'context.contextActivities.other[]').push(new Activity(activity));
};
Agent (identifier, name)
Provides an easy constructor for xAPI agent objects
Arguments | ||
---|---|---|
identifier | string |
One of the Inverse Functional Identifiers specified in the spec. That is, an email, a hashed email, an OpenID, or an account object. |
name | string |
The natural-language name of the agent optional |
// Sorry, no example available.
var Agent = function(identifier, name)
{
this.objectType = 'Agent';
this.name = name;
// figure out what type of identifier was given
if( identifier && (identifier.mbox || identifier.mbox_sha1sum || identifier.openid || identifier.account) ){
for(var i in identifier){
this[i] = identifier[i];
}
}
else if( /^mailto:/.test(identifier) ){
this.mbox = identifier;
}
else if( /^[0-9a-f]{40}$/i.test(identifier) ){
this.mbox_sha1sum = identifier;
}
else if( /^http[s]?:/.test(identifier) ){
this.openid = identifier;
}
else if( identifier && identifier.homePage && identifier.name ){
this.account = identifier;
}
};
Agent.prototype.toString = function(){
return this.name || this.mbox || this.openid || this.mbox_sha1sum || this.account.name;
};
Agent.prototype.isValid = function()
{
return this.mbox || this.mbox_sha1sum || this.openid
|| (this.account.homePage && this.account.name)
|| (this.objectType === 'Group' && this.member);
};
Group (identifier, members, name)
A type of agent, can contain multiple agents
Arguments | ||
---|---|---|
identifier | string |
(optional if |
members | string |
An array of Agents describing the membership of the group optional |
name | string |
The natural-language name of the agent optional |
// Sorry, no example available.
var Group = function(identifier, members, name)
{
Agent.call(this, identifier, name);
this.member = members;
this.objectType = 'Group';
};
Group.prototype = new Agent;
Verb (id, description)
Really only provides a convenient language map
Arguments | ||
---|---|---|
id | string |
The IRI of the action taken |
description | string |
An English-language description, or a Language Map optional |
// Sorry, no example available.
var Verb = function(id, description)
{
// if passed a verb object then copy and return
if( id && id.id ){
for(var i in id){
this[i] = id[i];
}
return;
}
// save id and build language map
this.id = id;
if(description)
{
if( typeof(description) === 'string' || description instanceof String ){
this.display = {'en-US': description};
}
else {
this.display = description;
}
}
};
Verb.prototype.toString = function(){
if(this.display && (this.display['en-US'] || this.display['en']))
return this.display['en-US'] || this.display['en'];
else
return this.id;
};
Verb.prototype.isValid = function(){
return this.id;
};
Activity (id, name, description)
Describes an object that an agent interacts with
Arguments | ||
---|---|---|
id | string |
The unique activity IRI |
name | string |
An English-language identifier for the activity, or a Language Map |
description | string |
An English-language description of the activity, or a Language Map |
// Sorry, no example available.
var Activity = function(id, name, description)
{
// if first arg is activity, copy everything over
if(id && id.id){
var act = id;
for(var i in act){
this[i] = act[i];
}
return;
}
this.objectType = 'Activity';
this.id = id;
if( name || description )
{
this.definition = {};
if( typeof(name) === 'string' || name instanceof String )
this.definition.name = {'en-US': name};
else if(name)
this.definition.name = name;
if( typeof(description) === 'string' || description instanceof String )
this.definition.description = {'en-US': description};
else if(description)
this.definition.description = description;
}
};
Activity.prototype.toString = function(){
if(this.definition && this.definition.name && (this.definition.name['en-US'] || this.definition.name['en']))
return this.definition.name['en-US'] || this.definition.name['en'];
else
return this.id;
};
Activity.prototype.isValid = function(){
return this.id && (!this.objectType || this.objectType === 'Activity');
};
StatementRef (id)
An object that refers to a separate statement
Arguments | ||
---|---|---|
id | string |
The UUID of another xAPI statement |
// Sorry, no example available.
var StatementRef = function(id){
if(id && id.id){
for(var i in id){
this[i] = id[i];
}
}
else {
this.objectType = 'StatementRef';
this.id = id;
}
};
StatementRef.prototype.toString = function(){
return 'statement('+this.id+')';
};
StatementRef.prototype.isValid = function(){
return this.id && this.objectType && this.objectType === 'StatementRef';
};
SubStatement (actor, verb, object)
A self-contained statement as the object of another statement
See XAPIStatement for constructor details
Arguments | ||
---|---|---|
actor | string |
The Agent or Group committing the action described by the statement |
verb | string |
The Verb for the action described by the statement |
object | string |
The receiver of the action. An Agent, Group, Activity, or StatementRef |
// Sorry, no example available.
var SubStatement = function(actor, verb, object){
XAPIStatement.call(this,actor,verb,object);
this.objectType = 'SubStatement';
delete this.id;
delete this.stored;
delete this.version;
delete this.authority;
};
SubStatement.prototype = new XAPIStatement;
SubStatement.prototype.toString = function(){
return '"' + SubStatement.prototype.prototype.toString.call(this) + '"';
};
XAPIStatement.Agent = Agent;
XAPIStatement.Group = Group;
XAPIStatement.Verb = Verb;
XAPIStatement.Activity = Activity;
XAPIStatement.StatementRef = StatementRef;
XAPIStatement.SubStatement = SubStatement;
ADL.XAPIStatement = XAPIStatement;
}(window.ADL = window.ADL || {}));
XAPIwrapper
Config ()
Config object used w/ url params to configure the lrs object
change these to match your lrs
Returns
object
config object
var conf = {
"endpoint" : "https://lrs.adlnet.gov/xapi/",
"auth" : "Basic " + toBase64('tom:1234'),
};
ADL.XAPIWrapper.changeConfig(conf);
var Config = function()
{
var conf = {};
conf['endpoint'] = "http://localhost:8000/xapi/";
//try
//{
conf['auth'] = "Basic " + toBase64('tom:1234');
//}
//catch (e)
//{
// log("Exception in Config trying to encode auth: " + e);
//}
// Statement defaults
// conf["actor"] = {"mbox":"default@example.com"};
// conf["registration"] = ruuid();
// conf["grouping"] = {"id":"ctxact:default/grouping"};
// conf["activity_platform"] = "default platform";
// Behavior defaults
// conf["strictCallbacks"] = false; // Strict error-first callbacks
return conf
}();
XAPIWrapper (config, verifyxapiversion)
XAPIWrapper Constructor
Arguments | ||
---|---|---|
config | object |
with a minimum of an endoint property |
verifyxapiversion | boolean |
indicating whether to verify the version of the LRS is compatible with this wrapper |
// Sorry, no example available.
var XAPIWrapper = function(config, verifyxapiversion)
{
this.lrs = getLRSObject(config || {});
if (this.lrs.user && this.lrs.password)
updateAuth(this.lrs, this.lrs.user, this.lrs.password);
this.base = getbase(this.lrs.endpoint);
this.withCredentials = false;
if (config && typeof(config.withCredentials) != 'undefined') {
this.withCredentials = config.withCredentials;
}
// Ensure that callbacks are always executed, first param is error (null if no error) followed
// by the result(s)
this.strictCallbacks = false;
this.strictCallbacks = config && config.strictCallbacks;
function getbase(url)
{
var l = document.createElement("a");
l.href = url;
if (l.protocol && l.host) {
return l.protocol + "//" + l.host;
} else if (l.href) {
// IE 11 fix.
var parts = l.href.split("//");
return parts[0] + "//" + parts[1].substr(0, parts[1].indexOf("/"));
}
else
ADL.XAPIWrapper.log("Couldn't create base url from endpoint: " + url);
}
function updateAuth(obj, username, password){
obj.auth = "Basic " + toBase64(username + ":" + password);
}
if (verifyxapiversion && testConfig.call(this))
{
window.ADL.XHR_request(this.lrs, this.lrs.endpoint+"about", "GET", null, null,
function(r){
if(r.status == 200)
{
try
{
var lrsabout = JSON.parse(r.response);
var versionOK = false;
for (var idx in lrsabout.version)
{
if (lrsabout.version.hasOwnProperty(idx))
if(lrsabout.version[idx] == ADL.XAPIWrapper.xapiVersion)
{
versionOK = true;
break;
}
}
if (!versionOK)
{
ADL.XAPIWrapper.log("The lrs version [" + lrsabout.version +"]"+
" does not match this wrapper's XAPI version [" + ADL.XAPIWrapper.xapiVersion + "]");
}
}
catch(e)
{
ADL.XAPIWrapper.log("The response was not an about object")
}
}
else
{
ADL.XAPIWrapper.log("The request to get information about the LRS failed: " + r);
}
}, null, false, null, this.withCredentials, false);
}
this.searchParams = function(){
var sp = {"format" : "exact"};
return sp;
};
this.hash = function(tohash){
if (!tohash) return null;
try
{
return toSHA1(tohash);
}
catch(e)
{
ADL.XAPIWrapper.log("Error trying to hash -- " + e);
return null;
}
};
this.changeConfig = function(config){
try
{
ADL.XAPIWrapper.log("updating lrs object with new configuration");
this.lrs = mergeRecursive(this.lrs, config);
if (config.user && config.password)
this.updateAuth(this.lrs, config.user, config.password);
this.base = getbase(this.lrs.endpoint);
this.withCredentials = config.withCredentials;
this.strictCallbacks = config.strictCallbacks;
}
catch(e)
{
ADL.XAPIWrapper.log("error while changing configuration -- " + e);
}
};
this.updateAuth = updateAuth;
};
// This wrapper is based on the Experience API Spec version:
XAPIWrapper.prototype.xapiVersion = "1.0.1";
prepareStatement (stmt)
Adds info from the lrs object to the statement, if available.
These values could be initialized from the Config object or from the url query string.
Arguments | ||
---|---|---|
stmt | object |
the statement object |
// Sorry, no example available.
XAPIWrapper.prototype.prepareStatement = function(stmt)
{
if(stmt.actor === undefined){
stmt.actor = JSON.parse(this.lrs.actor);
}
else if(typeof stmt.actor === "string") {
stmt.actor = JSON.parse(stmt.actor);
}
if (this.lrs.grouping ||
this.lrs.registration ||
this.lrs.activity_platform) {
if (!stmt.context) {
stmt.context = {};
}
}
if (this.lrs.grouping) {
if (!stmt.context.contextActivities) {
stmt.context.contextActivities = {};
}
// PR from brian-learningpool to resolve context overwriting
if (!Array.isArray(stmt.context.contextActivities.grouping)) {
stmt.context.contextActivities.grouping = [{ id : this.lrs.grouping }];
} else {
stmt.context.contextActivities.grouping.splice(0, 0, { id : this.lrs.grouping });
}
}
if (this.lrs.registration) {
stmt.context.registration = this.lrs.registration;
}
if (this.lrs.activity_platform) {
stmt.context.platform = this.lrs.activity_platform;
}
};
// tests the configuration of the lrs object
XAPIWrapper.prototype.testConfig = testConfig;
// writes to the console if available
XAPIWrapper.prototype.log = log;
// Default encoding
XAPIWrapper.prototype.defaultEncoding = 'utf-8';
sendStatement (stmt, callback)
Send a single statement to the LRS. Makes a Javascript object
with the statement id as 'id' available to the callback function.
Arguments | ||
---|---|---|
stmt | object |
statement object to send |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
object containing xhr object and id of statement
// Send Statement
var stmt = {"actor" : {"mbox" : "mailto:tom@example.com"},
"verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
"display" : {"en-US" : "answered"}},
"object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);
ADL.XAPIWrapper.log("[" + resp_obj.id + "]: " + resp_obj.xhr.status + " - " + resp_obj.xhr.statusText);
>> [3e616d1c-5394-42dc-a3aa-29414f8f0dfe]: 204 - NO CONTENT
// Send Statement with Callback
var stmt = {"actor" : {"mbox" : "mailto:tom@example.com"},
"verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
"display" : {"en-US" : "answered"}},
"object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
ADL.XAPIWrapper.sendStatement(stmt, function(resp, obj){
ADL.XAPIWrapper.log("[" + obj.id + "]: " + resp.status + " - " + resp.statusText);});
>> [4edfe763-8b84-41f1-a355-78b7601a6fe8]: 204 - NO CONTENT
XAPIWrapper.prototype.sendStatement = function(stmt, callback, attachments)
{
if (this.testConfig())
{
this.prepareStatement(stmt);
var id;
if (stmt['id'])
{
id = stmt['id'];
}
else
{
id = ADL.ruuid();
stmt['id'] = id;
}
var payload = JSON.stringify(stmt);
var extraHeaders = null;
if(attachments && attachments.length > 0)
{
extraHeaders = {}
payload = this.buildMultipartPost(stmt,attachments,extraHeaders);
}
var resp = ADL.XHR_request(this.lrs, this.lrs.endpoint+"statements",
"POST", payload, this.lrs.auth, callback, {"id":id}, null, extraHeaders,
this.withCredentials, this.strictCallbacks);
if (!callback)
return {"xhr":resp,
"id" :id};
}
};
XAPIWrapper.prototype.stringToArrayBuffer = function(content, encoding)
{
encoding = encoding || ADL.XAPIWrapper.defaultEncoding;
return new TextEncoder(encoding).encode(content).buffer;
};
XAPIWrapper.prototype.stringFromArrayBuffer = function(content, encoding) {
encoding = encoding || ADL.XAPIWrapper.defaultEncoding;
return new TextDecoder(encoding).decode(content);
};
sendStatements (stmtArray, callback)
Send a list of statements to the LRS.
Arguments | ||
---|---|---|
stmtArray | array |
the list of statement objects to send |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object
var stmt = {"actor" : {"mbox" : "mailto:tom@example.com"},
"verb" : {"id" : "http://adlnet.gov/expapi/verbs/answered",
"display" : {"en-US" : "answered"}},
"object" : {"id" : "http://adlnet.gov/expapi/activities/question"}};
var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);
ADL.XAPIWrapper.getStatements({"statementId":resp_obj.id});
>> {"version": "1.0.0",
"timestamp": "2013-09-09 21:36:40.185841+00:00",
"object": {"id": "http://adlnet.gov/expapi/activities/question", "objectType": "Activity"},
"actor": {"mbox": "mailto:tom@example.com", "name": "tom creighton", "objectType": "Agent"},
"stored": "2013-09-09 21:36:40.186124+00:00",
"verb": {"id": "http://adlnet.gov/expapi/verbs/answered", "display": {"en-US": "answered"}},
"authority": {"mbox": "mailto:tom@adlnet.gov", "name": "tom", "objectType": "Agent"},
"context": {"registration": "51a6f860-1997-11e3-8ffd-0800200c9a66"},
"id": "ea9c1d01-0606-4ec7-8e5d-20f87b1211ed"}
XAPIWrapper.prototype.sendStatements = function(stmtArray, callback)
{
if (this.testConfig())
{
for(var i in stmtArray)
{
if (stmtArray.hasOwnProperty(i))
this.prepareStatement(stmtArray[i]);
}
var resp = ADL.XHR_request(this.lrs,this.lrs.endpoint+"statements",
"POST", JSON.stringify(stmtArray), this.lrs.auth, callback, null,
false, null, this.withCredentials, this.strictCallbacks);
if (!callback)
{
return resp;
}
}
};
getStatements (searchparams, more, callback)
Get statement(s) based on the searchparams or more url.
Arguments | ||
---|---|---|
searchparams | object |
an ADL.XAPIWrapper.searchParams object of key(search parameter)-value(parameter value) pairs. |
more | string |
the more url found in the StatementResults object, if there are more statements available based on your get statements request. Pass the |
callback | function |
|
Returns
object
xhr response object or null if 404
var ret = ADL.XAPIWrapper.getStatements();
if (ret)
ADL.XAPIWrapper.log(ret.statements);
>> <Array of statements>
XAPIWrapper.prototype.getStatements = function(searchparams, more, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "statements";
if (more)
{
url = this.base + more;
}
else
{
var urlparams = new Array();
for (s in searchparams)
{
if (searchparams.hasOwnProperty(s))
{
if (s == "until" || s == "since") {
var d = new Date(searchparams[s]);
urlparams.push(s + "=" + encodeURIComponent(d.toISOString()));
} else {
urlparams.push(s + "=" + encodeURIComponent(searchparams[s]));
}
}
}
if (urlparams.length > 0)
url = url + "?" + urlparams.join("&");
}
var res = ADL.XHR_request(this.lrs,url, "GET", null, this.lrs.auth,
callback, null, false, null, this.withCredentials, this.strictCallbacks);
if(res === undefined || res.status == 404)
{
return null
}
try
{
return JSON.parse(res.response);
}
catch(e)
{
return res.response;
}
}
};
getActivities (activityid, callback)
Gets the Activity object from the LRS.
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity to get |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object or null if 404
var res = ADL.XAPIWrapper.getActivities("http://adlnet.gov/expapi/activities/question");
ADL.XAPIWrapper.log(res);
>> <Activity object>
XAPIWrapper.prototype.getActivities = function(activityid, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities?activityId=<activityid>";
url = url.replace('<activityid>', encodeURIComponent(activityid));
var result = ADL.XHR_request(this.lrs, url, "GET", null, this.lrs.auth,
callback, null, true, null, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result.response;
}
}
};
sendState (activityid, agent, stateid, registration, stateval, matchHash, noneMatchHash, callback)
Store activity state in the LRS
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity this state is about |
agent | object |
the agent this Activity state is related to |
stateid | string |
the id you want associated with this state |
registration | string |
the registraton id associated with this state optional |
stateval | string |
the state |
matchHash | string |
the hash of the state to replace or * to replace any optional |
noneMatchHash | string |
the hash of the current state or * to indicate no previous state optional |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
boolean
false if no activity state is included
var stateval = {"info":"the state info"};
ADL.XAPIWrapper.sendState("http://adlnet.gov/expapi/activities/question",
{"mbox":"mailto:tom@example.com"},
"questionstate", null, stateval);
XAPIWrapper.prototype.sendState = function(activityid, agent, stateid, registration, stateval, matchHash, noneMatchHash, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities/state?activityId=<activity ID>&agent=<agent>&stateId=<stateid>";
url = url.replace('<activity ID>',encodeURIComponent(activityid));
url = url.replace('<agent>',encodeURIComponent(JSON.stringify(agent)));
url = url.replace('<stateid>',encodeURIComponent(stateid));
if (registration)
{
url += "®istration=" + encodeURIComponent(registration);
}
var headers = null;
if(matchHash && noneMatchHash)
{
log("Can't have both If-Match and If-None-Match");
}
else if (matchHash)
{
headers = {"If-Match":ADL.formatHash(matchHash)};
}
else if (noneMatchHash)
{
headers = {"If-None-Match":ADL.formatHash(noneMatchHash)};
}
var method = "PUT";
if (stateval)
{
if (stateval instanceof Array)
{
stateval = JSON.stringify(stateval);
headers = headers || {};
headers["Content-Type"] ="application/json";
}
else if (stateval instanceof Object)
{
stateval = JSON.stringify(stateval);
headers = headers || {};
headers["Content-Type"] ="application/json";
method = "POST";
}
else
{
headers = headers || {};
headers["Content-Type"] ="application/octet-stream";
}
}
else
{
this.log("No activity state was included.");
return false;
}
//(lrs, url, method, data, auth, callback, callbackargs, ignore404, extraHeaders)
ADL.XHR_request(this.lrs, url, method, stateval, this.lrs.auth, callback,
null, null, headers, this.withCredentials, this.strictCallbacks);
}
};
getState (activityid, agent, stateid, registration, since, callback)
Get activity state from the LRS
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity this state is about |
agent | object |
the agent this Activity state is related to |
stateid | string |
the id of the state, if not included, the response will be a list of stateids associated with the activity and agent) optional |
registration | string |
the registraton id associated with this state optional |
since | object |
date object or date string telling the LRS to return objects newer than the date supplied optional |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object or null if 404
ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
{"mbox":"mailto:tom@example.com"}, "questionstate");
>> {info: "the state info"}
XAPIWrapper.prototype.getState = function(activityid, agent, stateid, registration, since, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities/state?activityId=<activity ID>&agent=<agent>";
url = url.replace('<activity ID>',encodeURIComponent(activityid));
url = url.replace('<agent>',encodeURIComponent(JSON.stringify(agent)));
if (stateid)
{
url += "&stateId=" + encodeURIComponent(stateid);
}
if (registration)
{
url += "®istration=" + encodeURIComponent(registration);
}
if(since)
{
since = isDate(since);
if (since != null) {
url += '&since=' + encodeURIComponent(since.toISOString());
}
}
var result = ADL.XHR_request(this.lrs, url, "GET", null, this.lrs.auth,
callback, null, true, null, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result.response;
}
}
};
deleteState (activityid, agent, stateid, registration, matchHash, noneMatchHash, callback)
Delete activity state in the LRS
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity this state is about |
agent | object |
the agent this Activity state is related to |
stateid | string |
the id you want associated with this state |
registration | string |
the registraton id associated with this state optional |
matchHash | string |
the hash of the state to replace or * to replace any optional |
noneMatchHash | string |
the hash of the current state or * to indicate no previous state optional |
callback | string |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object or null if 404
var stateval = {"info":"the state info"};
ADL.XAPIWrapper.sendState("http://adlnet.gov/expapi/activities/question",
{"mbox":"mailto:tom@example.com"},
"questionstate", null, stateval);
ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
{"mbox":"mailto:tom@example.com"}, "questionstate");
>> {info: "the state info"}
ADL.XAPIWrapper.deleteState("http://adlnet.gov/expapi/activities/question",
{"mbox":"mailto:tom@example.com"}, "questionstate");
>> XMLHttpRequest {statusText: "NO CONTENT", status: 204, response: "", responseType: "", responseXML: null…}
ADL.XAPIWrapper.getState("http://adlnet.gov/expapi/activities/question",
{"mbox":"mailto:tom@example.com"}, "questionstate");
>> 404
XAPIWrapper.prototype.deleteState = function(activityid, agent, stateid, registration, matchHash, noneMatchHash, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities/state?activityId=<activity ID>&agent=<agent>&stateId=<stateid>";
url = url.replace('<activity ID>',encodeURIComponent(activityid));
url = url.replace('<agent>',encodeURIComponent(JSON.stringify(agent)));
url = url.replace('<stateid>',encodeURIComponent(stateid));
if (registration)
{
url += "®istration=" + encodeURIComponent(registration);
}
var headers = null;
if(matchHash && noneMatchHash)
{
log("Can't have both If-Match and If-None-Match");
}
else if (matchHash)
{
headers = {"If-Match":ADL.formatHash(matchHash)};
}
else if (noneMatchHash)
{
headers = {"If-None-Match":ADL.formatHash(noneMatchHash)};
}
var result = ADL.XHR_request(this.lrs, url, "DELETE", null, this.lrs.auth,
callback, null, false, headers, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result;
}
}
};
sendActivityProfile (activityid, profileid, profileval, matchHash, noneMatchHash, callback)
Store activity profile in the LRS
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity this profile is about |
profileid | string |
the id you want associated with this profile |
profileval | string |
the profile |
matchHash | string |
the hash of the profile to replace or * to replace any optional |
noneMatchHash | string |
the hash of the current profile or * to indicate no previous profile optional |
callback | string |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
bolean
false if no activity profile is included
var profile = {"info":"the profile"};
ADL.XAPIWrapper.sendActivityProfile("http://adlnet.gov/expapi/activities/question",
"actprofile", profile, null, "*");
XAPIWrapper.prototype.sendActivityProfile = function(activityid, profileid, profileval, matchHash, noneMatchHash, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities/profile?activityId=<activity ID>&profileId=<profileid>";
url = url.replace('<activity ID>',encodeURIComponent(activityid));
url = url.replace('<profileid>',encodeURIComponent(profileid));
var headers = null;
if(matchHash && noneMatchHash)
{
log("Can't have both If-Match and If-None-Match");
}
else if (matchHash)
{
headers = {"If-Match":ADL.formatHash(matchHash)};
}
else if (noneMatchHash)
{
headers = {"If-None-Match":ADL.formatHash(noneMatchHash)};
}
var method = "PUT";
if (profileval)
{
if (profileval instanceof Array)
{
profileval = JSON.stringify(profileval);
headers = headers || {};
headers["Content-Type"] ="application/json";
}
else if (profileval instanceof Object)
{
profileval = JSON.stringify(profileval);
headers = headers || {};
headers["Content-Type"] ="application/json";
method = "POST";
}
else
{
headers = headers || {};
headers["Content-Type"] ="application/octet-stream";
}
}
else
{
this.log("No activity profile was included.");
return false;
}
ADL.XHR_request(this.lrs, url, method, profileval, this.lrs.auth, callback,
null, false, headers, this.withCredentials, this.strictCallbacks);
}
};
getActivityProfile (activityid, profileid, since, {function [callback] function to be called after the LRS responds)
Get activity profile from the LRS
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity this profile is about |
profileid | string |
the id of the profile, if not included, the response will be a list of profileids associated with the activity optional |
since | object |
date object or date string telling the LRS to return objects newer than the date supplied optional |
{function [callback] function to be called after the LRS responds |
|
Returns
object
xhr response object or null if 404
ADL.XAPIWrapper.getActivityProfile("http://adlnet.gov/expapi/activities/question",
"actprofile", null,
function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the profile"}
XAPIWrapper.prototype.getActivityProfile = function(activityid, profileid, since, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities/profile?activityId=<activity ID>";
url = url.replace('<activity ID>',encodeURIComponent(activityid));
if (profileid)
{
url += "&profileId=" + encodeURIComponent(profileid);
}
if(since)
{
since = isDate(since);
if (since != null) {
url += '&since=' + encodeURIComponent(since.toISOString());
}
}
var result = ADL.XHR_request(this.lrs, url, "GET", null, this.lrs.auth,
callback, null, true, null, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result.response;
}
}
};
deleteActivityProfile (activityid, profileid, matchHash, noneMatchHash, callback)
Delete activity profile in the LRS
Arguments | ||
---|---|---|
activityid | string |
the id of the Activity this profile is about |
profileid | string |
the id you want associated with this profile |
matchHash | string |
the hash of the profile to replace or * to replace any optional |
noneMatchHash | string |
the hash of the current profile or * to indicate no previous profile optional |
callback | string |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object or null if 404
ADL.XAPIWrapper.deleteActivityProfile("http://adlnet.gov/expapi/activities/question",
"actprofile");
>> XMLHttpRequest {statusText: "NO CONTENT", status: 204, response: "", responseType: "", responseXML: null…}
XAPIWrapper.prototype.deleteActivityProfile = function(activityid, profileid, matchHash, noneMatchHash, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "activities/profile?activityId=<activity ID>&profileId=<profileid>";
url = url.replace('<activity ID>',encodeURIComponent(activityid));
url = url.replace('<profileid>',encodeURIComponent(profileid));
var headers = null;
if(matchHash && noneMatchHash)
{
log("Can't have both If-Match and If-None-Match");
}
else if (matchHash)
{
headers = {"If-Match":ADL.formatHash(matchHash)};
}
else if (noneMatchHash)
{
headers = {"If-None-Match":ADL.formatHash(noneMatchHash)};
}
var result = ADL.XHR_request(this.lrs, url, "DELETE", null, this.lrs.auth,
callback, null, false, headers,this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result;
}
}
};
getAgents (agent, {function [callback] function to be called after the LRS responds)
Gets the Person object from the LRS based on an agent object.
The Person object may contain more information about an agent.
See the xAPI Spec for details.
Arguments | ||
---|---|---|
agent | object |
the agent object to get a Person |
{function [callback] function to be called after the LRS responds |
|
Returns
object
xhr response object or null if 404
var res = ADL.XAPIWrapper.getAgents({"mbox":"mailto:tom@example.com"});
ADL.XAPIWrapper.log(res);
>> <Person object>
XAPIWrapper.prototype.getAgents = function(agent, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "agents?agent=<agent>";
url = url.replace('<agent>', encodeURIComponent(JSON.stringify(agent)));
var result = ADL.XHR_request(this.lrs, url, "GET", null, this.lrs.auth,
callback, null, true, null, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result.response;
}
}
};
sendAgentProfile (agent, profileid, profileval, matchHash, noneMatchHash, callback)
Store agent profile in the LRS
Arguments | ||
---|---|---|
agent | object |
the agent this profile is related to |
profileid | string |
the id you want associated with this profile |
profileval | string |
the profile |
matchHash | string |
the hash of the profile to replace or * to replace any optional |
noneMatchHash | string |
the hash of the current profile or * to indicate no previous profile optional |
callback | string |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
false if no agent profile is included
var profile = {"info":"the agent profile"};
ADL.XAPIWrapper.sendAgentProfile({"mbox":"mailto:tom@example.com"},
"agentprofile", profile, null, "*");
XAPIWrapper.prototype.sendAgentProfile = function(agent, profileid, profileval, matchHash, noneMatchHash, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "agents/profile?agent=<agent>&profileId=<profileid>";
url = url.replace('<agent>',encodeURIComponent(JSON.stringify(agent)));
url = url.replace('<profileid>',encodeURIComponent(profileid));
var headers = null;
if(matchHash && noneMatchHash)
{
log("Can't have both If-Match and If-None-Match");
}
else if (matchHash)
{
headers = {"If-Match":ADL.formatHash(matchHash)};
}
else if (noneMatchHash)
{
headers = {"If-None-Match":ADL.formatHash(noneMatchHash)};
}
var method = "PUT";
if (profileval)
{
if (profileval instanceof Array)
{
profileval = JSON.stringify(profileval);
headers = headers || {};
headers["Content-Type"] ="application/json";
}
else if (profileval instanceof Object)
{
profileval = JSON.stringify(profileval);
headers = headers || {};
headers["Content-Type"] ="application/json";
method = "POST";
}
else
{
headers = headers || {};
headers["Content-Type"] ="application/octet-stream";
}
}
else
{
this.log("No agent profile was included.");
return false;
}
ADL.XHR_request(this.lrs, url, method, profileval, this.lrs.auth, callback,
null, false, headers, this.withCredentials, this.strictCallbacks);
}
};
getAgentProfile (agent, profileid, since, callback)
Get agnet profile from the LRS
Arguments | ||
---|---|---|
agent | object |
the agent associated with this profile |
profileid | string |
the id of the profile, if not included, the response will be a list of profileids associated with the agent optional |
since | object |
date object or date string telling the LRS to return objects newer than the date supplied optional |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object or null if 404
ADL.XAPIWrapper.getAgentProfile({"mbox":"mailto:tom@example.com"},
"agentprofile", null,
function(r){ADL.XAPIWrapper.log(JSON.parse(r.response));});
>> {info: "the agent profile"}
XAPIWrapper.prototype.getAgentProfile = function(agent, profileid, since, callback)
{
if (this.testConfig()){
var url = this.lrs.endpoint + "agents/profile?agent=<agent>";
url = url.replace('<agent>',encodeURIComponent(JSON.stringify(agent)));
url = url.replace('<profileid>',encodeURIComponent(profileid));
if (profileid)
{
url += "&profileId=" + encodeURIComponent(profileid);
}
if(since)
{
since = isDate(since);
if (since != null) {
url += '&since=' + encodeURIComponent(since.toISOString());
}
}
var result = ADL.XHR_request(this.lrs, url, "GET", null, this.lrs.auth,
callback, null, true, null, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result.response;
}
}
};
deleteAgentProfile (agent, profileid, matchHash, noneMatchHash, callback)
Delete agent profile in the LRS
Arguments | ||
---|---|---|
agent | oject |
the id of the Agent this profile is about |
profileid | string |
the id you want associated with this profile |
matchHash | string |
the hash of the profile to replace or * to replace any optional |
noneMatchHash | string |
the hash of the current profile or * to indicate no previous profile optional |
callback | string |
function to be called after the LRS responds to this request (makes the call asynchronous) |
Returns
object
xhr response object or null if 404
ADL.XAPIWrapper.deleteAgentProfile({"mbox":"mailto:tom@example.com"},
"agentprofile");
>> XMLHttpRequest {statusText: "NO CONTENT", status: 204, response: "", responseType: "", responseXML: null…}
XAPIWrapper.prototype.deleteAgentProfile = function(agent, profileid, matchHash, noneMatchHash, callback)
{
if (this.testConfig())
{
var url = this.lrs.endpoint + "agents/profile?agent=<agent>&profileId=<profileid>";
url = url.replace('<agent>',encodeURIComponent(JSON.stringify(agent)));
url = url.replace('<profileid>',encodeURIComponent(profileid));
var headers = null;
if(matchHash && noneMatchHash)
{
log("Can't have both If-Match and If-None-Match");
}
else if (matchHash)
{
headers = {"If-Match":ADL.formatHash(matchHash)};
}
else if (noneMatchHash)
{
headers = {"If-None-Match":ADL.formatHash(noneMatchHash)};
}
var result = ADL.XHR_request(this.lrs, url, "DELETE", null, this.lrs.auth,
callback, null, false, headers, this.withCredentials, this.strictCallbacks);
if(result === undefined || result.status == 404)
{
return null
}
try
{
return JSON.parse(result.response);
}
catch(e)
{
return result;
}
}
};
ie_request (method, url, headers, data)
formats a request in a way that IE will allow
Arguments | ||
---|---|---|
method | string |
the http request method (ex: "PUT", "GET") |
url | string |
the url to the request (ex: ADL.XAPIWrapper.lrs.endpoint + "statements") |
headers | array |
headers to include in the request optional |
data | string |
the body of the request, if there is one optional |
Returns
object
xhr response object
// Sorry, no example available.
function ie_request(method, url, headers, data)
{
var newUrl = url;
//Everything that was on query string goes into form vars
var formData = new Array();
var qsIndex = newUrl.indexOf('?');
if(qsIndex > 0){
formData.push(newUrl.substr(qsIndex+1));
newUrl = newUrl.substr(0, qsIndex);
}
//Method has to go on querystring, and nothing else
newUrl = newUrl + '?method=' + method;
//Headers
if(headers !== null){
for (var headerName in headers) {
if (headers.hasOwnProperty(headerName))
formData.push(headerName + "=" + encodeURIComponent(headers[headerName]));
}
}
//The original data is repackaged as "content" form var
if(data !== null){
formData.push('content=' + encodeURIComponent(data));
}
return {
"method":"POST",
"url":newUrl,
"headers":{},
"data":formData.join("&")
};
}
XHR_request (lrs, url, method, data, auth, callback, callbackargs, ignore404, extraHeaders, withCredentials, strictCallbacks)
makes a request to a server (if possible, use functions provided in XAPIWrapper)
Arguments | ||
---|---|---|
lrs | string |
the lrs connection info, such as endpoint, auth, etc |
url | string |
the url of this request |
method | string |
the http request method |
data | string |
the payload |
auth | string |
the value for the Authorization header |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
callbackargs | object |
additional javascript object to be passed to the callback function optional |
ignore404 | boolean |
allow page not found errors to pass |
extraHeaders | object |
other header key-values to be added to this request |
withCredentials | boolean | |
strictCallbacks | boolean |
Callback must be executed and first param is error or null if no error |
Returns
object
xhr response object
// Sorry, no example available.
ADL.XHR_request = function(lrs, url, method, data, auth, callback, callbackargs, ignore404, extraHeaders, withCredentials, strictCallbacks)
{
"use strict";
var xhr,
finished = false,
xDomainRequest = false,
ieXDomain = false,
ieModeRequest,
urlparts = url.toLowerCase().match(/^(.+):\/\/([^:\/]*):?(\d+)?(\/.*)?$/),
location = window.location,
urlPort,
result,
extended,
prop,
until;
//Consolidate headers
var headers = {};
headers["Content-Type"] = "application/json";
headers["Authorization"] = auth;
headers['X-Experience-API-Version'] = ADL.XAPIWrapper.xapiVersion;
if(extraHeaders !== null){
for (var headerName in extraHeaders) {
if (extraHeaders.hasOwnProperty(headerName))
headers[headerName] = extraHeaders[headerName];
}
}
//See if this really is a cross domain
xDomainRequest = (location.protocol.toLowerCase() !== urlparts[1] || location.hostname.toLowerCase() !== urlparts[2]);
if (!xDomainRequest) {
urlPort = (urlparts[3] === null ? ( urlparts[1] === 'http' ? '80' : '443') : urlparts[3]);
xDomainRequest = (urlPort === location.port);
}
//Add extended LMS-specified values to the URL
if (lrs !== null && lrs.extended !== undefined) {
extended = new Array();
for (prop in lrs.extended) {
extended.push(prop + "=" + encodeURIComponent(lrs.extended[prop]));
}
if (extended.length > 0) {
url += (url.indexOf("?") > -1 ? "&" : "?") + extended.join("&");
}
}
//If it's not cross domain or we're not using IE, use the usual XmlHttpRequest
var windowsVersionCheck = window.XDomainRequest && (window.XMLHttpRequest && new XMLHttpRequest().responseType === undefined);
if (!xDomainRequest || windowsVersionCheck === undefined || windowsVersionCheck===false) {
xhr = new XMLHttpRequest();
xhr.withCredentials = withCredentials; //allow cross domain cookie based auth
xhr.open(method, url, callback != null);
for(var headerName in headers){
xhr.setRequestHeader(headerName, headers[headerName]);
}
}
//Otherwise, use IE's XDomainRequest object
else {
ieXDomain = true;
ieModeRequest = ie_request(method, url, headers, data);
xhr = new XDomainRequest();
xhr.open(ieModeRequest.method, ieModeRequest.url);
}
//Setup request callback
function requestComplete() {
if(!finished){
// may be in sync or async mode, using XMLHttpRequest or IE XDomainRequest, onreadystatechange or
// onload or both might fire depending upon browser, just covering all bases with event hooks and
// using 'finished' flag to avoid triggering events multiple times
finished = true;
var notFoundOk = (ignore404 && xhr.status === 404);
if (xhr.status === undefined || (xhr.status >= 200 && xhr.status < 400) || notFoundOk) {
if (callback) {
if(callbackargs){
strictCallbacks ? callback(null, xhr, callbackargs) : callback(xhr, callbackargs);
}
else {
var body;
try {
body = JSON.parse(xhr.responseText);
}
catch(e){
body = xhr.responseText;
}
strictCallbacks ? callback(null, xhr, body) : callback(xhr,body);
}
} else {
result = xhr;
return xhr;
}
} else {
var warning;
try {
warning = "There was a problem communicating with the Learning Record Store. ( "
+ xhr.status + " | " + xhr.response+ " )" + url
} catch (ex) {
warning = ex.toString();
}
ADL.XAPIWrapper.log(warning);
ADL.xhrRequestOnError(xhr, method, url, callback, callbackargs, strictCallbacks);
result = xhr;
return xhr;
}
} else {
return result;
}
};
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
return requestComplete();
}
};
xhr.onload = requestComplete;
xhr.onerror = requestComplete;
//xhr.onerror = ADL.xhrRequestOnError(xhr, method, url);
xhr.send(ieXDomain ? ieModeRequest.data : data);
if (!callback) {
// synchronous
if (ieXDomain) {
// synchronous call in IE, with no asynchronous mode available.
until = 1000 + new Date();
while (new Date() < until && xhr.readyState !== 4 && !finished) {
delay();
}
}
return requestComplete();
}
};
xhrRequestOnError (xhr, method, url, callback, callbackargs, strictCallbacks)
Holder for custom global error callback
Arguments | ||
---|---|---|
xhr | object |
xhr object or null |
method | string |
XMLHttpRequest request method |
url | string |
full endpoint url |
callback | function |
function to be called after the LRS responds to this request (makes the call asynchronous) |
callbackargs | object |
additional javascript object to be passed to the callback function optional |
strictCallbacks | boolean |
Callback must be executed and first param is error or null if no error |
ADL.xhrRequestOnError = function(xhr, method, url, callback, callbackargs) {
console.log(xhr);
alert(xhr.status + " " + xhr.statusText + ": " + xhr.response);
};
ADL.xhrRequestOnError = function(xhr, method, url, callback, callbackargs, strictCallbacks){
if (callback && strictCallbacks) {
var status = xhr ? xhr.status : undefined;
var error;
if (status) {
error = new Error('Request error: ' + xhr.status);
} else if (status === 0 || status === null) { // 0 and null = aborted
error = new Error('Request error: aborted');
} else {
error = new Error('Reqeust error: unknown');
}
if (callbackargs) {
callback(error, xhr, callbackargs);
} else {
var body;
try {
body = JSON.parse(xhr.responseText);
} catch(e){
body = xhr.responseText;
}
callback(error, xhr, body);
}
}
};
ADL.formatHash = function(hash)
{
return (hash==="*") ? hash : '"'+hash+'"';
}
ADL.XAPIWrapper = new XAPIWrapper(Config, false);
}(window.ADL = window.ADL || {}));