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.