PageSourceSearch

https://h5p.org/sites/all/modules/h5p/library/js/h5p-x-api-event.js?the41y

js h5p.org collected 2026-09-24 07:20:34 UTC 8,760 bytes, 331 lines download raw bytes

1var H5P = window.H5P = window.H5P || {};
2
3/**
4 * Used for xAPI events.
5 *
6 * @class
7 * @extends H5P.Event
8 */
9H5P.XAPIEvent = function () {
10  H5P.Event.call(this, 'xAPI', {'statement': {}}, {bubbles: true, external: true});
11};
12
13H5P.XAPIEvent.prototype = Object.create(H5P.Event.prototype);
14H5P.XAPIEvent.prototype.constructor = H5P.XAPIEvent;
15
16/**
17 * Set scored result statements.
18 *
19 * @param {number} score
20 * @param {number} maxScore
21 * @param {object} instance
22 * @param {boolean} completion
23 * @param {boolean} success
24 */
25H5P.XAPIEvent.prototype.setScoredResult = function (score, maxScore, instance, completion, success) {
26  this.data.statement.result = {};
27
28  if (typeof score !== 'undefined') {
29    if (typeof maxScore === 'undefined') {
30      this.data.statement.result.score = {'raw': score};
31    }
32    else {
33      this.data.statement.result.score = {
34        'min': 0,
35        'max': maxScore,
36        'raw': score
37      };
38      if (maxScore > 0) {
39        this.data.statement.result.score.scaled = Math.round(score / maxScore * 10000) / 10000;
40      }
41    }
42  }
43
44  if (typeof completion === 'undefined') {
45    this.data.statement.result.completion = (this.getVerb() === 'completed' || this.getVerb() === 'answered');
46  }
47  else {
48    this.data.statement.result.completion = completion;
49  }
50
51  if (typeof success !== 'undefined') {
52    this.data.statement.result.success = success;
53  }
54
55  if (instance && instance.activityStartTime) {
56    var duration = Math.round((Date.now() - instance.activityStartTime ) / 10) / 100;
57    // xAPI spec allows a precision of 0.01 seconds
58
59    this.data.statement.result.duration = 'PT' + duration + 'S';
60  }
61};
62
63/**
64 * Set a verb.
65 *
66 * @param {string} verb
67 *   Verb in short form, one of the verbs defined at
68 *   {@link http://adlnet.gov/expapi/verbs/|ADL xAPI Vocabulary}
69 *
70 */
71H5P.XAPIEvent.prototype.setVerb = function (verb) {
72  if (H5P.jQuery.inArray(verb, H5P.XAPIEvent.allowedXAPIVerbs) !== -1) {
73    this.data.statement.verb = {
74      'id': 'http://adlnet.gov/expapi/verbs/' + verb,
75      'display': {
76        'en-US': verb
77      }
78    };
79  }
80  else if (verb.id !== undefined) {
81    this.data.statement.verb = verb;
82  }
83};
84
85/**
86 * Get the statements verb id.
87 *
88 * @param {boolean} full
89 *   if true the full verb id prefixed by http://adlnet.gov/expapi/verbs/
90 *   will be returned
91 * @returns {string}
92 *   Verb or null if no verb with an id has been defined
93 */
94H5P.XAPIEvent.prototype.getVerb = function (full) {
95  var statement = this.data.statement;
96  if ('verb' in statement) {
97    if (full === true) {
98      return statement.verb;
99    }
100    return statement.verb.id.slice(31);
101  }
102  else {
103    return null;
104  }
105};
106
107/**
108 * Set the object part of the statement.
109 *
110 * The id is found automatically (the url to the content)
111 *
112 * @param {Object} instance
113 *   The H5P instance
114 */
115H5P.XAPIEvent.prototype.setObject = function (instance) {
116  if (instance.contentId) {
117    this.data.statement.object = {
118      'id': this.getContentXAPIId(instance),
119      'objectType': 'Activity',
120      'definition': {
121        'extensions': {
122          'http://h5p.org/x-api/h5p-local-content-id': instance.contentId
123        }
124      }
125    };
126    if (instance.subContentId) {
127      this.data.statement.object.definition.extensions['http://h5p.org/x-api/h5p-subContentId'] = instance.subContentId;
128      // Don't set titles on main content, title should come from publishing platform
129      if (typeof instance.getTitle === 'function') {
130        this.data.statement.object.definition.name = {
131          "en-US": instance.getTitle()
132        };
133      }
134    }
135    else {
136      var content = H5P.getContentForInstance(instance.contentId);
137      if (content && content.metadata && content.metadata.title) {
138        this.data.statement.object.definition.name = {
139          "en-US": H5P.createTitle(content.metadata.title)
140        };
141      }
142    }
143  }
144  else {
145    // Content types view always expect to have a contentId when they are displayed.
146    // This is not the case if they are displayed in the editor as part of a preview.
147    // The fix is to set an empty object with definition for the xAPI event, so all
148    // the content types that rely on this does not have to handle it. This means
149    // that content types that are being previewed will send xAPI completed events,
150    // but since there are no scripts that catch these events in the editor,
151    // this is not a problem.
152    this.data.statement.object = {
153      definition: {}
154    };
155  }
156};
157
158/**
159 * Set the context part of the statement.
160 *
161 * @param {Object} instance
162 *   The H5P instance
163 */
164H5P.XAPIEvent.prototype.setContext = function (instance) {
165  if (instance.parent && (instance.parent.contentId || instance.parent.subContentId)) {
166    this.data.statement.context = {
167      "contextActivities": {
168        "parent": [
169          {
170            "id": this.getContentXAPIId(instance.parent),
171            "objectType": "Activity"
172          }
173        ]
174      }
175    };
176  }
177  if (instance.libraryInfo) {
178    if (this.data.statement.context === undefined) {
179      this.data.statement.context = {"contextActivities":{}};
180    }
181    this.data.statement.context.contextActivities.category = [
182      {
183        "id": "http://h5p.org/libraries/" + instance.libraryInfo.versionedNameNoSpaces,
184        "objectType": "Activity"
185      }
186    ];
187  }
188};
189
190/**
191 * Set the actor. Email and name will be added automatically.
192 */
193H5P.XAPIEvent.prototype.setActor = function () {
194  if (H5PIntegration.user !== undefined) {
195    this.data.statement.actor = {
196      'name': H5PIntegration.user.name,
197      'mbox': 'mailto:' + H5PIntegration.user.mail,
198      'objectType': 'Agent'
199    };
200  }
201  else {
202    var uuid;
203    try {
204      if (localStorage.H5PUserUUID) {
205        uuid = localStorage.H5PUserUUID;
206      }
207      else {
208        uuid = H5P.createUUID();
209        localStorage.H5PUserUUID = uuid;
210      }
211    }
212    catch (err) {
213      // LocalStorage and Cookies are probably disabled. Do not track the user.
214      uuid = 'not-trackable-' + H5P.createUUID();
215    }
216    this.data.statement.actor = {
217      'account': {
218        'name': uuid,
219        'homePage': H5PIntegration.siteUrl
220      },
221      'objectType': 'Agent'
222    };
223  }
224};
225
226/**
227 * Get the max value of the result - score part of the statement
228 *
229 * @returns {number}
230 *   The max score, or null if not defined
231 */
232H5P.XAPIEvent.prototype.getMaxScore = function () {
233  return this.getVerifiedStatementValue(['result', 'score', 'max']);
234};
235
236/**
237 * Get the raw value of the result - score part of the statement
238 *
239 * @returns {number}
240 *   The score, or null if not defined
241 */
242H5P.XAPIEvent.prototype.getScore = function () {
243  return this.getVerifiedStatementValue(['result', 'score', 'raw']);
244};
245
246/**
247 * Get content xAPI ID.
248 *
249 * @param {Object} instance
250 *   The H5P instance
251 */
252H5P.XAPIEvent.prototype.getContentXAPIId = function (instance) {
253  var xAPIId;
254  if (instance.contentId && H5PIntegration && H5PIntegration.contents && H5PIntegration.contents['cid-' + instance.contentId]) {
255    xAPIId =  H5PIntegration.contents['cid-' + instance.contentId].url;
256    if (instance.subContentId) {
257      xAPIId += '?subContentId=' +  instance.subContentId;
258    }
259  }
260  return xAPIId;
261};
262
263/**
264 * Check if this event is sent from a child (i.e not from grandchild)
265 *
266 * @return {Boolean}
267 */
268H5P.XAPIEvent.prototype.isFromChild = function () {
269  var parentId = this.getVerifiedStatementValue(['context', 'contextActivities', 'parent', 0, 'id']);
270  return !parentId || parentId.indexOf('subContentId') === -1;
271};
272
273/**
274 * Figure out if a property exists in the statement and return it
275 *
276 * @param {string[]} keys
277 *   List describing the property we're looking for. For instance
278 *   ['result', 'score', 'raw'] for result.score.raw
279 * @returns {*}
280 *   The value of the property if it is set, null otherwise.
281 */
282H5P.XAPIEvent.prototype.getVerifiedStatementValue = function (keys) {
283  var val = this.data.statement;
284  for (var i = 0; i < keys.length; i++) {
285    if (val[keys[i]] === undefined) {
286      return null;
287    }
288    val = val[keys[i]];
289  }
290  return val;
291};
292
293/**
294 * List of verbs defined at {@link http://adlnet.gov/expapi/verbs/|ADL xAPI Vocabulary}
295 *
296 * @type Array
297 */
298H5P.XAPIEvent.allowedXAPIVerbs = [
299  'answered',
300  'asked',
301  'attempted',
302  'attended',
303  'commented',
304  'completed',
305  'exited',
306  'experienced',
307  'failed',
308  'imported',
309  'initialized',
310  'interacted',
311  'launched',
312  'mastered',
313  'passed',
314  'preferred',
315  'progressed',
316  'registered',
317  'responded',
318  'resumed',
319  'scored',
320  'shared',
321  'suspended',
322  'terminated',
323  'voided',
324
325  // Custom verbs used for action toolbar below content
326  'downloaded',
327  'copied',
328  'accessed-reuse',
329  'accessed-embed',
330  'accessed-copyright'
331];

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.