xAPIWrapper 1.5.0 Reference

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";
        return conf
    }();

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 = {};
            }
            stmt.context.contextActivities.grouping = [{ 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;

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) the function will be passed the XMLHttpRequest object and an object with an id property assigned the id of the statement optional

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) 
    {
        if (this.testConfig())
        {
            this.prepareStatement(stmt);
            var id;
            if (stmt['id'])
            {
                id = stmt['id'];
            }
            else
            {
                id = ADL.ruuid();
                stmt['id'] = id;
            }
            var resp = ADL.XHR_request(this.lrs, this.lrs.endpoint+"statements", 
                "POST", JSON.stringify(stmt), this.lrs.auth, callback, {"id":id});
            if (!callback)
                return {"xhr":resp,
                        "id" :id};
        }
    };

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) the function will be passed the XMLHttpRequest object optional

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)
            {
                this.prepareStatement(stmtArray[i]);
            }
            var resp = ADL.XHR_request(this.lrs,this.lrs.endpoint+"statements", 
                "POST", JSON.stringify(stmtArray), this.lrs.auth, callback);
            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. Example: var myparams = ADL.XAPIWrapper.searchParams(); myparams['verb'] = ADL.verbs.completed.id; var completedStmts = ADL.XAPIWrapper.getStatements(myparams);
more string the more url found in the StatementResults object, if there are more statements available based on your get statements request. Pass the more url as this parameter to retrieve those statements.
callback function - function to be called after the LRS responds to this request (makes the call asynchronous) the function will be passed the XMLHttpRequest object optional

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 (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);
            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) the function will be passed the XMLHttpRequest object optional

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);
            
            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) the function will be passed the XMLHttpRequest object optional

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 += "&registration=" + 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":'"'+matchHash+'"'};
            }
            else if (noneMatchHash)
            {
                headers = {"If-None-Match":'"'+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);
        }
    };

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) the function will be passed the XMLHttpRequest object optional

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 += "&registration=" + 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);
            
            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) the function will be passed the XMLHttpRequest object optional

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 += "&registration=" + 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":'"'+matchHash+'"'};
            }
            else if (noneMatchHash)
            {
                headers = {"If-None-Match":'"'+noneMatchHash+'"'};
            }

            var result = ADL.XHR_request(this.lrs, url, "DELETE", null, this.lrs.auth, callback, null, headers);
            
            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) the function will be passed the XMLHttpRequest object optional

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":'"'+matchHash+'"'};
            }
            else if (noneMatchHash)
            {
                headers = {"If-None-Match":'"'+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);
        }
    };

getActivityProfile (activityid, profileid, since, callback)

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
callback function function to be called after the LRS responds to this request (makes the call asynchronous) the function will be passed the XMLHttpRequest object optional

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);
            
            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) the function will be passed the XMLHttpRequest object optional

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":'"'+matchHash+'"'};
            }
            else if (noneMatchHash)
            {
                headers = {"If-None-Match":'"'+noneMatchHash+'"'};
            }

            var result = ADL.XHR_request(this.lrs, url, "DELETE", null, this.lrs.auth, callback, null, headers);
            
            if(result === undefined || result.status == 404)
            {
                return null
            }
            
            try
            {
                return JSON.parse(result.response);
            }
            catch(e)
            {
                return result;
            }
        }
    };

getAgents (agent, callback)

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
callback function function to be called after the LRS responds to this request (makes the call asynchronous) the function will be passed the XMLHttpRequest object optional

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);
            
            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) the function will be passed the XMLHttpRequest object optional

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":'"'+matchHash+'"'};
            }
            else if (noneMatchHash)
            {
                headers = {"If-None-Match":'"'+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);
        }
    };

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) the function will be passed the XMLHttpRequest object optional

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);
            
            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) the function will be passed the XMLHttpRequest object optional

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":'"'+matchHash+'"'};
            }
            else if (noneMatchHash)
            {
                headers = {"If-None-Match":'"'+noneMatchHash+'"'};
            }

            var result = ADL.XHR_request(this.lrs, url, "DELETE", null, this.lrs.auth, callback, null, headers);
            
            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){
                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)

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

Returns
object   xhr response object

// Sorry, no example available.
ADL.XHR_request = function(lrs, url, method, data, auth, callback, callbackargs, ignore404, extraHeaders) 
    {
        "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){
                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);
        }
        
        //If it's not cross domain or we're not using IE, use the usual XmlHttpRequest
        if (!xDomainRequest || typeof(XDomainRequest) === 'undefined') {
            xhr = new XMLHttpRequest();
            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){
                            callback(xhr, callbackargs);
                        }
                        else {
                            try {
                                var body = JSON.parse(xhr.responseText);
                                callback(xhr,body);
                            }
                            catch(e){
                                callback(xhr,xhr.responseText);
                            }
                        }
                    } 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);
                    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)

Holder for custom global error callback

Arguments
xhr object xhr object or null
method string XMLHttpRequest request method
url string full endpoint url
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){};

    ADL.XAPIWrapper = new XAPIWrapper(Config, false);

}(window.ADL = window.ADL || {}));

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" }}
var XAPIStatement = function(actor,verb,object)
  {

    // 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;


    this.generateId = function(){
      this.id = ADL.ruuid();
    };
  };

  XAPIStatement.prototype.toString = function(){
    return this.actor.toString() + " " + this.verb.toString() + " " + this.object.toString();
  };

  XAPIStatement.prototype.isValid = function(){
    return this.actor && this.actor.isValid() 
      && this.verb && this.verb.isValid() 
      && this.object && this.object.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. See (https://github.com/adlnet/xAPI-Spec/blob/master/xAPI.md#inversefunctional)
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` specified) See Agent. optional
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 || {}));