Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- define([
- 'core/js/adapt',
- 'core/js/enums/completionStateEnum',
- './utils',
- './statements',
- 'libraries/async.min',
- 'libraries/tincan'
- ], function(Adapt, COMPLETION_STATE, Utils, Statements, Async) {
- 'use strict';
- /**
- * @callback ErrorOnlyCallback
- * @param {?Error} error
- */
- var xAPI = Backbone.Model.extend({
- /** Declare defaults and model properties */
- // Default model properties.
- defaults: {
- lang: 'en-US',
- displayLang: 'en-US',
- generateIds: false,
- activityId: null,
- actor: null,
- shouldTrackState: true,
- isInitialised: false,
- state: {}
- },
- startAttemptDuration: 0,
- startTimeStamp: null,
- courseName: '',
- courseDescription: '',
- defaultLang: 'en-US',
- isComplete: false,
- // Default events to send statements for.
- coreEvents: {
- Adapt: {
- 'router:page': true,
- 'router:menu': true,
- 'assessments:complete': true,
- 'questionView:recordInteraction': true
- },
- contentobjects: {
- 'change:_isComplete': true
- },
- articles: {
- 'change:_isComplete': true
- },
- blocks: {
- 'change:_isComplete': true
- },
- components: {
- 'change:_isComplete': true
- }
- },
- // An object describing the core Adapt framework collections.
- coreObjects: {
- course: 'course',
- contentObjects: ['menu', 'page'],
- articles: 'article',
- blocks: 'block',
- components: 'component'
- },
- /** Implementation starts here */
- initialize: function() {
- this.config = Adapt.config.get('_xapi');
- this.tincan = new TinCan({
- url: window.location.href,
- activity: Utils.CourseActivity,
- /*actor: {
- "mbox": "mailto:Tester@example.com",
- "name": "Nayan Khedkar",
- "objectType": "Agent"
- },*/
- recordStores: [{
- endpoint: this.config._endpoint,
- username: this.config._user,
- password: this.config._password,
- allowFail: false
- }]
- });
- this.set('isInitialised', true);
- this.set('activityId', this.config._activityID);
- this.set('actor', this.tincan.actor);
- this.getLocation();
- this._onWindowOnload = _.bind(this.onWindowUnload, this);
- $(window).on('beforeunload unload', this._onWindowOnload);
- },
- /**
- * Sends a 'terminated' statement to the LRS when the window is closed.
- */
- onWindowUnload: function() {
- this.setLocation();
- $(window).off('beforeunload unload', this._onWindowOnload);
- var statements = [];
- // Always send the 'terminated' verb.*/
- statements.push(this.getCourseStatement(Statements.terminated));
- if (!this.isComplete) {
- // If the course is still in progress, send the 'suspended' verb.
- statements.push(this.getCourseStatement(Statements.suspended));
- }else{
- statements.push(this.getRecordTestStatement());
- }
- debugger;
- this.tincan.sendStatements(statements,function(ee) {
- console.log("statements Exucated",ee);
- });
- debugger;
- console.log("Ended");
- //;
- },
- /**
- * Check Wrapper to see if all parameters needed are set
- */
- checkWrapperConfig: function() {
- if (this.xapiWrapper.lrs.endpoint && this.xapiWrapper.lrs.actor && this.xapiWrapper.lrs.auth && this.xapiWrapper.lrs.activity_id) {
- return true;
- } else {
- return false;
- }
- },
- getAttemptDuration: function() {
- return this.startAttemptDuration + this.getSessionDuration();
- },
- getSessionDuration: function() {
- return Math.abs((new Date()) - this.startTimeStamp);
- },
- setupListeners: function() {
- if (!this.get('isInitialised')) {
- Adapt.log.warn('adapt-contrib-xapi: Unable to setup listeners for xAPI');
- return;
- }
- if (this.get('shouldTrackState')) {
- this.listenTo(Adapt, 'state:change', this.sendState);
- }
- // Use the config to specify the core events.
- this.coreEvents = _.extend(this.coreEvents, this.getConfig('_coreEvents'));
- // Always listen out for course completion.
- this.listenTo(Adapt, 'tracking:complete', this.onTrackingComplete);
- // Conditionally listen to the events.
- // Visits to the menu.
- if (this.coreEvents['Adapt']['router:menu']) {
- this.listenTo(Adapt, 'router:menu', this.onItemExperience);
- }
- // Visits to a page.
- if (this.coreEvents['Adapt']['router:page']) {
- this.listenTo(Adapt, 'router:page', this.onItemExperience);
- }
- // When an interaction takes place on a question.
- if (this.coreEvents['Adapt']['questionView:recordInteraction']) {
- this.listenTo(Adapt, 'questionView:recordInteraction', this.onQuestionInteraction);
- }
- // When an assessment is completed.
- if (this.coreEvents['Adapt']['assessments:complete']) {
- this.listenTo(Adapt, 'assessments:complete', this.onAssessmentComplete);
- }
- // Standard completion events for the various collection types, i.e.
- // course, contentobjects, articles, blocks and components.
- _.each(_.keys(this.coreEvents), function(key) {
- if (key !== 'Adapt') {
- var val = this.coreEvents[key];
- if (typeof val === 'object' && val['change:_isComplete'] === true) {
- this.listenTo(Adapt[key], 'change:_isComplete', this.onItemComplete);
- }
- }
- }, this);
- },
- /**
- * Creates an xAPI statement related to the Adapt.course object.
- * @param {object | string} verb - A valid ADL.verbs object or key.
- * @param {object} result - An optional result object.
- * @return A valid ADL statement object.
- */
- getCourseStatement: function(verb, result) {
- if (typeof result === 'undefined') {
- result = {};
- }
- var object={};
- object.id = this.get('activityId');
- return this.getStatement(verb, object, result);
- },
- /**
- * Gets a name object from a given model.
- * @param {Backbone.Model} model - An instance of Adapt.Model (or Backbone.Model).
- * @return {object} An object containing a key-value pair with the language code and name.
- */
- getNameObject: function(model) {
- var name = {};
- name[this.get('displayLang')] = model.get('displayTitle') || model.get('title');
- return name;
- },
- /**
- * Gets the activity type for a given model.
- * @param {Backbone.Model} model - An instance of Adapt.Model (or Backbone.Model).
- * @return {string} A URL to the current activity type.
- */
- getActivityType: function(model) {
- var type = '';
- switch (model.get('_type')) {
- case 'component':
- {
- type = model.get('_isQuestionType') ? Utils.activityTypes.interaction : Utils.activityTypes.media;
- break;
- }
- case 'block':
- case 'article':
- case 'contentobject':
- {
- type = Utils.activityTypes.lesson; //??
- break;
- }
- case 'course':
- {
- type = Utils.activityTypes.course;
- break;
- }
- }
- return type;
- },
- /**
- * Sends an 'answered' statement to the LRS.
- * @param {ComponentView} view - An instance of Adapt.ComponentView.
- */
- onQuestionInteraction: function(view) {
- if (!view.model || view.model.get('_type') !== 'component' && !view.model.get('_isQuestionType')) {
- return;
- }
- var object = {};
- object.id = this.getUniqueIri(view.model);
- var isComplete = view.model.get('_isComplete');
- var lang = this.get('displayLang');
- var statement;
- var description = {};
- description[this.get('displayLang')] = view.model.get('instruction');
- object.definition = {
- name: this.getNameObject(view.model),
- description: description,
- type: Utils.activityTypes.interaction,
- interactionType: view.getResponseType()
- };
- if (typeof view.getInteractionObject === 'function') {
- // Get any extra interactions.
- _.extend(object.definition, view.getInteractionObject());
- // Ensure any 'description' properties are objects with the language map.
- _.each(_.keys(object.definition), function(key) {
- if (_.isArray(object.definition[key]) && object.definition[key].length !== 0) {
- for (var i = 0; i < object.definition[key].length; i++) {
- if (!object.definition[key][i].hasOwnProperty('description')) {
- break;
- }
- if (typeof object.definition[key][i].description === 'string') {
- var description = {};
- description[lang] = object.definition[key][i].description;
- object.definition[key][i].description = description;
- }
- }
- }
- });
- }
- var result = {
- score: {
- raw: view.model.get('_score') || 0
- },
- success: view.model.get('_isCorrect'),
- completion: isComplete,
- response: this.processInteractionResponse(object.definition.interactionType, view.getResponse())
- };
- // Answered
- statement = this.getStatement(Statements.answered, object, result);
- this.sendStatement(statement);
- },
- /**
- * In order to support SCORM 1.2 and SCORM 2004, some of the components return a non-standard
- * response.
- * @param {string} responseType - The type of the response.
- * @param {string} response - The unprocessed response string.
- * @returns {string} A response formatted for xAPI compatibility.
- */
- processInteractionResponse: function(responseType, response) {
- switch (responseType) {
- case 'choice':
- {
- response = response.replace(/,|#/g, '[,]');
- break;
- }
- case 'matching':
- {
- response = response.replace(/#/g, "[,]");
- response = response.replace(/\./g, "[.]");
- break;
- }
- }
- return response;
- },
- /**
- * Sends an xAPI statement when an item has been experienced.
- * @param {AdaptModel} model - An instance of AdaptModel, i.e. ContentObjectModel, etc.
- */
- onItemExperience: function(model) {
- if (model.get('_id') === 'course') {
- // We don't really want to track actions on the home menu.
- return;
- }
- var object = {};
- object.id = this.getUniqueIri(model);
- var statement;
- object.definition = {
- name: this.getNameObject(model)
- };
- // Experienced.
- statement = this.getStatement(Statements.experienced, object);
- this.sendStatement(statement);
- },
- // record the results of a test, passes in score as a percentage
- getRecordTestStatement: function(completionData) {
- completionData = completionData || this.get("completionData");
- var verb;
- // Check the completion status.
- switch (completionData.status) {
- case COMPLETION_STATE.PASSED:
- {
- verb = Statements.passed;
- break;
- }
- case COMPLETION_STATE.FAILED:
- {
- verb = Statements.failed;
- break;
- }
- default:
- {
- verb = Statements.completed;
- }
- }
- // send score
- var stmt = {
- verb: verb,
- object: {
- id: this.get('activityId')
- }
- };
- if (verb === Statements.completed) {
- stmt.result = {
- completion: true
- };
- } else {
- // The assessment(s) play a part in completion, so use their result.
- stmt.result = this.getAssessmentResultObject(completionData.assessment);
- }
- stmt.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(8000);;
- return stmt;
- },
- /**
- * Sends an xAPI statement when an item has been completed.
- * @param {AdaptModel} model - An instance of AdaptModel, i.e. ComponentModel, BlockModel, etc.
- */
- onItemComplete: function(model) {
- var result = {
- completion: true
- };
- // If this is a question component (interaction), do not record multiple statements.
- if (model.get('_type') === 'component' && model.get('_isQuestionType') === true && this.coreEvents['Adapt']['questionView:recordInteraction'] === true && this.coreEvents['components']['change:_isComplete'] === true) {
- // Return because 'Answered' will already have been passed.
- return;
- }
- var object = {};
- object.id = this.getUniqueIri(model);
- var statement;
- object.definition = {
- name: this.getNameObject(model)
- };
- // Completed.
- statement = this.getStatement(Statements.completed, object, result);
- this.sendStatement(statement);
- },
- /**
- * Takes an assessment state and returns a results object based on it.
- * @param {object} assessment - An instance of the assessment state.
- * @return {object} - A result object containing score, success and completion properties.
- */
- getAssessmentResultObject: function(assessment) {
- var result = {
- score: {
- scaled: (assessment.scoreAsPercent / 100),
- raw: assessment.score,
- min: 0,
- max: assessment.maxScore
- },
- success: assessment.isPass,
- completion: assessment.isComplete
- };
- return result;
- },
- /**
- * Sends an xAPI statement when an assessment has been completed.
- * @param {object} assessment - Object representing the state of the assessment.
- */
- onAssessmentComplete: function(assessment) {
- var self = this;
- // Instantiate a Model so it can be used to obtain an IRI.
- var fakeModel = new Backbone.Model({
- _id: assessment.articleId || assessment.id,
- _type: assessment.type,
- pageId: assessment.pageId
- });
- var object = {};
- object.id = this.getUniqueIri(fakeModel);
- var name = {};
- var statement;
- name[this.get('displayLang')] = assessment.id || 'Assessment';
- object.definition = {
- name: name,
- type: Utils.activityTypes.assessment
- };
- var result = this.getAssessmentResultObject(assessment);
- if (assessment.isPass) {
- // Passed.
- statement = this.getStatement(Statements.passed, object, result);
- } else {
- // Failed.
- statement = this.getStatement(Statements.failed, object, result);
- }
- // Delay so that component completion can be recorded before assessment completion.
- _.delay(function() {
- self.sendStatement(statement);
- }, 500);
- },
- /**
- * Gets a unique IRI for a given model.
- * @param {AdaptModel} model - An instance of an AdaptModel object.
- * @return {string} An IRI formulated specific to the passed model.
- */
- getUniqueIri: function(model) {
- var iri = this.get('activityId');
- var type = model.get('_type');
- if (type !== 'course') {
- if (type === 'article-assessment') {
- iri = iri + ['/#', 'assessment', model.get('_id')].join('/');
- } else {
- iri = iri + ['/#/id', model.get('_id')].join('/');
- }
- }
- return iri;
- },
- /**
- * Handler for the Adapt Framework's 'tracking:complete' event.
- * @param {object} completionData
- */
- onTrackingComplete: function(completionData) {
- var self = this;
- var result = {};
- var completionVerb;
- this.set("completionData",completionData);
- // Store a reference that the course has actually been completed.
- this.isComplete = true;
- _.defer(function() {
- // Send the completion status.
- //self.sendStatement(self.getRecordTestStatement(completionData));
- });
- },
- /**
- * Refresh course progress from loaded state.
- */
- restoreState: function() {
- var state = this.get('state');
- if (_.isEmpty(state)) {
- return;
- }
- var Adapt = require('core/js/adapt');
- if (state.components) {
- _.each(state.components, function(stateObject) {
- var restoreModel = Adapt.findById(stateObject._id);
- if (restoreModel) {
- restoreModel.setTrackableState(stateObject);
- } else {
- Adapt.log.warn('adapt-contrib-xapi: Unable to restore state for component: ' + stateObject._id);
- }
- });
- }
- if (state.blocks) {
- _.each(state.blocks, function(stateObject) {
- var restoreModel = Adapt.findById(stateObject._id);
- if (restoreModel) {
- restoreModel.setTrackableState(stateObject);
- } else {
- Adapt.log.warn('adapt-contrib-xapi: Unable to restore state for block: ' + stateObject._id);
- }
- });
- }
- },
- /**
- * Generate an XAPIstatement object for the xAPI wrapper sendStatement methods.
- * @param {object} verb - A valid ADL.verbs object.
- * @param {object} object -
- * @param {object} [result] - optional
- * @param {object} [context] - optional
- * @return {ADL.XAPIStatement} A formatted xAPI statement object.
- */
- getStatement: function(verb, object, result, context) {
- var statement = new TinCan.Statement({
- verb: verb,
- actor:this.tincan.actor,
- object: object
- });
- if (result && !_.isEmpty(result)) {
- statement.result = result;
- }
- if (context) {
- statement.context = context;
- }
- if (this.get('_generateIds')) {
- statement.generateId();
- }
- return statement;
- },
- /**
- * Sends the state to the or the given model to the configured LRS.
- * @param {AdaptModel} model - The AdaptModel whose state has changed.
- */
- sendState: function(model, modelState) {
- if (this.get('shouldTrackState') !== true) {
- return;
- }
- var activityId = this.get('activityId');
- var actor = this.get('actor');
- var type = model.get('_type');
- var state = this.get('state');
- var collectionName = _.findKey(this.coreObjects, function(o) {
- return o === type || o.indexOf(type) > -1
- });
- var stateCollection = _.isArray(state[collectionName]) ? state[collectionName] : [];
- var newState;
- if (collectionName !== 'course') {
- var index = _.findIndex(stateCollection, {
- _id: model.get('_id')
- });
- if (index !== -1) {
- stateCollection.splice(index, 1, modelState);
- } else {
- stateCollection.push(modelState);
- }
- newState = stateCollection;
- } else {
- newState = modelState;
- }
- // Update the locally held state.
- state[collectionName] = newState;
- this.set({
- state: state
- });
- var nState = {
- state: newState
- }
- // Pass the new state to the LRS.
- this.tincan.setState(collectionName, nState, {
- contentType: "application/json",
- overwriteJSON: false,
- callback: function() {
- console.log("State Sent");
- }
- });
- },
- setLocation: function() {
- var location = {
- id: Adapt.offlineStorage.get("location")
- };
- this.tincan.setState("location", location, {
- contentType: "application/json",
- overwriteJSON: false,
- callback: function() {
- console.log("location Sent")
- }
- });
- },
- getLocation: function() {
- var location = this.tincan.getState("location");
- if (location.err === null && location.state !== null && location.state.contents !== "") {
- Adapt.offlineStorage.set("location", location.state.contents.id);
- return location.state.contents;
- }
- },
- /**
- * Retrieves the state information for the current course.
- * @param {ErrorOnlyCallback} [callback]
- */
- getState: function(callback) {
- callback = _.isFunction(callback) ? callback : function() {};
- var self = this;
- var activityId = this.get('activityId');
- var actor = this.get('actor');
- var state = {};
- Async.each(_.keys(this.coreObjects), function(type, nextType) {
- var st = self.tincan.getState(type);
- if (st.err === null && st.state !== null && st.state.contents !== "") {
- state[type] = st.state.contents.state;
- }
- }, function(error) {
- if (error) {
- Adapt.log.error('adapt-contrib-xapi:', error);
- return callback(error);
- }
- if (!_.isEmpty(state)) {
- self.set({
- state: state
- });
- }
- Adapt.trigger('xapi:stateLoaded');
- callback();
- });
- },
- /**
- * Deletes all state information for the current course.
- * @param {ErrorOnlyCallback} [callback]
- */
- deleteState: function(callback) {
- callback = _.isFunction(callback) ? callback : function() {};
- },
- /**
- * Retrieve a config item for the current course, e.g. '_activityID'.
- * @param {string} key - The data attribute to fetch.
- * @return {object|boolean} The attribute value, or false if not found.
- */
- getConfig: function(key) {
- if (!this.config || key === '' || typeof this.config[key] === 'undefined') {
- return false;
- }
- return this.config[key];
- },
- /**
- * Sends a single xAPI statement to the LRS.
- * @param {ADL.XAPIStatement} statement - A valid ADL.XAPIStatement object.
- * @param {ADLCallback} [callback]
- */
- sendStatement: function(statement, callback) {
- callback = _.isFunction(callback) ? callback : function() {};
- if (!statement) {
- return;
- }
- Adapt.trigger('xapi:preSendStatement', statement);
- this.tincan.sendStatement(statement,callback);
- },
- /**
- * Sends multiple xAPI statements to the LRS.
- * @param {ADL.XAPIStatement[]} statements - An array of valid ADL.XAPIStatement objects.
- * @param {ErrorOnlyCallback} [callback]
- */
- sendStatements: function(statements, callback) {
- callback = _.isFunction(callback) ? callback : function() {};
- if (!statements || statements.length === 0) {
- return;
- }
- Adapt.trigger('xapi:preSendStatements', statements);
- this.tincan.sendStatements(statements,callback);
- },
- getGlobals: function() {
- return _.defaults(
- (
- Adapt &&
- Adapt.course &&
- Adapt.course.get('_globals') &&
- Adapt.course.get('_globals')._extensions &&
- Adapt.course.get('_globals')._extensions._xapi
- ) || {}, {
- 'confirm': 'OK',
- 'lrsConnectionErrorTitle': 'LRS not available',
- 'lrsConnectionErrorMessage': 'We were unable to connect to your Learning Record Store (LRS). This means that your progress cannot be recorded.'
- }
- );
- },
- showError: function() {
- if (this.getConfig('_lrsFailureBehaviour') === 'ignore') {
- return;
- }
- Adapt.trigger('notify:alert', {
- title: this.getGlobals().lrsConnectionErrorTitle,
- body: this.getGlobals().lrsConnectionErrorMessage,
- confirmText: this.getGlobals().confirm
- });
- }
- });
- return xAPI;
- });
Add Comment
Please, Sign In to add comment