1 2var VERSION = "7.12.0"; 3 4var PREFERENCE_DEFAULT = 0; 5var PREFERENCE_OFF = -1; 6var PREFERENCE_ON = 1; 7 8var LESSON_STATUS_PASSED = 1; //the user completed the content with a score sufficient to pass 9var LESSON_STATUS_COMPLETED = 2; //the user completed the content 10var LESSON_STATUS_FAILED = 3; //the user completed the content but his/her score was not sufficient to pass 11var LESSON_STATUS_INCOMPLETE = 4; //the user began the content but did not complete it 12var LESSON_STATUS_BROWSED = 5; //the user looked at the content but was not making a recorded attempt at it 13var LESSON_STATUS_NOT_ATTEMPTED = 6; //the user has not started the content 14 15var ENTRY_REVIEW = 1; 16var ENTRY_FIRST_TIME = 2; 17var ENTRY_RESUME = 3; 18 19var MODE_NORMAL = 1; 20var MODE_BROWSE = 2; 21var MODE_REVIEW = 3; 22 23var MAX_CMI_TIME = 36002439990; //max CMI Timespan 9999:99:99.99 24 25var NO_ERROR = 0; 26var ERROR_LMS = 1; 27var ERROR_INVALID_PREFERENCE = 2; 28var ERROR_INVALID_NUMBER = 3; 29var ERROR_INVALID_ID = 4; 30var ERROR_INVALID_STATUS = 5; 31var ERROR_INVALID_RESPONSE = 6; 32var ERROR_NOT_LOADED = 7; 33var ERROR_INVALID_INTERACTION_RESPONSE = 8; 34 35var EXIT_TYPE_SUSPEND = "SUSPEND"; 36var EXIT_TYPE_FINISH = "FINISH"; 37var EXIT_TYPE_TIMEOUT = "TIMEOUT"; 38var EXIT_TYPE_UNLOAD = "UNLOAD"; 39 40var INTERACTION_RESULT_CORRECT = "CORRECT"; 41var INTERACTION_RESULT_WRONG = "WRONG"; 42var INTERACTION_RESULT_UNANTICIPATED = "UNANTICIPATED"; 43var INTERACTION_RESULT_NEUTRAL = "NEUTRAL"; 44 45var INTERACTION_TYPE_TRUE_FALSE = "true-false"; 46var INTERACTION_TYPE_CHOICE = "choice"; 47var INTERACTION_TYPE_FILL_IN = "fill-in"; 48var INTERACTION_TYPE_LONG_FILL_IN = "long-fill-in"; 49var INTERACTION_TYPE_MATCHING = "matching"; 50var INTERACTION_TYPE_PERFORMANCE = "performance"; 51var INTERACTION_TYPE_SEQUENCING = "sequencing"; 52var INTERACTION_TYPE_LIKERT = "likert"; 53var INTERACTION_TYPE_NUMERIC = "numeric"; 54 55var DATA_CHUNK_PAIR_SEPARATOR = '###'; 56var DATA_CHUNK_VALUE_SEPARATOR = '$$'; 57 58//Configuration Parameters 59var APPID = "__APPID__"; //this is used for SCORM Cloud. Set to "" to disable. 60var CLOUDURL = "__CLOUDURL__"; 61 62var blnDebug = true; //set this to false if you don't want the overhead of recording debug information 63 64var strLMSStandard = "__LMS_STANDARD__"; //used in versions that support multiple standards, set to "NONE" to default 65 //to StandAlone mode. Possible values = "AUTO", "NONE", "SCORM", "AICC", "SCORM2004", "TCAPI", "CMI5" 66 //AUTO mode will automatically determine the best standard to use 67 //(it first tries AICC based on "AICC_URL" query param, then TCAPI and CMI5 based on "endpoint" and other 68 //query parameters, then SCORM 2004, then SCORM 1.2/1.1 then NONE 69 70var DEFAULT_EXIT_TYPE = EXIT_TYPE_SUSPEND; //When the content is unloaded without an API function indicating the type of exit, 71 //what default behavior do you want to assume. Use EXIT_TYPE_SUSPEND if you plan to 72 //call Finish when the content is complete. Use EXIT_TYPE_FINISH if you do not plan 73 //to call Finish. 74 75 76var AICC_LESSON_ID = "1"; //if recording question answers in AICC in an LMS that supports interactions, 77 //this field need to match the system_id on line in the .DES file that describes 78 //this course, the default is 1. Be sure that this value does not contain double quote characters (") 79 80var EXIT_BEHAVIOR = "SCORM_RECOMMENDED"; //used to control window closing behavior on call of ConcedeControl 81 //Possible Values: SCORM_RECOMMENDED, ALWAYS_CLOSE, ALWAYS_CLOSE_TOP, ALWAYS_CLOSE_PARENT, 82 // NOTHING, REDIR_CONTENT_FRAME, LMS_SPECIFIED_REDIRECT 83 // 84 // Not used for cmi5 85 86var EXIT_TARGET = "lms/goodbye.html"; //Used in conjunction with EXIT_BEHAVIOR, only with REDIR_CONTENT_FRAME. This should be a neutral page that is displayed 87 //after the course has exited, but before it has been taked away by the LMS 88 89var AICC_COMM_DISABLE_XMLHTTP = false; //false is the preferred value, true can be required in certain cross domain situations 90var AICC_COMM_DISABLE_IFRAME = false; //false is the preferred value, true can be required in certain cross domain situations 91 92var AICC_COMM_PREPEND_HTTP_IF_MISSING = true; //Some AICC LMS's will omit the "http://" from the AICC_URL value. If this is the case, 93 //set this setting to true to have the API prepe
93nd the "http://" value 94 95var AICC_REPORT_MIN_MAX_SCORE = true; //Some AICC LMS's have trouble processing a score which contains a min and max value. Setting this 96 //value to false allows you to turn off that reporting to accommodate those LMS's. 97 98 99var SHOW_DEBUG_ON_LAUNCH = false; //set this to true when debugging to force the debug window to launch immediately 100 101var DO_NOT_REPORT_INTERACTIONS = false; //set this to true to disable reporting of question results to the LMS as some LMS's 102 //particularly those supporting only AICC can have problems with interactions results 103 104 105var SCORE_CAN_ONLY_IMPROVE = false; //set this to true to ensure that on subsequent attempts, a learner's score can only go up 106 107var REVIEW_MODE_IS_READ_ONLY = true; //set this to true if no new data should be saved when a course is launched in review mode (normally this is the LMS's responsibility) 108 109 110var TCAPI_DONT_USE_BROKEN_URN_IDS = true; //Prior to 7.0, there was a regression that caused some activity IDs to be invalid URIs. With this flag set to true, 111 //the IDs will be set to valid URIs. However, this will cause a difference of IDs between interactions from before 112 //7.0 and after. Therefore, set this to false when running any courses that were recorded using a version of Driver earlier than 7.0. 113 114 115/* 116These variables control how long the API should wait on an AICC form submission before timing out 117AICC_RE_CHECK_LOADED_INTERVAL = Number of milliseconds the API waits between checks to see if the form is loaded 118AICC_RE_CHECK_ATTEMPTS_BEFORE_TIMEOUT = Number of times the API checks to see if the form is loaded 119AICC_RE_CHECK_LOADED_INTERVAL * AICC_RE_CHECK_ATTEMPTS_BEFORE_TIMEOUT = Desired time out in milliseconds 120*/ 121var AICC_RE_CHECK_LOADED_INTERVAL = 250; 122var AICC_RE_CHECK_ATTEMPTS_BEFORE_TIMEOUT = 240; 123 124var USE_AICC_KILL_TIME = true; //set this to false to disable the explicit extra waiting between AICC requests 125 126//This controls the entry default when it is not set by the LMS 127//Possible options are 128//ENTRY_REVIEW (normal default) - ENTRY_FIRST_TIME - ENTRY_RESUME 129var AICC_ENTRY_FLAG_DEFAULT = ENTRY_REVIEW; 130 131var AICC_USE_CUSTOM_COMMS = false; //Used to force the AICC communications through a custom routine (defined in indexAPI.html) instead of other methods 132 133var FORCED_COMMIT_TIME = "60000"; //Used to force CommitData back to the LMS at the desired interval (in milliseconds). Set to 0 (zero) to not force a commit time. 134 135var ALLOW_NONE_STANDARD = true; //Set this to false to show an error message if no LMS API can be found - Standalone will still work fine. 136 137var USE_2004_SUSPENDALL_NAVREQ = false; 138var USE_STRICT_SUSPEND_DATA_LIMITS = false; //determines if we check the suspend_data length in Driver 139var EXIT_SUSPEND_IF_COMPLETED = false; //determines if Suspend is sent for exit when SetReachedEnd is called - legacy driver defaulted to false 140var EXIT_NORMAL_IF_PASSED = false; //determines if exit is Normal when Passed - legacy driver defaulted to false and always sends suspend 141var AICC_ENCODE_PARAMETER_VALUES = true; //default is true; 142 143var PASS_FAIL_SETS_COMPLETION_FOR_2004 = true; //determines if SetPassed and SetFailed also set completion for SCORM 2004 144var ALLOW_INTERACTION_NULL_LEARNER_RESPONSE = false; //determines whether learner response is required when recording interactions 145var PREVENT_STATUS_CHANGE_DURING_INIT = false; //when true prevents initialization process from setting complete/success status from LMS result 146var USE_LEGACY_IDENTIFIERS_FOR_2004 = true; //set to `true` to disable conversion of identifiers to proper URIs (may make content non-compliant with SCORM spec) 147var URI_IDENTIFIER_PREFIX = "urn:scormdriver:"; //used to make proper URI identifiers out of identifiers that are detected as non-absolute URIs 148 149window.scormdriver_content = window; 150 151function LoadContent() {} 152 153function GetQueryStringValue(strElement, strQueryString){ 154 155 var aryPairs; 156 var foundValue; 157 158 //WriteToDebug("In GetQueryStringValue strElement=" + strElement + ", strQueryString=" + strQueryString); 159 160 //get rid of the leading "?" 161 strQueryString = strQueryString.substring(1); 162 163 //split into name/value pairs 164 aryPairs = strQueryString.split("&"); 165 166 foundValue = SearchQueryStringPairs(aryPairs, strElement); 167 168 if (foundValue === null){ 169 //sometimes we wind up with a querystring that looks like "indexAPI.html?ShowDebug=true?AICC_URL=http://www.blah.com/blahblah&AICC_SID=12345" 170 //try to handle this case by splitting on both ? and & 171 172 aryPairs = strQueryString.split(/[\?\&]/); 173 foundValue = SearchQueryStringPairs(aryPairs, strElement); 174 } 175 176 if (foundValue === null){ 177 //if we didn't find a match, return an empty string 178 WriteToDebug("GetQueryStringValue Element '" + strElement + "' Not Found, Returning: empty string"); 179 return ""; 180 } 181 else{ 182 WriteToDebug("GetQueryStringValue for '" + strElement + "' Returning: " + foundValue); 183 return foundValue; 184 } 185} 186 187function SearchQueryStringPairs(aryPairs, strElement){ 188 189 var i; 190 var intEqualPos; 191 var strArg = ""; 192 var strValue = ""; 193 194 strElement = strElement.toLowerCase(); 195 196 //search each querystring value and return the first match 197 for (i=0; i < aryPairs.length; i++){ 198 199 //WriteToDebug("Looking at element-" + aryPairs[i]); 200 201 intEqualPos = aryPairs[i].indexOf('='); 202 203 if (intEqualPos != -1){ 204 205 strArg = aryPairs[i].substring(0,intEqualPos); 206 207 //WriteToDebug("Arg=" + strArg); 208 209 if (EqualsIgnoreCase(strArg, strElement)){ 210 211 //WriteToDebug("Found Match"); 212 strValue = aryPairs[i].substring(intEqualPos+1); 213 214 //WriteToDebug("Decoding value-" + strValue); 215 216 strValue = new String(strValue) 217 218 strValue = strValue.replace(/\+/g, "%20") 219 strValue = unescape(strValue); 220 221 //WriteToDebug("Returning value-" + strValue); 222 223 return new String(strValue); 224 } 225 } 226 } 227 228 return null; 229} 230 231function ConvertStringToBoolean(str){ 232 233 var intTemp; 234 235 //WriteToDebug("In ConvertStringToBoolean"); 236 237 if (EqualsIgnoreCase(str, "true") || EqualsIgnoreCase(str, "t") || str.toLowerCase().indexOf("t") == 0){ 238 //WriteToDebug("Found alpha true"); 239 return true; 240 } 241 else{ 242 intTemp = parseInt(str, 10); 243 if (intTemp == 1 || intTemp==-1){ 244 //WriteToDebug("Found numeric true"); 245 return true; 246 } 247 else{ 248 //WriteToDebug("Returning false"); 249 return false;
250 } 251 } 252} 253 254 255 256function EqualsIgnoreCase(str1, str2){ 257 258 //WriteToDebug("In EqualsIgnoreCase str1=" + str1 + ", str2=" + str2); 259 260 var blnReturn; 261 262 str1 = new String(str1); 263 str2 = new String(str2); 264 265 blnReturn = (str1.toLowerCase() == str2.toLowerCase()) 266 267 //WriteToDebug("Returning " + blnReturn); 268 269 return blnReturn; 270} 271 272 273function ValidInteger(intNum){ 274 275 WriteToDebug("In ValidInteger intNum=" + intNum); 276 277 var str = new String(intNum); 278 279 if (str.indexOf("-", 0) == 0){ 280 str = str.substring(1, str.length - 1); 281 } 282 283 var regValidChars= new RegExp("[^0-9]"); 284 285 if (str.search(regValidChars) == -1){ 286 WriteToDebug("Returning true"); 287 return true; 288 } 289 290 WriteToDebug("Returning false"); 291 return false; 292} 293 294 295//--------------------------------------------------------------------------------- 296//Time Manipulation Functions 297 298function ConvertDateToIso8601TimeStamp(dtm){ 299 300 var strTimeStamp; 301 302 dtm = new Date(dtm); 303 304 var Year = dtm.getFullYear(); 305 var Month = dtm.getMonth() + 1; 306 var Day = dtm.getDate(); 307 var Hour = dtm.getHours(); 308 var Minute = dtm.getMinutes(); 309 var Second = dtm.getSeconds(); 310 311 Month = ZeroPad(Month, 2); 312 Day = ZeroPad(Day, 2); 313 Hour = ZeroPad(Hour, 2); 314 Minute = ZeroPad(Minute, 2); 315 Second = ZeroPad(Second, 2); 316 317 strTimeStamp = Year + "-" + Month + "-" + Day + "T" + Hour + ":" + Minute + ":" + Second; 318 319 320 //JBR 11-9-2011 - adding timezone offset 321 // should only be called for SCORM 2004+ 322 var tzoffset = -(dtm.getTimezoneOffset()/60); 323 if(tzoffset!=0){ 324 strTimeStamp += '.0'; // this seconds fraction is required if you have a TZOffset 325 if(tzoffset>0){ 326 // handle the fraction offest in minutes 327 if((''+tzoffset).indexOf('.')!=-1){ 328 var fraction = '0'+(''+tzoffset).substr((''+tzoffset).indexOf('.'),(''+tzoffset).length); 329 var base = (''+tzoffset).substr(0,(''+tzoffset).indexOf('.')); 330 fraction = (fraction*60); 331 strTimeStamp += '+'+ZeroPad(base+'.'+fraction,2); 332 }else{ 333 // add the '+' if it's needed (the - is handled by ZeroPad) 334 strTimeStamp += '+'+ZeroPad(tzoffset,2); 335 } 336 }else{ 337 strTimeStamp += ZeroPad(tzoffset,2); 338 } 339 } 340 341 return strTimeStamp; 342 343} 344 345function ConvertIso8601TimeStampToDate(strTimeStamp){ 346 347 strTimeStamp = new String(strTimeStamp); 348 var ary = new Array(); 349 ary = strTimeStamp.split(/[\:T+-]/); 350 351 var Year = ary[0]; 352 var Month = ary[1]-1; 353 var Day = ary[2]; 354 var Hour = ary[3]; 355 var Minute = ary[4]; 356 var Second = ary[5]; 357 358 359 return new Date(Year, Month, Day, Hour, Minute, Second, 0); 360 361} 362 363function ConvertDateToCMIDate(dtmDate){ 364 365 WriteToDebug("In ConvertDateToCMIDate"); 366 367 var strYear; 368 var strMonth; 369 var strDay; 370 var strReturn; 371 372 dtmDate = new Date(dtmDate); 373 374 strYear = dtmDate.getFullYear() 375 strMonth = (dtmDate.getMonth() + 1); 376 strDay = dtmDate.getDate(); 377 378 strReturn = ZeroPad(strYear, 4) + "/" + ZeroPad(strMonth, 2) + "/" + ZeroPad(strDay, 2); 379 380 return strReturn; 381} 382 383function ConvertDateToCMITime(dtmDate){ 384 385 var strHours; 386 var strMinutes; 387 var strSeconds; 388 var strReturn; 389 390 dtmDate = new Date(dtmDate); 391 392 strHours = dtmDate.getHours(); 393 strMinutes = dtmDate.getMinutes(); 394 strSeconds = dtmDate.getSeconds(); 395 396 strReturn = ZeroPad(strHours, 2) + ":" + ZeroPad(strMinutes, 2) + ":" + ZeroPad(strSeconds, 2); 397 398 return strReturn; 399} 400 401 402function ConvertCMITimeSpanToMS(strTime){ 403 404 WriteToDebug("In ConvertCMITimeSpanToMS, strTime=" + strTime); 405 406 var aryParts; 407 var intHours; 408 var intMinutes; 409 var intSeconds; 410 411 var intTotalMilliSeconds; 412 413 //split the string into its parts 414 aryParts = strTime.split(":"); 415 416 //make sure it's valid before we knock ourselves out 417 if (! IsValidCMITimeSpan(strTime)){ 418 WriteToDebug("ERROR - Invalid TimeSpan"); 419 SetErrorInfo(SCORM_ERROR_GENERAL, "LMS ERROR - Invalid time span passed to ConvertCMITimeSpanToMS, please contact technical support"); 420 return 0; 421 } 422 423 //seperate the parts and multiply by the appropriate constant (3600000 = num milliseconds in an hour, etc) 424 intHours = aryParts[0]; 425 intMinutes = aryParts[1]; 426 intSeconds = aryParts[2]; //don't need to worry about milliseconds b/c they are expressed as fractions of a second 427 428 WriteToDebug("intHours=" + intHours + " intMinutes=" + intMinutes + " intSeconds=" + intSeconds); 429 430 intTotalMilliSeconds = (intHours * 3600000) + (intMinutes * 60000) + (intSeconds * 1000); 431 432 //necessary because in JavaScript, some values for intSeconds (such as 2.01) will have a lot of decimal 433 //places when multiplied by 1000. For instance, 2.01 turns into 2009.999999999999997. 434 intTotalMilliSeconds = Math.round(intTotalMilliSeconds); 435 436 WriteToDebug("Returning " + intTotalMilliSeconds); 437 438 return intTotalMilliSeconds; 439} 440 441 442function ConvertScorm2004TimeToMS(strIso8601Time){ 443 444 WriteToDebug("In ConvertScorm2004TimeToMS, strIso8601Time=" + strIso8601Time); 445 446 var intTotalMs = 0; 447 448 var strNumberBuilder; 449 var strCurrentCharacter; 450 var blnInTimeSection; 451 452 453 var Seconds = 0; // 100 hundreths of a seconds 454 var Minutes = 0; // 60 seconds 455 var Hours = 0; // 60 minutes 456 var Days = 0; // 24 hours 457 var Months = 0; // assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month 458 var Years = 0; // assumed to be 12 "average" months 459 460 var MILLISECONDS_PER_SECOND = 1000; 461 var MILLISECONDS_PER_MINUTE = MILLISECONDS_PER_SECOND * 60; 462 var MILLISECONDS_PER_HOUR = MILLISECONDS_PER_MINUTE * 60; 463 var MILLISECONDS_PER_DAY = MILLISECONDS_PER_HOUR * 24; 464 var MILLISECONDS_PER_MONTH = MILLISECONDS_PER_DAY * (((365 * 4) + 1) / 48); 465 var MILLISECONDS_PER_YEAR = MILLISECONDS_PER_MONTH * 12; 466 467 468 strIso8601Time = new String(strIso8601Time); 469 470 strNumberBuilder = ""; 471 strCurrentCharacter = ""; 472 blnInTimeSection = false;
473 474 //start at 1 to get past the "P" 475 for (var i=1; i < strIso8601Time.length; i++){ 476 477 strCurrentCharacter = strIso8601Time.charAt(i); 478 479 480 if (IsIso8601SectionDelimiter(strCurrentCharacter)){ 481 482 switch (strCurrentCharacter.toUpperCase()){ 483 484 case "Y": 485 Years = parseInt(strNumberBuilder, 10); 486 break; 487 488 case "M": 489 if (blnInTimeSection){ 490 Minutes = parseInt(strNumberBuilder, 10); 491 } 492 else{ 493 Months = parseInt(strNumberBuilder, 10); 494 } 495 break; 496 497 case "D": 498 Days = parseInt(strNumberBuilder, 10); 499 break; 500 501 case "H": 502 Hours = parseInt(strNumberBuilder, 10); 503 break; 504 505 case "S": 506 Seconds = parseFloat(strNumberBuilder); 507 break; 508 509 case "T": 510 blnInTimeSection = true; 511 break; 512 513 } 514 515 strNumberBuilder = ""; 516 } 517 else{ 518 strNumberBuilder += "" + strCurrentCharacter; //use "" to keep the number as string concats instead of numeric additions 519 } 520 521 } 522 523 WriteToDebug("Years=" + Years + "\n" + 524 "Months=" + Months + "\n" + 525 "Days=" + Days + "\n" + 526 "Hours=" + Hours + "\n" + 527 "Minutes=" + Minutes + "\n" + 528 "Seconds=" + Seconds + "\n" 529 ); 530 531 intTotalMs = (Years * MILLISECONDS_PER_YEAR) + 532 (Months * MILLISECONDS_PER_MONTH) + 533 (Days * MILLISECONDS_PER_DAY) + 534 (Hours * MILLISECONDS_PER_HOUR) + 535 (Minutes * MILLISECONDS_PER_MINUTE) + 536 (Seconds * MILLISECONDS_PER_SECOND); 537 538 //necessary because in JavaScript, some values (such as 2.01) will have a lot of decimal 539 //places when multiplied by a larger number. For instance, 2.01 turns into 2009.999999999999997. 540 intTotalMs = Math.round(intTotalMs); 541 542 WriteToDebug ("returning-" + intTotalMs); 543 544 return intTotalMs; 545 546} 547 548function IsIso8601SectionDelimiter(str){ 549 550 if (str.search(/[PYMDTHS]/) >=0 ){ 551 return true; 552 } 553 else{ 554 return false; 555 } 556 557} 558 559 560function IsValidCMITimeSpan(strValue){ 561 562 WriteToDebug("In IsValidCMITimeSpan strValue=" + strValue); 563 564 //note that the spec does not say that minutes or seconds have to be < 60 565 566 var regValid = /^\d?\d?\d?\d:\d?\d:\d?\d(.\d\d?)?$/; 567 568 if (strValue.search(regValid) > -1){ 569 WriteToDebug("Returning True"); 570 return true; 571 } 572 else{ 573 WriteToDebug("Returning False"); 574 return false; 575 } 576} 577 578 579 580function IsValidIso8601TimeSpan(strValue){ 581 582 WriteToDebug("In IsValidIso8601TimeSpan strValue=" + strValue); 583 584 var regValid = /^P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(.\d\d?)?S)?)?$/; 585 586 if (strValue.search(regValid) > -1){ 587 WriteToDebug("Returning True"); 588 return true; 589 } 590 else{ 591 WriteToDebug("Returning False"); 592 return false; 593 } 594} 595 596function ConvertMilliSecondsToTCAPITime(intTotalMilliseconds, blnIncludeFraction){ 597 598 var intHours; 599 var intMinutes; 600 var intSeconds; 601 var intMilliseconds; 602 var intHundredths; 603 var strCMITimeSpan; 604 605 WriteToDebug("In ConvertMilliSecondsToTCAPITime, intTotalMilliseconds = " + intTotalMilliseconds + ", blnIncludeFraction = " + blnIncludeFraction); 606 607 if (blnIncludeFraction == null || blnIncludeFraction == undefined){ 608 blnIncludeFraction = true; 609 } 610 611 //extract time parts 612 intMilliseconds = intTotalMilliseconds % 1000; 613 614 intSeconds = ((intTotalMilliseconds - intMilliseconds) / 1000) % 60; 615 616 intMinutes = ((intTotalMilliseconds - intMilliseconds - (intSeconds * 1000)) / 60000) % 60; 617 618 intHours = (intTotalMilliseconds - intMilliseconds - (intSeconds * 1000) - (intMinutes * 60000)) / 3600000; 619 620 WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds); 621 622 /* 623 deal with exceptional case when content used a huge amount of time and interpreted CMITimstamp 624 to allow a number of intMinutes and seconds greater than 60 i.e. 9999:99:99.99 instead of 9999:60:60:99 625 note - this case is permissable under SCORM, but will be exceptionally rare 626 */ 627 628 if (intHours == 10000) 629 { 630 WriteToDebug("Max intHours detected"); 631 632 intHours = 9999; 633 634 intMinutes = (intTotalMilliseconds - (intHours * 3600000)) / 60000; 635 if (intMinutes == 100) 636 { 637 intMinutes = 99; 638 } 639 intMinutes = Math.floor(intMinutes); 640 641 intSeconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000)) / 1000; 642 if (intSeconds == 100) 643 { 644 intSeconds = 99; 645 } 646 intSeconds = Math.floor(intSeconds); 647 648 intMilliseconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000) - (intSeconds * 1000)); 649 650 WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds); 651 652 } 653 654 //drop the extra precision from the milliseconds 655 intHundredths = Math.floor(intMilliseconds / 10); 656 657 //put in padding 0's and concatinate to get the proper format 658 strCMITimeSpan = ZeroPad(intHours, 4) + ":" + ZeroPad(intMinutes, 2) + ":" + ZeroPad(intSeconds, 2); 659 660 if (blnIncludeFraction){
661 strCMITimeSpan += "." + intHundredths; 662 } 663 664 WriteToDebug("strCMITimeSpan=" + strCMITimeSpan); 665 666 //check for case where total milliseconds is greater than max supported by strCMITimeSpan 667 if (intHours > 9999) 668 { 669 strCMITimeSpan = "9999:99:99"; 670 671 if (blnIncludeFraction){ 672 strCMITimeSpan += ".99"; 673 } 674 } 675 676 WriteToDebug("returning " + strCMITimeSpan); 677 678 return strCMITimeSpan; 679 680} 681 682function ConvertMilliSecondsToSCORMTime(intTotalMilliseconds, blnIncludeFraction){ 683 684 var intHours; 685 var intMinutes; 686 var intSeconds; 687 var intMilliseconds; 688 var intHundredths; 689 var strCMITimeSpan; 690 691 WriteToDebug("In ConvertMilliSecondsToSCORMTime, intTotalMilliseconds = " + intTotalMilliseconds + ", blnIncludeFraction = " + blnIncludeFraction); 692 693 if (blnIncludeFraction == null || blnIncludeFraction == undefined){ 694 blnIncludeFraction = true; 695 } 696 697 //extract time parts 698 intMilliseconds = intTotalMilliseconds % 1000; 699 700 intSeconds = ((intTotalMilliseconds - intMilliseconds) / 1000) % 60; 701 702 intMinutes = ((intTotalMilliseconds - intMilliseconds - (intSeconds * 1000)) / 60000) % 60; 703 704 intHours = (intTotalMilliseconds - intMilliseconds - (intSeconds * 1000) - (intMinutes * 60000)) / 3600000; 705 706 WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds); 707 708 /* 709 deal with exceptional case when content used a huge amount of time and interpreted CMITimstamp 710 to allow a number of intMinutes and seconds greater than 60 i.e. 9999:99:99.99 instead of 9999:60:60:99 711 note - this case is permissable under SCORM, but will be exceptionally rare 712 */ 713 714 if (intHours == 10000) 715 { 716 WriteToDebug("Max intHours detected"); 717 718 intHours = 9999; 719 720 intMinutes = (intTotalMilliseconds - (intHours * 3600000)) / 60000; 721 if (intMinutes == 100) 722 { 723 intMinutes = 99; 724 } 725 intMinutes = Math.floor(intMinutes); 726 727 intSeconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000)) / 1000; 728 if (intSeconds == 100) 729 { 730 intSeconds = 99; 731 } 732 intSeconds = Math.floor(intSeconds); 733 734 intMilliseconds = (intTotalMilliseconds - (intHours * 3600000) - (intMinutes * 60000) - (intSeconds * 1000)); 735 736 WriteToDebug("Separated Parts, intHours=" + intHours + ", intMinutes=" + intMinutes + ", intSeconds=" + intSeconds + ", intMilliseconds=" + intMilliseconds); 737 738 } 739 740 //drop the extra precision from the milliseconds 741 intHundredths = Math.floor(intMilliseconds / 10); 742 743 //put in padding 0's and concatinate to get the proper format 744 strCMITimeSpan = ZeroPad(intHours, 4) + ":" + ZeroPad(intMinutes, 2) + ":" + ZeroPad(intSeconds, 2); 745 746 if (blnIncludeFraction){ 747 strCMITimeSpan += "." + intHundredths; 748 } 749 750 WriteToDebug("strCMITimeSpan=" + strCMITimeSpan); 751 752 //check for case where total milliseconds is greater than max supported by strCMITimeSpan 753 if (intHours > 9999) 754 { 755 strCMITimeSpan = "9999:99:99"; 756 757 if (blnIncludeFraction){ 758 strCMITimeSpan += ".99"; 759 } 760 } 761 762 WriteToDebug("returning " + strCMITimeSpan); 763 764 return strCMITimeSpan; 765 766} 767 768 769// TODO: using years and months in the ISO8601 interval gives varying results 770// because of the change in number of days in months (and years--leap) 771function ConvertMilliSecondsIntoSCORM2004Time(intTotalMilliseconds){ 772 773 WriteToDebug("In ConvertMilliSecondsIntoSCORM2004Time intTotalMilliseconds=" + intTotalMilliseconds); 774 775 var ScormTime = ""; 776 777 var HundredthsOfASecond; //decrementing counter - work at the hundreths of a second level because that is all the precision that is required 778 779 var Seconds; // 100 hundreths of a seconds 780 var Minutes; // 60 seconds 781 var Hours; // 60 minutes 782 var Days; // 24 hours 783 var Months; // assumed to be an "average" month (figures a leap year every 4 years) = ((365*4) + 1) / 48 days - 30.4375 days per month 784 var Years; // assumed to be 12 "average" months 785 786 var HUNDREDTHS_PER_SECOND = 100; 787 var HUNDREDTHS_PER_MINUTE = HUNDREDTHS_PER_SECOND * 60; 788 var HUNDREDTHS_PER_HOUR = HUNDREDTHS_PER_MINUTE * 60; 789 var HUNDREDTHS_PER_DAY = HUNDREDTHS_PER_HOUR * 24; 790 var HUNDREDTHS_PER_MONTH = HUNDREDTHS_PER_DAY * (((365 * 4) + 1) / 48); 791 var HUNDREDTHS_PER_YEAR = HUNDREDTHS_PER_MONTH * 12; 792 793
794 HundredthsOfASecond = Math.floor(intTotalMilliseconds / 10); 795 796 Years = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_YEAR); 797 HundredthsOfASecond -= (Years * HUNDREDTHS_PER_YEAR); 798 799 Months = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_MONTH); 800 HundredthsOfASecond -= (Months * HUNDREDTHS_PER_MONTH); 801 802 Days = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_DAY); 803 HundredthsOfASecond -= (Days * HUNDREDTHS_PER_DAY); 804 805 Hours = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_HOUR); 806 HundredthsOfASecond -= (Hours * HUNDREDTHS_PER_HOUR); 807 808 Minutes = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_MINUTE); 809 HundredthsOfASecond -= (Minutes * HUNDREDTHS_PER_MINUTE); 810 811 Seconds = Math.floor(HundredthsOfASecond / HUNDREDTHS_PER_SECOND); 812 HundredthsOfASecond -= (Seconds * HUNDREDTHS_PER_SECOND); 813 814 815 if (Years > 0) { 816 ScormTime += Years + "Y"; 817 } 818 if (Months > 0){ 819 ScormTime += Months + "M"; 820 } 821 if (Days > 0){ 822 ScormTime += Days + "D"; 823 } 824 825 //check to see if we have any time before adding the "T" 826 if ((HundredthsOfASecond + Seconds + Minutes + Hours) > 0 ){ 827 828 ScormTime += "T"; 829 830 if (Hours > 0){ 831 ScormTime += Hours + "H"; 832 } 833 834 if (Minutes > 0){ 835 ScormTime += Minutes + "M"; 836 } 837 838 if ((HundredthsOfASecond + Seconds) > 0){ 839 ScormTime += Seconds; 840 841 if (HundredthsOfASecond > 0){ 842 ScormTime += "." + HundredthsOfASecond; 843 } 844 845 ScormTime += "S"; 846 } 847 848 } 849 850 851 if (ScormTime == ""){ 852 ScormTime = "T0S"; 853 } 854 855 ScormTime = "P" + ScormTime; 856 857 WriteToDebug("Returning-" + ScormTime); 858 859 return ScormTime; 860} 861 862 863 864function ZeroPad(intNum, intNumDigits){ 865 866 WriteToDebug("In ZeroPad intNum=" + intNum + " intNumDigits=" + intNumDigits); 867 868 var strTemp; 869 var intLen; 870 var decimalToPad; 871 var i; 872 var isNeg = false; 873 //need to check to see if intNum has +/- at the front 874 875 strTemp = new String(intNum); 876 877 if(strTemp.indexOf('-') != -1){ 878 isNeg = true; 879 //remove the '-' for now 880 strTemp = strTemp.substr(1,strTemp.length); 881 } 882 883 //check for .5 (used in TimeZone parsing) 884 if(strTemp.indexOf('.') != -1){ 885 strTemp.replace('.',''); 886 decimalToPad = strTemp.substr(strTemp.indexOf('.')+1,strTemp.length); 887 strTemp = strTemp.substr(0,strTemp.indexOf('.')); 888 } 889 890 intLen = strTemp.length; 891 892 if (intLen > intNumDigits){ 893 WriteToDebug("Length of string is greater than num digits, trimming string"); 894 strTemp = strTemp.substr(0,intNumDigits); 895 } 896 else{ 897 for (i=intLen; i<intNumDigits; i++){ 898 strTemp = "0" + strTemp; 899 } 900 } 901 902 if(isNeg==true){ 903 //add the '-' back if needed 904 strTemp = '-'+strTemp; 905 } 906 907 if(decimalToPad!=null && decimalToPad!=''){ 908 if(decimalToPad.length==1){ 909 strTemp += ':'+decimalToPad+'0'; 910 }else{ 911 strTemp += ':'+decimalToPad; 912 } 913 } 914 915 WriteToDebug("Returning - " + strTemp); 916 return strTemp; 917} 918 919function IsValidDecimalRange(strValue){ 920 WriteToDebug("In IsDecimalRange, strValue=" + strValue); 921 922 var REG_EX_REAL_DELIMITER = "[:]", 923 aryNumericRangeSplit, 924 strMin, 925 strMax; 926 927 strValue = new String(strValue); 928 929 aryNumericRangeSplit = strValue.split(REG_EX_REAL_DELIMITER); 930 931 if (aryNumericRangeSplit.length === 2){ 932 //data is in min[:]max notation. Let's check it.... 933 strMin = Trim(aryNumericRangeSplit[0]); 934 strMax = Trim(aryNumericRangeSplit[1]); 935 936 //It's valid to have N[:]N, N[:]M, N[:], [:]N, [:] so we only check if the part is a valid decimal if it's been supplied. 937 if (strMin.length > 0 && !IsValidDecimal(strMin)) 938 { 939 WriteToDebug("Returning False - min value supplied range is not a valid decimal, min=" + strMin); 940 return false; 941 } 942 if (strMax.length > 0 && !IsValidDecimal(strMax)) 943 { 944 WriteToDebug("Returning False - max value supplied for range is not a valid decimal, max=" + strMax); 945 return false;
946 } 947 //min cannot be greater than max. It can be the same however.... 948 if (strMin.length > 0 && strMax.length > 0 && parseFloat(strMin) > parseFloat(strMax)) 949 { 950 WriteToDebug("Returning False - min value supplied for range is greater than the max, min=" + strMin + ", max=" + strMax); 951 return false; 952 } 953 return true; 954 955 } 956 957 WriteToDebug("Returning false - string supplied for range has incorrect number of parts, parts=" + aryNumericRangeSplit.length + ", strValue=" +strValue); 958 return false; 959} 960 961 962//This function assumes that the strValue parameter that is the numeric range has been previously 963//checked for validity using IsValidDecimalRange() prior to calling this function. 964 965 966//The scenarios for conversion are described below (format is <min>[:]<max>)..... 967 968//If the learner's response was correct return their response. This covers scenarios: 969//N[:]N + correct -> learner response 970//N[:]M + correct -> learner response 971//N[:] + correct -> learner response 972//[:]N + correct -> learner response 973//[:] + correct -> learner response 974 975//If <min> is present and the learner was incorrect, then return the min. This covers scenarios: 976//N[:]N + incorrect -> N 977//N[:]M + incorrect -> N 978//N[:] + incorrect -> N 979 980//If <max> is present and learner was incorrect, then return the max. This covers scenarios: 981//[:]N + incorrect -> N 982 983//[:] + incorrect -> this should not be possible as [:] indicates any number is correct and the learner response should 984//have been checked for being a decimal. Return null here. Calling methods should check for null and act accordingly 985function ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strValue, strLearnerResponse, blnCorrect) 986{ 987 WriteToDebug("In ConvertDecimalRangeToDecimalBasedOnLearnerResponse strValue=" + strValue + ",strLearnerResponse=" + strLearnerResponse + ",blnCorrect=" + blnCorrect); 988 989 var REG_EX_REAL_DELIMITER = "[:]", 990 aryNumericRangeSplit, 991 strMin, 992 strMax; 993 994 //If it's indicated the learner was correct then just return their answer. 995 if (blnCorrect) { 996 WriteToDebug("Returning strLearnerResponse"); 997 return strLearnerResponse; 998 } 999 1000 strValue = new String(strValue); 1001 aryNumericRangeSplit = strValue.split(REG_EX_REAL_DELIMITER); 1002 1003 if (aryNumericRangeSplit.length === 2) { 1004 strMin = Trim(aryNumericRangeSplit[0]); 1005 strMax = Trim(aryNumericRangeSplit[1]); 1006 1007 //if min is present 1008 if (strMin.length > 0) { 1009 WriteToDebug("Returning strMin"); 1010 return strMin; 1011 } 1012 //if max is present 1013 else if (strMax.length > 0) { 1014 WriteToDebug("Returning strMax"); 1015 return strMax; 1016 } 1017 } 1018 1019 //[:] + incorrect (plus if aryNumberRangeSplit doesn't contain 2 parts, 1020 //which it never should if range is checked for validity prior to this function) 1021 WriteToDebug("Returning null"); 1022 return null; 1023} 1024 1025 1026function IsValidDecimal(strValue){ 1027 1028 WriteToDebug("In IsValidDecimal, strValue=" + strValue); 1029 1030 strValue = new String(strValue); 1031 1032 //check for characters "0-9", ".", and "-" only 1033 if (strValue.search(/[^.\d-]/) > -1){ 1034 WriteToDebug("Returning False - character other than a digit, dash or period found"); 1035 return false; 1036 } 1037 1038 //if contains a dash, ensure it is first and that there is only 1 1039 if (strValue.search("-") > -1){ 1040 if (strValue.indexOf("-", 1) > -1){ 1041 WriteToDebug("Returning False - dash found in the middle of the string"); 1042 return false; 1043 } 1044 } 1045 1046 //ensure only 1 decimal point 1047 if (strValue.indexOf(".") != strValue.lastIndexOf(".")){ 1048 WriteToDebug("Returning False - more than one decimal point found"); 1049 return false; 1050 } 1051 1052 //ensure there is at least 1 digit 1053 if (strValue.search(/\d/) < 0){ 1054 WriteToDebug("Returning False - no digits found"); 1055 return false; 1056 } 1057 1058 WriteToDebug("Returning True"); 1059 return true; 1060 1061} 1062 1063 1064function IsAlphaNumeric(strValue){ 1065 WriteToDebug("In IsAlphaNumeric"); 1066 if (strValue.search(/\w/) < 0){ 1067 WriteToDebug("Returning false"); 1068 return false;
1069 } 1070 else{ 1071 WriteToDebug("Returning true"); 1072 return true; 1073 } 1074} 1075 1076 1077function ReverseNameSequence(strName) 1078{ 1079 var strFirstName; 1080 var strLastName; 1081 var intCommaLoc; 1082 1083 1084 //debug 1085 if (strName=="") strName="Not Found, Learner Name"; 1086 1087 intCommaLoc = strName.indexOf(","); 1088 strFirstName = strName.slice(intCommaLoc+1); 1089 strLastName = strName.slice(0, intCommaLoc); 1090 1091 strFirstName = Trim(strFirstName); 1092 strLastName = Trim(strLastName); 1093 1094 1095 1096 return strFirstName + ' ' + strLastName; 1097} 1098 1099 1100function LTrim(str) { // remove leading spaces 1101 str = new String(str); 1102 return(str.replace(/^\s+/, '')); 1103 1104} 1105 1106function RTrim(str) { // remove trailing spaces 1107 str = new String(str); 1108 return(str.replace(/\s+$/, '')); 1109} 1110 1111function Trim(strToTrim) { 1112 var str = LTrim(RTrim(strToTrim)); // remove leading and trailing spaces 1113 return (str.replace(/\s{2,}/g," ")); // replace multiple spaces with single spaces 1114} 1115 1116 1117 1118 1119function GetValueFromDataChunk(strID) 1120{ 1121 var strChunk = new String(GetDataChunk()); 1122 var aryPairs = new Array(); 1123 var aryValues = new Array(); 1124 var i; 1125 1126 aryPairs = strChunk.split(parent.DATA_CHUNK_PAIR_SEPARATOR); 1127 1128 for (i=0;i<aryPairs.length;i++) 1129 { 1130 aryValues = aryPairs[i].split(parent.DATA_CHUNK_VALUE_SEPARATOR); 1131 if (aryValues[0]==strID) return aryValues[1]; 1132 } 1133 1134 return ''; 1135} 1136 1137 1138function SetDataChunkValue(strID, strValue) 1139{ 1140 var strChunk = new String(GetDataChunk()); 1141 var aryPairs = new Array(); 1142 var aryValues = new Array(); 1143 var i; 1144 var blnFound = new Boolean(false); 1145 1146 1147 1148 aryPairs = strChunk.split(parent.DATA_CHUNK_PAIR_SEPARATOR); 1149 1150 for (i=0;i<aryPairs.length;i++) 1151 { 1152 aryValues = aryPairs[i].split(parent.DATA_CHUNK_VALUE_SEPARATOR); 1153 if (aryValues[0]==strID) 1154 { 1155 aryValues[1] = strValue; 1156 blnFound = true; 1157 aryPairs[i] = aryValues[0] + parent.DATA_CHUNK_VALUE_SEPARATOR + aryValues[1]; 1158 } 1159 } 1160 1161 1162 if (blnFound==true) 1163 { 1164 strChunk = aryPairs.join(parent.DATA_CHUNK_PAIR_SEPARATOR); 1165 } 1166 else 1167 { 1168 if (strChunk=='') 1169 { 1170 //if data chunk is empty, just add this value 1171 strChunk = strID + parent.DATA_CHUNK_VALUE_SEPARATOR + strValue; 1172 } 1173 else 1174 { 1175 //otherwise, append 1176 strChunk += parent.DATA_CHUNK_PAIR_SEPARATOR + strID + parent.DATA_CHUNK_VALUE_SEPARATOR + strValue; 1177 } 1178 } 1179 1180 SetDataChunk(strChunk); 1181 1182 return true; 1183} 1184 1185 1186function GetLastDirAndPageName(str) 1187{ 1188 var page = new String(str); 1189 var LastSlashLocation = page.lastIndexOf("/"); 1190 var SecondLastSlashLocation = page.lastIndexOf("/", LastSlashLocation-1); 1191 return page.substr(SecondLastSlashLocation+1); 1192 1193} 1194 1195 1196function RoundToPrecision(number, significantDigits){ 1197 1198 number = parseFloat(number); 1199 return (Math.round(number * Math.pow(10, significantDigits)) / Math.pow(10, significantDigits)) 1200} 1201 1202function IsAbsoluteUrl(urlStr) { 1203 return urlStr != null && (urlStr.indexOf("http://") == 0 || urlStr.indexOf("https://") == 0) 1204} 1205 1206function TouchCloud() { 1207 if(APPID != null && APPID != "" && APPID != "__APPID__" && CLOUDURL !== null && CLOUDURL.indexOf("http") === 0){ 1208 var cloudForm=document.createElement("form"); 1209 cloudForm.name = "cloudform"; 1210 cloudForm.id = "cloudform"; 1211 cloudForm.style = "display:none;"; 1212 1213 document.body.appendChild(cloudForm); 1214 1215 var elAppId = document.createElement("input"); 1216 elAppId.name = "appId"; 1217 elAppId.value = APPID; 1218 elAppId.type = "hidden"; 1219 cloudForm.appendChild(elAppId); 1220 1221 var elUrl = document.createElement("input"); 1222 elUrl.name = "servingUrl"; 1223 elUrl.type = "hidden"; 1224 elUrl.value = document.location.href; 1225 cloudForm.appendChild(elUrl); 1226 1227 var elVersion = document.createElement("input"); 1228 elVersion.name = "version"; 1229 elVersion.type = "hidden"; 1230 elVersion.value = VERSION; 1231 cloudForm.appendChild(elVersion); 1232 1233 cloudForm.target = "rusticisoftware_aicc_results"; //keep this name fairly unique to avoid a potential naming conflict with LMS frames 1234 cloudForm.action = CLOUDURL; 1235 document.getElementById('cloudform').submit(); 1236 return true; 1237 1238 }else{ 1239 return false;
1240 } 1241} 1242 1243function IsNumeric(n) { 1244 return !isNaN(parseFloat(n)) && isFinite(n); 1245} 1246 1247function loadScript(url, callback) { 1248 // add the script tag to the head 1249 var head = document.getElementsByTagName('head')[0], 1250 script = document.createElement('script'); 1251 script.type = 'text/javascript'; 1252 script.src = url; 1253 1254 // 1255 // this is a beast of browser compatibility woes, so some of this 1256 // is gleaned from looking at the source for YUI's Y.Get.Transaction 1257 // and based on IE < 9 not handling .onload as seen here: 1258 // 1259 // http://msdn.microsoft.com/en-us/library/ie/hh180173(v=vs.85).aspx 1260 // 1261 // and based on this table: 1262 // 1263 // http://pieisgood.org/test/script-link-events/ 1264 // 1265 // which was generated when creating Y.Get for YUI and linked from MDN here: 1266 // 1267 // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#See_also 1268 // 1269 if (! script.addEventListener || (document.documentMode && document.documentMode < 9)) { 1270 script.onreadystatechange = function () { 1271 if (/loaded|complete/.test(script.readyState)) { 1272 script.onreadystatechange = null; 1273 callback(); 1274 } 1275 }; 1276 } 1277 else { 1278 script.addEventListener("load", callback, false); 1279 } 1280 1281 // fire the loading 1282 head.appendChild(script); 1283} 1284var STANDARD = 'SCORM2004'; 1285 1286var SCORM2004_LOGOUT = "logout"; 1287var SCORM2004_SUSPEND = "suspend"; 1288var SCORM2004_NORMAL_EXIT = "normal"; 1289var SCORM2004_TIMEOUT = "time-out"; 1290 1291var SCORM2004_PASSED = "passed"; 1292var SCORM2004_FAILED = "failed"; 1293var SCORM2004_UNKNOWN = "unknown"; 1294 1295var SCORM2004_COMPLETED = "completed"; 1296var SCORM2004_INCOMPLETE = "incomplete"; 1297var SCORM2004_NOT_ATTEMPTED = "not attempted"; 1298 1299//var SCORM2004_BROWSED = "browsed"; 1300 1301var SCORM2004_CREDIT = "credit"; 1302var SCORM2004_NO_CREDIT = "no-credit"; 1303 1304var SCORM2004_BROWSE = "browse"; 1305var SCORM2004_NORMAL = "normal"; 1306var SCORM2004_REVIEW = "review"; 1307 1308var SCORM2004_ENTRY_ABINITIO = "ab-initio"; 1309var SCORM2004_ENTRY_RESUME = "resume"; 1310var SCORM2004_ENTRY_NORMAL = ""; 1311 1312var SCORM2004_TLA_EXIT_MESSAGE = "exit,message"; 1313var SCORM2004_TLA_EXIT_NO_MESSAGE = "exit,no message"; 1314var SCORM2004_TLA_CONTINUE_MESSAGE = "continue,message"; 1315var SCORM2004_TLA_CONTINUE_NO_MESSAGE = "continue,no message"; 1316 1317 1318var SCORM2004_RESULT_CORRECT = "correct"; 1319var SCORM2004_RESULT_WRONG = "incorrect"; 1320var SCORM2004_RESULT_UNANTICIPATED = "unanticipated"; 1321var SCORM2004_RESULT_NEUTRAL = "neutral"; 1322 1323var SCORM2004_INTERACTION_TYPE_TRUE_FALSE = "true-false"; 1324var SCORM2004_INTERACTION_TYPE_CHOICE = "choice"; 1325var SCORM2004_INTERACTION_TYPE_FILL_IN = "fill-in"; 1326var SCORM2004_INTERACTION_TYPE_LONG_FILL_IN = "long-fill-in"; 1327var SCORM2004_INTERACTION_TYPE_MATCHING = "matching"; 1328var SCORM2004_INTERACTION_TYPE_PERFORMANCE = "performance"; 1329var SCORM2004_INTERACTION_TYPE_SEQUENCING = "sequencing"; 1330var SCORM2004_INTERACTION_TYPE_LIKERT = "likert"; 1331var SCORM2004_INTERACTION_TYPE_NUMERIC = "numeric"; 1332 1333 1334var SCORM2004_NO_ERROR = "0"; 1335var SCORM2004_ERROR_INVALID_PREFERENCE = "-1"; 1336var SCORM2004_ERROR_INVALID_STATUS = "-2"; 1337var SCORM2004_ERROR_INVALID_SPEED = "-3"; 1338var SCORM2004_ERROR_INVALID_TIMESPAN = "-4"; 1339var SCORM2004_ERROR_INVALID_TIME_LIMIT_ACTION = "-5"; 1340var SCORM2004_ERROR_INVALID_DECIMAL = "-6"; 1341var SCORM2004_ERROR_INVALID_CREDIT = "-7"; 1342var SCORM2004_ERROR_INVALID_LESSON_MODE = "-8"; 1343var SCORM2004_ERROR_INVALID_ENTRY = "-9"; 1344 1345var SCORM2004_TRUE = "true"; 1346var SCORM2004_FALSE = "false"; 1347 1348var SCORM2004_EARLIEST_DATE = new Date("1/1/1900"); 1349 1350 1351 1352 1353var intSCORM2004Error = SCORM2004_NO_ERROR; 1354var strSCORM2004ErrorString = ""; 1355var strSCORM2004ErrorDiagnostic = ""; 1356 1357var SCORM2004_objAPI = null; 1358 1359var blnReviewModeSoReadOnly = false; 1360 1361var blnSCORM2004_SSP_Is_Supported = null; 1362 1363function SCORM2004_Initialize(){ 1364 1365 WriteToDebug("In SCORM2004_Initialize"); 1366 1367 var blnResult = true; 1368 1369 SCORM2004_ClearErrorInfo(); 1370 1371 WriteToDebug("Grabbing API"); 1372 1373 try{ 1374 SCORM2004_objAPI = SCORM2004_GrabAPI(); 1375 } 1376 catch (e){ 1377 WriteToDebug("Error grabbing 1.2 API-" + e.name + ":" + e.message); 1378 } 1379 1380 if (typeof(SCORM2004_objAPI) == "undefined" || SCORM2004_objAPI == null){ 1381 WriteToDebug("Unable to acquire SCORM API:")
1382 WriteToDebug("SCORM2004_objAPI=" + typeof(SCORM2004_objAPI)); 1383 1384 InitializeExecuted(false, "Error - unable to acquire LMS API, content may not play properly and results may not be recorded. Please contact technical support."); 1385 return false; 1386 } 1387 1388 WriteToDebug("Calling LMSInit"); 1389 1390 blnResult = SCORM2004_CallInitialize(); 1391 1392 if (! blnResult){ 1393 WriteToDebug("ERROR Initializing LMS"); 1394 1395 InitializeExecuted(false, "Error initializing communications with LMS"); 1396 1397 return false; 1398 } 1399 1400 1401 1402 if (! PREVENT_STATUS_CHANGE_DURING_INIT) { 1403 //only set the status to incomplete if it's not attempted yet 1404 if (SCORM2004_GetStatus() == LESSON_STATUS_NOT_ATTEMPTED){ 1405 WriteToDebug("Setting Status to Incomplete"); 1406 blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_INCOMPLETE); 1407 } 1408 } 1409 1410 1411 //we want to set the exit type to suspend immediately because some LMS's only store data if they get a suspend request 1412 blnResult = SCORM2004_CallSetValue("cmi.exit", SCORM2004_TranslateExitTypeToSCORM(DEFAULT_EXIT_TYPE)) && blnResult; 1413 1414 //if mode is review, check if we should go to read only mode 1415 if (SCORM2004_GetLessonMode() == MODE_REVIEW){ 1416 if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){ 1417 blnReviewModeSoReadOnly = true; 1418 } 1419 } 1420 1421 WriteToDebug("Calling InitializeExecuted with parameter-" + blnResult); 1422 1423 InitializeExecuted(blnResult, ""); 1424 1425 return; 1426 1427} 1428 1429 1430 1431 1432function SCORM2004_Finish(strExitType, blnStatusWasSet){ 1433 1434 WriteToDebug("In SCORM2004_Finish strExitType=" + strExitType + ", blnStatusWasSet=" + blnStatusWasSet); 1435 1436 var strStatusAfterCompletion; 1437 var blnResult = true; 1438 1439 SCORM2004_ClearErrorInfo(); 1440 1441 if ( (strExitType == EXIT_TYPE_FINISH) && ! blnStatusWasSet ){ 1442 1443 WriteToDebug("Getting completion status"); 1444 1445 strStatusAfterCompletion = SCORM2004_GetCompletionStatus(); 1446 1447 WriteToDebug("Setting completion status to " + strStatusAfterCompletion); 1448 1449 blnResult = SCORM2004_CallSetValue("cmi.completion_status", strStatusAfterCompletion) && blnResult; 1450 1451 } 1452 1453 if( strExitType == EXIT_TYPE_SUSPEND && USE_2004_SUSPENDALL_NAVREQ ) { 1454 WriteToDebug("Setting adl.nav.request to suspendAll"); 1455 blnResult = SCORM2004_CallSetValue("adl.nav.request","suspendAll"); 1456 } 1457 1458 WriteToDebug("Setting Exit"); 1459 1460 blnResult = SCORM2004_CallSetValue("cmi.exit", SCORM2004_TranslateExitTypeToSCORM(strExitType)) && blnResult; 1461 1462 WriteToDebug("Calling Commit"); 1463 1464 blnResult = SCORM2004_CallCommit() && blnResult; 1465 1466 WriteToDebug("Calling Finish"); 1467 1468 blnResult = SCORM2004_CallTerminate() && blnResult; 1469 1470 WriteToDebug("Returning " + blnResult); 1471 1472 return blnResult; 1473 1474} 1475 1476 1477function SCORM2004_CommitData(){ 1478 1479 WriteToDebug("In SCORM2004_CommitData"); 1480 SCORM2004_ClearErrorInfo(); 1481 return SCORM2004_CallCommit(); 1482 1483} 1484 1485 1486 1487//--------------------------------------------------------------------------------- 1488//General Get and Set Values 1489 1490function SCORM2004_GetStudentID(){ 1491 1492 WriteToDebug("In SCORM2004_GetStudentID"); 1493 SCORM2004_ClearErrorInfo(); 1494 return SCORM2004_CallGetValue("cmi.learner_id"); 1495 1496} 1497 1498function SCORM2004_GetStudentName(){ 1499 1500 WriteToDebug("In SCORM2004_GetStudentName"); 1501 SCORM2004_ClearErrorInfo(); 1502 return SCORM2004_CallGetValue("cmi.learner_name"); 1503 1504} 1505 1506function SCORM2004_GetBookmark(){ 1507 WriteToDebug("In SCORM2004_GetBookmark"); 1508 SCORM2004_ClearErrorInfo(); 1509 return SCORM2004_CallGetValue("cmi.location"); 1510 1511} 1512 1513function SCORM2004_SetBookmark(strBookmark){ 1514 WriteToDebug("In SCORM2004_SetBookmark strBookmark=" + strBookmark); 1515 SCORM2004_ClearErrorInfo(); 1516 return SCORM2004_CallSetValue("cmi.location", strBookmark); 1517} 1518 1519function SCORM2004_GetDataChunk(){ 1520 WriteToDebug("In SCORM2004_GetDataChunk"); 1521 SCORM2004_ClearErrorInfo(); 1522 return SCORM2004_CallGetValue("cmi.suspend_data"); 1523} 1524 1525function SCORM2004_SetDataChunk(strData){ 1526 // 4000 characters for 2nd edition 1527 // 64000 characters here for 3rd edition 1528 WriteToDebug("In SCORM2004_SetDataChunk"); 1529 SCORM2004_ClearErrorInfo(); 1530 1531 if(USE_STRICT_SUSPEND_DATA_LIMITS==true) { 1532 if(strData.length > 4000) { 1533 WriteToDebug("SCORM2004_SetDataChunk - suspend_data too large for SCORM 2004 2nd ed (4000 character limit) but will try to persist anyway."); 1534 if(strData.length > 64000) { 1535 WriteToDebug("SCORM2004_SetDataChunk - suspend_data too large for SCORM 2004 3rd & 4th ed (64000 character limit) so failing to persist."); 1536 return false;
1537 }else{ 1538 //value is between 4000 and 64000 so we can attempt to persist 1539 return SCORM2004_CallSetValue("cmi.suspend_data", strData); 1540 } 1541 }else{ 1542 //length is less than 4000 so it should be fine for all standards 1543 return SCORM2004_CallSetValue("cmi.suspend_data", strData); 1544 } 1545 }else{ 1546 //don't check any lengths and just send it to the API 1547 return SCORM2004_CallSetValue("cmi.suspend_data", strData); 1548 } 1549 1550} 1551 1552function SCORM2004_GetLaunchData(){ 1553 WriteToDebug("In SCORM2004_GetLaunchData"); 1554 SCORM2004_ClearErrorInfo(); 1555 return SCORM2004_CallGetValue("cmi.launch_data"); 1556} 1557 1558function SCORM2004_GetComments(){ 1559 1560 WriteToDebug("In SCORM2004_GetComments"); 1561 1562 SCORM2004_ClearErrorInfo(); 1563 1564 var intCommentCount; 1565 var strComments = ""; 1566 1567 intCommentCount = SCORM2004_CallGetValue("cmi.comments_from_learner._count"); 1568 1569 for (var i=0; i < intCommentCount; i++){ 1570 1571 if (strComments.length > 0){ 1572 strComments += " | "; 1573 } 1574 1575 strComments += SCORM2004_CallGetValue("cmi.comments_from_learner." + i + ".comment"); 1576 } 1577 1578 return strComments; 1579 1580} 1581 1582function SCORM2004_WriteComment(strComment){ 1583 1584 WriteToDebug("In SCORM2004_WriteComment strComment=" + strComment); 1585 1586 var intCurrentIndex; 1587 var blnResult; 1588 1589 SCORM2004_ClearErrorInfo(); 1590 1591 //remove the "|" since we can use the comments collection 1592 if (strComment.search(/ \| /) == 0){ 1593 strComment = strComment.substr(3); 1594 } 1595 1596 //remove encoding of "|" 1597 strComment.replace(/\|\|/g, "|") 1598 1599 intCurrentIndex = SCORM2004_CallGetValue("cmi.comments_from_learner._count"); 1600 1601 blnResult = SCORM2004_CallSetValue("cmi.comments_from_learner." + intCurrentIndex + ".comment", strComment); 1602 blnResult = SCORM2004_CallSetValue("cmi.comments_from_learner." + intCurrentIndex + ".timestamp", ConvertDateToIso8601TimeStamp(new Date())) && blnResult; 1603 1604 return blnResult; 1605} 1606 1607 1608function SCORM2004_GetLMSComments(){ 1609 1610 WriteToDebug("In SCORM2004_GetLMSComments"); 1611 1612 SCORM2004_ClearErrorInfo(); 1613 1614 var intCommentCount; 1615 var strComments = ""; 1616 1617 intCommentCount = SCORM2004_CallGetValue("cmi.comments_from_lms._count"); 1618 1619 for (var i=0; i < intCommentCount; i++){ 1620 1621 if (strComments.length > 0){ 1622 strComments += " \r\n"; 1623 } 1624 1625 strComments += SCORM2004_CallGetValue("cmi.comments_from_lms." + i + ".comment"); 1626 } 1627 1628 return strComments; 1629} 1630 1631 1632function SCORM2004_GetAudioPlayPreference(){ 1633 1634 var intTempPreference; 1635 1636 WriteToDebug("In SCORM2004_GetAudioPlayPreference"); 1637 1638 SCORM2004_ClearErrorInfo(); 1639 1640 intTempPreference = SCORM2004_CallGetValue("cmi.learner_preference.audio_level"); 1641 1642 if (intTempPreference == ""){ 1643 intTempPreference = 0; 1644 } 1645 1646 intTempPreference = parseInt(intTempPreference, 10); 1647 1648 WriteToDebug("intTempPreference=" + intTempPreference); 1649 1650 if (intTempPreference > 0){ 1651 WriteToDebug("Returning On"); 1652 return PREFERENCE_ON; 1653 } 1654 else if (intTempPreference <= 0) { 1655 WriteToDebug("Returning Off"); 1656 return PREFERENCE_OFF; 1657 } 1658 else{ 1659 WriteToDebug("Error: Invalid preference"); 1660 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_PREFERENCE, 1661 "Invalid audio preference received from LMS", 1662 "intTempPreference=" + intTempPreference); 1663 return null; 1664 } 1665} 1666 1667 1668 1669function SCORM2004_GetAudioVolumePreference(){ 1670 1671 var intTempPreference; 1672 1673 WriteToDebug("In SCORM2004_GetAudioVollumePreference"); 1674 1675 SCORM2004_ClearErrorInfo(); 1676 1677 intTempPreference = SCORM2004_CallGetValue("cmi.learner_preference.audio_level"); 1678 1679 WriteToDebug("intTempPreference=" + intTempPreference); 1680 1681 if (intTempPreference == "") { 1682 intTempPreference = 100; 1683 } 1684 1685 intTempPreference = parseInt(intTempPreference, 10); 1686 1687 if (intTempPreference <= 0){ 1688 WriteToDebug("Setting to 100"); 1689 intTempPreference = 100; 1690 } 1691 1692 if (! (intTempPreference > 0 && intTempPreference <= 100)){
1693 WriteToDebug("ERROR: invalid preference"); 1694 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_PREFERENCE, 1695 "Invalid audio preference received from LMS", 1696 "intTempPreference=" + intTempPreference); 1697 return null; 1698 } 1699 1700 WriteToDebug("Returning " + intTempPreference); 1701 return intTempPreference; 1702 1703} 1704 1705function SCORM2004_SetAudioPreference(PlayPreference, intPercentOfMaxVolume){ 1706 1707 WriteToDebug("In SCORM2004_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume); 1708 1709 SCORM2004_ClearErrorInfo(); 1710 1711 if (PlayPreference == PREFERENCE_OFF){ 1712 WriteToDebug("Setting percent to 0"); 1713 intPercentOfMaxVolume = 0; 1714 } 1715 1716 return SCORM2004_CallSetValue("cmi.learner_preference.audio_level", intPercentOfMaxVolume); 1717} 1718 1719 1720 1721function SCORM2004_SetLanguagePreference(strLanguage){ 1722 WriteToDebug("In SCORM2004_SetLanguagePreference strLanguage=" + strLanguage); 1723 SCORM2004_ClearErrorInfo(); 1724 return SCORM2004_CallSetValue("cmi.learner_preference.language", strLanguage); 1725 1726} 1727 1728 1729function SCORM2004_GetLanguagePreference(){ 1730 WriteToDebug("In SCORM2004_GetLanguagePreference"); 1731 SCORM2004_ClearErrorInfo(); 1732 return SCORM2004_CallGetValue("cmi.learner_preference.language"); 1733 1734} 1735 1736 1737function SCORM2004_SetSpeedPreference(intPercentOfMax){ 1738 1739 //SCORM 2004's scale is greater than 0, our range is 1 to 100, just store our range 1740 1741 WriteToDebug("In SCORM2004_SetSpeedPreference intPercentOfMax=" + intPercentOfMax); 1742 1743 SCORM2004_ClearErrorInfo(); 1744 1745 return SCORM2004_CallSetValue("cmi.learner_preference.delivery_speed", intPercentOfMax); 1746 1747} 1748 1749 1750function SCORM2004_GetSpeedPreference(){ 1751 1752 var intSCORMSpeed; 1753 var intPercentOfMax; 1754 1755 WriteToDebug("In SCORM2004_GetSpeedPreference"); 1756 1757 SCORM2004_ClearErrorInfo(); 1758 1759 intSCORMSpeed = SCORM2004_CallGetValue("cmi.learner_preference.delivery_speed"); 1760 1761 WriteToDebug("intSCORMSpeed=" + intSCORMSpeed); 1762 1763 if (intSCORMSpeed == ""){ 1764 WriteToDebug("Detected empty string, defaulting to 100"); 1765 intSCORMSpeed = 100; 1766 } 1767 1768 intSCORMSpeed = parseInt(intSCORMSpeed, 10); 1769 1770 if (intSCORMSpeed < 0){ 1771 WriteToDebug("ERROR - out of range"); 1772 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_SPEED, 1773 "Invalid speed preference received from LMS - out of range", 1774 "intSCORMSpeed=" + intSCORMSpeed); 1775 return null; 1776 } 1777 1778 WriteToDebug("intSCORMSpeed " + intSCORMSpeed); 1779 1780 return intSCORMSpeed; 1781 1782} 1783 1784 1785 1786 1787function SCORM2004_SetTextPreference(intPreference){ 1788 1789 WriteToDebug("In SCORM2004_SetTextPreference intPreference=" + intPreference); 1790 1791 SCORM2004_ClearErrorInfo(); 1792 1793 return SCORM2004_CallSetValue("cmi.learner_preference.audio_captioning", intPreference); 1794 1795} 1796 1797 1798function SCORM2004_GetTextPreference(){ 1799 1800 var intTempPreference; 1801 1802 WriteToDebug("In SCORM2004_GetTextPreference"); 1803 1804 SCORM2004_ClearErrorInfo(); 1805 1806 intTempPreference = SCORM2004_CallGetValue("cmi.learner_preference.audio_captioning"); 1807 intTempPreference = parseInt(intTempPreference, 10); 1808 1809 WriteToDebug("intTempPreference=" + intTempPreference); 1810 1811 if (intTempPreference > 0){ 1812 WriteToDebug("Returning On"); 1813 return PREFERENCE_ON; 1814 } 1815 else if (intTempPreference == 0 || intTempPreference == ""){ 1816 WriteToDebug("Returning Default"); 1817 return PREFERENCE_DEFAULT; 1818 } 1819 else if (intTempPreference < 0) { 1820 WriteToDebug("Returning Off"); 1821 return PREFERENCE_OFF; 1822 } 1823 else{ 1824 WriteToDebug("Error: Invalid preference"); 1825 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_PREFERENCE, 1826 "Invalid text preference received from LMS", 1827 "intTempPreference=" + intTempPreference); 1828 return null; 1829 } 1830} 1831 1832 1833 1834//--------------------------------------------------------------------------------- 1835//Time Management Functions 1836function SCORM2004_GetPreviouslyAccumulatedTime(){ 1837 1838 var strIso8601Time; 1839 var intMilliseconds; 1840 1841 WriteToDebug("In SCORM2004_GetPreviouslyAccumulatedTime"); 1842 1843 1844 SCORM2004_ClearErrorInfo(); 1845 1846 strIso8601Time = SCORM2004_CallGetValue("cmi.total_time") 1847 1848 WriteToDebug("strIso8601Time=" + strIso8601Time); 1849 1850 if (! IsValidIso8601TimeSpan(strIso8601Time)){ 1851 WriteToDebug("ERROR - Invalid Iso8601Time"); 1852 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_TIMESPAN, 1853 "Invalid timespan received from LMS", 1854 "strTime=" + strIso8601Time); 1855 return null; 1856 } 1857 1858 intMilliseconds = ConvertScorm2004TimeToMS(strIso8601Time); 1859 1860 WriteToDebug("Returning " + intMilliseconds); 1861 1862 return intMilliseconds; 1863 1864} 1865 1866 1867function SCORM2004_SaveTime(intMilliSeconds){ 1868 1869 var strISO8601Time; 1870 1871 WriteToDebug("In SCORM2004_SaveTime intMilliSeconds=" + intMilliSeconds); 1872 1873 SCORM2004_ClearErrorInfo(); 1874 1875 strISO8601Time = ConvertMilliSecondsIntoSCORM2004Time(intMilliSeconds); 1876 1877 WriteToDebug("strISO8601Time=" + strISO8601Time); 1878 1879 return SCORM2004_CallSetValue("cmi.session_time", strISO8601Time); 1880} 1881 1882function SCORM2004_GetMaxTimeAllowed(){ 1883 1884 var strIso8601Time; 1885 var intMilliseconds; 1886 1887 WriteToDebug("In SCORM2004_GetMaxTimeAllowed"); 1888 1889 1890 SCORM2004_ClearErrorInfo(); 1891 1892 strIso8601Time = SCORM2004_CallGetValue("cmi.max_time_allowed") 1893 1894 WriteToDebug("strIso8601Time=" + strIso8601Time); 1895 1896 if (strIso8601Time == ""){ 1897 strIso8601Time = "20Y"; 1898 } 1899 1900 if (! IsValidIso8601TimeSpan(strIso8601Time)){ 1901 WriteToDebug("ERROR - Invalid Iso8601Time"); 1902 SCORM2004_SetErrorInfoManually(SCORM_ERROR_INVALID_TIMESPAN, 1903 "Invalid timespan received from LMS", 1904 "strIso8601Time=" + strIso8601Time); 1905 return null; 1906 } 1907 1908 intMilliseconds = ConvertScorm2004TimeToMS(ConvertScorm2004TimeToMS); 1909 1910 WriteToDebug("intMilliseconds=" + intMilliseconds); 1911 1912 return intMilliseconds; 1913 1914} 1915 1916 1917 1918function SCORM2004_DisplayMessageOnTimeout(){ 1919 1920 var strTLA; 1921 WriteToDebug("In SCORM2004_DisplayMessageOnTimeout"); 1922 1923 SCORM2004_ClearErrorInfo(); 1924 1925 1926 strTLA = SCORM2004_CallGetValue("cmi.time_limit_action"); 1927 1928 WriteToDebug("strTLA=" + strTLA); 1929 1930 if (strTLA == SCORM2004_TLA_EXIT_MESSAGE || strTLA == SCORM2004_TLA_CONTINUE_MESSAGE){ 1931 WriteToDebug("returning true"); 1932 return true; 1933 } 1934 else if(strTLA == SCORM2004_TLA_EXIT_NO_MESSAGE || strTLA == SCORM2004_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){ 1935 WriteToDebug("returning false"); 1936 return false;
1937 } 1938 else{ 1939 WriteToDebug("Error invalid TLA"); 1940 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_TIME_LIMIT_ACTION, 1941 "Invalid time limit action received from LMS", 1942 "strTLA=" + strTLA); 1943 1944 return null; 1945 } 1946 1947} 1948 1949function SCORM2004_ExitOnTimeout(){ 1950 1951 var strTLA; 1952 1953 WriteToDebug("In SCORM2004_ExitOnTimeout"); 1954 1955 SCORM2004_ClearErrorInfo(); 1956 1957 strTLA = SCORM2004_CallGetValue("cmi.time_limit_action"); 1958 1959 WriteToDebug("strTLA=" + strTLA); 1960 1961 if (strTLA == SCORM2004_TLA_EXIT_MESSAGE || strTLA == SCORM2004_TLA_EXIT_NO_MESSAGE){ 1962 WriteToDebug("returning true"); 1963 return true; 1964 } 1965 else if(strTLA == SCORM2004_TLA_CONTINUE_MESSAGE || strTLA == SCORM2004_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){ 1966 WriteToDebug("returning false"); 1967 return false; 1968 } 1969 else{ 1970 WriteToDebug("ERROR invalid TLA"); 1971 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_TIME_LIMIT_ACTION, 1972 "Invalid time limit action received from LMS", 1973 "strTLA=" + strTLA); 1974 1975 return null; 1976 } 1977 1978} 1979 1980 1981function SCORM2004_GetPassingScore(){ 1982 var fltScore; 1983 1984 WriteToDebug("In SCORM2004_GetPassingScore"); 1985 1986 SCORM2004_ClearErrorInfo(); 1987 1988 fltScore = SCORM2004_CallGetValue("cmi.scaled_passing_score") 1989 1990 WriteToDebug("fltScore=" + fltScore); 1991 1992 if (fltScore == ""){ 1993 fltScore = 0; 1994 } 1995 1996 if ( ! IsValidDecimal(fltScore)){ 1997 WriteToDebug("Error - score is not a valid decimal"); 1998 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_DECIMAL, 1999 "Invalid mastery score received from LMS", 2000 "fltScore=" + fltScore); 2001 return null; 2002 } 2003 2004 fltScore = parseFloat(fltScore); 2005 2006 //multiply by 100 to normalize from -1 to 1 to 0-100 - (we always set the score so we know it won't be negative) 2007 fltScore = fltScore * 100; 2008 2009 WriteToDebug("returning fltScore-" + fltScore); 2010 2011 return fltScore; 2012} 2013 2014 2015function SCORM2004_SetScore(intScore, intMaxScore, intMinScore){ 2016 2017 var blnResult; 2018 var fltNormalizedScore; 2019 2020 intScore = RoundToPrecision(intScore, 7); 2021 intMaxScore = RoundToPrecision(intMaxScore, 7); 2022 intMinScore = RoundToPrecision(intMinScore, 7); 2023 2024 WriteToDebug("In SCORM2004_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 2025 2026 SCORM2004_ClearErrorInfo(); 2027 2028 fltNormalizedScore = RoundToPrecision(intScore / 100, 7); 2029 2030 blnResult = SCORM2004_CallSetValue("cmi.score.raw", intScore); 2031 blnResult = SCORM2004_CallSetValue("cmi.score.max", intMaxScore) && blnResult; 2032 blnResult = SCORM2004_CallSetValue("cmi.score.min", intMinScore) && blnResult; 2033 2034 blnResult = SCORM2004_CallSetValue("cmi.score.scaled", fltNormalizedScore) && blnResult; 2035 2036 WriteToDebug("Returning " + blnResult); 2037 2038 return blnResult; 2039} 2040 2041function SCORM2004_GetScore(){ 2042 2043 2044 WriteToDebug("In SCORM2004_GetScore"); 2045 2046 SCORM2004_ClearErrorInfo(); 2047 2048 return SCORM2004_CallGetValue("cmi.score.raw"); 2049 2050} 2051 2052function SCORM2004_GetScaledScore(){ 2053 2054 2055 WriteToDebug("In SCORM2004_GetScaledScore"); 2056 2057 SCORM2004_ClearErrorInfo(); 2058 2059 return SCORM2004_CallGetValue("cmi.score.scaled"); 2060 2061} 2062 2063function SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004InteractionType){ 2064 2065 var blnResult; 2066 var intInteractionIndex; 2067 var strResult; 2068 2069 if (!IsNumeric(blnCorrect)) { 2070 blnCorrect = new String(blnCorrect); //have to cast as string to support false 2071 } 2072 2073 SCORM2004_ClearErrorInfo(); 2074 2075 intInteractionIndex = SCORM2004_CallGetValue("cmi.interactions._count"); 2076 2077 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 2078 2079 if (intInteractionIndex == ""){ 2080 WriteToDebug("Setting Interaction Index to 0"); 2081 intInteractionIndex = 0; 2082 } 2083 2084 //need to leave support for blnCorrect=t/f for legacy implementations of RSECA 2085 if (blnCorrect == true || blnCorrect =="true" || blnCorrect == INTERACTION_RESULT_CORRECT){ 2086 strResult = SCORM2004_RESULT_CORRECT; 2087 } 2088 else if (String(blnCorrect) == "false" || blnCorrect == INTERACTION_RESULT_WRONG){ //compare against the string "false" because ("" == false) evaluates to true 2089 strResult = SCORM2004_RESULT_WRONG; 2090 } 2091 else if (blnCorrect == INTERACTION_RESULT_UNANTICIPATED){ 2092 strResult = SCORM2004_RESULT_UNANTICIPATED; 2093 } 2094 else if (blnCorrect == INTERACTION_RESULT_NEUTRAL){ 2095 strResult = SCORM2004_RESULT_NEUTRAL; 2096 } 2097 else if (IsNumeric(blnCorrect)) { 2098 strResult = blnCorrect; 2099 } 2100 else{ 2101 strResult = ""; 2102 } 2103 2104 WriteToDebug("strResult=" + strResult); 2105 2106 strID = CreateValidIdentifier(strID); 2107 2108 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".id", strID); 2109 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".type", SCORM2004InteractionType) && blnResult; 2110
2111 if (strResponse !== null) { 2112 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".learner_response", strResponse) && blnResult; 2113 } 2114 2115 if (strResult != undefined && strResult != null && strResult != ""){ 2116 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".result", strResult) && blnResult; 2117 } 2118 2119 if (strCorrectResponse != undefined && strCorrectResponse != null && strCorrectResponse != ""){ 2120 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern", strCorrectResponse) && blnResult; 2121 } 2122 2123 if (strDescription != undefined && strDescription != null && strDescription != ""){ 2124 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".description", strDescription) && blnResult; 2125 } 2126 2127 if (intWeighting != undefined && intWeighting != null && intWeighting != ""){ 2128 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".weighting", intWeighting) && blnResult; 2129 } 2130 2131 if (intLatency != undefined && intLatency != null && intLatency != ""){ 2132 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".latency", ConvertMilliSecondsIntoSCORM2004Time(intLatency)) && blnResult; 2133 } 2134 2135 if (strLearningObjectiveID != undefined && strLearningObjectiveID != null && strLearningObjectiveID != ""){ 2136 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".objectives.0.id", strLearningObjectiveID) && blnResult; 2137 } 2138 2139 blnResult = SCORM2004_CallSetValue("cmi.interactions." + intInteractionIndex + ".timestamp", ConvertDateToIso8601TimeStamp(dtmTime)) && blnResult; 2140 2141 WriteToDebug("Returning " + blnResult); 2142 2143 return blnResult; 2144} 2145 2146function SCORM2004_RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2147 2148 WriteToDebug("In SCORM2004_RecordTrueFalseInteraction strID=" + strID + ", strResponse=" + strResponse + 2149 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 2150 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2151 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2152 2153 var strResponse = null; 2154 var strCorrectResponse = null; 2155 2156 if (blnResponse){ 2157 strResponse = "true"; 2158 } 2159 else if (blnResponse !== null) { 2160 strResponse = "false"; 2161 } 2162 2163 if (blnCorrectResponse == true){ 2164 strCorrectResponse = "true"; 2165 } 2166 else if(blnCorrectResponse == false){ //test for false b/c it could be null in which case we want to leave it as null 2167 strCorrectResponse = "false"; 2168 } 2169 2170 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_TRUE_FALSE); 2171} 2172 2173 2174function SCORM2004_RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2175 2176 WriteToDebug("In SCORM2004_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse + 2177 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 2178 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2179 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2180 2181 var strResponse = null; 2182 var strCorrectResponse = ""; 2183 2184 if (aryResponse !== null) { 2185 strResponse = ""; 2186 2187 for (var i=0; i < aryResponse.length; i++){ 2188 2189 if (strResponse.length > 0) {strResponse += "[,]";} 2190 strResponse += aryResponse[i].Long; 2191 } 2192 } 2193 2194 for (var i=0; i < aryCorrectResponse.length; i++){ 2195 2196 if (strCorrectResponse.length > 0) {strCorrectResponse += "[,]";} 2197 strCorrectResponse += aryCorrectResponse[i].Long; 2198 } 2199 2200 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_CHOICE); 2201} 2202 2203 2204function SCORM2004_RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2205 2206 WriteToDebug("In SCORM2004_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse + 2207 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 2208 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2209 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2210 2211 var interactionType; 2212 2213 if (strCorrectResponse == null){ 2214 strCorrectResponse = ""; 2215 } 2216 2217 strCorrectResponse = new String(strCorrectResponse); 2218 2219 if (strCorrectResponse.length > 250 || strResponse.length > 250){ 2220 interactionType = SCORM2004_INTERACTION_TYPE_LONG_FILL_IN; 2221 } 2222 else{ 2223 interactionType = SCORM2004_INTERACTION_TYPE_FILL_IN; 2224 } 2225 2226 if (strCorrectResponse.length > 4000){strCorrectResponse = strCorrectResponse.substr(0, 4000);} 2227 2228 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, interactionType); 2229} 2230 2231function SCORM2004_RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2232 2233 WriteToDebug("In SCORM2004_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 2234 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 2235 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2236 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2237 2238 var strResponse = null; 2239 var strCorrectResponse = ""; 2240 2241 if (aryResponse !== null) { 2242 strResponse = ""; 2243 2244 for (var i=0; i < aryResponse.length; i++){ 2245
2246 if (strResponse.length > 0) {strResponse += "[,]";} 2247 strResponse += aryResponse[i].Source.Long + "[.]" + aryResponse[i].Target.Long; 2248 } 2249 } 2250 2251 for (var i=0; i < aryCorrectResponse.length; i++){ 2252 2253 if (strCorrectResponse.length > 0) {strCorrectResponse += "[,]";} 2254 strCorrectResponse += aryCorrectResponse[i].Source.Long + "[.]" + aryCorrectResponse[i].Target.Long; 2255 } 2256 2257 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_MATCHING); 2258} 2259 2260function SCORM2004_RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2261 2262 WriteToDebug("In SCORM2004_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse + 2263 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 2264 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2265 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2266 2267 if (strResponse !== null) { 2268 strResponse = new String(strResponse); 2269 if (strResponse.length > 250){strResponse = strResponse.substr(0, 250);} 2270 2271 //we're only recording the step answer, not the step name 2272 strResponse = "[.]" + strResponse; 2273 } 2274 2275 if (strCorrectResponse == null){ 2276 strCorrectResponse = ""; 2277 } 2278 2279 strCorrectResponse = new String(strCorrectResponse); 2280 if (strCorrectResponse.length > 250){strCorrectResponse = strCorrectResponse.substr(0, 250);} 2281 2282 //we're only recording the step answer, not the step name 2283 strCorrectResponse = "[.]" + strCorrectResponse; 2284 2285 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_PERFORMANCE); 2286} 2287 2288function SCORM2004_RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2289 2290 WriteToDebug("In SCORM2004_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 2291 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 2292 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2293 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2294 2295 var strResponse = null; 2296 var strCorrectResponse = ""; 2297 2298 if (aryResponse !== null) { 2299 strResponse = ""; 2300 2301 for (var i=0; i < aryResponse.length; i++){ 2302 2303 if (strResponse.length > 0) {strResponse += "[,]";} 2304 strResponse += aryResponse[i].Long; 2305 } 2306 } 2307 2308 for (var i=0; i < aryCorrectResponse.length; i++){ 2309 2310 if (strCorrectResponse.length > 0) {strCorrectResponse += "[,]";} 2311 strCorrectResponse += aryCorrectResponse[i].Long; 2312 } 2313 2314 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_SEQUENCING); 2315} 2316 2317 2318function SCORM2004_RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2319 2320 WriteToDebug("In RecordLikertInteraction strID=" + strID + ", response=" + response + 2321 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 2322 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2323 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2324 2325 var strResponse = null; 2326 var strCorrectResponse = ""; 2327 2328 if (response !== null) { 2329 strResponse = response.Long; 2330 } 2331 if (correctResponse != null){ 2332 strCorrectResponse = correctResponse.Long; 2333 } 2334 2335 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_LIKERT); 2336} 2337 2338function SCORM2004_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 2339 2340 WriteToDebug("In SCORM2004_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse + 2341 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 2342 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 2343 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 2344 2345 2346 if (strCorrectResponse != undefined && strCorrectResponse != null ) { 2347 //SCORM 2004 should use decimal ranges. If we receive a plain decimal we convert it to a range with the same 2348 //min and max values. 2349 if (IsValidDecimal(strCorrectResponse)) { 2350 strCorrectResponse = strCorrectResponse + "[:]" + strCorrectResponse; 2351 WriteToDebug("SCORM2004_RecordNumericInteraction received decimal correct response and converted to range, strCorrectResponse=" + strCorrectResponse); 2352 } 2353 2354 //Check that the range is valid. This checks both correct responses converted from decimals above and also non-decimal correct responses received. 2355 if (!IsValidDecimalRange(strCorrectResponse)) { 2356 WriteToDebug("Returning False - SCORM2004_RecordNumericInteraction received invali
2356d correct response decimal range, strCorrectResponse=" + strCorrectResponse); 2357 return false; 2358 } 2359 2360 //Handle numeric range interaction edge case..... 2361 //If the correct response is [:] and it's been indicated that the answer was incorrect we fail here. We _could_ accept this in SCORM2004 but rejecting it means 2362 //the behavior matches SCORM 1.2 where we reject such an interaction because we are unable to determine what the decimal correct response is (SCORM 1.2 does not accept ranges). 2363 //The interaction doesn't actually make sense. By this point we've ensured the response is a decimal, the correct response is any decimal..... yet it's indicated as incorrect??? 2364 //In order to ensure both standards remain in lockstep with regard to this we call the same function that SCORM 1.2 uses to convert from a range to a decimal and if null is 2365 //returned we error 2366 if(ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strCorrectResponse, strResponse, blnCorrect) === null) { 2367 WriteToDebug("Returning False - SCORM2004_RecordNumericInteraction received invalid correct response decimal range, response and correct indicator, strCorrectResponse=" + 2368 strCorrectResponse + ", strResponse=" + strResponse + ", blnCorrect=" + blnCorrect); 2369 return false; 2370 } 2371 } 2372 2373 return SCORM2004_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM2004_INTERACTION_TYPE_NUMERIC); 2374} 2375 2376 2377 2378function SCORM2004_GetEntryMode(){ 2379 var strEntry; 2380 2381 WriteToDebug("In SCORM2004_GetEntryMode"); 2382 2383 SCORM2004_ClearErrorInfo(); 2384 2385 strEntry = SCORM2004_CallGetValue("cmi.entry"); 2386 2387 WriteToDebug("strEntry=" + strEntry); 2388 2389 if (strEntry == SCORM2004_ENTRY_ABINITIO){ 2390 WriteToDebug("Returning first time"); 2391 return ENTRY_FIRST_TIME; 2392 } 2393 else if (strEntry == SCORM2004_ENTRY_RESUME){ 2394 WriteToDebug("Returning resume"); 2395 return ENTRY_RESUME; 2396 } 2397 else if (strEntry == SCORM2004_ENTRY_NORMAL){ 2398 WriteToDebug("returning normal"); 2399 return ENTRY_REVIEW; 2400 } 2401 else{ 2402 WriteToDebug("ERROR - invalid entry mode"); 2403 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_ENTRY, 2404 "Invalid entry vocab received from LMS", 2405 "strEntry=" + strEntry); 2406 return null; 2407 } 2408 2409} 2410 2411function SCORM2004_GetLessonMode(){ 2412 2413 var strLessonMode; 2414 2415 WriteToDebug("In SCORM2004_GetLessonMode"); 2416 2417 SCORM2004_ClearErrorInfo(); 2418 2419 strLessonMode = SCORM2004_CallGetValue("cmi.mode"); 2420 2421 WriteToDebug("strLessonMode=" + strLessonMode); 2422 2423 if (strLessonMode == SCORM2004_BROWSE){ 2424 WriteToDebug("Returning browse"); 2425 return MODE_BROWSE; 2426 } 2427 else if(strLessonMode == SCORM2004_NORMAL){ 2428 WriteToDebug("returning normal"); 2429 return MODE_NORMAL; 2430 } 2431 else if(strLessonMode == SCORM2004_REVIEW){ 2432 WriteToDebug("Returning Review"); 2433 return MODE_REVIEW; 2434 } 2435 else{ 2436 WriteToDebug("ERROR - invalid lesson mode"); 2437 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_LESSON_MODE, 2438 "Invalid lesson_mode vocab received from LMS", 2439 "strLessonMode=" + strLessonMode); 2440 return null; 2441 } 2442 2443} 2444 2445function SCORM2004_GetTakingForCredit(){ 2446 2447 var strCredit; 2448 2449 WriteToDebug("In SCORM2004_GetTakingForCredit"); 2450 2451 SCORM2004_ClearErrorInfo(); 2452 2453 strCredit = SCORM2004_CallGetValue("cmi.credit"); 2454 2455 WriteToDebug("strCredit=" + strCredit); 2456 2457 if (strCredit == "credit"){ 2458 WriteToDebug("Returning true"); 2459 return true; 2460 } 2461 else if (strCredit == "no-credit"){ 2462 WriteToDebug("Returning false"); 2463 return false; 2464 } 2465 else{ 2466 WriteToDebug("ERROR - invalid credit"); 2467 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_CREDIT, 2468 "Invalid credit vocab received from LMS", 2469 "strCredit=" + strCredit); 2470 return null; 2471 } 2472} 2473 2474 2475 2476function SCORM2004_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){ 2477 2478 var intObjectiveIndex; 2479 var blnResult; 2480 var fltNormalizedScore; 2481 2482 intScore = RoundToPrecision(intScore, 7); 2483 intMaxScore = RoundToPrecision(intMaxScore, 7); 2484 intMinScore = RoundToPrecision(intMinScore, 7); 2485 2486 WriteToDebug("In SCORM2004_SetObjectiveScore, strObejctiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 2487 2488 SCORM2004_ClearErrorInfo(); 2489 2490 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2491 2492 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2493 2494 fltNormalizedScore = RoundToPrecision(intScore / 100, 7); 2495 2496 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID); 2497 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.raw", intScore) && blnResult; 2498 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.max", intMaxScore) && blnResult; 2499 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.min", intMinScore) && blnResult; 2500 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".score.scaled", fltNormalizedScore) && blnResult; 2501 2502 WriteToDebug("Returning " + blnResult); 2503 2504 return blnResult; 2505 2506} 2507 2508 2509function SCORM2004_SetObjectiveStatus(strObjectiveID, Lesson_Status){ 2510 2511 var intObjectiveIndex; 2512 var blnResult; 2513 var strSCORMSuccessStatus = ""; 2514 var strSCORMCompletionStatus = ""; 2515 2516 WriteToDebug("In SCORM2004_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status); 2517 2518 SCORM2004_ClearErrorInfo(); 2519 2520 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2521 2522 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2523 2524 if (Lesson_Status == LESSON_STATUS_PASSED){ 2525 strSCORMSuccessStatus = SCORM2004_PASSED; 2526 strSCORMCompletionStatus = SCORM2004_COMPLETED; 2527 } 2528 else if (Lesson_Status == LESSON_STATUS_FAILED){ 2529 strSCORMSuccessStatus = SCORM2004_FAILED; 2530 strSCORMCompletionStatus = SCORM2004_COMPLETED; 2531 } 2532 else if (Lesson_Status == LESSON_STATUS_COMPLETED){ 2533 strSCORMSuccessStatus = SCORM2004_UNKNOWN; 2534 strSCORMCompletionStatus = SCORM2004_COMPLETED; 2535 } 2536 else if (Lesson_Status == LESSON_STATUS_BROWSED){ 2537 strSCORMSuccessStatus = SCORM2004_UNKNOWN; 2538 strSCORMCompletionStatus = SCORM2004_COMPLETED; 2539 } 2540 else if (Lesson_Status == LESSON_STATUS_INCOMPLETE){ 2541 strSCORMSuccessStatus = SCORM2004_UNKNOWN; 2542 strSCORMCompletionStatus = SCORM2004_INCOMPLETE; 2543 } 2544 else if (Lesson_Status == LESSON_STATUS_NOT_ATTEMPTED){ 2545 strSCORMSuccessStatus = SCORM2004_UNKNOWN; 2546 strSCORMCompletionStatus = SCORM2004_NOT_ATTEMPTED; 2547 } 2548 2549 WriteToDebug("strSCORMSuccessStatus=" + strSCORMSuccessStatus); 2550 WriteToDebug("strSCORMCompletionStatus=" + strSCORMCompletionStatus); 2551 2552 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID); 2553 2554 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".success_status", strSCORMSuccessStatus) && blnResult; 2555 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".completion_status", strSCORMCompletionStatus) && blnResult; 2556 2557 WriteToDebug("Returning " + blnResult); 2558 2559 return blnResult; 2560} 2561 2562 2563function SCORM2004_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){ 2564 2565 var intObjectiveIndex; 2566 2567 WriteToDebug("In SCORM2004_SetObjectiveDescription strObjectiveID=" + strObjectiveID + ", strObjectiveDescription=" + strObjectiveDescription); 2568 2569 SCORM2004_ClearErrorInfo(); 2570 2571 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2572 2573 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2574 2575 2576 2577 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID); 2578 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".description", strObjectiveDescription) && blnResult; 2579 2580 WriteToDebug("Returning " + blnResult); 2581 2582 return blnResult; 2583} 2584 2585 2586function SCORM2004_GetObjectiveScore(strObjectiveID){ 2587 2588 var intObjectiveIndex; 2589 2590 WriteToDebug("In SCORM2004_GetObjectiveScore, strObejctiveID=" + strObjectiveID); 2591 2592 SCORM2004_ClearErrorInfo(); 2593 2594 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2595 2596 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2597 2598 return SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".score.raw"); 2599 2600} 2601 2602 2603function SCORM2004_GetObjectiveStatus(strObjectiveID){ 2604 2605 var intObjectiveIndex; 2606 var strSuccessStatus; 2607 var strCompletionStatus; 2608 2609 WriteToDebug("In SCORM2004_GetObjectiveStatus, strObejctiveID=" + strObjectiveID); 2610 2611 SCORM2004_ClearErrorInfo(); 2612 2613 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2614 2615 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2616 2617 strSuccessStatus = SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".success_status"); 2618 strCompletionStatus = SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".completion_status"); 2619 2620 if (strSuccessStatus == SCORM2004_PASSED){ 2621 WriteToDebug("returning Passed"); 2622 return LESSON_STATUS_PASSED; 2623 } 2624 else if (strSuccessStatus == SCORM2004_FAILED){ 2625 WriteToDebug("Returning Failed"); 2626 return LESSON_STATUS_FAILED; 2627 } 2628 else if (strCompletionStatus == SCORM2004_COMPLETED){ 2629 WriteToDebug("Returning Completed"); 2630 return LESSON_STATUS_COMPLETED; 2631 } 2632 else if (strCompletionStatus == SCORM2004_INCOMPLETE){ 2633 WriteToDebug("Returning Incomplete"); 2634 return LESSON_STATUS_INCOMPLETE; 2635 } 2636 else if (strCompletionStatus == SCORM2004_NOT_ATTEMPTED || strCompletionStatus == SCORM2004_UNKNOWN || strCompletionStatus == ""){ 2637 WriteToDebug("Returning Not Attempted"); 2638 return LESSON_STATUS_NOT_ATTEMPTED; 2639 } 2640 else{ 2641 WriteToDebug("ERROR - status not found"); 2642 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_STATUS, 2643 "Invalid objective status received from LMS or initial status not yet recorded for objective", 2644 "strCompletionStatus=" + strCompletionStatus); 2645 return null; 2646 2647 } 2648 2649} 2650 2651function SCORM2004_GetObjectiveProgressMeasure(strObjectiveID){ 2652 2653 var strProgressMeasure = SCORM2004_CallGetValue("cmi.objectives." + strObjectiveID + ".progress_measure"); 2654 2655 return strProgressMeasure; 2656 2657} 2658 2659 2660function SCORM2004_GetObjectiveDescription(strObjectiveID){ 2661 2662 var intObjectiveIndex; 2663 var strSuccessStatus; 2664 var strCompletionStatus; 2665 2666 WriteToDebug("In SCORM2004_GetObjectiveDescription, strObejctiveID=" + strObjectiveID); 2667 2668 SCORM2004_ClearErrorInfo(); 2669 2670 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2671 2672 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2673 2674 strDescription = SCORM2004_CallGetValue("cmi.objectives." + intObjectiveIndex + ".description"); 2675 2676 return strDescription; 2677 2678} 2679 2680function SCORM2004_FindObjectiveIndexFromID(strObjectiveID){ 2681 2682 var intCount; 2683 var i; 2684 var strTempID; 2685 2686 WriteToDebug("In SCORM2004_FindObjectiveIndexFromID"); 2687 2688 intCount = SCORM2004_CallGetValue("cmi.objectives._count"); 2689 2690 if (intCount == ""){ 2691 WriteToDebug("Setting intCount=0"); 2692 return 0; 2693 } 2694 2695 intCount = parseInt(intCount, 10); 2696 2697 WriteToDebug("intCount=" + intCount); 2698 2699 for (i=0; i<intCount; i++){ 2700 2701 WriteToDebug("Checking index " + i); 2702 2703 strTempID = SCORM2004_CallGetValue("cmi.objectives." + i + ".id"); 2704 2705 WriteToDebug("ID=" + strTempID); 2706 2707 if (strTempID == strObjectiveID){ 2708 WriteToDebug("Found Matching index"); 2709 return i; 2710 } 2711 } 2712 2713 WriteToDebug("Did not find match, returning count"); 2714 2715 return intCount; 2716} 2717 2718function SCORM2004_CreateValidIdentifier(str) { 2719 // 2720 // some customers don't like the identifiers being turned into URIs and don't 2721 // mind that this means that SCORM content may not be spec compliant, because 2722 // LMSs don't seem to validate (or reject) non-URI identifiers, so allow them
2723 // to use the process Driver used before the 6.x version where URIs became 2724 // properly handled 2725 // 2726 if (USE_LEGACY_IDENTIFIERS_FOR_2004) { 2727 return CreateValidIdentifierLegacy(str); 2728 } 2729 2730 return CreateUriIdentifier(str); 2731} 2732 2733 2734//--------------------------------------------------------------------------------- 2735//Status Management Functions 2736 2737function SCORM2004_SetFailed(){ 2738 2739 WriteToDebug("In SCORM2004_SetFailed"); 2740 2741 var blnResult; 2742 var strCompletionStatus; 2743 2744 SCORM2004_ClearErrorInfo(); 2745 2746 blnResult = SCORM2004_CallSetValue("cmi.success_status", SCORM2004_FAILED); 2747 if (PASS_FAIL_SETS_COMPLETION_FOR_2004) { 2748 blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_COMPLETED) && blnResult; 2749 } else { 2750 // If completion hasn't been set yet, then reset blnStatusWasSet so that SetReachedEnd can set it later. 2751 strCompletionStatus = SCORM2004_CallGetValue("cmi.completion_status"); 2752 if (strCompletionStatus !== SCORM2004_COMPLETED) { 2753 WriteToDebug("Overriding blnStatusWasSet to false."); 2754 blnStatusWasSet = false; 2755 } 2756 } 2757 return blnResult; 2758} 2759 2760function SCORM2004_SetPassed(){ 2761 2762 WriteToDebug("In SCORM2004_SetPassed"); 2763 2764 var blnResult; 2765 var strCompletionStatus; 2766 2767 SCORM2004_ClearErrorInfo(); 2768 2769 blnResult = SCORM2004_CallSetValue("cmi.success_status", SCORM2004_PASSED); 2770 if (PASS_FAIL_SETS_COMPLETION_FOR_2004) { 2771 blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_COMPLETED) && blnResult; 2772 } else { 2773 // If completion hasn't been set yet, then reset blnStatusWasSet so that SetReachedEnd can set it later. 2774 strCompletionStatus = SCORM2004_CallGetValue("cmi.completion_status"); 2775 if (strCompletionStatus !== SCORM2004_COMPLETED) { 2776 WriteToDebug("Overriding blnStatusWasSet to false."); 2777 blnStatusWasSet = false; 2778 } 2779 } 2780 return blnResult; 2781} 2782 2783function SCORM2004_SetCompleted(){ 2784 2785 WriteToDebug("In SCORM2004_SetCompleted"); 2786 2787 var blnResult; 2788 2789 SCORM2004_ClearErrorInfo(); 2790 2791 blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_COMPLETED); 2792 2793 return blnResult; 2794} 2795 2796function SCORM2004_ResetStatus(){ 2797 2798 WriteToDebug("In SCORM2004_ResetStatus"); 2799 2800 var blnResult; 2801 2802 SCORM2004_ClearErrorInfo(); 2803 2804 blnResult = SCORM2004_CallSetValue("cmi.success_status", SCORM2004_UNKNOWN); 2805 blnResult = SCORM2004_CallSetValue("cmi.completion_status", SCORM2004_INCOMPLETE) && blnResult; 2806 2807 return blnResult; 2808} 2809 2810function SCORM2004_GetStatus(){ 2811 2812 var strSuccessStatus; 2813 var strCompletionStatus; 2814 2815 WriteToDebug("In SCORM2004_GetStatus"); 2816 2817 SCORM2004_ClearErrorInfo(); 2818 2819 strSuccessStatus = SCORM2004_CallGetValue("cmi.success_status"); 2820 strCompletionStatus = SCORM2004_CallGetValue("cmi.completion_status"); 2821 2822 WriteToDebug("strSuccessStatus=" + strSuccessStatus); 2823 WriteToDebug("strCompletionStatus=" + strCompletionStatus); 2824 2825 if (strSuccessStatus == SCORM2004_PASSED){ 2826 WriteToDebug("returning Passed"); 2827 return LESSON_STATUS_PASSED; 2828 } 2829 else if (strSuccessStatus == SCORM2004_FAILED){ 2830 WriteToDebug("Returning Failed"); 2831 return LESSON_STATUS_FAILED; 2832 } 2833 else if (strCompletionStatus == SCORM2004_COMPLETED){ 2834 WriteToDebug("Returning Completed"); 2835 return LESSON_STATUS_COMPLETED; 2836 } 2837 else if (strCompletionStatus == SCORM2004_INCOMPLETE){ 2838 WriteToDebug("Returning Incomplete"); 2839 return LESSON_STATUS_INCOMPLETE; 2840 } 2841 else if (strCompletionStatus == SCORM2004_NOT_ATTEMPTED || strCompletionStatus == SCORM2004_UNKNOWN){ 2842 WriteToDebug("Returning Not Attempted"); 2843 return LESSON_STATUS_NOT_ATTEMPTED; 2844 } 2845 else{ 2846 WriteToDebug("ERROR - status not found"); 2847 SCORM2004_SetErrorInfoManually(SCORM2004_ERROR_INVALID_STATUS, 2848 "Invalid lesson status received from LMS", 2849 "strCompletionStatus=" + strCompletionStatus); 2850 return null; 2851 2852 } 2853 2854 2855} 2856 2857//public 2858function SCORM2004_GetProgressMeasure(){ 2859 WriteToDebug("In SCORM2004_GetProgressMeasure"); 2860 2861 var blnResult; 2862 2863 SCORM2004_ClearErrorInfo(); 2864 2865 blnResult = SCORM2004_CallGetValue("cmi.progress_measure"); 2866 2867 return blnResult; 2868} 2869/* 2870 cmi.progress_measure cmi.completion_status 2871 0 Ãnot attemptedà 2872 1 Ãcompletedà 2873 0 > value < 1 Ãincompleteà (typically, unless a cmi.completion_threshold is defined and the cmi.progress_measure is >= the cmi.completion_threshold) 2874 2875 2876 GetValue(Ãcmi.progress_measureÃ) 2877 SetValue(Ãcmi.progress_measureÃ,Ã0.75Ã) 2878 SetValue(Ãcmi.progress_measureÃ,Ã1.0Ã) 2879 2880*/ 2881 2882//public 2883function SCORM2004_SetProgressMeasure(numMeasure){ 2884 WriteToDebug("In SCORM2004_SetProgressMeasure"); 2885 2886 var blnResult; 2887 2888 2889 SCORM2004_ClearErrorInfo(); 2890 2891 blnResult = SCORM2004_CallSetValue("cmi.progress_measure", numMeasure); 2892 2893 return blnResult; 2894} 2895 2896function SCORM2004_SetObjectiveProgressMeasure(strObjectiveID, numMeasure){ 2897 WriteToDebug("In SCORM2004_SetObjectiveProgressMeasure"); 2898 var intObjectiveIndex; 2899 var blnResult; 2900 2901 WriteToDebug("In SCORM2004_SetObjectiveProgressMeasure, strObejctiveID=" + strObjectiveID + ", numMeasure=" + numMeasure ); 2902 2903 SCORM2004_ClearErrorInfo(); 2904 2905 intObjectiveIndex = SCORM2004_FindObjectiveIndexFromID(strObjectiveID); 2906 2907 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 2908 2909 SCORM2004_ClearErrorInfo(); 2910 2911 blnResult = SCORM2004_CallSetValue("cmi.objectives." + intObjectiveIndex + ".progress_measure", numMeasure); 2912 2913 return blnResult; 2914} 2915 2916function SCORM2004_IsContentInBrowseMode(){ 2917 2918 var strLessonMode 2919 2920 WriteToDebug("In SCORM2004_IsContentInBrowseMode"); 2921 2922 strLessonMode = SCORM2004_CallGetValue("cmi.mode"); 2923 2924 WriteToDebug("SCORM2004_IsContentInBrowseMode, strLessonMode=" + strLessonMode); 2925 2926 if (strLessonMode == SCORM2004_BROWSE){ 2927 WriteToDebug("Returning true"); 2928 return true; 2929 } 2930 else{ 2931 WriteToDebug("Returning false"); 2932 return false;
2933 } 2934} 2935 2936 2937function SCORM2004_TranslateExitTypeToSCORM(strExitType){ 2938 2939 WriteToDebug("In SCORM2004_TranslatgeExitTypeToSCORM strExitType-" + strExitType); 2940 2941 if (strExitType == EXIT_TYPE_SUSPEND){ 2942 WriteToDebug("Returning suspend"); 2943 return SCORM2004_SUSPEND; 2944 } 2945 else if (strExitType == EXIT_TYPE_UNLOAD){ 2946 WriteToDebug("Returning Exit"); 2947 return SCORM2004_NORMAL_EXIT; 2948 } 2949 else if (strExitType == EXIT_TYPE_FINISH){ 2950 WriteToDebug("Returning Logout"); 2951 return SCORM2004_NORMAL_EXIT; 2952 } 2953 else if (strExitType == EXIT_TYPE_TIMEOUT){ 2954 WriteToDebug("Returning Timout"); 2955 return SCORM2004_TIMEOUT; 2956 } 2957} 2958 2959 2960function SCORM2004_GetCompletionStatus(){ 2961 2962 WriteToDebug("In SCORM2004_GetCompletionStatus"); 2963 2964 return SCORM2004_COMPLETED; 2965 2966} 2967 2968function SCORM2004_SetPointBasedScore(intScore, intMaxScore, intMinScore){ 2969 2970 var blnResult; 2971 var fltCalculatedScore; 2972 2973 WriteToDebug("In SCORM2004_SetPointBasedScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 2974 2975 SCORM2004_ClearErrorInfo(); 2976 2977 if(intScore >= intMinScore) 2978 { 2979 fltCalculatedScore = intScore / intMaxScore; 2980 }else{ 2981 WriteToDebug("intScore is lower than intMinScore. Overriding score with minscore for cmi.score.scaled"); 2982 fltCalculatedScore = intMinScore / intMaxScore; 2983 } 2984 fltCalculatedScore = RoundToPrecision(fltCalculatedScore, 7); 2985 2986 blnResult = SCORM2004_CallSetValue("cmi.score.raw", intScore); 2987 blnResult = SCORM2004_CallSetValue("cmi.score.max", intMaxScore) && blnResult; 2988 blnResult = SCORM2004_CallSetValue("cmi.score.min", intMinScore) && blnResult; 2989 2990 blnResult = SCORM2004_CallSetValue("cmi.score.scaled", fltCalculatedScore) && blnResult; 2991 2992 WriteToDebug("Returning " + blnResult); 2993 2994 return blnResult; 2995 2996} 2997 2998 2999//___________________________________________________________ 3000//Interaction Retrieval Functionality 3001//NOTE ON INTERACTION RETRIEVAL 3002//A. It is only available in certain standards, standards where it is unavailable will return nothing 3003//B. The interaction records are currently reported using "journaling", whereby each entry is appended 3004// Retrieval methods will retrieve only the most recent value 3005 3006 3007//___________________________________________________________ 3008//Helper Methods 3009function SCORM2004_FindInteractionIndexFromID(strInteractionID){ 3010 3011 //with interactions, this method returns the index for the MOST RECENT (by timestamp) 3012 //interaction with this identifier 3013 3014 var intCount; 3015 var i; 3016 var strTempID; 3017 var dtmTempDate = new Date(); 3018 var index; 3019 var currentIndexTimestamp = new Date("1/1/1900"); 3020 3021 3022 WriteToDebug("In SCORM2004_FindInteractionIndexFromID"); 3023 3024 intCount = SCORM2004_CallGetValue("cmi.interactions._count"); 3025 3026 if (intCount == ""){ 3027 WriteToDebug("Setting intCount=0"); 3028 return null; 3029 } 3030 3031 intCount = parseInt(intCount, 10); 3032 3033 WriteToDebug("intCount=" + intCount); 3034 3035 for (i=0; i<intCount; i++){ 3036 3037 WriteToDebug("Checking index " + i); 3038 3039 strTempID = SCORM2004_CallGetValue("cmi.interactions." + i + ".id"); 3040 3041 WriteToDebug("ID=" + strTempID); 3042 3043 if (strTempID == strInteractionID){ 3044 WriteToDebug("Found Matching index: " + i); 3045 //this is NOT a call to SCORM2004_GetInteractionTimestamp because that will use this method to loop 3046 dtmTempDate = ConvertIso8601TimeStampToDate(SCORM2004_CallGetValue("cmi.interactions." + i + ".timestamp")); 3047 3048 WriteToDebug("timestamp for " + i + ": " + dtmTempDate); 3049 3050 if (dtmTempDate>currentIndexTimestamp) 3051 { 3052 index = i; 3053 currentIndexTimestamp = dtmTempDate; 3054 } 3055 } 3056 } 3057 3058 if (index>=0) return index; 3059 3060 WriteToDebug("Did not find match, returning null"); 3061 3062 return null; 3063} 3064//___________________________________________________________ 3065 3066 3067function SCORM2004_GetInteractionType(strInteractionID) 3068{ 3069 3070 var intInteractionIndex; 3071 3072 WriteToDebug("In SCORM2004_GetInteractionType, strInteractionID=" + strInteractionID); 3073 3074 SCORM2004_ClearErrorInfo(); 3075 3076 intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3077 3078 3079 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3080 return null; 3081 } 3082 3083 3084 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3085 3086 var type = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".type"); 3087 3088 switch (type) 3089 { 3090 case SCORM2004_INTERACTION_TYPE_FILL_IN: 3091 return INTERACTION_TYPE_FILL_IN; 3092 case SCORM2004_INTERACTION_TYPE_LONG_FILL_IN: 3093 return INTERACTION_TYPE_LONG_FILL_IN; 3094 case SCORM2004_INTERACTION_TYPE_CHOICE: 3095 return INTERACTION_TYPE_CHOICE; 3096 case SCORM2004_INTERACTION_TYPE_LIKERT: 3097 return INTERACTION_TYPE_LIKERT;
3098 case SCORM2004_INTERACTION_TYPE_MATCHING: 3099 return INTERACTION_TYPE_MATCHING; 3100 case SCORM2004_INTERACTION_TYPE_NUMERIC: 3101 return INTERACTION_TYPE_NUMERIC; 3102 case SCORM2004_INTERACTION_TYPE_PERFORMANCE: 3103 return INTERACTION_TYPE_PERFORMANCE; 3104 case SCORM2004_INTERACTION_TYPE_SEQUENCING: 3105 return INTERACTION_TYPE_SEQUENCING; 3106 case SCORM2004_INTERACTION_TYPE_TRUE_FALSE: 3107 return INTERACTION_TYPE_TRUE_FALSE; 3108 default: 3109 return ""; 3110 } 3111 3112 3113} 3114 3115//public 3116function SCORM2004_GetInteractionTimestamp(strInteractionID) 3117{ 3118 WriteToDebug("In SCORM2004_GetInteractionTimestamp, strInteractionID=" + strInteractionID); 3119 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3120 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3121 3122 SCORM2004_ClearErrorInfo(); 3123 3124 3125 3126 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3127 return null; 3128 } 3129 3130 3131 3132 return SCORM2004_CallGetValue(ConvertIso8601TimeStampToDate("cmi.interactions." + intInteractionIndex + ".timestamp")); 3133 3134} 3135 3136 3137 3138//public 3139function SCORM2004_GetInteractionCorrectResponses(strInteractionID) 3140{ 3141 3142 WriteToDebug("In SCORM2004_GetInteractionCorrectResponses, strInteractionID=" + strInteractionID); 3143 3144 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3145 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3146 3147 SCORM2004_ClearErrorInfo(); 3148 3149 3150 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3151 return null; 3152 } 3153 3154 3155 var strType = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".type"); 3156 3157 var intCorrectResponseCount = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".correct_responses._count"); 3158 3159 3160 if (intCorrectResponseCount == ""){ 3161 WriteToDebug("Setting intCorrectResponseCount=0"); 3162 return 0; 3163 } 3164 3165 intCorrectResponseCount = parseInt(intCorrectResponseCount, 10); 3166 WriteToDebug("intCorrectResponseCount=" + intCorrectResponseCount); 3167 if (intCorrectResponseCount==0) return new Array(); 3168 if (intCorrectResponseCount>1) WriteToDebug("SCORM Driver is not currently implemented to support multiple correct response combinations and will only return the first"); 3169 3170 var strResponse = new String(SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern")); 3171 var aryResponse = strResponse.split("[,]"); 3172 WriteToDebug("aryResponse.length = " + aryResponse.length); 3173 aryResponse = SCORM2004_ProcessResponseArray(strType, aryResponse); 3174 3175 /* 3176 //this is the basis for handling N correct responses 3177 //because the API currently does not allow for the posting of multiple 3178 //correct response combinations, this functionality is not currently 3179 //implemented 3180 var aryResponses = new Array(); 3181 3182 for (i=0; i<intCorrectResponseCount; i++) 3183 { 3184 WriteToDebug("examining CorrectResponse: " + i); 3185 var strResponse = new String(SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".correct_responses." + i + ".pattern")); 3186 var aryResponse = strResponse.split("[,]"); 3187 WriteToDebug("aryResponses.length = " + aryResponses.length); 3188 aryResponse = SCORM2004_ProcessResponseArray(strType, aryResponse); 3189 aryResponses.push(aryResponse); 3190 } 3191 */ 3192 3193 3194 3195 3196 WriteToDebug("aryResponse.length = " + aryResponse.length); 3197 return aryResponse; 3198 3199 3200 3201} 3202 3203 3204 3205//public 3206function SCORM2004_GetInteractionWeighting(strInteractionID) 3207{ 3208 WriteToDebug("In SCORM2004_GetInteractionWeighting, strInteractionID=" + strInteractionID); 3209 3210 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3211 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3212 3213 SCORM2004_ClearErrorInfo(); 3214 3215 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3216 return null; 3217 } 3218 3219 return SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".weighting"); 3220 3221} 3222 3223 3224 3225//public 3226function SCORM2004_GetInteractionLearnerResponses(strInteractionID) 3227{ 3228 WriteToDebug("In SCORM2004_GetInteractionLearnerResponses, strInteractionID=" + strInteractionID); 3229 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3230 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3231 3232 SCORM2004_ClearErrorInfo(); 3233 3234 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3235 return null; 3236 } 3237 3238 3239 var strType = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".type"); 3240 3241 var strResponse = new String(SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".learner_response")); 3242 3243 var aryResponses = strResponse.split("[,]"); 3244 3245 WriteToDebug("aryResponses.length = " + aryResponses.length); 3246 3247 aryResponses = SCORM2004_ProcessResponseArray(strType, aryResponses); 3248 3249 return aryResponses; 3250} 3251 3252function SCORM2004_ProcessResponseArray(strInteractionType, aryResponses) 3253{ 3254 //process them (into ResponseIdentifiers, etc?) 3255 //only include the right amount of stuff 3256 3257 WriteToDebug("Processing Response Array with " + aryResponses.length + " pieces"); 3258 3259 3260 for(var i=0; i<aryResponses.length; i++) 3261 { 3262 if (strInteractionType == SCORM2004_INTERACTION_TYPE_MATCHING) 3263 { 3264 WriteToDebug("processing matching type, i=" + i); 3265 aryResponses[i] = CreateMatchingResponse(aryResponses[i]); 3266 } 3267 } 3268 3269 return aryResponses; 3270 3271} 3272 3273//public 3274function SCORM2004_GetInteractionResult(strInteractionID) 3275{ 3276 WriteToDebug("In SCORM2004_GetInteractionResult, strInteractionID=" + strInteractionID); 3277 3278 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3279 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3280 3281 SCORM2004_ClearErrorInfo(); 3282 3283 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3284 return null; 3285 } 3286 3287 3288 return SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".result"); 3289 3290 3291} 3292 3293 3294 3295//public 3296function SCORM2004_GetInteractionLatency(strInteractionID) 3297{ 3298 WriteToDebug("In SCORM2004_GetInteractionLatency, strInteractionID=" + strInteractionID); 3299 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3300 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3301 3302 SCORM2004_ClearErrorInfo(); 3303 3304 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3305 return null; 3306 } 3307 3308 var strLatency = SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".latency"); 3309 3310 WriteToDebug("latency returns: " + strLatency); 3311 var intLatency = ConvertScorm2004TimeToMS(strLatency); 3312 WriteToDebug("latency in milliseconds: " + intLatency); 3313 3314 return intLatency; 3315 3316} 3317 3318 3319 3320//public 3321function SCORM2004_GetInteractionDescription(strInteractionID) 3322{ 3323 WriteToDebug("In SCORM2004_GetInteractionDescription, strInteractionID=" + strInteractionID); 3324 var intInteractionIndex = SCORM2004_FindInteractionIndexFromID(strInteractionID); 3325 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 3326 3327 SCORM2004_ClearErrorInfo(); 3328 3329 if(intInteractionIndex == undefined || intInteractionIndex == null){ 3330 return null; 3331 } 3332 3333 3334 return SCORM2004_CallGetValue("cmi.interactions." + intInteractionIndex + ".description"); 3335 3336 3337} 3338 3339//________________________________________________ 3340 3341 3342//public 3343function SCORM2004_CreateDataBucket(strBucketId, intMinSize, intMaxSize, strPersistenceType){ 3344 3345 WriteToDebug("In SCORM2004_CreateDataBucket, strBucketId=" + strBucketId + ", intMinSize=" + intMinSize + ", intMaxSize=" + intMaxSize + ",
3345course=" + strPersistenceType); 3346 3347 if (SCORM2004_DetectSSPSupport()){ 3348 3349 if (SCORM2004_DoesBucketExist(strBucketId) == true){ 3350 WriteToDebug("Bucket already exists and can't be re-allocated."); 3351 return false; 3352 } 3353 else{ 3354 return SCORM2004_CallSetValue("ssp.allocate", "{bucketID=" + strBucketId + "}{requested=" + intMaxSize + "}{minimum=" + intMinSize + "}{reducible=true}{persistence="+ strPersistenceType +"}")//") 3355 } 3356 } 3357 else{ 3358 WriteToDebug("SSP is not supported in this LMS, returning false."); 3359 return false; 3360 } 3361} 3362 3363//public 3364function SCORM2004_GetDataFromBucket(strBucketId){ 3365 WriteToDebug("In SCORM2004_GetDataFromBucket, strBucketId=" + strBucketId); 3366 3367 if (SCORM2004_DetectSSPSupport()){ 3368 var data = SCORM2004_CallGetValue("ssp.data.{bucketID=" + strBucketId + "}"); 3369 return data; 3370 } 3371 else{ 3372 WriteToDebug("SSP is not supported in this LMS, returning empty string."); 3373 return ""; 3374 } 3375} 3376 3377//public 3378function SCORM2004_PutDataInBucket(strBucketId, strData, blnAppendToEnd){ 3379 WriteToDebug("In SCORM2004_PutDataInBucket, strBucketId=" + strBucketId + ", blnAppendToEnd=" + blnAppendToEnd + ", strData=" + strData); 3380 3381 if (SCORM2004_DetectSSPSupport()){ 3382 3383 if (blnAppendToEnd == true){ 3384 return SCORM2004_CallSetValue("ssp.appendData", "{bucketID=" + strBucketId + "}" + strData); 3385 } 3386 else{ 3387 return SCORM2004_CallSetValue("ssp.data", "{bucketID=" + strBucketId + "}" + strData); 3388 } 3389 } 3390 else{ 3391 WriteToDebug("SSP is not supported in this LMS, returning false."); 3392 return false; 3393 } 3394} 3395 3396//public 3397function SCORM2004_DetectSSPSupport(){ 3398 3399 WriteToDebug("In SCORM2004_DetectSSPSupport"); 3400 3401 if (blnSCORM2004_SSP_Is_Supported == true){ 3402 WriteToDebug("Support already detected, returning true"); 3403 return true; 3404 } 3405 3406 else if (blnSCORM2004_SSP_Is_Supported == false){ 3407 WriteToDebug("Support already determined to me missing, returning false"); 3408 return false; 3409 } 3410 3411 else{ 3412 //make an error-free call to the SSP data model to see if it is supported 3413 var intBucketCount = SCORM2004_CallGetValue("ssp._count"); 3414 3415 //if the call succeed, then SSP is supported, otherwise it is not 3416 if (SCORM2004_GetLastError() == NO_ERROR){ 3417 WriteToDebug("SSP data model call succeeded, SSP is supported"); 3418 blnSCORM2004_SSP_Is_Supported = true; 3419 return true; 3420 } 3421 else{ 3422 WriteToDebug("SSP data model call failed, SSP is NOT supported"); 3423 blnSCORM2004_SSP_Is_Supported = false; 3424 return false; 3425 } 3426 } 3427} 3428 3429//public 3430function SCORM2004_GetBucketInfo(strBucketId){ 3431 3432 WriteToDebug("In SCORM2004_GetBucketInfo, strBucketId=" + strBucketId); 3433 3434 var intTotalSpace = 0; 3435 var intUsedSpace = 0; 3436 3437 var strBucketState = new String(SCORM2004_CallGetValue("ssp.bucket_state.{bucketID=" + strBucketId + "}")); 3438 3439 if (strBucketState == "" || strBucketState == null || strBucketState == undefined){ 3440 WriteToDebug ("Could not retrieve bucket state, returning 0 total size and 0 used size"); 3441 return new SSPBucketSize(0, 0); 3442 } 3443 3444 var sectionArray = strBucketState.split("{"); 3445 3446 for (var section in sectionArray){ 3447 3448 section = new String(sectionArray[section]); 3449 3450 section = section.replace("}", ""); 3451 3452 if (section.indexOf("totalSpace", 0) == 0){ 3453 WriteToDebug("Found total space"); 3454 intTotalSpace = parseInt(section.substr(11), 10); 3455 WriteToDebug("total space=" + intTotalSpace); 3456 } 3457 else if (section.indexOf("used", 0) == 0){ 3458 WriteToDebug("Found used space"); 3459 intUsedSpace = parseInt(section.substr(5), 10); 3460 WriteToDebug("used=" + intUsedSpace); 3461 } 3462 } 3463 3464 var returnValue = new SSPBucketSize(intTotalSpace, intUsedSpace); 3465 3466 return returnValue; 3467} 3468 3469//private 3470function SCORM2004_DoesBucketExist(strBucketId){ 3471 3472 WriteToDebug("In SCORM2004_DoesBucketExist, strBucketId=" + strBucketId); 3473 3474 var intBucketCount = SCORM2004_CallGetValue("ssp._count"); 3475 intBucketCount = parseInt(intBucketCount, 10); 3476 3477 for (var i=0; i < intBucketCount; i++){ 3478 if (strBucketId == SCORM2004_CallGetValue("ssp." + i + ".id")){ 3479 WriteToDebug("Bucket '" + strBucketId + "' Exists"); 3480 return true; 3481 } 3482 } 3483 3484 WriteToDebug("Bucket '" + strBucketId + "' DOES NOT Exist"); 3485 return false;
3486} 3487 3488function SCORM2004_SetNavigationRequest(strNavRequest){ 3489 WriteToDebug("In SCORM2004_SetNavigationRequest, strNavRequest=" + strNavRequest); 3490 SCORM2004_ClearErrorInfo(); 3491 //first check to see if it is a choice nav request 3492 var regValidChoice = /^\{target=[.A-Za-z0-9_-]+\}choice$/; 3493 if (strNavRequest.match(regValidChoice)){ 3494 SCORM2004_CallSetValue("adl.nav.request",strNavRequest); 3495 return true; 3496 }else{ 3497 //check for other valid options 3498 switch(strNavRequest){ 3499 case "continue": 3500 break; 3501 case "previous": 3502 break; 3503 case "exit": 3504 break; 3505 case "exitAll": 3506 break; 3507 case "abandon": 3508 break; 3509 case "abandonAll": 3510 break; 3511 case "suspendAll": 3512 break; 3513 case "_none_": 3514 break; 3515 default: 3516 WriteToDebug("In SCORM2004_SetNavigationRequest, NavRequest is not valid - strNavRequest=" + strNavRequest); 3517 return false; 3518 } 3519 SCORM2004_CallSetValue("adl.nav.request",strNavRequest); 3520 return true; 3521 } 3522} 3523 3524function SCORM2004_GetNavigationRequest(){ 3525 WriteToDebug("In SCORM2004_GetNavigationRequest"); 3526 SCORM2004_ClearErrorInfo(); 3527 return SCORM2004_CallGetValue("adl.nav.request"); 3528} 3529 3530/* 3531o âcontinueâ: Indicates to the LMS that the content asserts that a Continue navigation request should be processed immediately following the SCOâs termination. 3532o âpreviousâ: Indicates to the LMS that the content asserts that a Previous navigation request should be processed immediately following the SCOâs termination. 3533o âchoiceâ: Indicates to the LMS that the content asserts that a Choice navigation request should be processed immediately following the SCOâs termination. 3534o âexitâ: Indicates to the LMS that the content asserts that an Exit navigation request should be processed immediately following the SCOâs termination. 3535o âexitAllâ: Indicates to the LMS that the content asserts that an Exit All navigation request should be processed immediately following the SCOâs termination. 3536o âabandonâ: Indicates to the LMS that the content asserts that an Abandon navigation request should be processed immediately following the SCOâs termination. 3537o âabandonAllâ: Indicates to the LMS that the content asserts that an Abandon All navigation request should be processed immediately following the SCOâs termination. 3538o âsuspendAllâ: Indicates to the LMS that the content asserts that a Suspend All navigation request should be processed immediately following the SCOâs termination. 3539o â_none_â: Indicates to the LMS that the content asserts that any previous navigation request indicated by the SCO should not be processed immediately following the SCOâs termination. Setting this value effectively clears any pending navigation request. 3540*/ 3541 3542//--------------------------------------------------------------------------------- 3543//Functions to Call the SCORM API 3544 3545//note: in all functions that interact with API, we concact any returned strings with "" to convert 3546//the value to a string type, do this b/c many SCORM API's will return a Java String instead of a Javascript 3547//string 3548 3549 3550function SCORM2004_CallInitialize(){ 3551 3552 var strResult; 3553 3554 WriteToDebug("In SCORM2004_CallInitialize"); 3555 3556 SCORM2004_objAPI = SCORM2004_GrabAPI(); 3557 3558 WriteToDebug("Calling Initialize"); 3559 3560 strResult = SCORM2004_objAPI.Initialize(""); 3561 strResult = strResult + ""; 3562 3563 WriteToDebug("strResult=" + strResult); 3564 3565 if (strResult == SCORM2004_FALSE){ 3566 3567 WriteToDebug("Detected failed call to initialize"); 3568 3569 SCORM2004_SetErrorInfo(); 3570 3571 WriteToDebug ("Error calling Initialize:"); 3572 WriteToDebug (" Error Number=" + intSCORM2004Error); 3573 WriteToDebug (" Error String=" + strSCORM2004ErrorString); 3574 WriteToDebug (" Diagnostic=" + strSCORM2004ErrorDiagnostic); 3575 3576 return false; 3577 3578 } 3579 3580 WriteToDebug("Returning true"); 3581 3582 return true; 3583} 3584 3585function SCORM2004_CallSetValue(strElement, strValue){ 3586 3587 var strResult; 3588 3589 WriteToDebug("SCORM2004_CallSetValue strElement=" + strElement + ", strValue=" + strValue); 3590 3591 if (blnReviewModeSoReadOnly === true){ 3592 WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting."); 3593 return true; 3594 } 3595 3596 SCORM2004_objAPI = SCORM2004_GrabAPI(); 3597 3598 WriteToDebug("Calling SetValue"); 3599 3600 strElement = strElement + ""; 3601 strValue = strValue + ""; 3602 3603 strResult = SCORM2004_objAPI.SetValue(strElement, strValue) 3604 strResult = strResult + ""; 3605 3606 WriteToDebug("strResult=" + strResult); 3607
3608 if (strResult == SCORM2004_FALSE){ 3609 3610 WriteToDebug("Detected Failed call to SetValue"); 3611 3612 SCORM2004_SetErrorInfo(); 3613 3614 WriteToDebug ("Error calling SetValue:"); 3615 WriteToDebug (" strElement=" + strElement); 3616 WriteToDebug (" strValue=" + strValue); 3617 WriteToDebug (" Error Number=" + intSCORM2004Error); 3618 WriteToDebug (" Error String=" + strSCORM2004ErrorString); 3619 WriteToDebug (" Diagnostic=" + strSCORM2004ErrorDiagnostic); 3620 3621 return false; 3622 } 3623 3624 WriteToDebug("Returning true"); 3625 3626 return true; 3627} 3628 3629function SCORM2004_CallGetValue(strElement){ 3630 3631 var strResult 3632 3633 WriteToDebug("In SCORM2004_CallGetValue strElement=" + strElement); 3634 3635 SCORM2004_objAPI = SCORM2004_GrabAPI(); 3636 3637 WriteToDebug("Call GetValue"); 3638 3639 strElement = strElement + ""; 3640 3641 strResult = SCORM2004_objAPI.GetValue(strElement) + "" 3642 3643 WriteToDebug("strResult=" + strResult); 3644 3645 intSCORM2004Error = SCORM2004_objAPI.GetLastError() 3646 intSCORM2004Error = intSCORM2004Error + ""; 3647 3648 WriteToDebug("intSCORM2004Error=" + intSCORM2004Error); 3649 3650 if (intSCORM2004Error != SCORM2004_NO_ERROR){ 3651 3652 WriteToDebug("Detected failed called to GetValue"); 3653 3654 SCORM2004_SetErrorInfo(); 3655 3656 WriteToDebug ("Error calling LMSGetValue:"); 3657 WriteToDebug (" strElement=" + strElement); 3658 WriteToDebug (" Error Number=" + intSCORM2004Error); 3659 WriteToDebug (" Error String=" + strSCORM2004ErrorString); 3660 WriteToDebug (" Diagnostic=" + strSCORM2004ErrorDiagnostic); 3661 } 3662 3663 WriteToDebug("Returning " + strResult); 3664 3665 return strResult; 3666 3667} 3668 3669 3670function SCORM2004_CallCommit(){ 3671 3672 var strResult; 3673 3674 WriteToDebug("In SCORM2004_CallCommit"); 3675 3676 SCORM2004_objAPI = SCORM2004_GrabAPI(); 3677 3678 WriteToDebug("Calling Commit"); 3679 3680 strResult = SCORM2004_objAPI.Commit(""); 3681 3682 strResult = strResult + ""; 3683 3684 WriteToDebug("strResult=" + strResult); 3685 3686 if (strResult == SCORM2004_FALSE){ 3687 3688 WriteToDebug("Detected failed call to Commit"); 3689 3690 SCORM2004_SetErrorInfo(); 3691 3692 WriteToDebug ("Error calling Commit:"); 3693 WriteToDebug (" Error Number=" + intSCORM2004Error); 3694 WriteToDebug (" Error String=" + strSCORM2004ErrorString); 3695 WriteToDebug (" Diagnostic=" + strSCORM2004ErrorDiagnostic); 3696 3697 return false; 3698 } 3699 3700 WriteToDebug("Returning true"); 3701 3702 return true; 3703} 3704 3705 3706function SCORM2004_CallTerminate(){ 3707 3708 var strResult; 3709 3710 WriteToDebug("In SCORM2004_CallTerminate"); 3711 3712 SCORM2004_objAPI = SCORM2004_GrabAPI(); 3713 3714 WriteToDebug("Calling Terminate"); 3715 3716 strResult = SCORM2004_objAPI.Terminate(""); 3717 3718 strResult = strResult + ""; 3719 3720 WriteToDebug("strResult=" + strResult); 3721 3722 if (strResult == SCORM2004_FALSE){ 3723 3724 WriteToDebug("Detected failed call to Terminate"); 3725 3726 SCORM2004_SetErrorInfo(); 3727 3728 WriteToDebug ("Error calling Terminate:"); 3729 WriteToDebug (" Error Number=" + intSCORM2004Error); 3730 WriteToDebug (" Error String=" + strSCORM2004ErrorString); 3731 WriteToDebug (" Diagnostic=" + strSCORM2004ErrorDiagnostic); 3732 3733 return false; 3734 } 3735 3736 WriteToDebug("Returning True"); 3737 3738 return true; 3739} 3740 3741 3742//--------------------------------------------------------------------------------- 3743//Error Handling Functions 3744function SCORM2004_ClearErrorInfo(){ 3745 3746 WriteToDebug("In SCORM2004_ClearErrorInfo"); 3747 3748 intSCORM2004Error = SCORM2004_NO_ERROR; 3749 strSCORM2004ErrorString = ""; 3750 strSCORM2004ErrorDiagnostic = ""; 3751} 3752 3753function SCORM2004_SetErrorInfo(){ 3754 3755 WriteToDebug("In SCORM2004_SetErrorInfo"); 3756 3757 intSCORM2004Error = SCORM2004_objAPI.GetLastError(); 3758 strSCORM2004ErrorString = SCORM2004_objAPI.GetErrorString(intSCORM2004Error); 3759 strSCORM2004ErrorDiagnostic = SCORM2004_objAPI.GetDiagnostic(""); 3760 3761 intSCORM2004Error = intSCORM2004Error + ""; 3762 strSCORM2004ErrorString = strSCORM2004ErrorString + ""; 3763 strSCORM2004ErrorDiagnostic = strSCORM2004ErrorDiagnostic + ""; 3764 3765 WriteToDebug("intSCORM2004Error=" + intSCORM2004Error); 3766 WriteToDebug("strSCORM2004ErrorString=" + strSCORM2004ErrorString); 3767 WriteToDebug("strSCORM2004ErrorDiagnostic=" + strSCORM2004ErrorDiagnostic); 3768} 3769 3770function SCORM2004_SetErrorInfoManually(intNum, strString, strDiagnostic){ 3771 3772 WriteToDebug("In SCORM2004_SetErrorInfoManually"); 3773 WriteToDebug("ERROR-Num=" + intNum); 3774 WriteToDebug(" String=" + strString); 3775 WriteToDebug(" Diag=" + strDiagnostic); 3776 3777 intSCORM2004Error = intNum; 3778 strSCORM2004ErrorString = strString; 3779 strSCORM2004ErrorDiagnostic = strDiagnostic; 3780 3781} 3782 3783function SCORM2004_GetLastError(){ 3784 3785 WriteToDebug("In SCORM2004_GetLastError"); 3786 3787 if (intSCORM2004Error == SCORM2004_NO_ERROR){ 3788 WriteToDebug("Returning No Error"); 3789 return NO_ERROR; 3790 } 3791 else { 3792 WriteToDebug("Returning " + intSCORMError); 3793 return intSCORM2004Error; 3794 } 3795} 3796 3797function SCORM2004_GetLastErrorDesc(){ 3798 WriteToDebug("In SCORM2004_GetLastErrorDesc, " + strSCORM2004ErrorString + "\n" + strSCORM2004ErrorDiagnostic); 3799 return strSCORM2004ErrorString + "\n" + strSCORM2004ErrorDiagnostic; 3800} 3801 3802 3803 3804//--------------------------------------------------------------------------------- 3805//API Locating Functions 3806 3807function SCORM2004_GrabAPI(){ 3808 3809 WriteToDebug("In SCORM2004_GrabAPI"); 3810 3811 if (typeof(SCORM2004_objAPI) == "undefined" || SCORM2004_objAPI == null){ 3812 WriteToDebug("Searching with Rustici Software algorithm"); 3813 SCORM2004_objAPI = SCORM2004_GetAPI(); 3814 } 3815 3816 if (typeof(SCORM2004_objAPI) == "undefined" || SCORM2004_objAPI == null || SCORM2004_objAPI ==false){ 3817 WriteToDebug("Searching with SearchForAPI"); 3818 SCORM2004_objAPI = SCORM2004_SearchForAPI(window); 3819 } 3820 3821 WriteToDebug("Grab API, returning, found API = " + (SCORM2004_objAPI != null)); 3822 3823 return SCORM2004_objAPI; 3824 3825} 3826 3827 3828 3829 3830/******************************************************************* 3831 * SCORM 2004 API Search Algorithm 3832 * Description - Improvement of the algorithm developed by ADL to 3833 find the SCORM 2004 API Adapter. The improvements eliminate
3834 errors, improve code clarity and eliminate the dependence 3835 on global variables. The errors removed include: 3836 - The "win" variable was never declared in a scope 3837 accessible to the GetAPI function 3838 - A call to API.version which is not part of the SCORM 3839 2004 specification 3840 - The previous algorithm was not able to find the API 3841 if it was located in the window's parent's opener 3842 * Original Author - ADL & Concurrent Technologies Corporation 3843 * Author - Mike Rustici (April 1, 2004) 3844 Rustici Software, LLC 3845 http://www.scorm.com 3846 [email protected] 3847*******************************************************************/ 3848 3849 3850 3851 3852/* 3853ScanParentsForApi 3854-Searches all the parents of a given window until 3855 it finds an object named "API_1484_11". If an 3856 object of that name is found, a reference to it 3857 is returned. Otherwise, this function returns null. 3858*/ 3859function SCORM2004_ScanParentsForApi(win) 3860{ 3861 3862 /* 3863 Establish an outrageously high maximum number of 3864 parent windows that we are will to search as a 3865 safe guard against an infinite loop. This is 3866 probably not strictly necessary, but different 3867 browsers can do funny things with undefined objects. 3868 */ 3869 var MAX_PARENTS_TO_SEARCH = 500; 3870 var nParentsSearched = 0; 3871 3872 /* 3873 Search each parent window until we either: 3874 -find the API, 3875 -encounter a window with no parent (parent is null 3876 or the same as the current window) 3877 -or, have reached our maximum nesting threshold 3878 */ 3879 while ( (win.API_1484_11 == null || win.API_1484_11 == undefined) && 3880 (win.parent != null) && (win.parent != win) && 3881 (nParentsSearched <= MAX_PARENTS_TO_SEARCH) 3882 ) 3883 { 3884 3885 nParentsSearched++; 3886 win = win.parent; 3887 } 3888 3889 /* 3890 If the API doesn't exist in the window we stopped looping on, 3891 then this will return null. 3892 */ 3893 return win.API_1484_11; 3894} 3895 3896 3897/* 3898GetAPI 3899-Searches all parent and opener windows relative to the 3900 current window for the SCORM 2004 API Adapter. 3901 Returns a reference to the API Adapter if found or null 3902 otherwise. 3903*/ 3904function SCORM2004_GetAPI() 3905{ 3906 3907 var API = null; 3908 3909 //Search all the parents of the current window if there are any 3910 if ((window.parent != null) && (window.parent != window)) 3911 { 3912 API = SCORM2004_ScanParentsForApi(window.parent); 3913 } 3914 3915 /* 3916 If we didn't find the API in this window's chain of parents, 3917 then search all the parents of the opener window if there is one 3918 */ 3919 if ((API == null) && (window.top.opener != null)) 3920 { 3921 API = SCORM2004_ScanParentsForApi(window.top.opener); 3922 } 3923 3924 return API; 3925} 3926 3927function SCORM2004_SearchForAPI(wndLookIn){ 3928 3929 WriteToDebug("SCORM2004_SearchForAPI"); 3930 3931 var objAPITemp = null; 3932 var strDebugID = ""; 3933 3934 strDebugID = "Name=" + wndLookIn.name + ", href=" + wndLookIn.location.href 3935 3936 objAPITemp = wndLookIn.API_1484_11; 3937 3938 if (SCORM2004_APIFound(objAPITemp)){ 3939 WriteToDebug("Found API in this window - " + strDebugID); 3940 return objAPITemp; 3941 } 3942 3943 if (SCORM2004_WindowHasParent(wndLookIn)){ 3944 WriteToDebug("Searching Parent - " + strDebugID); 3945 objAPITemp = SCORM2004_SearchForAPI(wndLookIn.parent); 3946 } 3947 3948 if (SCORM2004_APIFound(objAPITemp)){ 3949 WriteToDebug("Found API in a parent - " + strDebugID); 3950 return objAPITemp; 3951 } 3952 3953 if (SCORM2004_WindowHasOpener(wndLookIn)){ 3954 WriteToDebug("Searching Opener - " + strDebugID); 3955 objAPITemp = SCORM2004_SearchForAPI(wndLookIn.opener); 3956 } 3957 3958 if (SCORM2004_APIFound(objAPITemp)){ 3959 WriteToDebug("Found API in an opener - " + strDebugID); 3960 return objAPITemp; 3961 } 3962 3963 //look in child frames individually, don't call this function recursively 3964 //on them to prevent an infinite loop when it looks back up to the parents 3965 WriteToDebug("Looking in children - " + strDebugID); 3966 objAPITemp = SCORM2004_LookInChildren(wndLookIn); 3967 3968 if (SCORM2004_APIFound(objAPITemp)){ 3969 WriteToDebug("Found API in Children - " + strDebugID); 3970 return objAPITemp; 3971 } 3972 3973 WriteToDebug("Didn't find API in this window - " + strDebugID); 3974 return null; 3975} 3976 3977function SCORM2004_LookInChildren(wnd){ 3978 3979 WriteToDebug("SCORM2004_LookInChildren"); 3980 3981 var objAPITemp = null; 3982 3983 var strDebugID = ""; 3984 3985 strDebugID = "Name=" + wnd.name + ", href=" + wnd.location.href 3986 3987 for (var i=0; i < wnd.frames.length; i++){ 3988 3989 WriteToDebug("Looking in child frame " + i); 3990 3991 objAPITemp = wnd.frames[i].API_1484_11; 3992 3993 if (SCORM2004_APIFound(objAPITemp)){ 3994 WriteToDebug("Found API in child frame of " + strDebugID); 3995 return objAPITemp; 3996 } 3997 3998 WriteToDebug("Looking in this child's children " + strDebugID); 3999 objAPITemp = SCORM2004_LookInChildren(wnd.frames[i]); 4000 4001 if (SCORM2004_APIFound(objAPITemp)){ 4002 WriteToDebug("API found in this child's children " + strDebugID); 4003 return objAPITemp; 4004 } 4005 } 4006 4007 return null; 4008} 4009 4010function SCORM2004_WindowHasOpener(wnd){ 4011 WriteToDebug("In SCORM2004_WindowHasOpener"); 4012 if ((wnd.opener != null) && (wnd.opener != wnd) && (typeof(wnd.opener) != "undefined")){ 4013 WriteToDebug("Window Does Have Opener"); 4014 return true; 4015 } 4016 else{ 4017 WriteToDebug("Window Does Not Have Opener"); 4018 return false;
4019 } 4020} 4021 4022function SCORM2004_WindowHasParent(wnd){ 4023 WriteToDebug("In SCORM2004_WindowHasParent"); 4024 if ((wnd.parent != null) && (wnd.parent != wnd) && (typeof(wnd.parent) != "undefined")){ 4025 WriteToDebug("Window Does Have Parent"); 4026 return true; 4027 } 4028 else{ 4029 WriteToDebug("Window Does Not Have Parent"); 4030 return false; 4031 } 4032} 4033 4034 4035function SCORM2004_APIFound(obj){ 4036 WriteToDebug("In SCORM2004_APIFound"); 4037 if (obj == null || typeof(obj) == "undefined"){ 4038 WriteToDebug("API NOT Found"); 4039 return false; 4040 } 4041 else{ 4042 WriteToDebug("API Found"); 4043 return true; 4044 } 4045} 4046 4047var STANDARD = 'SCORM'; 4048 4049var SCORM_LOGOUT = "logout"; 4050var SCORM_SUSPEND = "suspend"; 4051var SCORM_NORMAL_EXIT = ""; 4052var SCORM_TIMEOUT = "time-out"; 4053 4054var SCORM_PASSED = "passed"; 4055var SCORM_FAILED = "failed"; 4056var SCORM_COMPLETED = "completed"; 4057var SCORM_BROWSED = "browsed"; 4058var SCORM_INCOMPLETE = "incomplete"; 4059var SCORM_NOT_ATTEMPTED = "not attempted"; 4060 4061var SCORM_CREDIT = "credit"; 4062var SCORM_NO_CREDIT = "no-credit"; 4063 4064var SCORM_BROWSE = "browse"; 4065var SCORM_NORMAL = "normal"; 4066var SCORM_REVIEW = "review"; 4067 4068var SCORM_ENTRY_ABINITIO = "ab-initio"; 4069var SCORM_ENTRY_RESUME = "resume"; 4070var SCORM_ENTRY_NORMAL = ""; 4071 4072 4073var SCORM_TLA_EXIT_MESSAGE = "exit,message"; 4074var SCORM_TLA_EXIT_NO_MESSAGE = "exit,no message"; 4075var SCORM_TLA_CONTINUE_MESSAGE = "continue,message"; 4076var SCORM_TLA_CONTINUE_NO_MESSAGE = "continue,no message"; 4077 4078var SCORM_RESULT_CORRECT = "correct"; 4079var SCORM_RESULT_WRONG = "wrong"; 4080var SCORM_RESULT_UNANTICIPATED = "unanticipated"; 4081var SCORM_RESULT_NEUTRAL = "neutral"; 4082 4083var SCORM_INTERACTION_TYPE_TRUE_FALSE = "true-false"; 4084var SCORM_INTERACTION_TYPE_CHOICE = "choice"; 4085var SCORM_INTERACTION_FILL_IN = "fill-in"; 4086var SCORM_INTERACTION_TYPE_MATCHING = "matching"; 4087var SCORM_INTERACTION_TYPE_PERFORMANCE = "performance"; 4088var SCORM_INTERACTION_TYPE_SEQUENCING = "sequencing"; 4089var SCORM_INTERACTION_TYPE_LIKERT = "likert"; 4090var SCORM_INTERACTION_TYPE_NUMERIC = "numeric"; 4091 4092 4093var SCORM_NO_ERROR = "0"; 4094var SCORM_ERROR_INVALID_PREFERENCE = "-1"; 4095var SCORM_ERROR_INVALID_STATUS = "-2"; 4096var SCORM_ERROR_INVALID_SPEED = "-3"; 4097var SCORM_ERROR_INVALID_TIMESPAN = "-4"; 4098var SCORM_ERROR_INVALID_TIME_LIMIT_ACTION = "-5"; 4099var SCORM_ERROR_INVALID_DECIMAL = "-6"; 4100var SCORM_ERROR_INVALID_CREDIT = "-7"; 4101var SCORM_ERROR_INVALID_LESSON_MODE = "-8"; 4102var SCORM_ERROR_INVALID_ENTRY = "-9"; 4103 4104var SCORM_TRUE = "true"; 4105var SCORM_FALSE = "false"; 4106 4107var SCORM_findAPITries = 0; 4108var SCORM_objAPI = null; 4109 4110var intSCORMError = SCORM_NO_ERROR; 4111var strSCORMErrorString = ""; 4112var strSCORMErrorDiagnostic = ""; 4113 4114var blnReviewModeSoReadOnly = false; 4115 4116function SCORM_Initialize(){ 4117 4118 var blnResult = true; 4119 4120 WriteToDebug("In SCORM_Initialize"); 4121 4122 SCORM_ClearErrorInfo(); 4123 4124 WriteToDebug("Grabbing API"); 4125 4126 try{ 4127 SCORM_objAPI = SCORM_GrabAPI(); 4128 } 4129 catch (e){ 4130 WriteToDebug("Error grabbing 1.2 API-" + e.name + ":" + e.message); 4131 } 4132 4133 if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){ 4134 WriteToDebug("Unable to acquire SCORM API:") 4135 WriteToDebug("SCORM_objAPI=" + typeof(SCORM_objAPI)); 4136 4137 InitializeExecuted(false, "Error - unable to acquire LMS API, content may not play properly and results may not be recorded. Please contact technical support."); 4138 return false; 4139 } 4140 4141 WriteToDebug("Calling LMSInit"); 4142 4143 blnResult = SCORM_CallLMSInitialize(); 4144 4145 if (! blnResult){ 4146 WriteToDebug("ERROR Initializing LMS"); 4147 4148 InitializeExecuted(false, "Error initializing communications with LMS"); 4149 4150 return false; 4151 } 4152 4153 //only reset status and such if we are not reviewing 4154 if (SCORM_GetLessonMode() != MODE_REVIEW){ 4155 4156 if (SCORM_IsContentInBrowseMode()){ 4157 WriteToDebug("Setting Status to Browsed"); 4158 blnResult = SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_BROWSED); 4159 } 4160 else{ 4161 if (! PREVENT_STATUS_CHANGE_DURING_INIT) { 4162 //only set the status to incomplete if it's not attempted yet 4163 if (SCORM_GetStatus() == LESSON_STATUS_NOT_ATTEMPTED){ 4164 WriteToDebug("Setting Status to Incomplete"); 4165 blnResult = SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_INCOMPLETE); 4166 } 4167 } 4168 } 4169 4170 //we want to set the exit type to suspend immediately because some LMS's only store data if they get a suspend request 4171 blnResult = SCORM_CallLMSSetValue("cmi.core.exit", SCORM_TranslateExitTypeToSCORM(DEFAULT_EXIT_TYPE)) && blnResult; 4172 } 4173 else{ 4174 //mode is review, check if we should go to read only mode 4175 if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){ 4176 blnReviewModeSoReadOnly = true; 4177 } 4178 } 4179 4180 WriteToDebug("Calling InitializeExecuted with parameter-" + blnResult); 4181 4182 InitializeExecuted(blnResult, ""); 4183 4184 return; 4185} 4186 4187 4188 4189
4190function SCORM_Finish(strExitType, blnStatusWasSet){ 4191 4192 var strStatusAfterCompletion; 4193 var blnResult = true; 4194 4195 WriteToDebug("In SCORM_Finish strExitType=" + strExitType + ", blnStatusWasSet=" + blnStatusWasSet); 4196 4197 SCORM_ClearErrorInfo(); 4198 4199 if ( (strExitType == EXIT_TYPE_FINISH) && ! blnStatusWasSet ){ 4200 4201 WriteToDebug("Getting completion status"); 4202 4203 strStatusAfterCompletion = SCORM_GetCompletionStatus(); 4204 4205 WriteToDebug("Setting completion status to " + strStatusAfterCompletion); 4206 4207 blnResult = SCORM_CallLMSSetValue("cmi.core.lesson_status", strStatusAfterCompletion) && blnResult; 4208 } 4209 4210 WriteToDebug("Setting Exit"); 4211 4212 blnResult = SCORM_CallLMSSetValue("cmi.core.exit", SCORM_TranslateExitTypeToSCORM(strExitType)) && blnResult; 4213 4214 WriteToDebug("Calling Commit"); 4215 4216 blnResult = SCORM_CallLMSCommit() && blnResult; 4217 4218 WriteToDebug("Calling Finish"); 4219 4220 blnResult = SCORM_CallLMSFinish() && blnResult; 4221 4222 WriteToDebug("Returning " + blnResult); 4223 4224 return blnResult; 4225 4226} 4227 4228 4229function SCORM_CommitData(){ 4230 WriteToDebug("In SCORM_CommitData"); 4231 SCORM_ClearErrorInfo(); 4232 return SCORM_CallLMSCommit(); 4233} 4234 4235 4236 4237//--------------------------------------------------------------------------------- 4238//General Get and Set Values 4239 4240function SCORM_GetStudentID(){ 4241 WriteToDebug("In SCORM_GetStudentID"); 4242 SCORM_ClearErrorInfo(); 4243 return SCORM_CallLMSGetValue("cmi.core.student_id"); 4244} 4245 4246function SCORM_GetStudentName(){ 4247 WriteToDebug("In SCORM_GetStudentName"); 4248 SCORM_ClearErrorInfo(); 4249 return SCORM_CallLMSGetValue("cmi.core.student_name"); 4250} 4251 4252function SCORM_GetBookmark(){ 4253 WriteToDebug("In SCORM_GetBookmark"); 4254 SCORM_ClearErrorInfo(); 4255 return SCORM_CallLMSGetValue("cmi.core.lesson_location"); 4256} 4257 4258function SCORM_SetBookmark(strBookmark){ 4259 WriteToDebug("In SCORM_SetBookmark strBookmark=" + strBookmark); 4260 SCORM_ClearErrorInfo(); 4261 return SCORM_CallLMSSetValue("cmi.core.lesson_location", strBookmark); 4262} 4263 4264function SCORM_GetDataChunk(){ 4265 WriteToDebug("In SCORM_GetDataChunk"); 4266 SCORM_ClearErrorInfo(); 4267 return SCORM_CallLMSGetValue("cmi.suspend_data"); 4268} 4269 4270function SCORM_SetDataChunk(strData){ 4271 //need to check for character limits here 4096 characters 4272 WriteToDebug("In SCORM_SetDataChunk"); 4273 SCORM_ClearErrorInfo(); 4274 4275 if(USE_STRICT_SUSPEND_DATA_LIMITS==true) { 4276 if(strData.length > 4096) { 4277 WriteToDebug("SCORM_SetDataChunk - suspend_data too large (4096 character limit for SCORM 1.2)"); 4278 return false; 4279 }else{ 4280 return SCORM_CallLMSSetValue("cmi.suspend_data", strData); 4281 } 4282 }else{ 4283 return SCORM_CallLMSSetValue("cmi.suspend_data", strData); 4284 } 4285} 4286 4287 4288function SCORM_GetLaunchData(){ 4289 WriteToDebug("In SCORM_GetLaunchData"); 4290 SCORM_ClearErrorInfo(); 4291 return SCORM_CallLMSGetValue("cmi.launch_data"); 4292} 4293 4294function SCORM_GetComments(){ 4295 WriteToDebug("In SCORM_GetComments"); 4296 SCORM_ClearErrorInfo(); 4297 return SCORM_CallLMSGetValue("cmi.comments"); 4298} 4299 4300function SCORM_WriteComment(strComment){ 4301 WriteToDebug("In SCORM_WriteComment strComment=" + strComment); 4302 SCORM_ClearErrorInfo(); 4303 return SCORM_CallLMSSetValue("cmi.comments", strComment); 4304} 4305 4306function SCORM_GetLMSComments(){ 4307 WriteToDebug("In SCORM_GetLMSComments"); 4308 SCORM_ClearErrorInfo(); 4309 return SCORM_CallLMSGetValue("cmi.comments_from_lms"); 4310} 4311 4312 4313function SCORM_GetAudioPlayPreference(){ 4314 4315 var intTempPreference; 4316 4317 WriteToDebug("In SCORM_GetAudioPlayPreference"); 4318 4319 SCORM_ClearErrorInfo(); 4320 4321 intTempPreference = SCORM_CallLMSGetValue("cmi.student_preference.audio"); 4322 4323 if (intTempPreference == ""){ 4324 intTempPreference = 0; 4325 } 4326 4327 intTempPreference = parseInt(intTempPreference, 10); 4328 4329 WriteToDebug("intTempPreference=" + intTempPreference); 4330 4331 if (intTempPreference > 0){ 4332 WriteToDebug("Returning On"); 4333 return PREFERENCE_ON; 4334 } 4335 else if (intTempPreference == 0){ 4336 WriteToDebug("Returning Default"); 4337 return PREFERENCE_DEFAULT; 4338 } 4339 else if (intTempPreference < 0) { 4340 WriteToDebug("returning Off"); 4341 return PREFERENCE_OFF; 4342 } 4343 else{ 4344 WriteToDebug("Error: Invalid preference"); 4345 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_PREFERENCE, 4346 "Invalid audio preference received from LMS", 4347 "intTempPreference=" + intTempPreference); 4348 return null; 4349 } 4350} 4351 4352 4353 4354function SCORM_GetAudioVolumePreference(){ 4355 4356 var intTempPreference; 4357 4358 WriteToDebug("In SCORM_GetAudioVollumePreference"); 4359 4360 SCORM_ClearErrorInfo(); 4361 4362 intTempPreference = SCORM_CallLMSGetValue("cmi.student_preference.audio"); 4363 4364 WriteToDebug("intTempPreference=" + intTempPreference); 4365 4366 if (intTempPreference == "") { 4367 intTempPreference = 100; 4368 } 4369 4370 intTempPreference = parseInt(intTempPreference, 10); 4371 4372 if (intTempPreference <= 0){ 4373 WriteToDebug("Setting to 100"); 4374 intTempPreference = 100; 4375 } 4376 4377 if (! (intTempPreference > 0 && intTempPreference <= 100)){
4378 WriteToDebug("ERROR: invalid preference"); 4379 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_PREFERENCE, 4380 "Invalid audio preference received from LMS", 4381 "intTempPreference=" + intTempPreference); 4382 return null; 4383 } 4384 4385 WriteToDebug("Returning " + intTempPreference); 4386 return intTempPreference; 4387 4388} 4389 4390function SCORM_SetAudioPreference(PlayPreference, intPercentOfMaxVolume){ 4391 4392 WriteToDebug("In SCORM_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume); 4393 4394 SCORM_ClearErrorInfo(); 4395 4396 if (PlayPreference == PREFERENCE_OFF){ 4397 WriteToDebug("Setting percent to -1 - OFF"); 4398 intPercentOfMaxVolume = -1; 4399 } 4400 4401 return SCORM_CallLMSSetValue("cmi.student_preference.audio", intPercentOfMaxVolume); 4402} 4403 4404 4405 4406function SCORM_SetLanguagePreference(strLanguage){ 4407 WriteToDebug("In SCORM_SetLanguagePreference strLanguage=" + strLanguage); 4408 SCORM_ClearErrorInfo(); 4409 return SCORM_CallLMSSetValue("cmi.student_preference.language", strLanguage); 4410 4411} 4412 4413 4414function SCORM_GetLanguagePreference(){ 4415 WriteToDebug("In SCORM_GetLanguagePreference"); 4416 SCORM_ClearErrorInfo(); 4417 return SCORM_CallLMSGetValue("cmi.student_preference.language"); 4418 4419} 4420 4421 4422function SCORM_SetSpeedPreference(intPercentOfMax){ 4423 4424 var intSCORMSpeed; //SCORM's scale is -100 to +100, our range is 1 to 100 4425 4426 WriteToDebug("In SCORM_SetSpeedPreference intPercentOfMax=" + intPercentOfMax); 4427 4428 SCORM_ClearErrorInfo(); 4429 4430 intSCORMSpeed = (intPercentOfMax * 2) - 100; 4431 4432 WriteToDebug("intSCORMSpeed=" + intSCORMSpeed); 4433 4434 return SCORM_CallLMSSetValue("cmi.student_preference.speed", intSCORMSpeed); 4435 4436} 4437 4438 4439function SCORM_GetSpeedPreference(){ 4440 4441 var intSCORMSpeed; 4442 var intPercentOfMax; 4443 4444 WriteToDebug("In SCORM_GetSpeedPreference"); 4445 4446 SCORM_ClearErrorInfo(); 4447 4448 intSCORMSpeed = SCORM_CallLMSGetValue("cmi.student_preference.speed"); 4449 4450 WriteToDebug("intSCORMSpeed=" + intSCORMSpeed); 4451 4452 if (intSCORMSpeed == ""){ 4453 WriteToDebug("Detected empty string, defaulting to 100"); 4454 intSCORMSpeed = 100; 4455 } 4456 4457 if ( ! ValidInteger(intSCORMSpeed) ){ 4458 WriteToDebug("ERROR - invalid integer"); 4459 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_SPEED, 4460 "Invalid speed preference received from LMS - not an integer", 4461 "intSCORMSpeed=" + intSCORMSpeed); 4462 return null; 4463 } 4464 4465 intSCORMSpeed = parseInt(intSCORMSpeed, 10); 4466 4467 if (intSCORMSpeed < -100 || intSCORMSpeed > 100){ 4468 WriteToDebug("ERROR - out of range"); 4469 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_SPEED, 4470 "Invalid speed preference received from LMS - out of range", 4471 "intSCORMSpeed=" + intSCORMSpeed); 4472 return null; 4473 } 4474 4475 intPercentOfMax = (intSCORMSpeed + 100) / 2; 4476 intPercentOfMax = parseInt(intPercentOfMax, 10); 4477 4478 WriteToDebug("Returning " + intPercentOfMax); 4479 4480 return intPercentOfMax; 4481 4482} 4483 4484 4485 4486 4487function SCORM_SetTextPreference(intPreference){ 4488 WriteToDebug("In SCORM_SetTextPreference intPreference=" + intPreference); 4489 4490 SCORM_ClearErrorInfo(); 4491 4492 return SCORM_CallLMSSetValue("cmi.student_preference.text", intPreference); 4493} 4494 4495 4496function SCORM_GetTextPreference(){ 4497 var intTempPreference; 4498 4499 WriteToDebug("In SCORM_GetTextPreference"); 4500 4501 SCORM_ClearErrorInfo(); 4502 4503 intTempPreference = SCORM_CallLMSGetValue("cmi.student_preference.text"); 4504 intTempPreference = parseInt(intTempPreference, 10); 4505 4506 WriteToDebug("intTempPreference=" + intTempPreference); 4507 4508 if (intTempPreference > 0){ 4509 WriteToDebug("Returning On"); 4510 return PREFERENCE_ON; 4511 } 4512 else if (intTempPreference == 0 || intTempPreference == ""){ 4513 WriteToDebug("Returning Default"); 4514 return PREFERENCE_DEFAULT; 4515 } 4516 else if (intTempPreference < 0) { 4517 WriteToDebug("returning Off"); 4518 return PREFERENCE_OFF; 4519 } 4520 else{ 4521 WriteToDebug("Error: Invalid preference"); 4522 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_PREFERENCE, 4523 "Invalid text preference received from LMS", 4524 "intTempPreference=" + intTempPreference); 4525 return null; 4526 } 4527} 4528 4529 4530 4531//--------------------------------------------------------------------------------- 4532//Time Management Functions 4533function SCORM_GetPreviouslyAccumulatedTime(){ 4534 4535 var strCMITime; 4536 var intMilliseconds; 4537 4538 WriteToDebug("In SCORM_GetPreviouslyAccumulatedTime"); 4539 4540 SCORM_ClearErrorInfo(); 4541 4542 strCMITime = SCORM_CallLMSGetValue("cmi.core.total_time") 4543 4544 WriteToDebug("strCMITime=" + strCMITime); 4545 4546 if (! IsValidCMITimeSpan(strCMITime)){ 4547 WriteToDebug("ERROR - Invalid CMITimeSpan"); 4548 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIMESPAN, 4549 "Invalid timespan received from LMS", 4550 "strTime=" + strCMITime); 4551 return null; 4552 } 4553 4554 intMilliseconds = ConvertCMITimeSpanToMS(strCMITime); 4555 4556 WriteToDebug("Returning " + intMilliseconds); 4557 4558 return intMilliseconds; 4559 4560} 4561 4562 4563function SCORM_SaveTime(intMilliSeconds){ 4564 4565 var strCMITime; 4566 4567 WriteToDebug("In SCORM_SaveTime intMilliSeconds=" + intMilliSeconds); 4568 4569 SCORM_ClearErrorInfo(); 4570 4571 strCMITime = ConvertMilliSecondsToSCORMTime(intMilliSeconds, true); 4572 4573 WriteToDebug("strCMITime=" + strCMITime); 4574 4575 return SCORM_CallLMSSetValue("cmi.core.session_time", strCMITime); 4576} 4577 4578function SCORM_GetMaxTimeAllowed(){ 4579 4580 var strCMITime; 4581 var intMilliseconds; 4582 4583 WriteToDebug("In SCORM_GetMaxTimeAllowed"); 4584 4585 SCORM_ClearErrorInfo(); 4586 4587 strCMITime = SCORM_CallLMSGetValue("cmi.student_data.max_time_allowed") 4588 4589 WriteToDebug("strCMITime=" + strCMITime); 4590 4591 if (strCMITime == ""){ 4592 strCMITime = "9999:99:99.99"; 4593 } 4594 4595 if (! IsValidCMITimeSpan(strCMITime)){ 4596 WriteToDebug("ERROR - Invalid CMITimeSpan"); 4597 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIMESPAN, 4598 "Invalid timespan received from LMS", 4599 "strTime=" + strCMITime); 4600 return null; 4601 } 4602 4603 intMilliseconds = ConvertCMITimeSpanToMS(strCMITime); 4604 4605 WriteToDebug("intMilliseconds=" + intMilliseconds); 4606 4607 return intMilliseconds; 4608} 4609 4610 4611 4612function SCORM_DisplayMessageOnTimeout(){ 4613 4614 var strTLA; 4615 4616 SCORM_ClearErrorInfo(); 4617 4618 WriteToDebug("In SCORM_DisplayMessageOnTimeout"); 4619 4620 strTLA = SCORM_CallLMSGetValue("cmi.student_data.time_limit_action"); 4621 4622 WriteToDebug("strTLA=" + strTLA); 4623 4624 if (strTLA == SCORM_TLA_EXIT_MESSAGE || strTLA == SCORM_TLA_CONTINUE_MESSAGE){ 4625 WriteToDebug("returning true"); 4626 return true; 4627 } 4628 else if(strTLA == SCORM_TLA_EXIT_NO_MESSAGE || strTLA == SCORM_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){ 4629 WriteToDebug("returning false"); 4630 return false;
4631 } 4632 else{ 4633 WriteToDebug("Error invalid TLA"); 4634 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIME_LIMIT_ACTION, 4635 "Invalid time limit action received from LMS", 4636 "strTLA=" + strTLA); 4637 4638 return null; 4639 } 4640 4641} 4642 4643function SCORM_ExitOnTimeout(){ 4644 4645 var strTLA; 4646 4647 WriteToDebug("In SCORM_ExitOnTimeout"); 4648 4649 SCORM_ClearErrorInfo(); 4650 4651 strTLA = SCORM_CallLMSGetValue("cmi.student_data.time_limit_action"); 4652 4653 WriteToDebug("strTLA=" + strTLA); 4654 4655 if (strTLA == SCORM_TLA_EXIT_MESSAGE || strTLA == SCORM_TLA_EXIT_NO_MESSAGE){ 4656 WriteToDebug("returning true"); 4657 return true; 4658 } 4659 else if(strTLA == SCORM_TLA_CONTINUE_MESSAGE || strTLA == SCORM_TLA_CONTINUE_NO_MESSAGE || strTLA == ""){ 4660 WriteToDebug("returning false"); 4661 return false; 4662 } 4663 else{ 4664 WriteToDebug("ERROR invalid TLA"); 4665 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_TIME_LIMIT_ACTION, 4666 "Invalid time limit action received from LMS", 4667 "strTLA=" + strTLA); 4668 4669 return null; 4670 } 4671 4672} 4673 4674 4675function SCORM_GetPassingScore(){ 4676 var fltScore; 4677 4678 WriteToDebug("In SCORM_GetPassingScore"); 4679 4680 SCORM_ClearErrorInfo(); 4681 4682 fltScore = SCORM_CallLMSGetValue("cmi.student_data.mastery_score") 4683 4684 WriteToDebug("fltScore=" + fltScore); 4685 4686 if (fltScore == ""){ 4687 fltScore = 0; 4688 } 4689 4690 if ( ! IsValidDecimal(fltScore)){ 4691 WriteToDebug("Error - score is not a valid decimal"); 4692 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_DECIMAL, 4693 "Invalid mastery score received from LMS", 4694 "fltScore=" + fltScore); 4695 return null; 4696 } 4697 4698 fltScore = parseFloat(fltScore); 4699 4700 WriteToDebug("returning fltScore"); 4701 4702 return fltScore; 4703} 4704 4705 4706function SCORM_SetScore(intScore, intMaxScore, intMinScore){ 4707 4708 var blnResult; 4709 intScore = RoundToPrecision(intScore, 7); 4710 intMaxScore = RoundToPrecision(intMaxScore, 7); 4711 intMinScore = RoundToPrecision(intMinScore, 7); 4712 4713 WriteToDebug("In SCORM_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 4714 4715 SCORM_ClearErrorInfo(); 4716 4717 blnResult = SCORM_CallLMSSetValue("cmi.core.score.raw", intScore); 4718 blnResult = SCORM_CallLMSSetValue("cmi.core.score.max", intMaxScore) && blnResult; 4719 blnResult = SCORM_CallLMSSetValue("cmi.core.score.min", intMinScore) && blnResult; 4720 4721 WriteToDebug("Returning " + blnResult); 4722 4723 return blnResult; 4724} 4725 4726function SCORM_GetScore(){ 4727 4728 4729 WriteToDebug("In SCORM_GetScore"); 4730 4731 SCORM_ClearErrorInfo(); 4732 4733 return SCORM_CallLMSGetValue("cmi.core.score.raw"); 4734 4735} 4736 4737function SCORM_SetPointBasedScore(intScore, intMaxScore, intMinScore){ 4738 WriteToDebug("SCORM_SetPointBasedScore - SCORM 1.1 and 1.2 do not support SetPointBasedScore, falling back to SetScore"); 4739 return SCORM_SetScore(intScore, intMaxScore, intMinScore); 4740} 4741 4742function SCORM_GetScaledScore(intScore, intMaxScore, intMinScore){ 4743 WriteToDebug("SCORM_GetScaledScore - SCORM 1.1 and 1.2 do not support GetScaledScore, returning false"); 4744 return false; 4745} 4746 4747function SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, 4748 intWeighting, intLatency, strLearningObjectiveID, dtmTime, scormInteractionType, 4749 strAlternateResponse, strAlternateCorrectResponse){ 4750 4751 var blnResult; 4752 var blnTempResult; 4753 var intInteractionIndex; 4754 var strResult; 4755 4756 4757 SCORM_ClearErrorInfo(); 4758 4759 //In SCORM 1.2, we always want to add a new interaction rather than updating an old interaction. 4760 //This is because some LMS vendors have mis-interpreted the "write only" stipulation on interactions to mean "write once" 4761 intInteractionIndex = SCORM_CallLMSGetValue("cmi.interactions._count"); 4762 4763 WriteToDebug("intInteractionIndex=" + intInteractionIndex); 4764 4765 if (intInteractionIndex == ""){ 4766 WriteToDebug("Setting Interaction Index to 0"); 4767 intInteractionIndex = 0; 4768 } 4769 4770 //need to leave support for blnCorrect=t/f for legacy implementations of RSECA 4771 if (IsNumeric(blnCorrect)) { 4772 strResult = blnCorrect; 4773 } 4774 else { 4775 if (blnCorrect == true || blnCorrect == INTERACTION_RESULT_CORRECT){ 4776 strResult = SCORM_RESULT_CORRECT; 4777 } 4778 else if (blnCorrect == "" || blnCorrect == "false" || blnCorrect == INTERACTION_RESULT_WRONG){ //compare against the string "false" because ("" == false) evaluates to true 4779 strResult = SCORM_RESULT_WRONG; 4780 } 4781 else if (blnCorrect == INTERACTION_RESULT_UNANTICIPATED){ 4782 strResult = SCORM_RESULT_UNANTICIPATED; 4783 } 4784 else if (blnCorrect == INTERACTION_RESULT_NEUTRAL){ 4785 strResult = SCORM_RESULT_NEUTRAL; 4786 } 4787 } 4788 4789 WriteToDebug("strResult=" + strResult); 4790 4791 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".id", strID); 4792 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".type", scormInteractionType) && blnResult; 4793 4794 4795 //try to save the data using the verbose description of the interaciton results (this is not strictly conformant, but most LMS's will allow it and it still passes the Test Suite 4796 //if the long version errs, try again with the short version (strict adherence to the standard) - this applies to response and correct response 4797
4798 if (strResponse !== null) { 4799 blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".student_response", strResponse); 4800 4801 if (blnTempResult == false && strAlternateResponse !== null){ 4802 blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".student_response", strAlternateResponse); 4803 4804 // 4805 // by trying the long version and then falling back to the short version there may have 4806 // been an error set that should be cleared if the short version works, unfortunately 4807 // this obscures any error in the first two calls, but any error in those calls would 4808 // have already been obscured by trying a long version that failed, therefore it was 4809 // determined that it was better to go ahead and clear the error because that particular 4810 // piece of data (the response) was actually captured, and any of the calls after this 4811 // point could still have a proper error *and* that the first two calls are the ones 4812 // most likely to succeed and if they don't then things are really broken 4813 // 4814 if (blnTempResult == true) { 4815 SCORM_ClearErrorInfo(); 4816 } 4817 } 4818 } 4819 4820 blnResult = blnResult && blnTempResult; 4821 4822 if (strCorrectResponse != undefined && strCorrectResponse != null && strCorrectResponse != ""){ 4823 4824 blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern", strCorrectResponse); 4825 if (blnTempResult == false){ 4826 blnTempResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".correct_responses.0.pattern", strAlternateCorrectResponse); 4827 4828 // see comment above 4829 if (blnTempResult == true) { 4830 SCORM_ClearErrorInfo(); 4831 } 4832 } 4833 4834 blnResult = blnResult && blnTempResult; 4835 } 4836 4837 if (strResult != undefined && strResult != null && strResult != ""){ 4838 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".result", strResult) && blnResult; 4839 } 4840 4841 //ignore the description parameter in SCORM 1.2, there is nothing we can do with it 4842 4843 if (intWeighting != undefined && intWeighting != null && intWeighting != ""){ 4844 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".weighting", intWeighting) && blnResult; 4845 } 4846 4847 if (intLatency != undefined && intLatency != null && intLatency != ""){ 4848 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".latency", ConvertMilliSecondsToSCORMTime(intLatency, true)) && blnResult; 4849 } 4850 4851 if (strLearningObjectiveID != undefined && strLearningObjectiveID != null && strLearningObjectiveID != ""){ 4852 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".objectives.0.id", strLearningObjectiveID) && blnResult; 4853 } 4854 4855 blnResult = SCORM_CallLMSSetValue("cmi.interactions." + intInteractionIndex + ".time", ConvertDateToCMITime(dtmTime)) && blnResult; 4856 4857 WriteToDebug("Returning " + blnResult); 4858 4859 return blnResult; 4860} 4861 4862function SCORM_RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 4863 4864 WriteToDebug("In SCORM_RecordTrueFalseInteraction strID=" + strID + ", strResponse=" + strResponse + 4865 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 4866 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 4867 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 4868 4869 var strResponse = null; 4870 var strCorrectResponse = null; 4871 4872 if (blnResponse == true){ 4873 strResponse = "t"; 4874 } 4875 else if (blnResponse !== null) { 4876 strResponse = "f"; 4877 } 4878 4879 if (blnCorrectResponse == true){ 4880 strCorrectResponse = "t"; 4881 } 4882 else if(blnCorrectResponse == false){ //test for false b/c it could be null in which case we want to leave it as `null` 4883 strCorrectResponse = "f"; 4884 } 4885 4886 return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, 4887 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_TRUE_FALSE, 4888 strResponse, strCorrectResponse); 4889} 4890 4891function SCORM_RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 4892 4893 WriteToDebug("In SCORM_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse + 4894 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 4895 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 4896 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 4897 4898 4899 var strResponse = null; 4900 var strResponseLong = null; 4901 4902 var strCorrectResponse = ""; 4903 var strCorrectResponseLong = ""; 4904 4905 if (aryResponse !== null) { 4906 strResponse = ""; 4907 strResponseLong = ""; 4908 4909 for (var i=0; i < aryResponse.length; i++){ 4910
4911 if (strResponse.length > 0) {strResponse += ",";} 4912 if (strResponseLong.length > 0) {strResponseLong += ",";} 4913 4914 strResponse += aryResponse[i].Short; 4915 strResponseLong += aryResponse[i].Long; 4916 } 4917 } 4918 4919 for (var i=0; i < aryCorrectResponse.length; i++){ 4920 4921 if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} 4922 if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} 4923 4924 strCorrectResponse += aryCorrectResponse[i].Short; 4925 strCorrectResponseLong += aryCorrectResponse[i].Long; 4926 } 4927 4928 var blnSuccessfullySaved; 4929 4930 blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, 4931 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_CHOICE, 4932 strResponse, strCorrectResponse); 4933 4934 return blnSuccessfullySaved; 4935 4936} 4937 4938 4939function SCORM_RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 4940 4941 WriteToDebug("In SCORM_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse + 4942 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 4943 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 4944 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 4945 4946 4947 strResponse = new String(strResponse); 4948 if (strResponse.length > 255){strResponse = strResponse.substr(0, 255);} 4949 4950 if (strCorrectResponse == null){ 4951 strCorrectResponse = ""; 4952 } 4953 4954 strCorrectResponse = new String(strCorrectResponse); 4955 if (strCorrectResponse.length > 255){strCorrectResponse = strCorrectResponse.substr(0, 255);} 4956 4957 return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, 4958 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_FILL_IN, 4959 strResponse, strCorrectResponse); 4960} 4961 4962function SCORM_RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 4963 4964 WriteToDebug("In SCORM_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 4965 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 4966 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 4967 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 4968 4969 var strResponse = null; 4970 var strResponseLong = null; 4971 4972 var strCorrectResponse = ""; 4973 var strCorrectResponseLong = ""; 4974 4975 if (aryResponse !== null) { 4976 strResponse = ""; 4977 strResponseLong = ""; 4978 4979 for (var i=0; i < aryResponse.length; i++){ 4980 4981 if (strResponse.length > 0) {strResponse += ",";} 4982 if (strResponseLong.length > 0) {strResponseLong += ",";} 4983 4984 strResponse += aryResponse[i].Source.Short + "." + aryResponse[i].Target.Short; 4985 strResponseLong += aryResponse[i].Source.Long + "." + aryResponse[i].Target.Long; 4986 } 4987 } 4988 4989 for (var i=0; i < aryCorrectResponse.length; i++){ 4990 4991 if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} 4992 if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} 4993 4994 strCorrectResponse += aryCorrectResponse[i].Source.Short + "." + aryCorrectResponse[i].Target.Short; 4995 strCorrectResponseLong += aryCorrectResponse[i].Source.Long + "." + aryCorrectResponse[i].Target.Long; 4996 } 4997 4998 var blnSuccessfullySaved; 4999 5000 blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, 5001 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_MATCHING, 5002 strResponse, strCorrectResponse); 5003 5004 5005 return blnSuccessfullySaved; 5006 5007} 5008 5009function SCORM_RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 5010 5011 WriteToDebug("In SCORM_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse + 5012 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 5013 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 5014 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 5015
5016 if (strResponse !== null) { 5017 strResponse = new String(strResponse); 5018 if (strResponse.length > 255){strResponse = strResponse.substr(0, 255);} 5019 } 5020 5021 if (strCorrectResponse == null){ 5022 strCorrectResponse = ""; 5023 } 5024 5025 strCorrectResponse = new String(strCorrectResponse); 5026 if (strCorrectResponse.length > 255){strCorrectResponse = strCorrectResponse.substr(0, 255);} 5027 5028 return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, 5029 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_PERFORMANCE, 5030 strResponse, strCorrectResponse); 5031} 5032 5033function SCORM_RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 5034 5035 WriteToDebug("In SCORM_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 5036 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 5037 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 5038 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 5039 5040 5041 var strResponse = null; 5042 var strResponseLong = null; 5043 5044 var strCorrectResponse = ""; 5045 var strCorrectResponseLong = ""; 5046 5047 if (aryResponse !== null) { 5048 strResponse = ""; 5049 strResponseLong = ""; 5050 5051 for (var i=0; i < aryResponse.length; i++){ 5052 5053 if (strResponse.length > 0) {strResponse += ",";} 5054 if (strResponseLong.length > 0) {strResponseLong += ",";} 5055 5056 strResponse += aryResponse[i].Short; 5057 strResponseLong += aryResponse[i].Long; 5058 } 5059 } 5060 5061 for (var i=0; i < aryCorrectResponse.length; i++){ 5062 5063 if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} 5064 if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} 5065 5066 strCorrectResponse += aryCorrectResponse[i].Short; 5067 strCorrectResponseLong += aryCorrectResponse[i].Long; 5068 } 5069 5070 var blnSuccessfullySaved; 5071 5072 blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, 5073 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_SEQUENCING, 5074 strResponse, strCorrectResponse); 5075 5076 5077 return blnSuccessfullySaved; 5078 5079} 5080 5081function SCORM_RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 5082 5083 WriteToDebug("In SCORM_RecordLikertInteraction strID=" + strID + ", response=" + response + 5084 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 5085 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 5086 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 5087 5088 var strResponse = null; 5089 var strResponseLong = null; 5090 5091 var strCorrectResponse = ""; 5092 var strCorrectResponseLong = ""; 5093 5094 if (response !== null) { 5095 strResponse = response.Short; 5096 strResponseLong = response.Long; 5097 } 5098 5099 if (correctResponse != null){ 5100 strCorrectResponse = correctResponse.Short; 5101 strCorrectResponseLong = correctResponse.Long; 5102 } 5103 5104 var blnSuccessfullySaved; 5105 5106 blnSuccessfullySaved = SCORM_RecordInteraction(strID, strResponseLong, blnCorrect, strCorrectResponseLong, strDescription, 5107 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_LIKERT, 5108 strResponse, strCorrectResponse); 5109 5110 return blnSuccessfullySaved; 5111} 5112 5113function SCORM_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 5114 5115 WriteToDebug("In SCORM_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse + 5116 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 5117 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 5118 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 5119 5120 if (strCorrectResponse != undefined && strCorrectResponse != null) { 5121 if (IsValidDecimalRange(strCorrectResponse)) 5122 { 5123 //we could receive a decimal range correct response from SCORM 2004/Tin Can/Cmi5. If we do we need to convert that 5124 //to a decimal for use in SCORM 1.2. 5125 strCorrectResponse = ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strCorrectResponse, strResponse, blnCorrect); 5126 } 5127 5128 //SCORM 1.2 does not accept decimal range correct answers, so if it's not a decimal by this point we fail 5129 if (!IsValidDecimal(strCorrectResponse)){ 5130 WriteToDebug("Returning False - SCORM_RecordNumericInteraction received invali
5130d correct response (not a decimal), strCorrectResponse=" +strCorrectResponse); 5131 return false; 5132 } 5133 } 5134 5135 return SCORM_RecordInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, 5136 intWeighting, intLatency, strLearningObjectiveID, dtmTime, SCORM_INTERACTION_TYPE_NUMERIC, 5137 strResponse, strCorrectResponse); 5138} 5139 5140 5141function SCORM_GetEntryMode(){ 5142 var strEntry; 5143 5144 WriteToDebug("In SCORM_GetEntryMode"); 5145 5146 SCORM_ClearErrorInfo(); 5147 5148 strEntry = SCORM_CallLMSGetValue("cmi.core.entry"); 5149 5150 WriteToDebug("strEntry=" + strEntry); 5151 5152 if (strEntry == SCORM_ENTRY_ABINITIO){ 5153 WriteToDebug("Returning first time"); 5154 return ENTRY_FIRST_TIME; 5155 } 5156 else if (strEntry == SCORM_ENTRY_RESUME){ 5157 WriteToDebug("Returning resume"); 5158 return ENTRY_RESUME; 5159 } 5160 else if (strEntry == SCORM_ENTRY_NORMAL){ 5161 WriteToDebug("returning normal"); 5162 return ENTRY_REVIEW; 5163 } 5164 else{ 5165 WriteToDebug("ERROR - invalide entry mode"); 5166 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_ENTRY, 5167 "Invalid entry vocab received from LMS", 5168 "strEntry=" + strEntry); 5169 return null; 5170 } 5171 5172} 5173 5174function SCORM_GetLessonMode(){ 5175 5176 var strLessonMode; 5177 5178 WriteToDebug("In SCORM_GetLessonMode"); 5179 5180 SCORM_ClearErrorInfo(); 5181 5182 strLessonMode = SCORM_CallLMSGetValue("cmi.core.lesson_mode"); 5183 5184 WriteToDebug("strLessonMode=" + strLessonMode); 5185 5186 if (strLessonMode == SCORM_BROWSE){ 5187 WriteToDebug("Returning browse"); 5188 return MODE_BROWSE; 5189 } 5190 else if(strLessonMode == SCORM_NORMAL){ 5191 WriteToDebug("returning normal"); 5192 return MODE_NORMAL; 5193 } 5194 else if(strLessonMode == SCORM_REVIEW){ 5195 WriteToDebug("Returning Review"); 5196 return MODE_REVIEW; 5197 } 5198 else{ 5199 WriteToDebug("ERROR - invalid lesson mode"); 5200 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_LESSON_MODE, 5201 "Invalid lesson_mode vocab received from LMS", 5202 "strLessonMode=" + strLessonMode); 5203 return null; 5204 } 5205 5206} 5207 5208function SCORM_GetTakingForCredit(){ 5209 5210 var strCredit; 5211 5212 WriteToDebug("In SCORM_GetTakingForCredit"); 5213 5214 SCORM_ClearErrorInfo(); 5215 5216 strCredit = SCORM_CallLMSGetValue("cmi.core.credit"); 5217 5218 WriteToDebug("strCredit=" + strCredit); 5219 5220 if (strCredit == "credit"){ 5221 WriteToDebug("Returning true"); 5222 return true; 5223 } 5224 else if (strCredit == "no-credit"){ 5225 WriteToDebug("Returning false"); 5226 return false; 5227 } 5228 else{ 5229 WriteToDebug("ERROR - invalid credit"); 5230 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_CREDIT, 5231 "Invalid credit vocab received from LMS", 5232 "strCredit=" + strCredit); 5233 return null; 5234 } 5235} 5236 5237 5238 5239function SCORM_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){ 5240 5241 var intObjectiveIndex; 5242 var blnResult; 5243 5244 intScore = RoundToPrecision(intScore, 7); 5245 intMaxScore = RoundToPrecision(intMaxScore, 7); 5246 intMinScore = RoundToPrecision(intMinScore, 7); 5247 5248 5249 WriteToDebug("In SCORM_SetObjectiveScore, strObejctiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 5250 5251 SCORM_ClearErrorInfo(); 5252 5253 intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID); 5254 5255 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 5256 5257 blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID); 5258 blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".score.raw", intScore) && blnResult; 5259 blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".score.max", intMaxScore) && blnResult; 5260 blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".score.min", intMinScore) && blnResult; 5261 5262 WriteToDebug("Returning " + blnResult); 5263 5264 return blnResult; 5265 5266} 5267 5268function SCORM_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){ 5269 5270 var intObjectiveIndex; 5271 var blnResult; 5272 5273 WriteToDebug("In SCORM_SetObjectiveDescription, strObjectiveDescription=" + strObjectiveDescription); 5274 WriteToDebug("Objective Descriptions are not supported prior to SCORM 2004"); 5275 5276 SCORM_ClearErrorInfo(); 5277 5278 blnResult = SCORM_TRUE; 5279 5280 WriteToDebug("Returning " + blnResult); 5281 5282 return blnResult; 5283 5284} 5285 5286 5287 5288function SCORM_SetObjectiveStatus(strObjectiveID, Lesson_Status){ 5289 5290 var intObjectiveIndex; 5291 var blnResult; 5292 var strSCORMStatus = ""; 5293 5294 WriteToDebug("In SCORM_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status); 5295 5296 SCORM_ClearErrorInfo(); 5297 5298 intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID); 5299 5300 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 5301 5302 if (Lesson_Status == LESSON_STATUS_PASSED){ 5303 strSCORMStatus = SCORM_PASSED; 5304 } 5305 else if (Lesson_Status == LESSON_STATUS_FAILED){ 5306 strSCORMStatus = SCORM_FAILED; 5307 } 5308 else if (Lesson_Status == LESSON_STATUS_COMPLETED){ 5309 strSCORMStatus = SCORM_COMPLETED; 5310 } 5311 else if (Lesson_Status == LESSON_STATUS_BROWSED){ 5312 strSCORMStatus = SCORM_BROWSED; 5313 } 5314 else if (Lesson_Status == LESSON_STATUS_INCOMPLETE){ 5315 strSCORMStatus = SCORM_INCOMPLETE; 5316 } 5317 else if (Lesson_Status == LESSON_STATUS_NOT_ATTEMPTED){ 5318 strSCORMStatus = SCORM_NOT_ATTEMPTED; 5319 } 5320 5321 WriteToDebug("strSCORMStatus=" + strSCORMStatus); 5322 5323 blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".id", strObjectiveID); 5324 blnResult = SCORM_CallLMSSetValue("cmi.objectives." + intObjectiveIndex + ".status", strSCORMStatus) && blnResult; 5325 5326 WriteToDebug("Returning " + blnResult); 5327 5328 return blnResult; 5329} 5330 5331 5332 5333function SCORM_GetObjectiveScore(strObjectiveID){ 5334 5335 var intObjectiveIndex; 5336 5337 WriteToDebug("In SCORM_GetObjectiveScore, strObejctiveID=" + strObjectiveID); 5338 5339 SCORM_ClearErrorInfo(); 5340 5341 intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID); 5342 5343 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 5344 5345 return SCORM_CallLMSGetValue("cmi.objectives." + intObjectiveIndex + ".score.raw"); 5346 5347} 5348 5349 5350function SCORM_GetObjectiveDescription(strObjectiveID){ 5351 5352 WriteToDebug("In SCORM_GetObjectiveDescription, strObejctiveID=" + strObjectiveID); 5353 WriteToDebug("ObjectiveDescription is not supported prior to SCORM 2004"); 5354 return ""; 5355 5356} 5357 5358 5359function SCORM_GetObjectiveStatus(strObjectiveID){ 5360 5361 var intObjectiveIndex; 5362 var strStatus; 5363 5364 WriteToDebug("In SCORM_GetObjectiveStatus, strObejctiveID=" + strObjectiveID); 5365 5366 SCORM_ClearErrorInfo(); 5367 5368 intObjectiveIndex = SCORM_FindObjectiveIndexFromID(strObjectiveID); 5369 5370 WriteToDebug("intObjectiveIndex=" + intObjectiveIndex); 5371 5372 strStatus = SCORM_CallLMSGetValue("cmi.objectives." + intObjectiveIndex + ".status"); 5373 5374 if (strStatus == SCORM_PASSED){ 5375 WriteToDebug("returning Passed"); 5376 return LESSON_STATUS_PASSED; 5377 } 5378 else if (strStatus == SCORM_FAILED){ 5379 WriteToDebug("Returning Failed"); 5380 return LESSON_STATUS_FAILED; 5381 } 5382 else if (strStatus == SCORM_COMPLETED){ 5383 WriteToDebug("Returning Completed"); 5384 return LESSON_STATUS_COMPLETED; 5385 } 5386 else if (strStatus == SCORM_BROWSED){ 5387 WriteToDebug("Returning Browsed"); 5388 return LESSON_STATUS_BROWSED; 5389 } 5390 else if (strStatus == SCORM_INCOMPLETE){ 5391 WriteToDebug("Returning Incomplete"); 5392 return LESSON_STATUS_INCOMPLETE; 5393 } 5394 else if (strStatus == SCORM_NOT_ATTEMPTED || strStatus == ""){ 5395 WriteToDebug("Returning Not Attempted"); 5396 return LESSON_STATUS_NOT_ATTEMPTED; 5397 } 5398 else{ 5399 WriteToDebug("ERROR - status not found"); 5400 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_STATUS, 5401 "Invalid objective status received from LMS or initial status not yet recorded for objective", 5402 "strStatus=" + strStatus); 5403 return null; 5404 5405 } 5406 5407} 5408 5409 5410function SCORM_FindObjectiveIndexFromID(strObjectiveID){ 5411 5412 var intCount; 5413 var i; 5414 var strTempID; 5415 5416 WriteToDebug("In SCORM_FindObjectiveIndexFromID"); 5417 5418 intCount = SCORM_CallLMSGetValue("cmi.objectives._count"); 5419 5420 if (intCount == ""){ 5421 WriteToDebug("Setting intCount=0"); 5422 return 0; 5423 } 5424 5425 intCount = parseInt(intCount, 10); 5426 5427 WriteToDebug("intCount=" + intCount); 5428 5429 for (i=0; i<intCount; i++){ 5430 5431 WriteToDebug("Checking index " + i); 5432 5433 strTempID = SCORM_CallLMSGetValue("cmi.objectives." + i + ".id"); 5434 5435 WriteToDebug("ID=" + strTempID); 5436 5437 if (strTempID == strObjectiveID){ 5438 WriteToDebug("Found Matching index"); 5439 return i; 5440 } 5441 } 5442 5443 WriteToDebug("Did not find match, returning count"); 5444 5445 return intCount; 5446} 5447 5448function SCORM_CreateValidIdentifier(str) { 5449 return CreateValidIdentifierLegacy(str); 5450} 5451 5452//___________________________________________________________ 5453//Interaction Retrieval Functionality 5454//NOTE ON INTERACTION RETRIEVAL 5455//A. It is only available in certain standards, standards where it is unavailable will return nothing 5456//B. The interaction records are currently reported using "journaling", whereby each entry is a
5456ppended 5457// Retrieval methods will retrieve only the most recent value 5458 5459 5460//___________________________________________________________ 5461//Helper Methods 5462function SCORM_FindInteractionIndexFromID(strInteractionID){ 5463 5464 WriteToDebug("SCORM_FindInteractionIndexFromID - SCORM does not support interaction retrieval, returning null"); 5465 5466 return null; 5467} 5468//___________________________________________________________ 5469 5470 5471function SCORM_GetInteractionType(strInteractionID) 5472{ 5473 5474 WriteToDebug("SCORM_GetInteractionType - SCORM does not support interaction retrieval, returning empty string"); 5475 return ''; 5476 5477 5478} 5479 5480//public 5481function SCORM_GetInteractionTimestamp(strInteractionID) 5482{ 5483 WriteToDebug("SCORM_GetInteractionTimestamp - SCORM does not support interaction retrieval, returning empty string"); 5484 return ''; 5485} 5486 5487 5488 5489//public 5490function SCORM_GetInteractionCorrectResponses(strInteractionID) 5491{ 5492 5493 WriteToDebug("SCORM_GetInteractionCorrectResponses - SCORM does not support interaction retrieval, returning empty array"); 5494 return new Array(); 5495 5496 5497} 5498 5499 5500 5501//public 5502function SCORM_GetInteractionWeighting(strInteractionID) 5503{ 5504 WriteToDebug("SCORM_GetInteractionWeighting - SCORM does not support interaction retrieval, returning empty string"); 5505 return ''; 5506 5507} 5508 5509 5510 5511//public 5512function SCORM_GetInteractionLearnerResponses(strInteractionID) 5513{ 5514 WriteToDebug("SCORM_GetInteractionLearnerResponses - SCORM does not support interaction retrieval, returning empty array"); 5515 return new Array(); 5516 5517 5518} 5519 5520 5521//public 5522function SCORM_GetInteractionResult(strInteractionID) 5523{ 5524 WriteToDebug("SCORM_GetInteractionResult - SCORM does not support interaction retrieval, returning empty string"); 5525 return ''; 5526 5527} 5528 5529 5530 5531//public 5532function SCORM_GetInteractionLatency(strInteractionID) 5533{ 5534 WriteToDebug("SCORM_GetInteractionDescription - SCORM does not support interaction retrieval, returning empty string"); 5535 return ''; 5536 5537} 5538 5539 5540 5541//public 5542function SCORM_GetInteractionDescription(strInteractionID) 5543{ 5544 WriteToDebug("SCORM_GetInteractionDescription - SCORM does not support interaction retrieval, returning empty string"); 5545 return ''; 5546 5547 5548} 5549 5550//________________________________________________ 5551 5552 5553//public 5554function SCORM_CreateDataBucket(strBucketId, intMinSize, intMaxSize){ 5555 WriteToDebug("SCORM_CreateDataBucket - SCORM 1.1 and 1.2 do not support SSP, returning false"); 5556 return false; 5557} 5558 5559//public 5560function SCORM_GetDataFromBucket(strBucketId){ 5561 WriteToDebug("SCORM_GetDataFromBucket - SCORM 1.1 and 1.2 do not support SSP, returning empty string"); 5562 return ""; 5563} 5564 5565//public 5566function SCORM_PutDataInBucket(strBucketId, strData, blnAppendToEnd){ 5567 WriteToDebug("SCORM_PutDataInBucket - SCORM 1.1 and 1.2 do not support SSP, returning false"); 5568 return false; 5569} 5570 5571//public 5572function SCORM_DetectSSPSupport(){ 5573 WriteToDebug("SCORM_DetectSSPSupport - SCORM 1.1 and 1.2 do not support SSP, returning false"); 5574 return false; 5575} 5576 5577//public 5578function SCORM_GetBucketInfo(strBucketId){ 5579 WriteToDebug("AICC_DetectSSPSupport - SCORM 1.1 and 1.2 do not support SSP, returning empty SSPBucketSize"); 5580 return new SSPBucketSize(0, 0); 5581} 5582 5583//--------------------------------------------------------------------------------- 5584//Status Management Functions 5585 5586function SCORM_SetFailed(){ 5587 WriteToDebug("In SCORM_SetFailed"); 5588 SCORM_ClearErrorInfo(); 5589 return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_FAILED); 5590} 5591 5592function SCORM_SetPassed(){ 5593 WriteToDebug("In SCORM_SetPassed"); 5594 SCORM_ClearErrorInfo(); 5595 return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_PASSED); 5596} 5597 5598function SCORM_SetCompleted(){ 5599 WriteToDebug("In SCORM_SetCompleted"); 5600 SCORM_ClearErrorInfo(); 5601 return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_COMPLETED); 5602} 5603 5604function SCORM_ResetStatus(){ 5605 WriteToDebug("In SCORM_ResetStatus"); 5606 SCORM_ClearErrorInfo(); 5607 return SCORM_CallLMSSetValue("cmi.core.lesson_status", SCORM_INCOMPLETE); 5608} 5609 5610function SCORM_GetStatus(){ 5611 5612 var strStatus; 5613 5614 WriteToDebug("In SCORM_GetStatus"); 5615 5616 SCORM_ClearErrorInfo(); 5617 5618 strStatus = SCORM_CallLMSGetValue("cmi.core.lesson_status"); 5619 5620 WriteToDebug("strStatus=" + strStatus); 5621 5622 if (strStatus == SCORM_PASSED){ 5623 WriteToDebug("returning Passed"); 5624 return LESSON_STATUS_PASSED; 5625 } 5626 else if (strStatus == SCORM_FAILED){ 5627 WriteToDebug("Returning Failed"); 5628 return LESSON_STATUS_FAILED; 5629 } 5630 else if (strStatus == SCORM_COMPLETED){ 5631 WriteToDebug("Returning Completed"); 5632 return LESSON_STATUS_COMPLETED; 5633 } 5634 else if (strStatus == SCORM_BROWSED){ 5635 WriteToDebug("Returning Browsed"); 5636 return LESSON_STATUS_BROWSED; 5637 } 5638 else if (strStatus == SCORM_INCOMPLETE){ 5639 WriteToDebug("Returning Incomplete"); 5640 return LESSON_STATUS_INCOMPLETE; 5641 } 5642 else if (strStatus == SCORM_NOT_ATTEMPTED){ 5643 WriteToDebug("Returning Not Attempted"); 5644 return LESSON_STATUS_NOT_ATTEMPTED; 5645 } 5646 else{ 5647 WriteToDebug("ERROR - status not found"); 5648 SCORM_SetErrorInfoManually(SCORM_ERROR_INVALID_STATUS, 5649 "Invalid lesson status received from LMS", 5650 "strStatus=" + strStatus); 5651 return null; 5652 5653 } 5654 5655} 5656 5657//public 5658function SCORM_GetProgressMeasure(){ 5659 WriteToDebug("SCORM_GetProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false"); 5660 return false;
5661} 5662//public 5663function SCORM_SetProgressMeasure(){ 5664 WriteToDebug("SCORM_SetProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false"); 5665 return false; 5666} 5667 5668//public 5669function SCORM_GetObjectiveProgressMeasure(){ 5670 WriteToDebug("SCORM_GetObjectiveProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false"); 5671 return false; 5672} 5673//public 5674function SCORM_SetObjectiveProgressMeasure(){ 5675 WriteToDebug("SCORM_SetObjectiveProgressMeasure - SCORM 1.1 and 1.2 do not support progress_measure, returning false"); 5676 return false; 5677} 5678 5679 5680function SCORM_IsContentInBrowseMode(){ 5681 5682 var strLessonMode 5683 5684 WriteToDebug("In SCORM_IsContentInBrowseMode"); 5685 5686 strLessonMode = SCORM_CallLMSGetValue("cmi.core.lesson_mode"); 5687 5688 WriteToDebug("SCORM_IsContentInBrowseMode, strLessonMode=" + strLessonMode); 5689 5690 if (strLessonMode == SCORM_BROWSE){ 5691 WriteToDebug("Returning true"); 5692 return true; 5693 } 5694 else{ 5695 WriteToDebug("Returning false"); 5696 return false; 5697 } 5698} 5699 5700 5701function SCORM_TranslateExitTypeToSCORM(strExitType){ 5702 5703 WriteToDebug("In SCORM_TranslatgeExitTypeToSCORM strExitType-" + strExitType); 5704 5705 if (strExitType == EXIT_TYPE_SUSPEND){ 5706 WriteToDebug("Returning suspend"); 5707 return SCORM_SUSPEND; 5708 } 5709 else if (strExitType == EXIT_TYPE_UNLOAD){ 5710 WriteToDebug("Returning Exit"); 5711 return SCORM_NORMAL_EXIT; 5712 } 5713 else if (strExitType == EXIT_TYPE_FINISH){ 5714 WriteToDebug("Returning Logout"); 5715 return SCORM_NORMAL_EXIT; 5716 } 5717 else if (strExitType == EXIT_TYPE_TIMEOUT){ 5718 WriteToDebug("Returning Timout"); 5719 return SCORM_TIMEOUT; 5720 } 5721} 5722 5723 5724function SCORM_GetCompletionStatus(){ 5725 5726 WriteToDebug("In SCORM_GetCompletionStatus"); 5727 5728 if (SCORM_IsContentInBrowseMode()){ 5729 WriteToDebug("Returning browsed"); 5730 return SCORM_BROWSED; 5731 } 5732 else{ 5733 WriteToDebug("Returning Completed"); 5734 return SCORM_COMPLETED; 5735 } 5736 5737} 5738 5739 5740function SCORM_SetNavigationRequest(strNavRequest){ 5741 WriteToDebug("SCORM_GetNavigationRequest - SCORM 1.1 and 1.2 do not support navigation requests, returning false"); 5742 return false; 5743} 5744 5745function SCORM_GetNavigationRequest(){ 5746 WriteToDebug("SCORM_GetNavigationRequest - SCORM 1.1 and 1.2 do not support navigation requests, returning false"); 5747 return false; 5748} 5749 5750//--------------------------------------------------------------------------------- 5751//Functions to Call the SCORM API 5752 5753//note: in all functions that interact with API, we concact any returned strings with "" to convert 5754//the value to a string type, do this b/c many SCORM API's will return a Java String instead of a Javascript 5755//string 5756 5757 5758function SCORM_CallLMSInitialize(){ 5759 5760 var strResult; 5761 5762 WriteToDebug("In SCORM_CallLMSInitialize"); 5763 5764 SCORM_objAPI = SCORM_GrabAPI(); 5765 5766 WriteToDebug("Calling LMSInitialize"); 5767 5768 strResult = SCORM_objAPI.LMSInitialize(""); 5769 strResult = strResult + ""; 5770 5771 WriteToDebug("strResult=" + strResult); 5772 5773 if (strResult == SCORM_FALSE){ 5774 5775 WriteToDebug("Detected failed call to initialize"); 5776 5777 SCORM_SetErrorInfo(); 5778 5779 WriteToDebug ("Error calling LMSInitialize:"); 5780 WriteToDebug (" intSCORMError=" + intSCORMError); 5781 WriteToDebug (" SCORMErrorString=" + strSCORMErrorString); 5782 WriteToDebug (" Diagnostic=" + strSCORMErrorDiagnostic); 5783 5784 return false; 5785 5786 } 5787 5788 WriteToDebug("Returning true"); 5789 5790 return true; 5791} 5792 5793function SCORM_CallLMSSetValue(strElement, strValue){ 5794 5795 var strResult; 5796 5797 WriteToDebug("SCORM_CallLMSSetValue strElement=" + strElement + ", strValue=" + strValue); 5798 5799 if (blnReviewModeSoReadOnly === true){ 5800 WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting."); 5801 return true; 5802 } 5803 5804 SCORM_objAPI = SCORM_GrabAPI(); 5805 5806 WriteToDebug("Calling LMSSetValue"); 5807 5808 strElement = strElement + ""; 5809 strValue = strValue + ""; 5810 5811 strResult = SCORM_objAPI.LMSSetValue(strElement, strValue) 5812 strResult = strResult + ""; 5813 5814 WriteToDebug("strResult=" + strResult); 5815
5816 if (strResult == SCORM_FALSE){ 5817 5818 WriteToDebug("Detected Failed call to LMSSetvalue"); 5819 5820 SCORM_SetErrorInfo(); 5821 5822 WriteToDebug ("Error calling LMSSetValue:"); 5823 WriteToDebug (" strElement=" + strElement); 5824 WriteToDebug (" strValue=" + strValue); 5825 WriteToDebug (" intSCORMError=" + intSCORMError); 5826 WriteToDebug (" SCORMErrorString=" + strSCORMErrorString); 5827 WriteToDebug (" Diagnostic=" + strSCORMErrorDiagnostic); 5828 5829 return false; 5830 } 5831 5832 WriteToDebug("Returning true"); 5833 5834 return true; 5835} 5836 5837function SCORM_CallLMSGetValue(strElement){ 5838 5839 var strResult; 5840 5841 WriteToDebug("In SCORM_CallLMSGetValue strElement=" + strElement); 5842 5843 SCORM_objAPI = SCORM_GrabAPI(); 5844 5845 WriteToDebug("Call LMSGetValue"); 5846 5847 strElement = strElement + ""; 5848 5849 strResult = SCORM_objAPI.LMSGetValue(strElement) + "" 5850 5851 WriteToDebug("strResult=" + strResult); 5852 5853 intSCORMError = SCORM_objAPI.LMSGetLastError() 5854 intSCORMError = intSCORMError + ""; 5855 5856 WriteToDebug("intSCORMError=" + intSCORMError); 5857 5858 if (intSCORMError != SCORM_NO_ERROR){ 5859 5860 WriteToDebug("Detected failed called to LMSGetValue"); 5861 5862 SCORM_SetErrorInfo(); 5863 5864 WriteToDebug ("Error calling LMSGetValue:"); 5865 WriteToDebug (" strElement=" + strElement); 5866 WriteToDebug (" intSCORMError=" + intSCORMError); 5867 WriteToDebug (" SCORMErrorString=" + strSCORMErrorString); 5868 WriteToDebug (" Diagnostic=" + strSCORMErrorDiagnostic); 5869 } 5870 5871 WriteToDebug("Returning " + strResult); 5872 5873 return strResult; 5874 5875} 5876 5877 5878function SCORM_CallLMSCommit(){ 5879 5880 var strResult; 5881 5882 WriteToDebug("In SCORM_CallLMSCommit"); 5883 5884 SCORM_objAPI = SCORM_GrabAPI(); 5885 5886 WriteToDebug("Calling LMSCommit"); 5887 5888 strResult = SCORM_objAPI.LMSCommit(""); 5889 5890 strResult = strResult + ""; 5891 5892 WriteToDebug("strResult=" + strResult); 5893 5894 if (strResult == SCORM_FALSE){ 5895 5896 WriteToDebug("Detected failed call to LMSCommit"); 5897 5898 SCORM_SetErrorInfo(); 5899 5900 WriteToDebug ("Error calling LMSCommit:"); 5901 WriteToDebug (" intSCORMError=" + intSCORMError); 5902 WriteToDebug (" SCORMErrorString=" + strSCORMErrorString); 5903 WriteToDebug (" Diagnostic=" + strSCORMErrorDiagnostic); 5904 5905 return false; 5906 } 5907 5908 WriteToDebug("Returning true"); 5909 5910 return true; 5911} 5912 5913 5914function SCORM_CallLMSFinish(){ 5915 5916 var strResult; 5917 5918 WriteToDebug("In SCORM_CallLMSFinish"); 5919 5920 SCORM_objAPI = SCORM_GrabAPI(); 5921 5922 WriteToDebug("Calling LMS Finish"); 5923 5924 strResult = SCORM_objAPI.LMSFinish(""); 5925 5926 strResult = strResult + ""; 5927 5928 WriteToDebug("strResult=" + strResult); 5929 5930 if (strResult == SCORM_FALSE){ 5931 5932 WriteToDebug("Detected failed call to LMSFinish"); 5933 5934 SCORM_SetErrorInfo(); 5935 5936 WriteToDebug ("Error calling LMSFinish:"); 5937 WriteToDebug (" intSCORMError=" + intSCORMError); 5938 WriteToDebug (" SCORMErrorString=" + strSCORMErrorString); 5939 WriteToDebug (" Diagnostic=" + strSCORMErrorDiagnostic); 5940 5941 return false; 5942 } 5943 5944 WriteToDebug("Returning True"); 5945 5946 return true; 5947} 5948 5949 5950//--------------------------------------------------------------------------------- 5951//Error Handling Functions 5952function SCORM_ClearErrorInfo(){ 5953 5954 WriteToDebug("In SCORM_ClearErrorInfo"); 5955 5956 intSCORMError = SCORM_NO_ERROR; 5957 strSCORMErrorString = ""; 5958 strSCORMErrorDiagnostic = ""; 5959} 5960 5961function SCORM_SetErrorInfo(){ 5962 5963 WriteToDebug("In SCORM_SetErrorInfo"); 5964 5965 intSCORMError = SCORM_objAPI.LMSGetLastError(); 5966 strSCORMErrorString = SCORM_objAPI.LMSGetErrorString(intSCORMError); 5967 strSCORMErrorDiagnostic = SCORM_objAPI.LMSGetDiagnostic(""); 5968 5969 intSCORMError = intSCORMError + ""; 5970 strSCORMErrorString = strSCORMErrorString + ""; 5971 strSCORMErrorDiagnostic = strSCORMErrorDiagnostic + ""; 5972 5973 WriteToDebug("intSCORMError=" + intSCORMError); 5974 WriteToDebug("strSCORMErrorString=" + strSCORMErrorString); 5975 WriteToDebug("strSCORMErrorDiagnostic=" + strSCORMErrorDiagnostic); 5976} 5977 5978function SCORM_SetErrorInfoManually(intNum, strString, strDiagnostic){ 5979 5980 WriteToDebug("In SCORM_SetErrorInfoManually"); 5981 WriteToDebug("ERROR-Num=" + intNum); 5982 WriteToDebug(" String=" + strString); 5983 WriteToDebug(" Diag=" + strDiagnostic); 5984 5985 intSCORMError = intNum; 5986 strSCORMErrorString = strString; 5987 strSCORMErrorDiagnostic = strDiagnostic; 5988 5989} 5990 5991function SCORM_GetLastError(){ 5992 5993 WriteToDebug("In SCORM_GetLastError"); 5994 5995 if (intSCORMError == SCORM_NO_ERROR){ 5996 WriteToDebug("Returning No Error"); 5997 return NO_ERROR; 5998 } 5999 else { 6000 WriteToDebug("Returning " + intSCORMError); 6001 return intSCORMError; 6002 } 6003} 6004 6005function SCORM_GetLastErrorDesc(){ 6006 WriteToDebug("In SCORM_GetLastErrorDesc, " + strSCORMErrorString + "\n" + strSCORMErrorDiagnostic); 6007 return strSCORMErrorString + "\n" + strSCORMErrorDiagnostic; 6008} 6009 6010 6011 6012//--------------------------------------------------------------------------------- 6013//API Locating Functions 6014 6015function SCORM_GrabAPI(){ 6016 6017 WriteToDebug("In SCORM_GrabAPI"); 6018 6019 //if we haven't already located the API, find it using our improved ADL algorithm 6020 if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){ 6021 WriteToDebug("Searching with improved ADL algorithm"); 6022 SCORM_objAPI = SCORM_GetAPI(); 6023 } 6024 6025 //if it's still not found, look in every concievable spot...some older LMS's bury it in
6025wierd places 6026 //drop this because it can cause problems when the content is launched in a cross domain envrionment...for instance the 6027 //standard detection algorithm could come upon a frame from a different domain using this algorithm when the content is 6028 //launched under AICC 6029 6030 //TODO: a better solution might be to wrap this in a try/catch block 6031 6032 //if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){ 6033 // WriteToDebug("Searching everywhere with Rustici Software algorithm"); 6034 // SCORM_objAPI = SCORM_SearchForAPI(window); 6035 //} 6036 6037 if (typeof(SCORM_objAPI) == "undefined" || SCORM_objAPI == null){ 6038 SCORM_objAPI = SCORM_SearchForAPI(window); 6039 } 6040 6041 WriteToDebug("SCORM_GrabAPI, returning"); 6042 6043 return SCORM_objAPI; 6044 6045} 6046 6047 6048function SCORM_SearchForAPI(wndLookIn){ 6049 6050 WriteToDebug("SCORM_SearchForAPI"); 6051 6052 var objAPITemp = null; 6053 var strDebugID = ""; 6054 6055 strDebugID = "Name=" + wndLookIn.name + ", href=" + wndLookIn.location.href 6056 6057 objAPITemp = wndLookIn.API; 6058 6059 if (SCORM_APIFound(objAPITemp)){ 6060 WriteToDebug("Found API in this window - " + strDebugID); 6061 return objAPITemp; 6062 } 6063 6064 if (SCORM_WindowHasParent(wndLookIn)){ 6065 WriteToDebug("Searching Parent - " + strDebugID); 6066 objAPITemp = SCORM_SearchForAPI(wndLookIn.parent); 6067 } 6068 6069 if (SCORM_APIFound(objAPITemp)){ 6070 WriteToDebug("Found API in a parent - " + strDebugID); 6071 return objAPITemp; 6072 } 6073 6074 if (SCORM_WindowHasOpener(wndLookIn)){ 6075 WriteToDebug("Searching Opener - " + strDebugID); 6076 objAPITemp = SCORM_SearchForAPI(wndLookIn.opener); 6077 } 6078 6079 if (SCORM_APIFound(objAPITemp)){ 6080 WriteToDebug("Found API in an opener - " + strDebugID); 6081 return objAPITemp; 6082 } 6083 6084 //look in child frames individually, don't call this function recursively 6085 //on them to prevent an infinite loop when it looks back up to the parents 6086 WriteToDebug("Looking in children - " + strDebugID); 6087 objAPITemp = SCORM_LookInChildren(wndLookIn); 6088 6089 if (SCORM_APIFound(objAPITemp)){ 6090 WriteToDebug("Found API in Children - " + strDebugID); 6091 return objAPITemp; 6092 } 6093 6094 WriteToDebug("Didn't find API in this window - " + strDebugID); 6095 return null; 6096} 6097 6098 6099function SCORM_LookInChildren(wnd){ 6100 6101 WriteToDebug("SCORM_LookInChildren"); 6102 6103 var objAPITemp = null; 6104 6105 var strDebugID = ""; 6106 6107 strDebugID = "Name=" + wnd.name + ", href=" + wnd.location.href 6108 6109 for (var i=0; i < wnd.frames.length; i++){ 6110 6111 WriteToDebug("Looking in child frame " + i); 6112 6113 objAPITemp = wnd.frames[i].API; 6114 6115 if (SCORM_APIFound(objAPITemp)){ 6116 WriteToDebug("Found API in child frame of " + strDebugID); 6117 return objAPITemp; 6118 } 6119 6120 WriteToDebug("Looking in this child's children " + strDebugID); 6121 objAPITemp = SCORM_LookInChildren(wnd.frames[i]); 6122 6123 if (SCORM_APIFound(objAPITemp)){ 6124 WriteToDebug("API found in this child's children " + strDebugID); 6125 return objAPITemp; 6126 } 6127 } 6128 6129 return null; 6130} 6131 6132function SCORM_WindowHasOpener(wnd){ 6133 WriteToDebug("In SCORM_WindowHasOpener"); 6134 if ((wnd.opener != null) && (wnd.opener != wnd) && (typeof(wnd.opener) != "undefined")){ 6135 WriteToDebug("Window Does Have Opener"); 6136 return true; 6137 } 6138 else{ 6139 WriteToDebug("Window Does Not Have Opener"); 6140 return false; 6141 } 6142} 6143 6144function SCORM_WindowHasParent(wnd){ 6145 WriteToDebug("In SCORM_WindowHasParent"); 6146 if ((wnd.parent != null) && (wnd.parent != wnd) && (typeof(wnd.parent) != "undefined")){ 6147 WriteToDebug("Window Does Have Parent"); 6148 return true; 6149 } 6150 else{ 6151 WriteToDebug("Window Does Not Have Parent"); 6152 return false; 6153 } 6154} 6155 6156 6157function SCORM_APIFound(obj){ 6158 WriteToDebug("In SCORM_APIFound"); 6159 if (obj == null || typeof(obj) == "undefined"){ 6160 WriteToDebug("API NOT Found"); 6161 return false; 6162 } 6163 else{ 6164 WriteToDebug("API Found"); 6165 return true; 6166 } 6167} 6168 6169 6170 6171 6172/******************************************************************* 6173 * SCORM 2004 API Search Algorithm 6174 * Description - Improvement of the algorithm developed by ADL to 6175 find the SCORM 2004 API Adapter. The improvements eliminate
6176 errors, improve code clarity and eliminate the dependence 6177 on global variables. The errors removed include: 6178 - The "win" variable was never declared in a scope 6179 accessible to the GetAPI function 6180 - A call to API.version which is not part of the SCORM 6181 2004 specification 6182 - The previous algorithm was not able to find the API 6183 if it was located in the window's parent's opener 6184 * Original Author - ADL & Concurrent Technologies Corporation 6185 * Author - Mike Rustici (April 1, 2004) 6186 Rustici Software, LLC 6187 http://www.scorm.com 6188 [email protected] 6189*******************************************************************/ 6190 6191 6192 6193 6194/* 6195ScanParentsForApi 6196-Searches all the parents of a given window until 6197 it finds an object named "API". If an 6198 object of that name is found, a reference to it 6199 is returned. Otherwise, this function returns null. 6200*/ 6201function SCORM_ScanParentsForApi(win) 6202{ 6203 6204 WriteToDebug("In SCORM_ScanParentsForApi, win=" + win.location); 6205 6206 /* 6207 Establish an outrageously high maximum number of 6208 parent windows that we are will to search as a 6209 safe guard against an infinite loop. This is 6210 probably not strictly necessary, but different 6211 browsers can do funny things with undefined objects. 6212 */ 6213 var MAX_PARENTS_TO_SEARCH = 500; 6214 var nParentsSearched = 0; 6215 6216 /* 6217 Search each parent window until we either: 6218 -find the API, 6219 -encounter a window with no parent (parent is null 6220 or the same as the current window) 6221 -or, have reached our maximum nesting threshold 6222 */ 6223 while ( (win.API == null || win.API === undefined) && 6224 (win.parent != null) && (win.parent != win) && 6225 (nParentsSearched <= MAX_PARENTS_TO_SEARCH) 6226 ) 6227 { 6228 6229 nParentsSearched++; 6230 win = win.parent; 6231 } 6232 6233 /* 6234 If the API doesn't exist in the window we stopped looping on, 6235 then this will return null. 6236 */ 6237 return win.API; 6238} 6239 6240 6241/* 6242GetAPI 6243-Searches all parent and opener windows relative to the 6244 current window for the SCORM API Adapter. 6245 Returns a reference to the API Adapter if found or null 6246 otherwise. 6247*/ 6248function SCORM_GetAPI() 6249{ 6250 WriteToDebug("In SCORM_GetAPI"); 6251 6252 var API = null; 6253 6254 //Search all the parents of the current window if there are any 6255 if ((window.parent != null) && (window.parent != window)) 6256 { 6257 WriteToDebug("SCORM_GetAPI, searching parent"); 6258 API = SCORM_ScanParentsForApi(window.parent); 6259 } 6260 6261 /* 6262 If we didn't find the API in this window's chain of parents, 6263 then search all the parents of the opener window if there is one 6264 */ 6265 if ((API == null) && (window.top.opener != null)) 6266 { 6267 WriteToDebug("SCORM_GetAPI, searching opener"); 6268 API = SCORM_ScanParentsForApi(window.top.opener); 6269 } 6270 6271 return API; 6272} 6273 6274 6275var STANDARD = 'AICC'; 6276 6277var blnDirtyAICCData = false; 6278var blnCommitSavedData = false; 6279 6280var intAICCErrorNum = NO_ERROR; 6281var strAICCErrorDesc = ""; 6282 6283var aryAICCFoundItems = new Array(); //stores a list of data elements that have been found in a AICC GetParam result 6284 6285var blnUseLongInteractionResultValues = true; //we start off trying to submit verbose descriptions of interaction results, if this errors, we revert back to strict conformance 6286 6287var blnReviewModeSoReadOnly = false; 6288 6289//these variables come from and can go stright to the LMS 6290var AICC_LMS_Version = ""; 6291var AICC_Student_ID = ""; 6292var AICC_Student_Name = ""; 6293var AICC_Lesson_Location = ""; 6294var AICC_Score = ""; 6295var AICC_Credit = ""; 6296var AICC_Lesson_Status = ""; 6297var AICC_Time = ""; 6298var AICC_Mastery_Score = ""; 6299var AICC_Lesson_Mode = ""; 6300var AICC_Max_Time_Allowed = ""; 6301var AICC_Time_Limit_Action = ""; 6302var AICC_Audio = ""; 6303var AICC_Speed = ""; 6304var AICC_Language = ""; 6305var AICC_Text = ""; 6306var AICC_Launch_Data = ""; 6307var AICC_Data_Chunk = ""; 6308var AICC_Comments = ""; 6309var AICC_Objectives = null; 6310var AICC_CourseID = ""; 6311 6312//these variables are more logical representations of the above variables 6313var AICC_fltScoreRaw = ""; 6314var AICC_fltScoreMax = ""; 6315var AICC_fltScoreMin = ""; 6316var AICC_blnCredit = true; 6317var AICC_strLessonMode = MODE_NORMAL; 6318var AICC_intPreviouslyAccumulatedMilliseconds = 0; 6319var AICC_intMaxTimeAllowedMilliseconds = MAX_CMI_TIME; 6320var AICC_blnExitOnTimeout = false;
6321var AICC_blnShowMessageOnTimeout = true; 6322var AICC_TextPreference = PREFERENCE_DEFAULT; 6323var AICC_Status = LESSON_STATUS_NOT_ATTEMPTED; 6324var AICC_Entry = AICC_ENTRY_FLAG_DEFAULT; //must be the default b/c absense of entry flag indicates "review" 6325var AICC_AudioPlayPreference = PREFERENCE_DEFAULT; 6326var AICC_intAudioVolume = 100; 6327var AICC_intPercentOfMaxSpeed = 100; 6328var AICC_intSessionTimeMilliseconds = 0; 6329var AICC_aryObjectivesRead = new Array(); //objectives reported from LMS, read results from here, also store copy of currently set objectives here 6330var AICC_aryObjectivesWrite = new Array(); //objectives altered during this session (that is all we're supposed to report to AICC LMS) 6331var AICC_aryCommentsFromLearner = new Array(); 6332var AICC_aryInteractions = new Array(); 6333 6334//constants to define the meaning of the positions in the arrays to store objective information 6335//these arrays contain a top level array index by the obj index, these positions contain subarrays with these indicies 6336var AICC_OBJ_ARRAY_ID = 0; 6337var AICC_OBJ_ARRAY_SCORE = 1; 6338var AICC_OBJ_ARRAY_STATUS = 2; 6339 6340//constants to define the meaning of the positions in the arrays to store interaction information 6341var AICC_INTERACTIONS_ID = 0; 6342var AICC_INTERACTIONS_RESPONSE = 1; 6343var AICC_INTERACTIONS_CORRECT = 2; 6344var AICC_INTERACTIONS_CORRECT_RESPONSE = 3; 6345var AICC_INTERACTIONS_TIME_STAMP = 4; 6346var AICC_INTERACTIONS_TYPE = 5; 6347var AICC_INTERACTIONS_WEIGHTING = 6; 6348var AICC_INTERACTIONS_LATENCY = 7; 6349var AICC_INTERACTIONS_RESPONSE_LONG = 8; 6350var AICC_INTERACTIONS_CORRECT_RESPONSE_LONG = 9; 6351 6352var AICC_INTERACTION_TYPE_TRUE_FALSE = "T"; 6353var AICC_INTERACTION_TYPE_CHOICE = "C"; 6354var AICC_INTERACTION_TYPE_FILL_IN = "F"; 6355var AICC_INTERACTION_TYPE_MATCHING = "M"; 6356var AICC_INTERACTION_TYPE_PERFORMANCE = "P"; 6357var AICC_INTERACTION_TYPE_SEQUENCING = "S"; 6358var AICC_INTERACTION_TYPE_LIKERT = "L"; 6359var AICC_INTERACTION_TYPE_NUMERIC = "N"; 6360 6361var AICC_RESULT_CORRECT = "C"; 6362var AICC_RESULT_WRONG = "W"; 6363var AICC_RESULT_UNANTICIPATED = "U"; 6364var AICC_RESULT_NEUTRAL = "N"; 6365 6366 6367//error constants 6368var AICC_NO_ERROR = "0"; 6369var AICC_ERROR_INVALID_PREFERENCE = "-1"; 6370var AICC_ERROR_INVALID_STATUS = "-2"; 6371var AICC_ERROR_INVALID_SPEED = "-3"; 6372var AICC_ERROR_INVALID_TIMESPAN = "-4"; 6373var AICC_ERROR_INVALID_TIME_LIMIT_ACTION = "-5"; 6374var AICC_ERROR_INVALID_DECIMAL = "-6"; 6375var AICC_ERROR_INVALID_CREDIT = "-7"; 6376var AICC_ERROR_INVALID_LESSON_MODE = "-8"; 6377var AICC_ERROR_INVALID_ENTRY = "-9"; 6378 6379var blnReviewModeSoReadOnly = false; 6380 6381function AICC_Initialize(){ 6382 6383 WriteToDebug("In AICC_Initialize"); 6384 6385 //make getparam request, this will parse the data into local variables then begin loading the content 6386 6387 window.AICCComm.MakeGetParamRequest(); 6388 6389 return; 6390} 6391 6392function AICC_InitializeExecuted(){ 6393 WriteToDebug("In AICC_InitializeExecuted"); 6394 //only reset status and such if we are not reviewing 6395 if (AICC_GetLessonMode() != MODE_REVIEW){ 6396 //only set the status to incomplete if it's not attempted yet 6397 if (AICC_GetStatus() == LESSON_STATUS_NOT_ATTEMPTED){ 6398 WriteToDebug("Setting Status to Incomplete"); 6399 AICC_Status = LESSON_STATUS_INCOMPLETE; 6400 } 6401 } 6402 else{ 6403 //mode is review, check if we should go to read only mode 6404 if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){ 6405 blnReviewModeSoReadOnly = true; 6406 } 6407 } 6408} 6409 6410 6411function AICC_Finish(strExitType, blnStatusWasSet){ 6412 6413 WriteToDebug("In AICC_Finish, strExitType=" + strExitType + ", blnStatusWasSet=" + blnStatusWasSet); 6414 6415 if (! blnStatusWasSet){ 6416 if ( (strExitType == EXIT_TYPE_FINISH)){ 6417 6418 WriteToDebug("Setting status to complete"); 6419 6420 AICC_Status = LESSON_STATUS_COMPLETED; 6421 } 6422 else{ 6423 WriteToDebug("Setting status to incomplete"); 6424 6425 AICC_Status = LESSON_STATUS_INCOMPLETE; 6426 } 6427 } 6428 6429 window.AICCComm.PrepareCourseExit(this.actionConceded); 6430 6431 //Commit Data 6432 AICC_CommitData(); 6433 6434 //Make ExitAU Request 6435 window.AICCComm.MakeExitAURequest(); 6436 6437 return true; 6438} 6439 6440 6441function AICC_CommitData(){ 6442 6443 var strAICCData; 6444 6445 WriteToDebug("In AICC_CommitData"); 6446 6447 if (blnReviewModeSoReadOnly === true){ 6448 WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting."); 6449 return true; 6450 } 6451 6452 //if there is data to save, make a putparam request 6453 blnCommitSavedData = false;
6454 if (IsThereDirtyAICCData()){ 6455 AICC_SaveTime(GetSessionAccumulatedTime()); 6456 6457 blnCommitSavedData = true; 6458 6459 WriteToDebug("Found Dirty Data"); 6460 6461 strAICCData = FormAICCPostData(); 6462 6463 window.AICCComm.MakePutParamRequest(strAICCData); 6464 6465 //if there is no course id, then the LMS doesn't support interactions 6466 //MR - 5/24/05 - removed the course id check to allow aicc courses to be imported into an LMS without using the descriptor files 6467 //also, Saba doesn't send the Course Id field even when imported with the descriptor files 6468 if ( /*AICC_CourseID != "" && */ AICC_aryInteractions.length > 0){ 6469 6470 WriteToDebug("Saving Interactions"); 6471 6472 AICC_SendInteractions(); 6473 } 6474 6475 ClearDirtyAICCData(); //if the PutParam request fails, the data will be set back to dirty via a call to AICC_PutParamFailed() 6476 6477 } 6478 6479 return true; 6480} 6481 6482 6483function AICC_SendInteractions(){ 6484 6485 WriteToDebug("In AICC_SendInteractions."); 6486 6487 if (blnReviewModeSoReadOnly === true){ 6488 WriteToDebug("Mode is Review and configuration setting dictates this should be read only so exiting."); 6489 return true; 6490 } 6491 6492 var strAICCData = FormAICCInteractionsData(); 6493 window.AICCComm.MakePutInteractionsRequest(strAICCData); 6494 //AICC interactions are additive, so we need to remove them once they are sent 6495 //now that we've sent this batch of interactions, clear out the AICC_aryInteractions array for new ones 6496 AICC_aryInteractions = new Array(); 6497} 6498 6499function AICC_GetStudentID(){ 6500 WriteToDebug("In AICC_GetStudentID, Returning " + AICC_Student_ID); 6501 6502 return AICC_Student_ID ; 6503} 6504 6505function AICC_GetStudentName(){ 6506 WriteToDebug("In AICC_GetStudentName, Returning " + AICC_Student_Name); 6507 6508 return AICC_Student_Name; 6509} 6510 6511function AICC_GetBookmark(){ 6512 WriteToDebug("In AICC_GetBookmark, Returning " + AICC_Lesson_Location); 6513 6514 return AICC_Lesson_Location; 6515} 6516 6517function AICC_SetBookmark(strBookmark){ 6518 WriteToDebug("In AICC_SetBookmark, strBookmark=" + strBookmark); 6519 6520 SetDirtyAICCData(); 6521 6522 AICC_Lesson_Location = strBookmark; 6523 6524 return true; 6525} 6526 6527function AICC_GetDataChunk(){ 6528 WriteToDebug("In AICC_GetDataChunk, Returning " + AICC_Data_Chunk); 6529 return AICC_Data_Chunk ; 6530} 6531 6532function AICC_SetDataChunk(strData){ 6533 //need to check for character limits here 4096 characters 6534 WriteToDebug("In AICC_SetDataChunk, strData=" + strData ); 6535 if(USE_STRICT_SUSPEND_DATA_LIMITS==true) { 6536 if(strData.length > 4096) { 6537 WriteToDebug("SCORM_SetDataChunk - suspend_data too large (4096 character limit for AICC)"); 6538 return false; 6539 }else{ 6540 SetDirtyAICCData(); 6541 AICC_Data_Chunk = strData; 6542 AICC_CommitData(); 6543 return true; 6544 } 6545 }else{ 6546 SetDirtyAICCData(); 6547 AICC_Data_Chunk = strData; 6548 AICC_CommitData(); 6549 return true; 6550 } 6551} 6552 6553 6554function AICC_GetLaunchData(){ 6555 WriteToDebug("In AICC_GetLaunchData, Returning " + AICC_Launch_Data ); 6556 6557 return AICC_Launch_Data ; 6558} 6559 6560function AICC_GetComments(){ 6561 WriteToDebug("In AICC_GetComments, Returning " + AICC_aryCommentsFromLearner.join(" | ")); 6562 6563 //not available in AICC - return cached comments from this session 6564 return AICC_aryCommentsFromLearner.join(" | "); 6565} 6566 6567function AICC_WriteComment(strComment){ 6568 WriteToDebug("In AICC_WriteComment, strComment=" + strComment); 6569 6570 var intNextIndex; 6571 6572 //remove the "|" since AICC has its own delimiters 6573 if (strComment.search(/ \| /) == 0){ 6574 strComment = strComment.substr(3); 6575 } 6576 6577 //remove encoding of "|" 6578 strComment.replace(/\|\|/g, "|") 6579 6580 //add the comment to an array of comments 6581 intNextIndex = AICC_aryCommentsFromLearner.length; 6582 6583 WriteToDebug("Adding comment to array"); 6584 6585 AICC_aryCommentsFromLearner[intNextIndex] = strComment; 6586 6587 SetDirtyAICCData(); 6588 6589 return true; 6590} 6591 6592function AICC_GetLMSComments(){ 6593 WriteToDebug("In AICC_GetLMSComments, Returning " + AICC_Comments ); 6594 6595 return AICC_Comments; 6596} 6597 6598 6599function AICC_GetAudioPlayPreference(){ 6600 WriteToDebug("In AICC_GetAudioPlayPreference, Returning " + AICC_AudioPlayPreference); 6601 6602 return AICC_AudioPlayPreference; 6603} 6604 6605 6606 6607function AICC_GetAudioVolumePreference(){ 6608 WriteToDebug("In AICC_GetAudioVolumePreference, Returning " + AICC_intAudioVolume); 6609 6610 return AICC_intAudioVolume; 6611} 6612 6613 6614function AICC_SetAudioPreference(PlayPreference, intPercentOfMaxVolume){ 6615 WriteToDebug("In AICC_SetAudioPreference, Returning true"); 6616 6617 AICC_AudioPlayPreference = PlayPreference; 6618 AICC_intAudioVolume = intPercentOfMaxVolume; 6619 6620 SetDirtyAICCData(); 6621 return true; 6622} 6623 6624 6625 6626 6627function AICC_SetLanguagePreference(strLanguage){ 6628 WriteToDebug("In AICC_SetLanguagePreference, Returning true"); 6629 6630 SetDirtyAICCData(); 6631 6632 AICC_Language = strLanguage; 6633 6634 return true; 6635} 6636 6637 6638function AICC_GetLanguagePreference(){ 6639 WriteToDebug("In AICC_GetLanguagePreference, Returning " + AICC_Language); 6640 6641 return AICC_Language; 6642} 6643 6644 6645function AICC_SetSpeedPreference(intPercentOfMax){ 6646 WriteToDebug("In AICC_SetSpeedPreference, Returning true"); 6647 6648 AICC_intPercentOfMaxSpeed = intPercentOfMax; 6649 6650 SetDirtyAICCData(); 6651 6652 return true; 6653} 6654 6655 6656function AICC_GetSpeedPreference(){ 6657 WriteToDebug("In AICC_GetSpeedPreference, Returning " + AICC_intPercentOfMaxSpeed); 6658 6659 return AICC_intPercentOfMaxSpeed; 6660} 6661 6662function AICC_SetTextPreference(intPreference){ 6663 WriteToDebug("In AICC_SetTextPreference, Returning true"); 6664 6665 AICC_TextPreference = intPreference; 6666 6667 SetDirtyAICCData(); 6668 6669 return true; 6670} 6671 6672 6673function AICC_GetTextPreference(){ 6674 WriteToDebug("In AICC_GetTextPreference, Returning " + AICC_TextPreference); 6675 6676 return AICC_TextPreference; 6677} 6678 6679function AICC_GetPreviouslyAccumulatedTime(){ 6680 WriteToDebug("In AICC_GetPreviouslyAccumulatedTime, Returning " + AICC_intPreviouslyAccumulatedMilliseconds); 6681 6682 return AICC_intPreviouslyAccumulatedMilliseconds; 6683} 6684 6685 6686function AICC_SaveTime(intMilliSeconds){ 6687 WriteToDebug("In intMilliSeconds, Returning true"); 6688 6689 AICC_intSessionTimeMilliseconds = intMilliSeconds; 6690 6691 return true; 6692} 6693 6694function AICC_GetMaxTimeAllowed(){ 6695 WriteToDebug("In AICC_GetMaxTimeAllowed, Returning " + AICC_intMaxTimeAllowedMilliseconds); 6696 6697 return AICC_intMaxTimeAllowedMilliseconds; 6698} 6699 6700 6701 6702function AICC_DisplayMessageOnTimeout(){ 6703 WriteToDebug("In AICC_DisplayMessageOnTimeout, Returning " + AICC_blnShowMessageOnTimeout); 6704 6705 return AICC_blnShowMessageOnTimeout; 6706} 6707 6708function AICC_ExitOnTimeout(){ 6709 WriteToDebug("In AICC_ExitOnTimeout, Returning " + AICC_blnExitOnTimeout); 6710 6711 return AICC_blnExitOnTimeout; 6712} 6713 6714 6715function AICC_GetPassingScore(){ 6716 WriteToDebug("In AICC_GetPassingScore, Returning " + AICC_Mastery_Score); 6717 6718 return AICC_Mastery_Score; 6719 6720} 6721 6722 6723 6724function AICC_GetScore(){ 6725 WriteToDebug("In AICC_GetScore, Returning " + AICC_fltScoreRaw); 6726 6727 return AICC_fltScoreRaw; 6728} 6729 6730function AICC_SetScore(fltScore, fltMaxScore, fltMinScore){ 6731 WriteToDebug("In AICC_SetScore, fltScore=" + fltScore + ", fltMaxScore=" + fltMaxScore + ", fltMinScore=" + fltMinScore); 6732 6733 AICC_fltScoreRaw = fltScore; 6734 AICC_fltScoreMax = fltMaxScore; 6735 AICC_fltScoreMin = fltMinScore; 6736 6737 SetDirtyAICCData(); 6738 6739 return true; 6740} 6741 6742 6743function AICC_RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 6744 6745 WriteToDebug("In AICC_RecordTrueFalseInteraction strID=" + strID + ", blnResponse=" + blnResponse + 6746 ", blnCorrect=" + blnCorrect + ", blnCorrectResponse=" + blnCorrectResponse + 6747 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 6748 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 6749 6750 6751 var intTotalInteractions; 6752 var aryData = new Array(10); 6753 6754 intTotalInteractions = AICC_aryInteractions.length; 6755 6756 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 6757 if (intLatency == null || intLatency == undefined){intLatency="";} 6758 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 6759 6760 var strResponse = ""; 6761 var strCorrectResponse = ""; 6762 6763 if (blnResponse !== null) { 6764 if (blnResponse) { 6765 strResponse = "t"; 6766 } 6767 else { 6768 strResponse = "f"; 6769 } 6770 } 6771 6772 if (blnCorrectResponse == true){ 6773 strCorrectResponse = "t"; 6774 } 6775 else if(blnCorrectResponse == false){ //test for false b/c it could be null in which case we want to leave it as "" 6776 strCorrectResponse = "f"; 6777 } 6778 6779 aryData[AICC_INTERACTIONS_ID] = strID; 6780 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 6781 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 6782 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 6783 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 6784 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_TRUE_FALSE; 6785 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 6786 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 6787 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponse; 6788 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse; 6789 6790 6791 //in AICC ignore the description field since there is nothing we can do with it 6792 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 6793 6794 AICC_aryInteractions[intTotalInteractions] = aryData; 6795 6796 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 6797 6798 SetDirtyAICCData(); 6799 return true; 6800} 6801 6802function AICC_RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 6803 6804 WriteToDebug("In AICC_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse + 6805 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 6806 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 6807 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 6808 6809 6810 var intTotalInteractions; 6811 var aryData = new Array(10); 6812 6813 intTotalInteractions = AICC_aryInteractions.length; 6814 6815 if (intWeighting == null || intWeighting == undefined){intWeighting="";} 6816 if (intLatency == null || intLatency == undefined){intLatency="";} 6817 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 6818 6819 6820 var strResponse = ""; 6821 var strResponseLong = ""; 6822 6823 var strCorrectResponse = ""; 6824 var strCorrectResponseLong = ""; 6825 6826 if (aryResponse !== null) { 6827 for (var i=0; i < aryResponse.length; i++){ 6828
6829 if (strResponse.length > 0) {strResponse += ",";} 6830 if (strResponseLong.length > 0) {strResponseLong += ",";} 6831 6832 strResponse += aryResponse[i].Short.replace(",", ""); 6833 strResponseLong += aryResponse[i].Long.replace(",", ""); 6834 } 6835 } 6836 6837 for (var i=0; i < aryCorrectResponse.length; i++){ 6838 6839 if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} 6840 if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} 6841 6842 strCorrectResponse += aryCorrectResponse[i].Short.replace(",", ""); 6843 strCorrectResponseLong += aryCorrectResponse[i].Long.replace(",", ""); 6844 } 6845 6846 aryData[AICC_INTERACTIONS_ID] = strID; 6847 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 6848 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 6849 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 6850 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 6851 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_CHOICE; 6852 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 6853 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 6854 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponseLong; 6855 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong; 6856 6857 //in AICC ignore the description field since there is nothing we can do with it 6858 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 6859 6860 AICC_aryInteractions[intTotalInteractions] = aryData; 6861 6862 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 6863 6864 SetDirtyAICCData(); 6865 return true; 6866} 6867 6868function AICC_RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 6869 6870 WriteToDebug("In AICC_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse + 6871 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 6872 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 6873 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 6874 6875 6876 var intTotalInteractions; 6877 var aryData = new Array(10); 6878 6879 intTotalInteractions = AICC_aryInteractions.length; 6880 6881 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 6882 if (intLatency == null || intLatency == undefined){intLatency="";} 6883 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 6884 if (strResponse === null) {strResponse = "";} 6885 if (strCorrectResponse == null || strCorrectResponse == undefined){strCorrectResponse="";} 6886 6887 strResponse = new String(strResponse); 6888 if (strResponse.length > 255) {strResponse = strResponse.substr(0, 255);} 6889 6890 strCorrectResponse = new String(strCorrectResponse); 6891 if (strCorrectResponse.length > 255) {strCorrectResponse = strCorrectResponse.substr(0, 255);} 6892 6893 aryData[AICC_INTERACTIONS_ID] = strID; 6894 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 6895 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 6896 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 6897 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 6898 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_FILL_IN; 6899 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 6900 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 6901 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponse; 6902 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse; 6903 6904 //in AICC ignore the description field since there is nothing we can do with it 6905 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 6906 6907 AICC_aryInteractions[intTotalInteractions] = aryData; 6908 6909 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 6910 6911 SetDirtyAICCData(); 6912 return true; 6913} 6914 6915function AICC_RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 6916 6917 WriteToDebug("In AICC_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 6918 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 6919 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 6920 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 6921 6922 6923 var intTotalInteractions; 6924 var aryData = new Array(10); 6925 6926 intTotalInteractions = AICC_aryInteractions.length; 6927 6928 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 6929 if (intLatency == null || intLatency == undefined){intLatency="";} 6930 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 6931 6932 6933 var strResponse = ""; 6934 var strResponseLong = ""; 6935 6936 var strCorrectResponse = ""; 6937 var strCorrectResponseLong = ""; 6938 6939 if (aryResponse !== null) { 6940 for (var i=0; i < aryResponse.length; i++){ 6941
6942 if (strResponse.length > 0) {strResponse += ",";} 6943 if (strResponseLong.length > 0) {strResponseLong += ",";} 6944 6945 strResponse += aryResponse[i].Source.Short.replace(",", "").replace(".", "") + "." + aryResponse[i].Target.Short.replace(",", "").replace(".", ""); 6946 strResponseLong += aryResponse[i].Source.Long.replace(",", "").replace(".", "") + "." + aryResponse[i].Target.Long.replace(",", "").replace(".", ""); 6947 } 6948 } 6949 6950 for (var i=0; i < aryCorrectResponse.length; i++){ 6951 6952 if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} 6953 if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} 6954 6955 if (aryCorrectResponse[i].Source.Short != "" && aryCorrectResponse[i].Source.Long != ""){ 6956 strCorrectResponse += aryCorrectResponse[i].Source.Short.replace(",", "").replace(".", "") + "." + aryCorrectResponse[i].Target.Short.replace(",", "").replace(".", ""); 6957 strCorrectResponseLong += aryCorrectResponse[i].Source.Long.replace(",", "").replace(".", "") + "." + aryCorrectResponse[i].Target.Long.replace(",", "").replace(".", ""); 6958 } 6959 } 6960 6961 aryData[AICC_INTERACTIONS_ID] = strID; 6962 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 6963 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 6964 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 6965 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 6966 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_MATCHING; 6967 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 6968 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 6969 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponseLong; 6970 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong; 6971 6972 //in AICC ignore the description field since there is nothing we can do with it 6973 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 6974 6975 AICC_aryInteractions[intTotalInteractions] = aryData; 6976 6977 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 6978 6979 SetDirtyAICCData(); 6980 return true; 6981} 6982 6983function AICC_RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 6984 6985 WriteToDebug("In AICC_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse + 6986 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 6987 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 6988 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 6989 6990 6991 var intTotalInteractions; 6992 var aryData = new Array(10); 6993 6994 intTotalInteractions = AICC_aryInteractions.length; 6995 6996 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 6997 if (intLatency == null || intLatency == undefined){intLatency="";} 6998 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 6999 if (strResponse === null) {strResponse = "";} 7000 if (strCorrectResponse == null || strCorrectResponse == undefined){strCorrectResponse="";} 7001 7002 strResponse = new String(strResponse); 7003 if (strResponse.length > 255) {strResponse = strResponse.substr(0, 255);} 7004 7005 strCorrectResponse = new String(strCorrectResponse); 7006 if (strCorrectResponse.length > 255) {strCorrectResponse = strCorrectResponse.substr(0, 255);} 7007 7008 aryData[AICC_INTERACTIONS_ID] = strID; 7009 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 7010 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 7011 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 7012 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 7013 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_PERFORMANCE; 7014 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 7015 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 7016 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponse; 7017 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse; 7018 7019 //in AICC ignore the description field since there is nothing we can do with it 7020 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 7021 7022 AICC_aryInteractions[intTotalInteractions] = aryData; 7023 7024 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 7025 7026 SetDirtyAICCData(); 7027 return true; 7028} 7029 7030function AICC_RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 7031 7032 WriteToDebug("In AICC_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 7033 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 7034 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 7035 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 7036 7037 7038 var intTotalInteractions; 7039 var aryData = new Array(10); 7040 7041 intTotalInteractions = AICC_aryInteractions.length; 7042 7043 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 7044 if (intLatency == null || intLatency == undefined){intLatency="";} 7045 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 7046 7047 var strResponse = ""; 7048 var strResponseLong = ""; 7049 7050 var strCorrectResponse = ""; 7051 var strCorrectResponseLong = ""; 7052 7053 if (aryResponse !== null) { 7054 for (var i=0; i < aryResponse.length; i++){ 7055
7056 if (strResponse.length > 0) {strResponse += ",";} 7057 if (strResponseLong.length > 0) {strResponseLong += ",";} 7058 7059 strResponse += aryResponse[i].Short.replace(",", ""); 7060 strResponseLong += aryResponse[i].Long.replace(",", ""); 7061 } 7062 } 7063 7064 for (var i=0; i < aryCorrectResponse.length; i++){ 7065 7066 if (strCorrectResponse.length > 0) {strCorrectResponse += ",";} 7067 if (strCorrectResponseLong.length > 0) {strCorrectResponseLong += ",";} 7068 7069 strCorrectResponse += aryCorrectResponse[i].Short.replace(",", ""); 7070 strCorrectResponseLong += aryCorrectResponse[i].Long.replace(",", ""); 7071 } 7072 7073 aryData[AICC_INTERACTIONS_ID] = strID; 7074 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 7075 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 7076 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 7077 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 7078 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_SEQUENCING; 7079 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 7080 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 7081 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponseLong; 7082 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong; 7083 7084 //in AICC ignore the description field since there is nothing we can do with it 7085 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 7086 7087 AICC_aryInteractions[intTotalInteractions] = aryData; 7088 7089 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 7090 7091 SetDirtyAICCData(); 7092 return true; 7093} 7094 7095function AICC_RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 7096 7097 WriteToDebug("In RecordLikertInteraction strID=" + strID + ", response=" + response + 7098 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 7099 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 7100 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 7101 7102 7103 var intTotalInteractions; 7104 var aryData = new Array(10); 7105 7106 intTotalInteractions = AICC_aryInteractions.length; 7107 7108 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 7109 if (intLatency == null || intLatency == undefined){intLatency="";} 7110 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";} 7111 7112 var strResponse = ""; 7113 var strResponseLong = ""; 7114 if (response !== null) { 7115 strResponse = response.Short; 7116 strResponseLong = response.Long; 7117 } 7118 7119 var strCorrectResponse = ""; 7120 var strCorrectResponseLong = ""; 7121 7122 if (correctResponse != null){ 7123 strCorrectResponse = correctResponse.Short; 7124 strCorrectResponseLong = correctResponse.Long; 7125 } 7126 7127 aryData[AICC_INTERACTIONS_ID] = strID; 7128 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 7129 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 7130 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 7131 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 7132 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_LIKERT; 7133 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 7134 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 7135 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponseLong; 7136 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponseLong; 7137 7138 7139 //in AICC ignore the description field since there is nothing we can do with it 7140 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 7141 7142 AICC_aryInteractions[intTotalInteractions] = aryData; 7143 7144 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 7145 7146 SetDirtyAICCData(); 7147 return true; 7148} 7149 7150function AICC_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime){ 7151 7152 WriteToDebug("In AICC_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse + 7153 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 7154 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 7155 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime); 7156 7157 7158 var intTotalInteractions; 7159 var aryData = new Array(10); 7160 7161 intTotalInteractions = AICC_aryInteractions.length; 7162 7163 if (intWeighting == null || intWeighting == undefined ){intWeighting="";} 7164 if (intLatency == null || intLatency == undefined){intLatency="";} 7165 if (blnCorrect == null || blnCorrect == undefined){blnCorrect="";}
7166 if (strResponse === null) { strResponse = ""; } 7167 7168 if (strCorrectResponse != undefined && strCorrectResponse != null) { 7169 if (IsValidDecimalRange(strCorrectResponse)) 7170 { 7171 //we could receive a decimal range correct response from SCORM 2004/Tin Can/Cmi5. If we do we need to convert that 7172 //to a decimal for use in AICC. 7173 strCorrectResponse = ConvertDecimalRangeToDecimalBasedOnLearnerResponse(strCorrectResponse, strResponse, blnCorrect); 7174 } 7175 //AICC does not accept decimal range correct answers, so if it's not a decimal by this point we fail 7176 if (!IsValidDecimal(strCorrectResponse)){ 7177 WriteToDebug("Returning False - AICC_RecordNumericInteraction received invalid correct response (not a decimal), strCorrectResponse=" +strCorrectResponse); 7178 return false; 7179 } 7180 } else { 7181 strCorrectResponse = ""; 7182 } 7183 7184 aryData[AICC_INTERACTIONS_ID] = strID; 7185 aryData[AICC_INTERACTIONS_RESPONSE] = strResponse; 7186 aryData[AICC_INTERACTIONS_CORRECT] = blnCorrect; 7187 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE] = strCorrectResponse; 7188 aryData[AICC_INTERACTIONS_TIME_STAMP] = dtmTime; 7189 aryData[AICC_INTERACTIONS_TYPE] = AICC_INTERACTION_TYPE_NUMERIC; 7190 aryData[AICC_INTERACTIONS_WEIGHTING] = intWeighting; 7191 aryData[AICC_INTERACTIONS_LATENCY] = intLatency; 7192 aryData[AICC_INTERACTIONS_RESPONSE_LONG] = strResponse; 7193 aryData[AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]= strCorrectResponse; 7194 7195 //in AICC ignore the description field since there is nothing we can do with it 7196 //in AICC, don't try to set the interaction objective because these need to specified in the descriptor files 7197 7198 AICC_aryInteractions[intTotalInteractions] = aryData; 7199 7200 WriteToDebug("Added to interactions array, index=" + intTotalInteractions); 7201 7202 SetDirtyAICCData(); 7203 return true; 7204} 7205 7206function AICC_GetEntryMode(){ 7207 WriteToDebug("In AICC_GetEntryMode, Returning " + AICC_Entry); 7208 7209 return AICC_Entry; 7210} 7211 7212function AICC_GetLessonMode(){ 7213 WriteToDebug("In AICC_GetLessonMode, Returning " + AICC_strLessonMode); 7214 7215 return AICC_strLessonMode; 7216} 7217 7218function AICC_GetTakingForCredit(){ 7219 WriteToDebug("In AICC_GetTakingForCredit, Returning " + AICC_blnCredit); 7220 7221 return AICC_blnCredit; 7222} 7223 7224 7225 7226function AICC_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){ 7227 7228 WriteToDebug("In AICC_SetObjectiveScore, strObjectiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 7229 7230 var intNextID; 7231 var intObjIndex; 7232 var strAICCScore = ""; 7233 7234 intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead); 7235 7236 if (intObjIndex != null){ 7237 WriteToDebug("Found read objective"); 7238 AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_SCORE
7238] = intScore; 7239 } 7240 else{ 7241 WriteToDebug("Adding new read objective"); 7242 7243 intNextID = AICC_aryObjectivesRead.length; 7244 7245 AICC_aryObjectivesRead[parseInt(intNextID, 10)] = new Array(3); 7246 AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID; 7247 AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE] = intScore; 7248 AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = ""; 7249 } 7250 7251 7252 intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesWrite); 7253 7254 if (intObjIndex != null){ 7255 WriteToDebug("Found write objective"); 7256 AICC_aryObjectivesWrite[intObjIndex][AICC_OBJ_ARRAY_SCORE] = intScore; 7257 } 7258 else{ 7259 WriteToDebug("Adding new write objective"); 7260 7261 intNextID = AICC_aryObjectivesWrite.length; 7262 7263 AICC_aryObjectivesWrite[parseInt(intNextID, 10)] = new Array(3); 7264 7265 strAICCScore = intScore; 7266 7267 //prior to version 3, AICC scores cannot contain a decimal 7268 if (AICC_LMS_Version < 3 && strAICCScore != ""){ 7269 strAICCScore = parseInt(strAICCScore, 10); 7270 } 7271 7272 if ( (AICC_REPORT_MIN_MAX_SCORE === undefined || 7273 AICC_REPORT_MIN_MAX_SCORE === null || 7274 AICC_REPORT_MIN_MAX_SCORE === true) && 7275 (AICC_LMS_Version >= 3 ) //min and max scores are only allowed after version 3 7276 ){ 7277 7278 if ((intMaxScore != "") || (intMinScore != "")) { 7279 WriteToDebug("Appending Max and Min scores"); 7280 strAICCScore += "," + intMaxScore + "," + intMinScore; 7281 } 7282 7283 } 7284 7285 AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID; 7286 AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE
7286] = strAICCScore; 7287 AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = ""; 7288 } 7289 7290 SetDirtyAICCData(); 7291 7292 return true; 7293} 7294 7295 7296function AICC_SetObjectiveStatus(strObjectiveID, Lesson_Status){ 7297 7298 WriteToDebug("In AICC_SetObjectiveStatus, strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status); 7299 7300 var intNextID; 7301 var intObjIdex; 7302 7303 intObjIdex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead); 7304 7305 if (intObjIdex != null){ 7306 WriteToDebug("Found read objective"); 7307 AICC_aryObjectivesRead[intObjIdex][AICC_OBJ_ARRAY_STATUS] = Lesson_Status; 7308 } 7309 else{ 7310 WriteToDebug("Adding new read objective"); 7311 intNextID = AICC_aryObjectivesRead.length; 7312 7313 AICC_aryObjectivesRead[parseInt(intNextID, 10)] = new Array(3); 7314 7315 AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID; 7316 AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = Lesson_Status; 7317 AICC_aryObjectivesRead[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE
7317] = ""; 7318 } 7319 7320 7321 intObjIdex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesWrite); 7322 7323 if (intObjIdex != null){ 7324 WriteToDebug("Found write objective"); 7325 AICC_aryObjectivesWrite[intObjIdex][AICC_OBJ_ARRAY_STATUS] = Lesson_Status; 7326 } 7327 else{ 7328 WriteToDebug("Adding new write objective"); 7329 intNextID = AICC_aryObjectivesWrite.length; 7330 7331 AICC_aryObjectivesWrite[parseInt(intNextID, 10)] = new Array(3); 7332 7333 AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_ID] = strObjectiveID; 7334 AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_STATUS] = Lesson_Status; 7335 AICC_aryObjectivesWrite[parseInt(intNextID, 10)][AICC_OBJ_ARRAY_SCORE] = ""; 7336 } 7337 7338 SetDirtyAICCData(); 7339 7340 return true; 7341} 7342 7343 7344function AICC_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){ 7345 7346 WriteToDebug("In AICC_SetObjectiveDescription, strObjectiveID=" + strObjectiveID + ", strObjectiveDescription=" + strObjectiveDescription); 7347 WriteToDebug("Objective descriptions are not supported prior to SCORM 2004"); 7348 7349 return true; 7350 7351} 7352 7353 7354function AICC_GetObjectiveScore(strObjectiveID){ 7355 WriteToDebug("In AICC_SetObjectiveScore, strObjectiveID=" + strObjectiveID); 7356 7357 var intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead) 7358 7359 if (intObjIndex != null){ 7360 WriteToDebug("Found objective, returning " + AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_SCORE
7360]); 7361 return AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_SCORE]; 7362 } 7363 else{ 7364 WriteToDebug("Did not find objective, returning ''"); 7365 return ""; 7366 } 7367} 7368 7369function AICC_GetObjectiveDescription(strObjectiveID){ 7370 WriteToDebug("In AICC_GetObjectiveDescription, strObjectiveID=" + strObjectiveID); 7371 WriteToDebug("Objective descriptions are not supported prior to SCORM 2004"); 7372 7373 return ""; 7374} 7375 7376function AICC_GetObjectiveStatus(strObjectiveID){ 7377 7378 WriteToDebug("In AICC_SetObjectiveStatus, strObjectiveID=" + strObjectiveID); 7379 7380 var intObjIndex = FindObjectiveById(strObjectiveID, AICC_aryObjectivesRead) 7381 7382 if (intObjIndex != null){ 7383 WriteToDebug("Found objective, returning " + AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_STATUS]); 7384 return AICC_aryObjectivesRead[intObjIndex][AICC_OBJ_ARRAY_STATUS]; 7385 } 7386 else{ 7387 WriteToDebug("Did not find objective, returning " + LESSON_STATUS_NOT_ATTEMPTED); 7388 return LESSON_STATUS_NOT_ATTEMPTED; 7389 } 7390} 7391 7392function AICC_SetFailed(){ 7393 WriteToDebug("In AICC_SetFailed, Returning true"); 7394 7395 AICC_Status = LESSON_STATUS_FAILED; 7396 7397 SetDirtyAICCData(); 7398 7399 return true; 7400} 7401 7402function AICC_SetPassed(){ 7403 WriteToDebug("In AICC_SetPassed, Returning true"); 7404 7405 AICC_Status = LESSON_STATUS_PASSED; 7406 7407 SetDirtyAICCData(); 7408 7409 return true; 7410} 7411 7412function AICC_SetCompleted(){ 7413 WriteToDebug("In AICC_SetCompleted, Returning true"); 7414 7415 AICC_Status = LESSON_STATUS_COMPLETED; 7416 7417 SetDirtyAICCData(); 7418 7419 return true; 7420} 7421 7422function AICC_ResetStatus(){ 7423 WriteToDebug("In AICC_ResetStatus, Returning true"); 7424 7425 AICC_Status = LESSON_STATUS_INCOMPLETE; 7426 7427 SetDirtyAICCData(); 7428 7429 return true; 7430} 7431 7432function AICC_GetStatus(){ 7433 WriteToDebug("In AICC_GetStatus, Returning " + AICC_Status); 7434 7435 return AICC_Status; 7436} 7437 7438//public 7439function AICC_GetProgressMeasure(){ 7440 WriteToDebug("AICC_GetProgressMeasure - AICC does not support progress_measure, returning false"); 7441 return false; 7442} 7443//public 7444function AICC_SetProgressMeasure(){ 7445 WriteToDebug("AICC_SetProgressMeasure - AICC does not support progress_measure, returning false"); 7446 return false; 7447} 7448 7449//public 7450function AICC_GetObjectiveProgressMeasure(){ 7451 WriteToDebug("AICC_GetObjectiveProgressMeasure - AICC does not support progress_measure, returning false"); 7452 return false; 7453} 7454//public 7455function AICC_SetObjectiveProgressMeasure(){ 7456 WriteToDebug("AICC_SetObjectiveProgressMeasure - AICC does not support progress_measure, returning false"); 7457 return false; 7458} 7459 7460function AICC_SetPointBasedScore(intScore, intMaxScore, intMinScore){ 7461 WriteToDebug("AICC_SetPointBasedScore - AICC does not support SetPointBasedScore, falling back to SetScore"); 7462 return AICC_SetScore(intScore, intMaxScore, intMinScore); 7463} 7464 7465function AICC_GetScaledScore(intScore, intMaxScore, intMinScore){ 7466 WriteToDebug("AICC_GetScaledScore - AICC does not support GetScaledScore, returning false"); 7467 return false; 7468} 7469 7470function AICC_GetLastError(){ 7471 WriteToDebug("In AICC_GetLastError, Returning " + intAICCErrorNum); 7472 7473 return intAICCErrorNum; 7474} 7475 7476function AICC_GetLastErrorDesc(){ 7477 WriteToDebug("In AICC_GetLastErrorDesc, Returning '" + strAICCErrorDesc + "'"); 7478 7479 return strAICCErrorDesc; 7480} 7481 7482 7483 7484//============================================================================== 7485 7486 7487function AICC_PutParamFailed(){ 7488 WriteToDebug("ERROR: In AICC_PutParamFailed"); 7489 7490 //set dirty data 7491 SetDirtyAICCData(); 7492 7493 //currently, just leaves the data as dirty, could also re-try or display an error as desired 7494 7495 return; 7496} 7497 7498function AICC_PutInteractionsFailed(){ 7499 WriteToDebug("ERROR: In AICC_PutInteractionsFailed"); 7500 7501 //set dirty data 7502 SetDirtyAICCData(); 7503 7504 //the LMS probably only accepts strictly conformant interaction result values, so retry one more time 7505 //with the short values which will be used from now on 7506 7507 //this call originates from the child frame so be sure to reference the parent 7508 if (parent.blnUseLongInteractionResultValues == true){ 7509 7510 parent.blnUseLongInteractionResultValues = false;
7511 7512 parent.AICC_CommitData(); 7513 } 7514 7515 return; 7516} 7517 7518function AICC_SetErrorInfo(strErrorNumLine, strErrorDescLine){ 7519 WriteToDebug("ERROR: In AICC_SetErrorInfo, strErrorNumLine=" + strErrorNumLine + ", strErrorDescLine=" + strErrorDescLine); 7520 7521 if (strErrorNumLine.toLowerCase().search(/error\s*=\s*0/) == -1){ 7522 WriteToDebug("Detected No Error"); 7523 intAICCErrorNum = NO_ERROR; 7524 strAICCErrorDesc = ""; 7525 } 7526 else{ 7527 WriteToDebug("Setting Error Info"); 7528 AICC_SetError(GetValueFromAICCLine(strAICCErrorLine), GetValueFromAICCLine(strAICCErrorDesc)) 7529 } 7530} 7531 7532function AICC_SetError(intErrorNum, strErrorDesc){ 7533 WriteToDebug("ERROR: In AICC_SetError, intErrorNum=" + intErrorNum + ", strErrorDesc=" + strErrorDesc); 7534 7535 intAICCErrorNum = intErrorNum; 7536 strAICCErrorDesc = strAICCErrorDesc; 7537} 7538 7539function SetDirtyAICCData(){ 7540 WriteToDebug("In SetDirtyAICCData"); 7541 blnDirtyAICCData = true; 7542} 7543 7544function ClearDirtyAICCData(){ 7545 WriteToDebug("In ClearDirtyAICCData"); 7546 blnDirtyAICCData = false; 7547} 7548 7549function IsThereDirtyAICCData(){ 7550 WriteToDebug("In IsThereDirtyAICCData, returning " + blnDirtyAICCData); 7551 return blnDirtyAICCData; 7552} 7553 7554 7555function GetValueFromAICCLine(strLine){ 7556 7557 WriteToDebug("In GetValueFromAICCLine, strLine=" + strLine); 7558 7559 //find equal sign 7560 //if found equal sign 7561 //get all characters after equal sign 7562 //remove leading and trailing white space (cr, lf, tab, space, etc) 7563 7564 var intPos; 7565 var strValue = ""; 7566 var strTemp; 7567 7568 strLine = new String(strLine); 7569 7570 intPos = strLine.indexOf("="); 7571 7572 WriteToDebug("intPos=" + intPos); 7573 7574 if (intPos > -1 && ((intPos + 1) < strLine.length)){ 7575 7576 WriteToDebug("Grabbing value"); 7577 7578 strTemp = strLine.substring(intPos+1); 7579 7580 WriteToDebug("strTemp=" + strTemp); 7581 7582 strTemp = strTemp.replace(/^\s*/, ""); //replace leading whitespace 7583 strTemp = strTemp.replace(/\s*$/, ""); //replace trailing whitespace 7584 7585 strValue = strTemp; 7586 } 7587 7588 WriteToDebug("returning " + strValue); 7589 7590 return strValue; 7591 7592} 7593 7594function GetNameFromAICCLine(strLine){ 7595 //find equal sign 7596 //if found equal sign 7597 //get all characters after equal sign 7598 //remove leading and trailing white space (cr, lf, tab, space, etc) 7599 //else, look for brackets and return contents of brackets 7600 7601 WriteToDebug("In GetNameFromAICCLine, strLine=" + strLine); 7602 7603 var intPos; 7604 var strTemp; 7605 7606 var strName = ""; 7607 7608 strLine = new String(strLine); 7609 7610 intPos = strLine.indexOf("="); 7611 7612 WriteToDebug("intPos=" + intPos); 7613 7614 if (intPos > -1 && intPos < strLine.length){ 7615 7616 WriteToDebug("Grabbing name from name/value pair"); 7617 7618 strTemp = strLine.substring(0, intPos); 7619 7620 WriteToDebug("strTemp=" + strTemp); 7621 7622 strTemp = strTemp.replace(/^\s*/, ""); 7623 strTemp = strTemp.replace(/\s*$/, ""); 7624 7625 strName = strTemp; 7626 } 7627 7628 else{ 7629 7630 WriteToDebug("Grabbing name from group / section heading"); 7631 7632 intPos = strLine.indexOf("["); 7633 7634 WriteToDebug("intPos=" + intPos); 7635 7636 if (intPos > -1){ 7637 7638 WriteToDebug("Replacing []"); 7639 7640 strTemp = strLine.replace(/[\[|\]]/g, ""); //replace the square brackets 7641 7642 WriteToDebug("strTemp=" + strTemp); 7643 7644 strTemp = strTemp.replace(/^\s*/, ""); //replace leading whitespace 7645 strTemp = strTemp.replace(/\s*$/, ""); //replace trailing whitespace 7646 7647 strName = strTemp; 7648 } 7649 7650 } 7651 7652 7653 WriteToDebug("returning " + strName); 7654 7655 return strName; 7656} 7657 7658 7659 7660function GetIndexFromAICCName(strLineName){ 7661 7662 WriteToDebug("In GetIndexFromAICCName, strLineName=" + strLineName); 7663 7664 //find period 7665 //if found period 7666 //get all characters after preiod 7667 //if there is an equal sign (we got an entire line instead of a line name 7668 //remove all characters after the equal sign 7669 //remove leading and trailing white space (cr, lf, tab, space, etc) 7670 7671 var intPos; 7672 var strIndex = ""; 7673 var strTemp = ""; 7674 7675 strLine = new String(strLineName); 7676 7677 intPos = strLine.indexOf("."); 7678 7679 WriteToDebug("intPos=" + intPos); 7680 7681 if (intPos > -1 && (intPos+1) < strLine.length){ 7682 7683 WriteToDebug("Grabbing index"); 7684 7685 strTemp = strLine.substring(intPos + 1); 7686 7687 7688 WriteToDebug("strTemp=" + strTemp); 7689 7690 WriteToDebug("Checking for equal sign"); 7691 7692 intPos = strTemp.indexOf("="); 7693 7694 if (intPos > -1 && intPos < strTemp.length){ 7695 WriteToDebug("Found and removing equal sign"); 7696 strTemp = strLine.substring(0, intPos); 7697 } 7698 7699 WriteToDebug("Removing white space"); 7700 7701 strTemp = strTemp.replace(/^\s*/, ""); //replace leading whitespace 7702 strTemp = strTemp.replace(/\s*$/, ""); //replace trailing whitespace 7703 7704 strIndex = strTemp; 7705 } 7706 7707 WriteToDebug("returning " + strIndex); 7708 7709 return strIndex; 7710} 7711 7712 7713//============================================================================== 7714 7715function ParseGetParamData(strLMSResult){ 7716 7717 WriteToDebug("In ParseGetParamData"); 7718 7719 var aryAICCResponseLines; 7720 var strLine; 7721 var strLineName; 7722 var strLineValue; 7723 var i, j; //loop line counters 7724 7725 //parse LMS Result into local variables 7726 7727 strLMSResult = new String(strLMSResult); 7728 aryAICCResponseLines = strLMSResult.split("\n"); //only use \n instead of \r\n b/c some LMS's will only use one character 7729 7730 WriteToDebug("Split String"); 7731 7732 for (i=0; i < aryAICCResponseLines.length; i++){ 7733 7734 WriteToDebug("Processing Line #" + i + ": " + aryAICCResponseLines[i]); 7735 7736 strLine = aryAICCResponseLines[i]; 7737 7738 strLineName = ""; 7739 strLineValue = ""; 7740 7741 if (strLine.length > 0){ 7742 7743 WriteToDebug("Found non-zero length string"); 7744 7745 //remove \r from the beginning or end of the string in case we missed it in the original array split 7746 if (strLine.charAt(0) == "\r"){ 7747 WriteToDebug("Detected leading \\r"); 7748 strLine = strLine.substr(1); 7749 } 7750 if (strLine.charAt(strLine.length - 1) == "\r"){ 7751 WriteToDebug("Detected trailing \\r"); 7752 strLine = strLine.substr(0, strLine.length - 1); 7753 } 7754 7755 if (strLine.charAt(0) != ";") { //semi-colon indicates a comment line, ignore these 7756 WriteToDebug("Found non-comment line"); 7757 7758 strLineName = GetNameFromAICCLine(strLine); 7759 strLineValue = GetValueFromAICCLine(strLine); 7760 7761 WriteToDebug("strLineName=" + strLineName + ", strLineValue=" + strLineValue); 7762 } 7763 } 7764 7765 strLineName = strLineName.toLowerCase(); 7766 7767 if (! AICC_HasItemBeenFound(strLineName)){ //only process an item the first time it is found since only the first instance is significant 7768
7769 WriteToDebug("Detected an un-found item"); 7770 7771 AICC_FoundItem(strLineName); 7772 7773 switch (strLineName){ 7774 7775 case "version": //version 7776 WriteToDebug("Item is version"); 7777 var tempVersion = parseFloat(strLineValue); 7778 7779 if (isNaN(tempVersion)){tempVersion=0;} 7780 7781 AICC_LMS_Version = tempVersion; 7782 7783 break; 7784 7785 case "student_id": //student id 7786 WriteToDebug("Item is student_id"); 7787 AICC_Student_ID = strLineValue; 7788 break; 7789 7790 case "student_name": //student name 7791 WriteToDebug("Item is student_name"); 7792 AICC_Student_Name = strLineValue; 7793 break; 7794 7795 case "lesson_location": //bookmark 7796 WriteToDebug("Item is lesson_location"); 7797 AICC_Lesson_Location = strLineValue; 7798 break; 7799 7800 case "score": //score 7801 WriteToDebug("Item is score"); 7802 AICC_Score = strLineValue; 7803 7804 AICC_SeperateScoreValues(AICC_Score); 7805 7806 break; 7807 7808 case "credit": //credit 7809 WriteToDebug("Item is credit"); 7810 AICC_Credit = strLineValue; 7811 7812 AICC_TranslateCredit(AICC_Credit); 7813 7814 break; 7815 7816 case "lesson_status": //status, flag for entry mode 7817 WriteToDebug("Item is lesson_status"); 7818 AICC_Lesson_Status = strLineValue; 7819 7820 AICC_TranslateLessonStatus(AICC_Lesson_Status); 7821 7822 break; 7823 7824 case "time": //previous time 7825 WriteToDebug("Item is time"); 7826 AICC_Time = strLineValue; 7827 7828 AICC_TranslateTimeToMilliseconds(AICC_Time); 7829 7830 break; 7831 7832 case "mastery_score": //passing score 7833 WriteToDebug("Item is mastery_score"); 7834 AICC_Mastery_Score = strLineValue; 7835 7836 AICC_ValidateMasteryScore(AICC_Mastery_Score); 7837 break; 7838 7839 case "lesson_mode": //lesson mode (browse, normal, review) 7840 WriteToDebug("Item is lesson_mode"); 7841 AICC_Lesson_Mode = strLineValue; 7842 7843 AICC_TranslateLessonMode(AICC_Lesson_Mode); 7844 7845 break; 7846 7847 case "max_time_allowed": //max time allowed 7848 WriteToDebug("Item is max_time_allowed"); 7849 AICC_Max_Time_Allowed = strLineValue; 7850 7851 AICC_TranslateMaxTimeToMilliseconds(AICC_Max_Time_Allowed); 7852 7853 break; 7854 7855 case "time_limit_action": //display message on timeout, exit on timeout 7856 WriteToDebug("Item is time_limit_action"); 7857 AICC_Time_Limit_Action = strLineValue; 7858 7859 AICC_TranslateTimeLimitAction(AICC_Time_Limit_Action); 7860 7861 break; 7862 7863 case "audio": //audio play, audio speed 7864 WriteToDebug("Item is audio"); 7865 AICC_Audio = strLineValue; 7866 7867 AICC_TranslateAudio(AICC_Audio); 7868 7869 break; 7870 7871 case "speed": //content speed 7872 WriteToDebug("Item is speed"); 7873 AICC_Speed = strLineValue; 7874 7875 AICC_TranslateSpeed(AICC_Speed); 7876 7877 break; 7878 7879 case "language": //language 7880 WriteToDebug("Item is language"); 7881 AICC_Language = strLineValue; 7882 7883 break; 7884 7885 case "text": //text 7886 WriteToDebug("Item is text"); 7887 AICC_Text = strLineValue; 7888 7889 AICC_TranslateTextPreference(AICC_Text); 7890 7891 break; 7892 7893 case "course_id": //course id 7894 WriteToDebug("Item is course id"); 7895 AICC_CourseID = strLineValue; 7896 7897 break; 7898 7899 case "core_vendor": //launch data 7900 WriteToDebug("Item is core_vendor"); 7901 7902 AICC_Launch_Data = ""; 7903 7904 strLine = ""; 7905 j=1; 7906 7907 if ((i+j) < aryAICCResponseLines.length){ 7908 strLine = aryAICCResponseLines[i+j]; 7909 } 7910 7911 //loop to the end of the file or current group 7912 while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){ 7913 7914 if (strLine.charAt(0) != ";"){ 7915 AICC_Launch_Data += strLine + "\n"; //add \n to make up for the one we dropped when splitting the string into the array 7916 } 7917 7918 j = j + 1; 7919 7920 if ((i+j) < aryAICCResponseLines.length){ 7921 strLine = aryAICCResponseLines[i+j]; 7922 } 7923 } 7924 7925 i = i + j - 1 7926 7927 AICC_Launch_Data = AICC_Launch_Data.replace(/\s*$/, ""); //replace trailing whitespace (we've added an extra \n to the end of the string) 7928 7929 break; 7930 7931 case "core_lesson": //data chunk 7932 WriteToDebug("Item is core_lesson"); 7933 7934 AICC_Data_Chunk = ""; 7935 7936 strLine = ""; 7937 j=1; 7938 7939 if ((i+j) < aryAICCResponseLines.length){ 7940 strLine = aryAICCResponseLines[i+j]; 7941 } 7942 7943 //loop to the end of the file or current group 7944 while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){ 7945 7946 if (strLine.charAt(0) != ";"){ 7947 AICC_Data_Chunk += strLine + "\n"; //add \n to make up for the one we dropped when splitting the string into the array 7948 } 7949 7950 j = j + 1; 7951 7952 if ((i+j) < aryAICCResponseLines.length){ 7953 strLine = aryAICCResponseLines[i+j]; 7954 } 7955 } 7956 7957 i = i + j - 1 7958 7959 AICC_Data_Chunk = AICC_Data_Chunk.replace(/\s*$/, ""); //replace trailing whitespace (we've added an extra \n to the end of the string) 7960 break; 7961 7962 case "comments": //comments from LMS 7963 WriteToDebug("Item is comments"); 7964 7965 AICC_Comments = ""; 7966 7967 strLine = ""; 7968 j=1; 7969 7970 if ((i+j) < aryAICCResponseLines.length){ 7971 strLine = aryAICCResponseLines[i+j]; 7972 } 7973 7974 //loop to the end of the file or current group 7975 while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){ 7976 7977 if (strLine.charAt(0) != ";"){ 7978 AICC_Comments += strLine + "\n"; //add \n to make up for the one we dropped when splitting the string into the array 7979 } 7980 7981 j = j + 1; 7982 7983 if ((i+j) < aryAICCResponseLines.length){ 7984 strLine = aryAICCResponseLines[i+j]; 7985 } 7986 } 7987 7988 i = i + j - 1 7989 7990 AICC_Comments = AICC_Comments.replace(/\s*$/, ""); //replace trailing whitespace (we've added an extra \n to the end of the string) 7991 7992 break; 7993 7994 case "objectives_status": //objectives 7995 WriteToDebug("Item is objectives_status"); 7996 7997 AICC_Objectives = ""; 7998 7999 strLine = ""; 8000 j=1; 8001 8002 if ((i+j) < aryAICCResponseLines.length){ 8003 strLine = aryAICCResponseLines[i+j]; 8004 } 8005 8006 //loop to the end of the file or current group 8007 while ( ((i+j) < aryAICCResponseLines.length) && (! IsGroupIdentifier(strLine))){ 8008 8009 if (strLine.charAt(0) != ";"){ 8010 AICC_Objectives += strLine + "\n"; //add \n to make up for the one we dropped when splitting the string into the array 8011 } 8012 8013 j = j + 1; 8014 8015 if ((i+j) < aryAICCResponseLines.length){ 8016 strLine = aryAICCResponseLines[i+j]; 8017 } 8018 } 8019 8020 i = i + j - 1 8021 AICC_Objectives = AICC_Objectives.replace(/\s*$/, ""); //replace trailing whitespace (we've added an extra \r\n to the end of the string) 8022 8023 AICC_FormatObjectives(AICC_Objectives); 8024 8025 break; 8026 8027 default: 8028 //comment or empty line so do nothing 8029 WriteToDebug("Unknown Item Found"); 8030 break; 8031 8032 } //end switch 8033 } //end if AICC_HasItemBeenFound 8034 } //end for 8035 8036 8037 return true; 8038 8039} 8040 8041 8042function IsGroupIdentifier(strLine){ 8043 WriteToDebug("In IsGroupIdentifier, strLine=" + strLine); 8044 8045 var intPos; 8046 8047 //remove leading white space 8048 strLine = strLine.replace(/^\s*/, ""); 8049 8050 intPos = strLine.search(/\[[\w]+\]/); 8051 8052 WriteToDebug("intPos=" + intPos); 8053
8054 if (intPos == 0){ 8055 WriteToDebug("Returning True"); 8056 return true; 8057 } 8058 else{ 8059 WriteToDebug("Returning False"); 8060 return false; 8061 } 8062} 8063 8064function AICC_FoundItem(strItem){ 8065 WriteToDebug("In AICC_FoundItem, strItem=" + strItem); 8066 aryAICCFoundItems[strItem] = true; 8067} 8068 8069function AICC_HasItemBeenFound(strItem){ 8070 WriteToDebug("In AICC_HasItemBeenFound, strItem=" + strItem); 8071 8072 if (aryAICCFoundItems[strItem] == true){ 8073 WriteToDebug("Returning True"); 8074 return true; 8075 } 8076 else{ 8077 WriteToDebug("Returning False"); 8078 return false; 8079 } 8080} 8081 8082 8083 8084//================================================================== 8085//functions for dealing with specific data elements 8086//================================================================== 8087function AICC_SeperateScoreValues(AICC_Score){ 8088 WriteToDebug("In AICC_SeperateScoreValues, AICC_Score=" + AICC_Score); 8089 8090 var aryScores; 8091 aryScore = AICC_Score.split(","); 8092 8093 AICC_fltScoreRaw = aryScore[0]; 8094 8095 if (IsValidDecimal(AICC_fltScoreRaw)){ 8096 WriteToDebug("Found a valid decimal"); 8097 AICC_fltScoreRaw = parseFloat(AICC_fltScoreRaw); 8098 } 8099 else{ 8100 WriteToDebug("ERROR - score from LMS is not a valid decimal"); 8101 AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "score is not a valid decimal") 8102 } 8103 8104 if (aryScore.length > 1){ 8105 WriteToDebug("Max score found"); 8106 AICC_fltScoreMax = aryScore[1]; 8107 if ( IsValidDecimal(AICC_fltScoreMax)){ 8108 WriteToDebug("Found a valid decimal"); 8109 AICC_fltScoreMax = parseFloat(AICC_fltScoreMax); 8110 } 8111 else{ 8112 WriteToDebug("ERROR - max score from LMS is not a valid decimal"); 8113 AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "max score is not a valid decimal") 8114 } 8115 } 8116 8117 if (aryScore.length > 2){ 8118 WriteToDebug("Max score found"); 8119 AICC_fltScoreMin = aryScore[2]; 8120 if (IsValidDecimal(AICC_fltScoreMin)){ 8121 WriteToDebug("Found a valid decimal"); 8122 AICC_fltScoreMin = parseFloat(AICC_fltScoreMin); 8123 } 8124 else{ 8125 WriteToDebug("ERROR - min score from LMS is not a valid decimal"); 8126 AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "min score is not a valid decimal") 8127 } 8128 8129 } 8130 8131} 8132 8133 8134function AICC_ValidateMasteryScore(strScore){ 8135 WriteToDebug("In AICC_ValidateMasteryScore, strScore=" + strScore); 8136 8137 if (IsValidDecimal(strScore)){ 8138 AICC_Mastery_Score = parseFloat(strScore); 8139 } 8140 else{ 8141 WriteToDebug("ERROR - mastery score from LMS is not a valid decimal"); 8142 AICC_SetError(AICC_ERROR_INVALID_DECIMAL, "mastery score is not a valid decimal") 8143 } 8144 8145} 8146 8147function AICC_TranslateCredit(strCredit){ 8148 WriteToDebug("In AICC_TranslateCredit, strCredit=" + strCredit); 8149 8150 var strFirstChar; 8151 8152 strFirstChar = strCredit.toLowerCase().charAt(0); 8153 8154 if (strFirstChar == "c"){ 8155 WriteToDebug("Credit = true"); 8156 AICC_blnCredit = true; 8157 } 8158 else if (strFirstChar == "n"){ 8159 WriteToDebug("Credit = false"); 8160 AICC_blnCredit = false 8161 } 8162 else{ 8163 WriteToDebug("ERROR - credit value from LMS is not a valid"); 8164 AICC_SetError(AICC_ERROR_INVALID_CREDIT, "credit value from LMS is not a valid") 8165 } 8166 8167} 8168 8169function AICC_TranslateLessonMode(strMode){ 8170 WriteToDebug("In AICC_TranslateLessonMode, strMode=" + strMode); 8171 var strFirstChar; 8172 8173 strFirstChar = strMode.toLowerCase().charAt(0); 8174 8175 if (strFirstChar == "b"){ 8176 WriteToDebug("Lesson Mode = Browse"); 8177 AICC_strLessonMode = MODE_BROWSE; 8178 } 8179 else if (strFirstChar == "n"){ 8180 WriteToDebug("Lesson Mode = normal"); 8181 AICC_strLessonMode = MODE_NORMAL; 8182 } 8183 else if (strFirstChar == "r"){ 8184 WriteToDebug("Lesson Mode = review"); 8185 AICC_strLessonMode = MODE_REVIEW; 8186 8187 if (!(typeof(REVIEW_MODE_IS_READ_ONLY) == "undefined") && REVIEW_MODE_IS_READ_ONLY === true){ 8188 blnReviewModeSoReadOnly = true; 8189 } 8190 8191 } 8192 else{ 8193 WriteToDebug("ERROR - lesson_mode value from LMS is not a valid"); 8194 AICC_SetError(AICC_ERROR_INVALID_LESSON_MODE, "lesson_mode value from LMS is not a valid") 8195 8196 } 8197} 8198 8199 8200function AICC_TranslateTimeToMilliseconds(strCMITime){ 8201 WriteToDebug("In AICC_TranslateTimeToMilliseconds, strCMITime=" + strCMITime); 8202 8203 if (IsValidCMITimeSpan(strCMITime)){ 8204 AICC_intPreviouslyAccumulatedMilliseconds = ConvertCMITimeSpanToMS(strCMITime); 8205 } 8206 else{ 8207 WriteToDebug("ERROR - Invalid CMITimeSpan"); 8208 AICC_SetError(AICC_ERROR_INVALID_TIMESPAN, "Invalid timespan (previously accumulated time) received from LMS"); 8209 } 8210 8211} 8212 8213function AICC_TranslateMaxTimeToMilliseconds(strCMITime){ 8214 WriteToDebug("In AICC_TranslateMaxTimeToMilliseconds, strCMITime=" + strCMITime); 8215 8216 if (IsValidCMITimeSpan(strCMITime)){ 8217 AICC_intMaxTimeAllowedMilliseconds = ConvertCMITimeSpanToMS(strCMITime); 8218 } 8219 else{ 8220 WriteToDebug("ERROR - Invalid CMITimeSpan"); 8221 AICC_SetError(AICC_ERROR_INVALID_TIMESPAN, "Invalid timespan (max time allowed) received from LMS"); 8222 } 8223 8224} 8225 8226function AICC_TranslateTimeLimitAction(strTimeLimitAction){ 8227 WriteToDebug("In AICC_TranslateTimeLimitAction, strTimeLimitAction=" + strTimeLimitAction); 8228 8229 var arySplit; 8230 var blnError = false;
8231 var strChar1 = ""; 8232 var strChar2 = ""; 8233 8234 //note - order of characters is not significant 8235 8236 arySplit = strTimeLimitAction.split(","); 8237 8238 if (arySplit.length == 2){ 8239 8240 WriteToDebug("Found 2 elements"); 8241 8242 strChar1 = arySplit[0].charAt(0).toLowerCase(); 8243 strChar2 = arySplit[1].charAt(0).toLowerCase(); 8244 8245 WriteToDebug("Got characters, strChar1=" + strChar1 + ", strChar2=" + strChar2); 8246 8247 if ( (strChar1 != "e" && strChar1 != "c" && strChar1 != "m" && strChar1 != "n") || 8248 (strChar2 != "e" && strChar2 != "c" && strChar2 != "m" && strChar2 != "n") || 8249 (strChar1 == strChar2) 8250 ) { 8251 blnError = true 8252 WriteToDebug("Found an invalid character, or 2 identical characters"); 8253 } 8254 8255 if (strChar1 == "e" || strChar2 == "e") {AICC_blnExitOnTimeout = true;} 8256 if (strChar1 == "c" || strChar2 == "c") {AICC_blnExitOnTimeout = false;} 8257 8258 if (strChar1 == "n" || strChar2 == "n") {AICC_blnShowMessageOnTimeout = false;} 8259 if (strChar1 == "m" || strChar2 == "m") {AICC_blnShowMessageOnTimeout = true;} 8260 8261 WriteToDebug("AICC_blnExitOnTimeout=" + AICC_blnExitOnTimeout + ", AICC_blnShowMessageOnTimeout" + AICC_blnShowMessageOnTimeout); 8262 } 8263 else{ 8264 WriteToDebug("Line does not contain two comma-delimited elements"); 8265 blnError = true; 8266 } 8267 8268 if (blnError){ 8269 WriteToDebug("ERROR - Invalid Time Limit Action"); 8270 AICC_SetError(AICC_ERROR_INVALID_TIME_LIMIT_ACTION, "Invalid time limit action received from LMS"); 8271 8272 } 8273 8274} 8275 8276 8277function AICC_TranslateTextPreference(strPreference){ 8278 WriteToDebug("In AICC_TranslateTextPreference, strPreference=" + strPreference); 8279 8280 if (strPreference == -1){ 8281 WriteToDebug("Text Preference = off"); 8282 AICC_TextPreference = PREFERENCE_OFF; 8283 } 8284 else if (strPreference == 0){ 8285 WriteToDebug("Text Preference = default"); 8286 AICC_TextPreference = PREFERENCE_DEFAULT; 8287 } 8288 else if (strPreference == 1){ 8289 WriteToDebug("Text Preference = on"); 8290 AICC_TextPreference = PREFERENCE_ON; 8291 } 8292 else{ 8293 WriteToDebug("ERROR - Invalid Text Preference"); 8294 AICC_SetError(AICC_ERROR_INVALID_PREFERENCE, "Invalid Text Preference received from LMS"); 8295 8296 } 8297} 8298 8299 8300function AICC_TranslateLessonStatus(strStatus){ 8301 WriteToDebug("In AICC_TranslateLessonStatus, strStatus=" + strStatus); 8302 8303 var strFirstChar; 8304 var intPos; 8305 var strEntry; 8306 8307 strFirstChar = strStatus.charAt(0).toLowerCase(); 8308 8309 AICC_Status = AICC_ConvertAICCStatusIntoLocalStatus(strFirstChar); 8310 8311 WriteToDebug("AICC_Status=" + AICC_Status); 8312 8313 //check for an entry flag 8314 intPos = strStatus.indexOf(","); 8315 if (intPos > 0){ 8316 strEntry = strStatus.substr(intPos); 8317 strEntry = strEntry.replace(/,/, ""); 8318 8319 strFirstChar = strEntry.charAt(0).toLowerCase(); 8320 8321 if (strFirstChar == "a"){ 8322 WriteToDebug("Entry is Ab initio"); 8323 AICC_Entry = ENTRY_FIRST_TIME; 8324 } 8325 else if (strFirstChar == "r"){ 8326 WriteToDebug("Entry is Resume"); 8327 AICC_Entry = ENTRY_RESUME; 8328 } 8329 else{ 8330 WriteToDebug("ERROR - entry not found"); 8331 AICC_SetError(AICC_ERROR_INVALID_ENTRY, "Invalid lesson status received from LMS"); 8332 } 8333 } 8334 8335} 8336 8337 8338function AICC_ConvertAICCStatusIntoLocalStatus(strFirstCharOfAICCStatus){ 8339 WriteToDebug("In AICC_ConvertAICCStatusIntoLocalStatus, strFirstCharOfAICCStatus=" + strFirstCharOfAICCStatus); 8340 8341 if (strFirstCharOfAICCStatus == "p"){ 8342 WriteToDebug("Status is Passed"); 8343 return LESSON_STATUS_PASSED; 8344 } 8345 else if (strFirstCharOfAICCStatus == "f"){ 8346 WriteToDebug("Status is Failed"); 8347 return LESSON_STATUS_FAILED; 8348 } 8349 else if (strFirstCharOfAICCStatus == "c"){ 8350 WriteToDebug("Status is Completed"); 8351 return LESSON_STATUS_COMPLETED; 8352 } 8353 else if (strFirstCharOfAICCStatus == "b"){ 8354 WriteToDebug("Status is Browsed"); 8355 return LESSON_STATUS_BROWSED; 8356 } 8357 else if (strFirstCharOfAICCStatus == "i"){ 8358 WriteToDebug("Status is Incomplete"); 8359 return LESSON_STATUS_INCOMPLETE; 8360 } 8361 else if (strFirstCharOfAICCStatus == "n"){ 8362 WriteToDebug("Status is Not Attempted"); 8363 return LESSON_STATUS_NOT_ATTEMPTED; 8364 } 8365 else{ 8366 WriteToDebug("ERROR - status not found"); 8367 AICC_SetError(SCORM_ERROR_INVALID_STATUS, "Invalid status"); 8368 return LESSON_STATUS_NOT_ATTEMPTED; 8369 } 8370} 8371 8372 8373function AICC_TranslateAudio(strAudio){ 8374 8375 WriteToDebug("In AICC_TranslateAudio, strAudio=" + strAudio); 8376 8377 var intTempPreference = parseInt(strAudio, 10); 8378 8379 WriteToDebug("intTempPreference=" + intTempPreference); 8380 8381 if (intTempPreference > 0 && intTempPreference <= 100){ 8382 WriteToDebug("Returning On"); 8383 AICC_AudioPlayPreference = PREFERENCE_ON; 8384 AICC_intAudioVolume = intTempPreference; 8385 } 8386 else if (intTempPreference == 0){ 8387 WriteToDebug("Returning Default"); 8388 AICC_AudioPlayPreference = PREFERENCE_DEFAULT; 8389 } 8390 else if (intTempPreference < 0) { 8391 WriteToDebug("returning Off"); 8392 AICC_AudioPlayPreference = PREFERENCE_OFF; 8393 } 8394 else{ 8395 //preference is not a number 8396 WriteToDebug("Error: Invalid preference"); 8397 AICC_SetError(AICC_ERROR_INVALID_PREFERENCE, "Invalid audio preference received from LMS"); 8398 } 8399} 8400 8401 8402function AICC_TranslateSpeed(intAICCSpeed){ 8403 8404 WriteToDebug("In AICC_TranslateSpeed, intAICCSpeed=" + intAICCSpeed); 8405 8406 var intPercentOfMax; 8407 8408 if ( ! ValidInteger(intAICCSpeed) ){ 8409 WriteToDebug("ERROR - invalid integer"); 8410 AICC_SetError(AICC_ERROR_INVALID_SPEED, "Invalid speed preference received from LMS - not an integer"); 8411 return; 8412 } 8413 8414 intAICCSpeed = parseInt(intAICCSpeed, 10) 8415 8416 if (intAICCSpeed < -100 || intAICCSpeed > 100){ 8417 WriteToDebug("ERROR - out of range"); 8418 AICC_SetError(AICC_ERROR_INVALID_SPEED, "Invalid speed preference received from LMS - out of range"); 8419 return; 8420 8421 } 8422 8423 AICC_Speed = intAICCSpeed; 8424 8425 intPercentOfMax = (intAICCSpeed + 100) / 2; 8426 intPercentOfMax = parseInt(intPercentOfMax, 10); 8427 8428 WriteToDebug("Returning " + intPercentOfMax); 8429 8430 AICC_intPercentOfMaxSpeed = intPercentOfMax; 8431 8432} 8433 8434 8435 8436function AICC_FormatObjectives(strObjectivesFromLMS){ 8437 8438 WriteToDebug("In AICC_FormatObjectives, strObjectivesFromLMS=" + strObjectivesFromLMS); 8439 8440 var aryLines; 8441 var i; 8442 var strLineName; 8443 var strLineValue; 8444 var strLineType; 8445 var strIndex; 8446 8447 aryLines = strObjectivesFromLMS.split("\n"); 8448 8449 //establish the read array with empty elements for all the IDs we'll encounter 8450 for (i=0; i < aryLines.length; i++){ 8451 WriteToDebug("Extracting Index From Line: " + aryLines[i]); 8452 8453 strLineName = GetNameFromAICCLine(aryLines[i]); 8454 strIndex = GetIndexFromAICCName(strLineName); 8455 8456 strIndex = parseInt(strIndex, 10); 8457 8458 WriteToDebug("strIndex: " + strIndex); 8459 8460 AICC_aryObjectivesRead[parseInt(strIndex, 10)] = new Array(3); 8461 } 8462 8463 8464 //loop through the line again and now populate the actual data 8465 for (i=0; i < aryLines.length; i++){ 8466 WriteToDebug("Populating Line " + aryLines[i]); 8467 8468 strLineName = GetNameFromAICCLine(aryLines[i]); 8469 strLineValue = GetValueFromAICCLine(aryLines[i]); 8470 8471 strIndex = GetIndexFromAICCName(strLineName); 8472 8473 strIndex = strIndex; 8474 8475 WriteToDebug("strLineName: " + strLineName); 8476 WriteToDebug("strLineValue: " + strLineValue); 8477 WriteToDebug("strIndex: " + strIndex); 8478 8479 strLineType = strLineName.substr(0,4).toLowerCase(); 8480 8481 if (strLineType == "j_id"){ 8482 WriteToDebug("Found ID"); 8483 AICC_aryObjectivesRead[parseInt(strIndex, 10)][AICC_OBJ_ARRAY_ID] = strLineValue; 8484 } 8485 else if (strLineType =="j_st"){ 8486 WriteToDebug("Found Status"); 8487 AICC_aryObjectivesRead[parseInt(strIndex, 10)][AICC_OBJ_ARRAY_STATUS] = AICC_ConvertAICCStatusIntoLocalStatus(strLineValue.charAt(0).toLowerCase()); 8488 } 8489 else if (strLineType == "j_sc"){ 8490 WriteToDebug("Found Score"); 8491 AICC_aryObjectivesRead[parseInt(strIndex, 10)][AICC_OBJ_ARRAY_SCORE
8491] = AICC_ExtractSingleScoreFromObjective(strLineValue); 8492 } 8493 else{ 8494 WriteToDebug("WARNING - unidentified objective data found - " + aryLines[i]); 8495 } 8496 8497 } 8498 8499} 8500 8501 8502function AICC_ExtractSingleScoreFromObjective(strLineValue){ 8503 WriteToDebug("In AICC_ExtractSingleScoreFromObjective, strLineValue=" + strLineValue); 8504 8505 //AICC objectives can have multiple score sets (raw,max,min) seperated by semi-colons,they represent the various attempts at an objective 8506 //we're just concerned with the most recent attempt 8507 8508 var aryParts; 8509 8510 aryParts = strLineValue.split(";"); //drop the previous instances if any 8511 aryParts = aryParts[0].split(","); //drop the max and min scores if any 8512 8513 8514 WriteToDebug("returning " + aryParts[0]); 8515 8516 return aryParts[0]; 8517 8518} 8519 8520function FindObjectiveById(strID, aryObjectives){ 8521 8522 WriteToDebug("In FindObjectiveById, strID=" + strID); 8523 8524 for (var i=0; i <= aryObjectives.length; i++){ 8525 8526 WriteToDebug("Searching element " + i); 8527 8528 if (aryObjectives[i]){ 8529 WriteToDebug("Element Exists"); 8530 8531 if (aryObjectives[i][AICC_OBJ_ARRAY_ID].toString() == strID.toString()){ 8532 WriteToDebug("Element matches"); 8533 return i; 8534 } 8535 } 8536 } 8537 8538 return null; 8539} 8540 8541function AICC_CreateValidIdentifier(str) { 8542 return CreateValidIdentifierLegacy(str); 8543} 8544 8545//___________________________________________________________ 8546//Interaction Retrieval Functionality 8547//NOTE ON INTERACTION RETRIEVAL 8548//A. It is only available in certain standards, standards where it is unavailable will return nothing 8549//B. The interaction records are currently reported using "journaling", whereby each entry is appended 8550// Retrieval methods will retrieve only the most recent value 8551 8552 8553//___________________________________________________________ 8554//Helper Methods 8555function AICC_FindInteractionIndexFromID(strInteractionID){ 8556 8557 WriteToDebug("AICC_FindInteractionIndexFromID - AICC does not support interaction retrieval, returning null"); 8558 8559 return null; 8560} 8561//___________________________________________________________ 8562 8563 8564function AICC_GetInteractionType(strInteractionID) 8565{ 8566 8567 WriteToDebug("AICC_GetInteractionType - AICC does not support interaction retrieval, returning empty string"); 8568 return ''; 8569 8570 8571} 8572 8573//public 8574function AICC_GetInteractionTimestamp(strInteractionID) 8575{ 8576 WriteToDebug("AICC_GetInteractionTimestamp - AICC does not support interaction retrieval, returning empty string"); 8577 return ''; 8578} 8579 8580 8581 8582//public 8583function AICC_GetInteractionCorrectResponses(strInteractionID) 8584{ 8585 8586 WriteToDebug("AICC_GetInteractionCorrectResponses - AICC does not support interaction retrieval, returning empty array"); 8587 return new Array(); 8588 8589 8590} 8591 8592 8593 8594//public 8595function AICC_GetInteractionWeighting(strInteractionID) 8596{ 8597 WriteToDebug("AICC_GetInteractionWeighting - AICC does not support interaction retrieval, returning empty string"); 8598 return ''; 8599 8600} 8601 8602 8603 8604//public 8605function AICC_GetInteractionLearnerResponses(strInteractionID) 8606{ 8607 WriteToDebug("AICC_GetInteractionLearnerResponses - AICC does not support interaction retrieval, returning empty array"); 8608 return new Array(); 8609 8610 8611} 8612 8613 8614//public 8615function AICC_GetInteractionResult(strInteractionID) 8616{ 8617 WriteToDebug("AICC_GetInteractionResult - AICC does not support interaction retrieval, returning empty string"); 8618 return ''; 8619 8620} 8621 8622 8623 8624//public 8625function AICC_GetInteractionLatency(strInteractionID) 8626{ 8627 WriteToDebug("AICC_GetInteractionDescription - AICC does not support interaction retrieval, returning empty string"); 8628 return ''; 8629 8630} 8631 8632 8633 8634//public 8635function AICC_GetInteractionDescription(strInteractionID) 8636{ 8637 WriteToDebug("AICC_GetInteractionDescription - AICC does not support interaction retrieval, returning empty string"); 8638 return ''; 8639} 8640 8641//________________________________________________ 8642 8643//public 8644function AICC_CreateDataBucket(strBucketId, intMinSize, intMaxSize){ 8645 WriteToDebug("AICC_CreateDataBucket - AICC does not support SSP, returning false"); 8646 return false; 8647} 8648 8649//public 8650function AICC_GetDataFromBucket(strBucketId){ 8651 WriteToDebug("AICC_GetDataFromBucket - AICC does not support SSP, returning empty string"); 8652 return ""; 8653} 8654 8655//public 8656function AICC_PutDataInBucket(strBucketId, strData, blnAppendToEnd){ 8657 WriteToDebug("AICC_PutDataInBucket - AICC does not support SSP, returning false"); 8658 return false;
8659} 8660 8661//public 8662function AICC_DetectSSPSupport(){ 8663 WriteToDebug("AICC_DetectSSPSupport - AICC does not support SSP, returning false"); 8664 return false; 8665} 8666 8667//public 8668function AICC_GetBucketInfo(strBucketId){ 8669 WriteToDebug("AICC_DetectSSPSupport - AICC does not support SSP, returning empty SSPBucketSize"); 8670 return new SSPBucketSize(0, 0); 8671} 8672 8673 8674function AICC_SetNavigationRequest(strNavRequest){ 8675 WriteToDebug("AICC_SetNavigationRequest - AICC does not support navigation requests, returning false"); 8676 return false; 8677} 8678 8679function AICC_GetNavigationRequest(){ 8680 WriteToDebug("AICC_SetNavigationRequest - AICC does not support navigation requests, returning false"); 8681 return false; 8682} 8683 8684//================================================================== 8685//================================================================== 8686 8687function FormAICCPostData(){ 8688 WriteToDebug("In FormAICCPostData"); 8689 8690 var strAICCData = ""; 8691 8692 8693 strAICCData += "[Core]\r\n"; 8694 8695 strAICCData += "Lesson_Location=" + AICC_Lesson_Location + "\r\n"; 8696 strAICCData += "Lesson_Status=" + AICC_TranslateLessonStatusToAICC(AICC_Status) + "\r\n"; 8697 strAICCData += "Score=" + AICC_TranslateScoreToAICC() + "\r\n"; 8698 strAICCData += "Time=" + AICC_TranslateTimeToAICC() + "\r\n"; 8699 8700 strAICCData += "[Comments]\r\n" + AICC_TranslateCommentsToAICC() + "\r\n"; 8701 8702 strAICCData += "[Objectives_Status]\r\n" + AICC_TranslateObjectivesToAICC() + "\r\n"; 8703 8704 strAICCData += "[Student_Preferences]\r\n"; 8705 8706 strAICCData += "Audio=" + AICC_TranslateAudioToAICC() + "\r\n"; 8707 strAICCData += "Language=" + AICC_Language + "\r\n"; 8708 strAICCData += "Speed=" + AICC_TranslateSpeedToAICC() + "\r\n"; 8709 strAICCData += "Text=" + AICC_TranslateTextToAICC() + "\r\n"; 8710 8711 strAICCData += "[Core_Lesson]\r\n"; 8712 strAICCData += AICC_Data_Chunk; 8713 8714 WriteToDebug("FormAICCPostData returning: " + strAICCData); 8715 8716 return strAICCData; 8717} 8718 8719function AICC_TranslateLessonStatusToAICC(intStatus){ 8720 8721 WriteToDebug("In AICC_TranslateLessonStatusToAICC"); 8722 8723 switch (intStatus){ 8724 8725 case LESSON_STATUS_PASSED: 8726 WriteToDebug("Status is passed"); 8727 AICC_Lesson_Status = "P"; 8728 break; 8729 8730 case LESSON_STATUS_COMPLETED: 8731 WriteToDebug("Status is completed"); 8732 AICC_Lesson_Status = "C"; 8733 break; 8734 8735 case LESSON_STATUS_FAILED: 8736 WriteToDebug("Status is failed"); 8737 AICC_Lesson_Status = "F"; 8738 break; 8739 8740 case LESSON_STATUS_INCOMPLETE: 8741 WriteToDebug("Status is incomplete"); 8742 AICC_Lesson_Status = "I"; 8743 break; 8744 8745 case LESSON_STATUS_BROWSED: 8746 WriteToDebug("Status is browsed"); 8747 AICC_Lesson_Status = "B"; 8748 break; 8749 8750 case LESSON_STATUS_NOT_ATTEMPTED: 8751 WriteToDebug("Status is not attempted"); 8752 AICC_Lesson_Status = "N"; 8753 break; 8754 } 8755 8756 return AICC_Lesson_Status; 8757} 8758 8759function AICC_TranslateScoreToAICC(){ 8760 8761 WriteToDebug("In AICC_TranslateScoreToAICC"); 8762 8763 AICC_Score = AICC_fltScoreRaw; 8764 8765 //prior to version 3, AICC scores cannot contain a decimal 8766 if (AICC_LMS_Version < 3 && AICC_fltScoreRaw != ""){ 8767 AICC_Score = parseInt(AICC_Score, 10); 8768 } 8769 8770 //some older versions may not have this setting available 8771 if ( (AICC_REPORT_MIN_MAX_SCORE === undefined || 8772 AICC_REPORT_MIN_MAX_SCORE === null || 8773 AICC_REPORT_MIN_MAX_SCORE === true) && 8774 (AICC_LMS_Version >= 3 ) //min and max scores are only allowed after version 3 8775 ){ 8776 8777 WriteToDebug("Using max and min values if available."); 8778 8779 if ((AICC_fltScoreMax != "") || (AICC_fltScoreMin != "")) { 8780 WriteToDebug("Appending Max and Min scores");
8781 AICC_Score += "," + AICC_fltScoreMax + "," + AICC_fltScoreMin; 8782 } 8783 8784 } 8785 8786 WriteToDebug("AICC_Score=" + AICC_Score); 8787 8788 return AICC_Score; 8789} 8790 8791function AICC_TranslateTimeToAICC(){ 8792 8793 WriteToDebug("In AICC_TranslateTimeToAICC"); 8794 8795 var strTime; 8796 8797 strTime = ConvertMilliSecondsToSCORMTime(AICC_intSessionTimeMilliseconds, false); 8798 8799 return strTime; 8800} 8801 8802function AICC_TranslateCommentsToAICC(){ 8803 8804 WriteToDebug("In AICC_TranslateCommentsToAICC"); 8805 8806 var strComments = ""; 8807 for (var i=0; i < AICC_aryCommentsFromLearner.length; i++){ 8808 strComments += "<" + (i+1) + ">" + AICC_aryCommentsFromLearner[i] + "<e." + (i+1) + ">"; 8809 } 8810 return strComments; 8811} 8812 8813function AICC_TranslateObjectivesToAICC(){ 8814 8815 WriteToDebug("In AICC_TranslateObjectivesToAICC"); 8816 8817 var strObjectives = ""; 8818 8819 for (var i=0; i<AICC_aryObjectivesWrite.length; i++){ 8820 8821 WriteToDebug("Looking at index: " + i); 8822 8823 if (AICC_aryObjectivesWrite[i]){ 8824 WriteToDebug("Element " + i + " exists, id=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_ID] + ", score=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_SCORE
8824] + ", status=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_STATUS]); 8825 8826 strObjectives += "J_ID." + (i+1) + "=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_ID] + "\r\n"; 8827 8828 if (AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_SCORE] != ""){ 8829 strObjectives += "J_Score." + (i+1) + "=" + AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_SCORE] + "\r\n"; 8830 } 8831 8832 if (AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_STATUS] != ""){ 8833 strObjectives += "J_Status." + (i+1) + "=" + AICC_TranslateLessonStatusToAICC(AICC_aryObjectivesWrite[i][AICC_OBJ_ARRAY_STATUS]) + "\r\n"; 8834 } 8835 } 8836 } 8837 8838 return strObjectives; 8839} 8840 8841function AICC_TranslateAudioToAICC(){ 8842 8843 WriteToDebug("In AICC_TranslateAudioToAICC"); 8844 8845 var strReturn; 8846 8847 switch (AICC_AudioPlayPreference){ 8848 8849 case PREFERENCE_ON: 8850 WriteToDebug("Preference is ON"); 8851 strReturn = AICC_intAudioVolume; 8852 break; 8853 8854 case PREFERENCE_DEFAULT: 8855 WriteToDebug("Preference is DEFAULT"); 8856 strReturn = 0; 8857 break; 8858 8859 case PREFERENCE_OFF: 8860 WriteToDebug("Preference is OFF"); 8861 strReturn = -1; 8862 break; 8863 } 8864 8865 return strReturn; 8866} 8867 8868 8869 8870function AICC_TranslateSpeedToAICC(){ 8871 WriteToDebug("In AICC_TranslateSpeedToAICC"); 8872 8873 var intAICCSpeed; 8874 8875 intAICCSpeed = (AICC_intPercentOfMaxSpeed * 2) - 100; 8876 8877 return intAICCSpeed; 8878} 8879 8880function AICC_TranslateTextToAICC(){ 8881 WriteToDebug("In AICC_TranslateTextToAICC"); 8882 8883 var strPreference = 0; 8884 8885 if (AICC_TextPreference == PREFERENCE_OFF){ 8886 strPreference = -1; 8887 } 8888 else if (AICC_TextPreference == PREFERENCE_DEFAULT){ 8889 strPreference = 0; 8890 } 8891 else if (AICC_TextPreference == PREFERENCE_ON){ 8892 strPreference = 1; 8893 } 8894 8895 return strPreference; 8896} 8897 8898 8899 8900//================================================================== 8901//interactions 8902//================================================================== 8903 8904 8905function FormAICCInteractionsData(){ 8906 8907 WriteToDebug("In FormAICCInteractionsData"); 8908 8909 var strInteractions; 8910 var strDate; 8911 var strTime; 8912 var strResult = ""; 8913 8914 strInteractions = '"course_id","student_id","lesson_id","date","time","interaction_id",' + 8915 '"objective_id","type_interaction","correct_response","student_response",' + 8916 '"result","weighting","latency"\r\n'; 8917 8918 var blnCorrect = ""; 8919 var strResponse = ""; 8920 var strCorrectResponse = ""; 8921 var strLatency = ""; 8922 8923 for (var i=0; i < AICC_aryInteractions.length; i++){ 8924 8925 blnCorrect = AICC_aryInteractions[i][AICC_INTERACTIONS_CORRECT]; 8926 strResult = ""; 8927 8928 //need to leave support for blnCorrect=t/f for legacy implementations of RSECA 8929 //if it's null, leave it as "" (not every interaction is correct/incorrect 8930 8931 if (blnCorrect == true || blnCorrect == INTERACTION_RESULT_CORRECT){ 8932 strResult = AICC_RESULT_CORRECT; 8933 } 8934 else if (blnCorrect == "false" || blnCorrect == INTERACTION_RESULT_WRONG){ //compare against the string "false" because ("" == false) evaluates to true 8935 strResult = AICC_RESULT_WRONG; 8936 } 8937 else if (blnCorrect == INTERACTION_RESULT_UNANTICIPATED){ 8938 strResult = AICC_RESULT_UNANTICIPATED; 8939 } 8940 else if (blnCorrect == INTERACTION_RESULT_NEUTRAL){ 8941 strResult = AICC_RESULT_NEUTRAL; 8942 } 8943 8944 strDate = ConvertDateToCMIDate(AICC_aryInteractions[i][AICC_INTERACTIONS_TIME_STAMP]); 8945 strTime = ConvertDateToCMITime(AICC_aryInteractions[i][AICC_INTERACTIONS_TIME_STAMP]); 8946 8947 8948 if (blnUseLongInteractionResultValues == true){ 8949 strResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_RESPONSE_LONG]; 8950 strCorrectResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_CORRECT_RESPONSE_LONG]; 8951 } 8952 else{ 8953 strResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_RESPONSE]; 8954 strCorrectResponse = AICC_aryInteractions[i][AICC_INTERACTIONS_CORRECT_RESPONSE]; 8955 } 8956
8957 strResponse = new String(strResponse); 8958 strCorrectResponse = new String(strCorrectResponse); 8959 8960 var tempLatency = AICC_aryInteractions[i][AICC_INTERACTIONS_LATENCY]; 8961 8962 if (tempLatency !== null && tempLatency !== undefined && tempLatency != ""){ 8963 strLatency = ConvertMilliSecondsToSCORMTime(tempLatency, false); 8964 } 8965 8966 strInteractions += '"' + AICC_CourseID.replace("\"", "") + '","' + AICC_Student_ID.replace("\"", "") + '","' + AICC_LESSON_ID.replace("\"", "") + '","' + 8967 strDate + '","' + strTime + '","' + AICC_aryInteractions[i][AICC_INTERACTIONS_ID].replace("\"", "") + '",' + 8968 '""' + ',"' + AICC_aryInteractions[i][AICC_INTERACTIONS_TYPE] + '","' + strCorrectResponse.replace("\"", "") + '","' + 8969 strResponse.replace("\"", "") + '","' + strResult + '","' + 8970 AICC_aryInteractions[i][AICC_INTERACTIONS_WEIGHTING] + '","' + strLatency + '"\r\n'; 8971 } 8972 8973 return strInteractions; 8974} 8975 8976 8977/* 8978moved to UtilityFunctions.js ConvertToCMIDate 8979function ConvertToCMIDate(dtmDate){ 8980 8981 WriteToDebug("In ConvertToCMIDate"); 8982 8983 var strYear; 8984 var strMonth; 8985 var strDay; 8986 var strReturn; 8987 8988 dtmDate = new Date(dtmDate); 8989 8990 strYear = dtmDate.getFullYear() 8991 strMonth = dtmDate.getMonth(); 8992 strDay = dtmDate.getDate(); 8993 8994 strReturn = ZeroPad(strYear, 4) + "/" + ZeroPad(strMonth, 2) + "/" + ZeroPad(strDay, 2); 8995 8996 return strReturn; 8997} 8998*/ 8999/* 9000moved to UtilityFunctions.js ConvertDateToCMITime 9001function ConvertToCMITime(dtmDate){ 9002 9003 WriteToDebug("In ConvertToCMITime"); 9004 9005 var strHours; 9006 var strMinutes; 9007 var strSeconds; 9008 var strReturn; 9009 9010 dtmDate = new Date(dtmDate); 9011 9012 strHours = dtmDate.getHours(); 9013 strMinutes = dtmDate.getMinutes(); 9014 strSeconds = dtmDate.getSeconds(); 9015 9016 strReturn = ZeroPad(strHours, 2) + ":" + ZeroPad(strMinutes) + ":" + ZeroPad(strSeconds, 2); 9017 9018 return strReturn; 9019} 9020*/ 9021 9022//================================================================== 9023//debug stuff 9024//================================================================== 9025 9026//used for debug only 9027function DisplayAICCVariables(){ 9028 9029 var strAlert = ""; 9030 9031 strAlert += "AICC_Student_ID = " + AICC_Student_ID + "\n"; 9032 strAlert += "AICC_Student_Name = " + AICC_Student_Name + "\n"; 9033 strAlert += "AICC_Lesson_Location = " + AICC_Lesson_Location + "\n"; 9034 strAlert += "AICC_Score = " + AICC_Score + "\n"; 9035 strAlert += "AICC_Credit = " + AICC_Credit + "\n"; 9036 strAlert += "AICC_Lesson_Status = " + AICC_Lesson_Status + "\n"; 9037 strAlert += "AICC_Time = " + AICC_Time + "\n"; 9038 strAlert += "AICC_Mastery_Score = " + AICC_Mastery_Score + "\n"; 9039 strAlert += "AICC_Lesson_Mode = " + AICC_Lesson_Mode + "\n"; 9040 strAlert += "AICC_Max_Time_Allowed = " + AICC_Max_Time_Allowed + "\n"; 9041 strAlert += "AICC_Time_Limit_Action = " + AICC_Time_Limit_Action + "\n"; 9042 strAlert += "AICC_Audio = " + AICC_Audio + "\n"; 9043 strAlert += "AICC_Speed = " + AICC_Speed + "\n"; 9044 strAlert += "AICC_Language = " + AICC_Language + "\n"; 9045 strAlert += "AICC_Text = " + AICC_Text + "\n"; 9046 strAlert += "AICC_Launch_Data = " + AICC_Launch_Data + "\n"; 9047 strAlert += "AICC_Data_Chunk = " + AICC_Data_Chunk + "\n"; 9048 strAlert += "AICC_Comments = " + AICC_Comments + "\n"; 9049 strAlert += "AICC_Objectives = " + AICC_Objectives + "\n"; 9050 9051 alert(strAlert) 9052} 9053 9054 9055 9056 9057function NONE_Initialize(){ 9058 WriteToDebug("In NONE_Initialize, Returning true"); 9059 9060 InitializeExecuted(true, ""); 9061 9062 return true; 9063} 9064 9065 9066function NONE_Finish(strExitType, blnStatusWasSet){ 9067 WriteToDebug("In NONE_Finish, Returning true"); 9068 return true; 9069} 9070 9071 9072function NONE_CommitData(){ 9073 WriteToDebug("In NONE_CommitData, Returning true"); 9074 return true; 9075} 9076 9077function NONE_GetStudentID(){ 9078 WriteToDebug("In NONE_GetStudentID, Returning ''"); 9079 return ""; 9080} 9081 9082function NONE_GetStudentName(){ 9083 WriteToDebug("In NONE_GetStudentName, Returning ''"); 9084 return ""; 9085} 9086 9087function NONE_GetBookmark(){ 9088 WriteToDebug("In NONE_GetBookmark, Returning ''"); 9089 return ""; 9090} 9091 9092function NONE_SetBookmark(strBookmark){ 9093 WriteToDebug("In NONE_SetBookmark, Returning true"); 9094 return true; 9095} 9096 9097function NONE_GetDataChunk(){ 9098 WriteToDebug("In NONE_GetDataChunk, Returning ''"); 9099 return ""; 9100} 9101 9102function NONE_SetDataChunk(strData){ 9103 WriteToDebug("In NONE_SetDataChunk, Returning true"); 9104 return true; 9105} 9106 9107 9108function NONE_GetLaunchData(){ 9109 WriteToDebug("In NONE_GetLaunchData, Returning ''"); 9110 return ""; 9111} 9112 9113function NONE_GetComments(){ 9114 WriteToDebug("In NONE_GetComments, Returning ''"); 9115 return ""; 9116} 9117 9118function NONE_WriteComment(strComment){ 9119 WriteToDebug("In NONE_WriteComment, Returning true"); 9120 return true; 9121} 9122 9123function NONE_GetLMSComments(){ 9124 WriteToDebug("In NONE_GetLMSComments, Returning ''"); 9125 return ""; 9126} 9127 9128 9129function NONE_GetAudioPlayPreference(){ 9130 WriteToDebug("In NONE_GetAudioPlayPreference, Returning " + PREFERENCE_DEFAULT); 9131 return PREFERENCE_DEFAULT; 9132} 9133 9134 9135 9136function NONE_GetAudioVolumePreference(){ 9137 WriteToDebug("In NONE_GetAudioVolumePreference, Returning 100"); 9138 return 100; 9139} 9140 9141 9142function NONE_SetAudioPreference(PlayPreference, intPercentOfMaxSpeed){ 9143 WriteToDebug("In NONE_SetAudioPreference, Returning true"); 9144 return true; 9145} 9146 9147 9148 9149function NONE_SetLanguagePreference(strLanguage){ 9150 WriteToDebug("In NONE_SetLanguagePreference, Returning true"); 9151 return true; 9152} 9153 9154 9155function NONE_GetLanguagePreference(){ 9156 WriteToDebug("In NONE_GetLanguagePreference, Returning ''"); 9157 return ""; 9158} 9159 9160 9161function NONE_SetSpeedPreference(intPercentOfMax){ 9162 WriteToDebug("In NONE_SetSpeedPreference, Returning true"); 9163 return true; 9164} 9165 9166 9167function NONE_GetSpeedPreference(){ 9168 WriteToDebug("In NONE_GetSpeedPreference, Returning 100"); 9169 return 100; 9170} 9171 9172function NONE_SetTextPreference(intPreference){ 9173 WriteToDebug("In NONE_SetTextPreference, Returning true"); 9174 return true; 9175} 9176 9177 9178function NONE_GetTextPreference(){ 9179 WriteToDebug("In NONE_GetTextPreference, Returning " + PREFERENCE_DEFAULT); 9180 return PREFERENCE_DEFAULT; 9181} 9182 9183function NONE_GetPreviouslyAccumulatedTime(){ 9184 WriteToDebug("In NONE_GetPreviouslyAccumulatedTime, Returning 0"); 9185 return 0; 9186} 9187 9188 9189function NONE_SaveTime(intMilliSeconds){ 9190 WriteToDebug("In intMilliSeconds, Returning true"); 9191 return true; 9192} 9193 9194function NONE_GetMaxTimeAllowed(){ 9195 WriteToDebug("In NONE_GetMaxTimeAllowed, Returning 36002439999"); 9196 return MAX_CMI_TIME; 9197} 9198 9199 9200 9201function NONE_DisplayMessageOnTimeout(){ 9202 WriteToDebug("In NONE_DisplayMessageOnTimeout, Returning false"); 9203 return false;
9204} 9205 9206function NONE_ExitOnTimeout(){ 9207 WriteToDebug("In NONE_ExitOnTimeout, Returning false"); 9208 return false; 9209} 9210 9211 9212function NONE_GetPassingScore(){ 9213 WriteToDebug("In NONE_GetPassingScore, Returning ''"); 9214 return ''; 9215} 9216 9217 9218 9219function NONE_GetScore(){ 9220 WriteToDebug("In NONE_GetScore, Returning 0"); 9221 return 0; 9222} 9223 9224function NONE_SetScore(intScore, intMaxScore, intMinScore){ 9225 WriteToDebug("In NONE_SetScore, Returning true"); 9226 return true; 9227} 9228 9229 9230 9231function NONE_RecordTrueFalseInteraction(){ 9232 WriteToDebug("In NONE_RecordTrueFalseInteraction, Returning true"); 9233 return true; 9234} 9235function NONE_RecordMultipleChoiceInteraction(strID, strResponse, blnCorrect, strCorrectResponse){ 9236 WriteToDebug("In NONE_RecordMultipleChoiceInteraction, Returning true"); 9237 return true; 9238} 9239function NONE_RecordFillInInteraction(){ 9240 WriteToDebug("In NONE_RecordFillInInteraction, Returning true"); 9241 return true; 9242} 9243function NONE_RecordMatchingInteraction(){ 9244 WriteToDebug("In NONE_RecordMatchingInteraction, Returning true"); 9245 return true; 9246} 9247function NONE_RecordPerformanceInteraction(){ 9248 WriteToDebug("In NONE_RecordPerformanceInteraction, Returning true"); 9249 return true; 9250} 9251function NONE_RecordSequencingInteraction(){ 9252 WriteToDebug("In NONE_RecordSequencingInteraction, Returning true"); 9253 return true; 9254} 9255function NONE_RecordLikertInteraction(){ 9256 WriteToDebug("In RecordLikertInteraction, Returning true"); 9257 return true; 9258} 9259function NONE_RecordNumericInteraction(){ 9260 WriteToDebug("In NONE_RecordNumericInteraction, Returning true"); 9261 return true; 9262} 9263 9264 9265function NONE_GetEntryMode(){ 9266 WriteToDebug("In NONE_GetEntryMode, Returning " + ENTRY_FIRST_TIME); 9267 return ENTRY_FIRST_TIME; 9268} 9269 9270function NONE_GetLessonMode(){ 9271 WriteToDebug("In NONE_GetLessonMode, Returning " + MODE_NORMAL); 9272 return MODE_NORMAL; 9273} 9274 9275function NONE_GetTakingForCredit(){ 9276 WriteToDebug("In NONE_GetTakingForCredit, Returning true"); 9277 return true; 9278} 9279 9280 9281 9282function NONE_SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){ 9283 WriteToDebug("In NONE_SetObjectiveScore, Returning true"); 9284 return true; 9285} 9286 9287 9288function NONE_SetObjectiveStatus(strObjectiveID, Lesson_Status){ 9289 WriteToDebug("In NONE_SetObjectiveStatus, Returning true"); 9290 return true; 9291} 9292 9293function NONE_SetObjectiveDescription(strObjectiveID, strObjectiveDescription){ 9294 WriteToDebug("In NONE_SetObjectiveDescription, Returning true"); 9295 return true; 9296} 9297 9298 9299function NONE_GetObjectiveScore(strObjectiveID){ 9300 WriteToDebug("In NONE_SetObjectiveScore, Returning ''"); 9301 return ''; 9302} 9303 9304 9305function NONE_GetObjectiveStatus(strObjectiveID){ 9306 WriteToDebug("In NONE_SetObjectiveStatus, Returning Not Attempted"); 9307 return LESSON_STATUS_NOT_ATTEMPTED; 9308} 9309 9310function NONE_GetObjectiveDescription(strObjectiveID){ 9311 WriteToDebug("In NONE_GetObjectiveDescription, ''"); 9312 return ""; 9313} 9314 9315function NONE_CreateValidIdentifier(str) { 9316 return CreateUriIdentifier(str); 9317} 9318 9319//___________________________________________________________ 9320//Interaction Retrieval Functionality 9321//NOTE ON INTERACTION RETRIEVAL 9322//A. It is only available in certain standards, standards where it is unavailable will return nothing 9323//B. The interaction records are currently reported using "journaling", whereby each entry is appended 9324// Retrieval methods will retrieve only the most recent value 9325 9326 9327//___________________________________________________________ 9328//Helper Methods 9329function NONE_FindInteractionIndexFromID(strInteractionID){ 9330 9331 WriteToDebug("NONE_FindInteractionIndexFromID - NONE does not support interaction retrieval, returning null"); 9332 9333 return null; 9334} 9335//___________________________________________________________ 9336 9337 9338function NONE_GetInteractionType(strInteractionID) 9339{ 9340 9341 WriteToDebug("NONE_GetInteractionType - NONE does not support interaction retrieval, returning empty string"); 9342 return ''; 9343 9344 9345} 9346 9347//public 9348function NONE_GetInteractionTimestamp(strInteractionID) 9349{ 9350 WriteToDebug("NONE_GetInteractionTimestamp - NONE does not support interaction retrieval, returning empty string"); 9351 return ''; 9352} 9353 9354 9355 9356//public 9357function NONE_GetInteractionCorrectResponses(strInteractionID) 9358{ 9359 9360 WriteToDebug("NONE_GetInteractionCorrectResponses - NONE does not support interaction retrieval, returning empty array"); 9361 return new Array(); 9362 9363 9364} 9365 9366 9367 9368//public 9369function NONE_GetInteractionWeighting(strInteractionID) 9370{ 9371 WriteToDebug("NONE_GetInteractionWeighting - NONE does not support interaction retrieval, returning empty string"); 9372 return ''; 9373 9374} 9375 9376 9377 9378//public 9379function NONE_GetInteractionLearnerResponses(strInteractionID) 9380{ 9381 WriteToDebug("NONE_GetInteractionLearnerResponses - NONE does not support interaction retrieval, returning empty array"); 9382 return new Array(); 9383 9384 9385} 9386 9387 9388//public 9389function NONE_GetInteractionResult(strInteractionID) 9390{ 9391 WriteToDebug("NONE_GetInteractionResult - NONE does not support interaction retrieval, returning empty string"); 9392 return ''; 9393 9394} 9395 9396 9397 9398//public 9399function NONE_GetInteractionLatency(strInteractionID) 9400{ 9401 WriteToDebug("NONE_GetInteractionDescription - NONE does not support interaction retrieval, returning empty string"); 9402 return ''; 9403 9404} 9405 9406 9407 9408//public 9409function NONE_GetInteractionDescription(strInteractionID) 9410{ 9411 WriteToDebug("NONE_GetInteractionDescription - NONE does not support interaction retrieval, returning empty string"); 9412 return ''; 9413 9414 9415} 9416 9417//public 9418function NONE_CreateDataBucket(strBucketId, intMinSize, intMaxSize){ 9419 WriteToDebug("NONE_CreateDataBucket - NONE does not support SSP, returning false"); 9420 return false;
vendor: 4,960 bytes, lines 9421-9538
9421} 9422 9423//public 9424function NONE_GetDataFromBucket(strBucketId){ 9425 WriteToDebug("NONE_GetDataFromBucket - NONE does not support SSP, returning empty string"); 9426 return ""; 9427} 9428 9429//public 9430function NONE_PutDataInBucket(strBucketId, strData, blnAppendToEnd){ 9431 WriteToDebug("NONE_PutDataInBucket - NONE does not support SSP, returning false"); 9432 return false; 9433} 9434 9435//public 9436function NONE_DetectSSPSupport(){ 9437 WriteToDebug("NONE_DetectSSPSupport - NONE does not support SSP, returning false"); 9438 return false; 9439} 9440 9441//public 9442function NONE_GetBucketInfo(strBucketId){ 9443 WriteToDebug("NONE_DetectSSPSupport - NONE does not support SSP, returning empty SSPBucketSize"); 9444 return new SSPBucketSize(0, 0); 9445} 9446 9447 9448//________________________________________________ 9449 9450function NONE_SetNavigationRequest(strNavRequest){ 9451 WriteToDebug("NONE_SetNavigationRequest - NONE does not support navigation requests, returning false"); 9452 return false; 9453} 9454 9455function NONE_GetNavigationRequest(){ 9456 WriteToDebug("NONE_GetNavigationRequest - NONE does not support navigation requests, returning false"); 9457 return false; 9458} 9459 9460 9461function NONE_SetFailed(){ 9462 WriteToDebug("In NONE_SetFailed, Returning true"); 9463 return true; 9464} 9465 9466function NONE_SetPassed(){ 9467 WriteToDebug("In NONE_SetPassed, Returning true"); 9468 return true; 9469} 9470 9471function NONE_SetCompleted(){ 9472 WriteToDebug("In NONE_SetCompleted, Returning true"); 9473 return true; 9474} 9475 9476function NONE_ResetStatus(){ 9477 WriteToDebug("In NONE_ResetStatus, Returning true"); 9478 return true; 9479} 9480 9481function NONE_GetStatus(){ 9482 WriteToDebug("In NONE_GetStatus, Returning " + LESSON_STATUS_INCOMPLETE); 9483 return LESSON_STATUS_INCOMPLETE; 9484} 9485 9486//public 9487function NONE_GetProgressMeasure(){ 9488 WriteToDebug("NONE_GetProgressMeasure - NONE does not support progress_measure, returning false"); 9489 return false; 9490} 9491//public 9492function NONE_SetProgressMeasure(){ 9493 WriteToDebug("NONE_SetProgressMeasure - NONE does not support progress_measure, returning false"); 9494 return false; 9495} 9496 9497//public 9498function NONE_GetObjectiveProgressMeasure(){ 9499 WriteToDebug("NONE_GetObjectiveProgressMeasure - NONE does not support progress_measure, returning false"); 9500 return false; 9501} 9502//public 9503function NONE_SetObjectiveProgressMeasure(){ 9504 WriteToDebug("NONE_SetObjectiveProgressMeasure - NONE does not support progress_measure, returning false"); 9505 return false; 9506} 9507 9508function NONE_SetPointBasedScore(intScore, intMaxScore, intMinScore){ 9509 WriteToDebug("NONE_SetPointBasedScore - NONE does not support SetPointBasedScore, returning false"); 9510 return false; 9511} 9512 9513function NONE_GetScaledScore(intScore, intMaxScore, intMinScore){ 9514 WriteToDebug("NONE_GetScaledScore - NONE does not support GetScaledScore, returning false"); 9515 return false; 9516} 9517 9518function NONE_GetLastError(){ 9519 WriteToDebug("In NONE_GetLastError, Returning " + NO_ERROR); 9520 return NO_ERROR; 9521} 9522 9523function NONE_GetLastErrorDesc(){ 9524 WriteToDebug("In NONE_GetLastErrorDesc, Returning ''"); 9525 return ""; 9526} 9527 9528"0.50.0"; 9529/* 9530CryptoJS v3.1.2 9531code.google.com/p/crypto-js 9532(c) 2009-2013 by Jeff Mott. All rights reserved. 9533code.google.com/p/crypto-js/wiki/License 9534*/ 9535var CryptoJS=CryptoJS||function(e,m){var p={},j=p.lib={},l=function(){},f=j.Base={extend:function(a){l.prototype=this;var c=new l;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 9536n=j.WordArray=f.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=m?c:4*a.length},toString:function(a){return(a||h).stringify(this)},concat:function(a){var c=this.words,q=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b<a;b++)c[d+b>>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535<q.length)for(b=0;b<a;b+=4)c[d+b>>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 953732-8*(c%4);a.length=e.ceil(c/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b<a;b+=4)c.push(4294967296*e.random()|0);return new n.init(c,a)}}),b=p.enc={},h=b.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++){var f=c[d>>>2]>>>24-8*(d%4)&255;b.push((f>>>4).toString(16));b.push((f&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d+=2)b[d>>>3]|=parseInt(a.substr(d, 95382),16)<<24-4*(d%8);return new n.init(b,c/2)}},g=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d<a;d++)b.push(String.fromCharCode(c[d>>>2]>>>24-8*(d%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d<c;d++)b[d>>>
95382]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return new n.init(b,c)}},r=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(g.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return g.parse(unescape(encodeURIComponent(a)))}}, 9539k=j.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new n.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,d=c.sigBytes,f=this.blockSize,h=d/(4*f),h=a?e.ceil(h):e.max((h|0)-this._minBufferSize,0);a=h*f;d=e.min(4*a,d);if(a){for(var g=0;g<a;g+=f)this._doProcessBlock(b,g);g=b.splice(0,a);c.sigBytes-=d}return new n.init(g,d)},clone:function(){var a=f.clone.call(this); 9540a._data=this._data.clone();return a},_minBufferSize:0});j.Hasher=k.extend({cfg:f.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){k.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,b){return(new a.init(b)).finalize(c)}},_createHmacHelper:function(a){return function(b,f){return(new s.HMAC.init(a, 9541f)).finalize(b)}}});var s=p.algo={};return p}(Math); 9542(function(){var e=CryptoJS,m=e.lib,p=m.WordArray,j=m.Hasher,l=[],m=e.algo.SHA1=j.extend({_doReset:function(){this._hash=new p.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(f,n){for(var b=this._hash.words,h=b[0],g=b[1],e=b[2],k=b[3],j=b[4],a=0;80>a;a++){if(16>a)l[a]=f[n+a]|0;else{var c=l[a-3]^l[a-8]^l[a-14]^l[a-16];l[a]=c<<1|c>>>31}c=(h<<5|h>>>27)+j+l[a];c=20>
vendor: 5,250 bytes, lines 9542-9564
9542a?c+((g&e|~g&k)+1518500249):40>a?c+((g^e^k)+1859775393):60>a?c+((g&e|g&k|e&k)-1894007588):c+((g^e^ 9543k)-899497514);j=k;k=e;e=g<<30|g>>>2;g=h;h=c}b[0]=b[0]+h|0;b[1]=b[1]+g|0;b[2]=b[2]+e|0;b[3]=b[3]+k|0;b[4]=b[4]+j|0},_doFinalize:function(){var f=this._data,e=f.words,b=8*this._nDataBytes,h=8*f.sigBytes;e[h>>>5]|=128<<24-h%32;e[(h+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(h+64>>>9<<4)+15]=b;f.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=j.clone.call(this);e._hash=this._hash.clone();return e}});e.SHA1=j._createHelper(m);e.HmacSHA1=j._createHmacHelper(m)})(); 9544 9545/* 9546CryptoJS v3.1.2 9547code.google.com/p/crypto-js 9548(c) 2009-2013 by Jeff Mott. All rights reserved. 9549code.google.com/p/crypto-js/wiki/License 9550*/ 9551var CryptoJS=CryptoJS||function(h,s){var f={},t=f.lib={},g=function(){},j=t.Base={extend:function(a){g.prototype=this;var c=new g;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 9552q=t.WordArray=j.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||u).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 955332-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new q.init(c,a)}}),v=f.enc={},u=v.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b, 95542),16)<<24-4*(b%8);return new q.init(d,c/2)}},k=v.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new q.init(d,c)}},l=v.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}}, 9555x=t.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=l.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var m=0;m<a;m+=e)this._doProcessBlock(d,m);m=d.splice(0,a);c.sigBytes-=b}return new q.init(m,b)},clone:function(){var a=j.clone.call(this); 9556a._data=this._data.clone();return a},_minBufferSize:0});t.Hasher=x.extend({cfg:j.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){x.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new w.HMAC.init(a, 9557d)).finalize(c)}}});var w=f.algo={};return f}(Math); 9558(function(h){for(var s=CryptoJS,f=s.lib,t=f.WordArray,g=f.Hasher,f=s.algo,j=[],q=[],v=function(a){return 4294967296*(a-(a|0))|0},u=2,k=0;64>k;){var l;a:{l=u;for(var x=h.sqrt(l),w=2;w<=x;w++)if(!(l%w)){l=!1;break a}l=!0}l&&(8>k&&(j[k]=v(h.pow(u,0.5))),q[k]=v(h.pow(u,1/3)),k++);u++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new t.init(j.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],m=b[2],h=b[3],p=b[4],j=b[5],k=b[6],l=b[7],n=0;64>n;n++){if(16>n)a[n]= 9559c[d+n]|0;else{var r=a[n-15],g=a[n-2];a[n]=((r<<25|r>>>7)^(r<<14|r>>>18)^r>>>3)+a[n-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[n-16]}r=l+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))+(p&j^~p&k)+q[n]+a[n];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&m^f&m);l=k;k=j;j=p;p=h+r|0;h=m;m=f;f=e;e=r+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+m|0;b[3]=b[3]+h|0;b[4]=b[4]+p|0;b[5]=b[5]+j|0;b[6]=b[6]+k|0;b[7]=b[7]+l|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; 9560d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=g._createHelper(f);s.HmacSHA256=g._createHmacHelper(f)})(Math); 9561 9562/* 9563CryptoJS v3.1.2 9564code.google.com/p/crypto-js
vendor: 5,309 bytes, lines 9565-9738
9565(c) 2009-2013 by Jeff Mott. All rights reserved. 9566code.google.com/p/crypto-js/wiki/License 9567*/ 9568(function () { 9569 // Shortcuts 9570 var C = CryptoJS; 9571 var C_lib = C.lib; 9572 var WordArray = C_lib.WordArray; 9573 var C_enc = C.enc; 9574 9575 /** 9576 * Base64 encoding strategy. 9577 */ 9578 var Base64 = C_enc.Base64 = { 9579 /** 9580 * Converts a word array to a Base64 string. 9581 * 9582 * @param {WordArray} wordArray The word array. 9583 * 9584 * @return {string} The Base64 string. 9585 * 9586 * @static 9587 * 9588 * @example 9589 * 9590 * var base64String = CryptoJS.enc.Base64.stringify(wordArray); 9591 */ 9592 stringify: function (wordArray) { 9593 // Shortcuts 9594 var words = wordArray.words; 9595 var sigBytes = wordArray.sigBytes; 9596 var map = this._map; 9597 9598 // Clamp excess bits 9599 wordArray.clamp(); 9600 9601 // Convert 9602 var base64Chars = []; 9603 for (var i = 0; i < sigBytes; i += 3) { 9604 var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 9605 var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; 9606 var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; 9607 9608 var triplet = (byte1 << 16) | (byte2 << 8) | byte3; 9609 9610 for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { 9611 base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); 9612 } 9613 } 9614 9615 // Add padding 9616 var paddingChar = map.charAt(64); 9617 if (paddingChar) { 9618 while (base64Chars.length % 4) { 9619 base64Chars.push(paddingChar); 9620 } 9621 } 9622 9623 return base64Chars.join(''); 9624 }, 9625 9626 /** 9627 * Converts a Base64 string to a word array. 9628 * 9629 * @param {string} base64Str The Base64 string. 9630 * 9631 * @return {WordArray} The word array. 9632 * 9633 * @static 9634 * 9635 * @example 9636 * 9637 * var wordArray = CryptoJS.enc.Base64.parse(base64String); 9638 */ 9639 parse: function (base64Str) { 9640 // Shortcuts 9641 var base64StrLength = base64Str.length; 9642 var map = this._map; 9643 9644 // Ignore padding 9645 var paddingChar = map.charAt(64); 9646 if (paddingChar) { 9647 var paddingIndex = base64Str.indexOf(paddingChar); 9648 if (paddingIndex != -1) { 9649 base64StrLength = paddingIndex; 9650 } 9651 } 9652 9653 // Convert 9654 var words = []; 9655 var nBytes = 0; 9656 for (var i = 0; i < base64StrLength; i++) { 9657 if (i % 4) { 9658 var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2); 9659 var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2); 9660 words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8); 9661 nBytes++; 9662 } 9663 } 9664 9665 return WordArray.create(words, nBytes); 9666 }, 9667 9668 _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 9669 }; 9670}()); 9671 9672/* 9673CryptoJS v3.1.2 9674code.google.com/p/crypto-js 9675(c) 2009-2013 by Jeff Mott. All rights reserved. 9676code.google.com/p/crypto-js/wiki/License 9677*/ 9678(function () { 9679 // Check if typed arrays are supported 9680 if (typeof ArrayBuffer != 'function') { 9681 return; 9682 } 9683 9684 // Shortcuts 9685 var C = CryptoJS; 9686 var C_lib = C.lib; 9687 var WordArray = C_lib.WordArray; 9688 9689 // Reference original init 9690 var superInit = WordArray.init; 9691 9692 // Augment WordArray.init to handle typed arrays 9693 var subInit = WordArray.init = function (typedArray) { 9694 // Convert buffers to uint8 9695 if (typedArray instanceof ArrayBuffer) { 9696 typedArray = new Uint8Array(typedArray); 9697 } 9698 9699 // Convert other array views to uint8 9700 if ( 9701 typedArray instanceof Int8Array || 9702 typedArray instanceof Uint8ClampedArray || 9703 typedArray instanceof Int16Array || 9704 typedArray instanceof Uint16Array || 9705 typedArray instanceof Int32Array || 9706 typedArray instanceof Uint32Array || 9707 typedArray instanceof Float32Array || 9708 typedArray instanceof Float64Array 9709 ) { 9710 typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); 9711 } 9712 9713 // Handle Uint8Array 9714 if (typedArray instanceof Uint8Array) { 9715 // Shortcut 9716 var typedArrayByteLength = typedArray.byteLength; 9717 9718 // Extract bytes 9719 var words = []; 9720 for (var i = 0; i < typedArrayByteLength; i++) { 9721 words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); 9722 } 9723 9724 // Initialize this word array 9725 superInit.call(this, words, typedArrayByteLength); 9726 } else { 9727 // Else call normal init 9728 superInit.apply(this, arguments); 9729 } 9730 }; 9731 9732 subInit.prototype = WordArray; 9733}()); 9734 9735/*! 9736 Copyright 2012 Rustici Software 9737 9738 Licensed under the Apache License, Version 2.0 (the "License");
9739 you may not use this file except in compliance with the License. 9740 You may obtain a copy of the License at 9741 9742 http://www.apache.org/licenses/LICENSE-2.0 9743 9744 Unless required by applicable law or agreed to in writing, software 9745 distributed under the License is distributed on an "AS IS" BASIS, 9746 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9747 See the License for the specific language governing permissions and 9748 limitations under the License. 9749*/ 9750 9751/** 9752TinCan client library 9753 9754TODO: 9755 9756* Add statement queueing 9757 9758@module TinCan 9759**/ 9760var TinCan; 9761 9762(function () { 9763 "use strict"; 9764 var _reservedQSParams = { 9765 // 9766 // these are TC spec reserved words that may end up in queries to the endpoint 9767 // 9768 statementId: true, 9769 voidedStatementId: true, 9770 verb: true, 9771 object: true, 9772 registration: true, 9773 context: true, 9774 actor: true, 9775 since: true, 9776 until: true, 9777 limit: true, 9778 authoritative: true, 9779 sparse: true, 9780 instructor: true, 9781 ascending: true, 9782 continueToken: true, 9783 agent: true, 9784 activityId: true, 9785 stateId: true, 9786 profileId: true, 9787 9788 // 9789 // these are suggested by the LMS launch spec addition that TinCanJS consumes 9790 // 9791 activity_platform: true, 9792 grouping: true, 9793 "Accept-Language": true 9794 }; 9795 9796 /** 9797 @class TinCan 9798 @constructor 9799 @param {Object} [options] Configuration used to initialize. 9800 @param {String} [options.url] URL for determining launch provided 9801 configuration options 9802 @param {Array} [options.recordStores] list of pre-configured LRSes 9803 @param {Object|TinCan.Agent} [options.actor] default actor 9804 @param {Object|TinCan.Activity} [options.activity] default activity 9805 @param {String} [options.registration] default registration
9806 @param {Object|TinCan.Context} [options.context] default context 9807 **/ 9808 TinCan = function (cfg) { 9809 this.log("constructor"); 9810 9811 /** 9812 @property recordStores 9813 @type Array 9814 */ 9815 this.recordStores = []; 9816 9817 /** 9818 Default actor used when preparing statements that 9819 don't yet have an actor set, and for saving state, etc. 9820 9821 @property actor 9822 @type Object 9823 */ 9824 this.actor = null; 9825 9826 /** 9827 Default activity, may be used as a statement 'target' 9828 or incorporated into 'context' 9829 9830 @property activity 9831 @type Object 9832 */ 9833 this.activity = null; 9834 9835 /** 9836 Default registration, included in default context when 9837 provided, otherwise used in statement queries 9838 9839 @property registration 9840 @type String 9841 */ 9842 this.registration = null; 9843 9844 /** 9845 Default context used when preparing statements that 9846 don't yet have a context set, or mixed in when one 9847 has been provided, properties do NOT override on mixing 9848 9849 @property context 9850 @type Object 9851 */ 9852 this.context = null; 9853 9854 this.init(cfg); 9855 }; 9856 9857 TinCan.prototype = { 9858 LOG_SRC: "TinCan", 9859 9860 /** 9861 Safe version of logging, only displays when .DEBUG is true, and console.log 9862 is available 9863 9864 @method log 9865 @param {String} msg Message to output 9866 */ 9867 log: function (msg, src) { 9868 /* globals console */ 9869 if (TinCan.DEBUG && typeof console !== "undefined" && console.log) { 9870 src = src || this.LOG_SRC || "TinCan"; 9871 9872 console.log("TinCan." + src + ": " + msg); 9873 } 9874 }, 9875 9876 /** 9877 @method init 9878 @param {Object} [options] Configuration used to initialize (see TinCan constructor). 9879 */ 9880 init: function (cfg) { 9881 this.log("init"); 9882 var i; 9883 9884 cfg = cfg || {}; 9885 9886 if (cfg.hasOwnProperty("url") && cfg.url !== "") { 9887 this._initFromQueryString(cfg.url); 9888 } 9889 9890 if (cfg.hasOwnProperty("recordStores") && cfg.recordStores !== undefined) { 9891 for (i = 0; i < cfg.recordStores.length; i += 1) { 9892 this.addRecordStore(cfg.recordStores[i]); 9893 } 9894 } 9895 if (cfg.hasOwnProperty("activity")) { 9896 if (cfg.activity instanceof TinCan.Activity) { 9897 this.activity = cfg.activity; 9898 } 9899 else { 9900 this.activity = new TinCan.Activity (cfg.activity); 9901 } 9902 } 9903 if (cfg.hasOwnProperty("actor")) { 9904 if (cfg.actor instanceof TinCan.Agent) { 9905 this.actor = cfg.actor; 9906 } 9907 else { 9908 this.actor = new TinCan.Agent (cfg.actor); 9909 } 9910 } 9911 if (cfg.hasOwnProperty("context")) { 9912 if (cfg.context instanceof TinCan.Context) { 9913 this.context = cfg.context; 9914 } 9915 else { 9916 this.context = new TinCan.Context (cfg.context); 9917 } 9918 } 9919 if (cfg.hasOwnProperty("registration")) { 9920 this.registration = cfg.registration; 9921 } 9922 }, 9923 9924 /** 9925 @method _initFromQueryString 9926 @param {String} url 9927 @private 9928 */ 9929 _initFromQueryString: function (url) { 9930 this.log("_initFromQueryString"); 9931 9932 var i, 9933 prop, 9934 qsParams = TinCan.Utils.parseURL(url).params, 9935 lrsProps = ["endpoint", "auth"], 9936 lrsCfg = {}, 9937 contextCfg, 9938 extended = null 9939 ; 9940 9941 if (qsParams.hasOwnProperty("actor")) { 9942 this.log("_initFromQueryString - found actor: " + qsParams.actor); 9943 try { 9944 this.actor = TinCan.Agent.fromJSON(qsParams.actor); 9945 delete qsParams.actor; 9946 } 9947 catch (ex) { 9948 this.log("_initFromQueryString - failed to set actor: " + ex); 9949 } 9950 } 9951 9952 if (qsParams.hasOwnProperty("activity_id")) { 9953 this.activity = new TinCan.Activity ( 9954 { 9955 id: qsParams.activity_id 9956 } 9957 ); 9958 delete qsParams.activity_id; 9959 } 9960 9961 if ( 9962 qsParams.hasOwnProperty("activity_platform") || 9963 qsParams.hasOwnProperty("registration") || 9964 qsParams.hasOwnProperty("grouping") 9965 ) { 9966 contextCfg = {}; 9967 9968 if (qsParams.hasOwnProperty("activity_platform")) { 9969 contextCfg.platform = qsParams.activity_platform; 9970 delete qsParams.activity_platform; 9971 } 9972 if (qsParams.hasOwnProperty("registration")) { 9973 // 9974 // stored in two locations cause we always want it in the default 9975 // context, but we also want to be able to get to it for Statement 9976 // queries 9977 // 9978 contextCfg.registration = this.registration = qsParams.registration; 9979 delete qsParams.registration; 9980 } 9981 if (qsParams.hasOwnProperty("grouping")) { 9982 contextCfg.contextActivities = {}; 9983 contextCfg.contextActivities.grouping = qsParams.grouping; 9984 delete qsParams.grouping; 9985 } 9986
9987 this.context = new TinCan.Context (contextCfg); 9988 } 9989 9990 // 9991 // order matters here, process the URL provided LRS last because it gets 9992 // all the remaining parameters so that they get passed through 9993 // 9994 if (qsParams.hasOwnProperty("endpoint")) { 9995 for (i = 0; i < lrsProps.length; i += 1) { 9996 prop = lrsProps[i]; 9997 if (qsParams.hasOwnProperty(prop)) { 9998 lrsCfg[prop] = qsParams[prop]; 9999 delete qsParams[prop]; 10000 } 10001 } 10002 10003 // remove our reserved params so they don't end up in the extended object 10004 for (i in qsParams) { 10005 if (qsParams.hasOwnProperty(i)) { 10006 if (_reservedQSParams.hasOwnProperty(i)) { 10007 delete qsParams[i]; 10008 } else { 10009 extended = extended || {}; 10010 extended[i] = qsParams[i]; 10011 } 10012 } 10013 } 10014 if (extended !== null) { 10015 lrsCfg.extended = extended; 10016 } 10017 10018 lrsCfg.allowFail = false; 10019 10020 this.addRecordStore(lrsCfg); 10021 } 10022 }, 10023 10024 /** 10025 @method addRecordStore 10026 @param {Object} Configuration data 10027 10028 * TODO: 10029 * check endpoint for trailing '/' 10030 * check for unique endpoints 10031 */ 10032 addRecordStore: function (cfg) { 10033 this.log("addRecordStore"); 10034 var lrs; 10035 if (cfg instanceof TinCan.LRS) { 10036 lrs = cfg; 10037 } 10038 else { 10039 lrs = new TinCan.LRS (cfg); 10040 } 10041 this.recordStores.push(lrs); 10042 }, 10043 10044 /** 10045 @method prepareStatement 10046 @param {Object|TinCan.Statement} Base statement properties or 10047 pre-created TinCan.Statement instance 10048 @return {TinCan.Statement} 10049 */ 10050 prepareStatement: function (stmt) { 10051 this.log("prepareStatement"); 10052 if (! (stmt instanceof TinCan.Statement)) { 10053 stmt = new TinCan.Statement (stmt); 10054 } 10055 10056 if (stmt.actor === null && this.actor !== null) { 10057 stmt.actor = this.actor; 10058 } 10059 if (stmt.target === null && this.activity !== null) { 10060 stmt.target = this.activity; 10061 } 10062 10063 if (this.context !== null) { 10064 if (stmt.context === null) { 10065 stmt.context = this.context; 10066 } 10067 else { 10068 if (stmt.context.registration === null) { 10069 stmt.context.registration = this.context.registration; 10070 } 10071 if (stmt.context.platform === null) { 10072 stmt.context.platform = this.context.platform; 10073 } 10074 10075 if (this.context.contextActivities !== null) { 10076 if (stmt.context.contextActivities === null) { 10077 stmt.context.contextActivities = this.context.contextActivities; 10078 } 10079 else { 10080 if (this.context.contextActivities.grouping !== null && stmt.context.contextActivities.grouping === null) { 10081 stmt.context.contextActivities.grouping = this.context.contextActivities.grouping; 10082 } 10083 if (this.context.contextActivities.parent !== null && stmt.context.contextActivities.parent === null) { 10084 stmt.context.contextActivities.parent = this.context.contextActivities.parent; 10085 } 10086 if (this.context.contextActivities.other !== null && stmt.context.contextActivities.other === null) { 10087 stmt.context.contextActivities.other = this.context.contextActivities.other; 10088 } 10089 } 10090 } 10091 } 10092 } 10093 10094 return stmt; 10095 }, 10096 10097 /** 10098 Calls saveStatement on each configured LRS, provide callback to make it asynchronous 10099 10100 @method sendStatement
10101 @param {TinCan.Statement|Object} statement Send statement to LRS 10102 @param {Function} [callback] Callback function to execute on completion 10103 */ 10104 sendStatement: function (stmt, callback) { 10105 this.log("sendStatement"); 10106 10107 // would prefer to use .bind instead of 'self' 10108 var self = this, 10109 lrs, 10110 statement = this.prepareStatement(stmt), 10111 rsCount = this.recordStores.length, 10112 i, 10113 results = [], 10114 callbackWrapper, 10115 callbackResults = [] 10116 ; 10117 10118 if (rsCount > 0) { 10119 /* 10120 if there is a callback that is a function then we need 10121 to wrap that function with a function that becomes 10122 the new callback that reduces a closure count of the 10123 requests that don't have allowFail set to true and 10124 when that number hits zero then the original callback 10125 is executed 10126 */ 10127 if (typeof callback === "function") { 10128 callbackWrapper = function (err, xhr) { 10129 var args; 10130 10131 self.log("sendStatement - callbackWrapper: " + rsCount); 10132 if (rsCount > 1) { 10133 rsCount -= 1; 10134 callbackResults.push( 10135 { 10136 err: err, 10137 xhr: xhr 10138 } 10139 ); 10140 } 10141 else if (rsCount === 1) { 10142 callbackResults.push( 10143 { 10144 err: err, 10145 xhr: xhr 10146 } 10147 ); 10148 args = [ 10149 callbackResults, 10150 statement 10151 ]; 10152 callback.apply(this, args); 10153 } 10154 else { 10155 self.log("sendStatement - unexpected record store count: " + rsCount); 10156 } 10157 }; 10158 } 10159 10160 for (i = 0; i < rsCount; i += 1) { 10161 lrs = this.recordStores[i]; 10162 10163 results.push( 10164 lrs.saveStatement(statement, { callback: callbackWrapper }) 10165 ); 10166 } 10167 } 10168 else { 10169 this.log("[warning] sendStatement: No LRSs added yet (statement not sent)"); 10170 if (typeof callback === "function") { 10171 callback.apply(this, [ null, statement ]); 10172 } 10173 } 10174 10175 return { 10176 statement: statement, 10177 results: results 10178 }; 10179 }, 10180 10181 /** 10182 Calls retrieveStatement on the first LRS, provide callback to make it asynchronous 10183 10184 @method getStatement 10185 @param {String} [stmtId] Statement ID to get 10186 @param {Function} [callback] Callback function to execute on completion 10187 @param {Object} [cfg] Configuration data 10188 @param {Object} [params] Query parameters 10189 @param {Boolean} [attachments] Include attachments in multipart response or don't (defualt: false) 10190 @return {Array|Result} Array of results, or single result 10191 10192 TODO: make TinCan track statements it has seen in a local cache to be returned easily 10193 */ 10194 getStatement: function (stmtId, callback, cfg) { 10195 this.log("getStatement"); 10196 10197 var lrs; 10198 10199 cfg = cfg || {}; 10200 cfg.params = cfg.params || {}; 10201 10202 if (this.recordStores.length > 0) { 10203 // 10204 // for statements (for now) we only need to read from the first LRS 10205 // in the future it may make sense to get all from all LRSes and 10206 // compare to remove duplicates or allow inspection of them for differences? 10207 // 10208 // TODO: make this the first non-allowFail LRS but for now it should 10209 // be good enough to make it the first since we know the LMS provided 10210 // LRS is the first 10211 // 10212 lrs = this.recordStores[0]; 10213
10214 return lrs.retrieveStatement(stmtId, { callback: callback, params: cfg.params }); 10215 } 10216 10217 this.log("[warning] getStatement: No LRSs added yet (statement not retrieved)"); 10218 }, 10219 10220 /** 10221 Creates a statement used for voiding the passed statement/statement ID and calls 10222 send statement with the voiding statement. 10223 10224 @method voidStatement 10225 @param {TinCan.Statement|String} statement Statement or statement ID to void 10226 @param {Function} [callback] Callback function to execute on completion 10227 @param {Object} [options] Options used to build voiding statement 10228 @param {TinCan.Agent} [options.actor] Agent to be used as 'actor' in voiding statement 10229 */ 10230 voidStatement: function (stmt, callback, options) { 10231 this.log("voidStatement"); 10232 10233 // would prefer to use .bind instead of 'self' 10234 var self = this, 10235 lrs, 10236 actor, 10237 voidingStatement, 10238 rsCount = this.recordStores.length, 10239 i, 10240 results = [], 10241 callbackWrapper, 10242 callbackResults = [] 10243 ; 10244 10245 if (stmt instanceof TinCan.Statement) { 10246 stmt = stmt.id; 10247 } 10248 10249 if (typeof options.actor !== "undefined") { 10250 actor = options.actor; 10251 } 10252 else if (this.actor !== null) { 10253 actor = this.actor; 10254 } 10255 10256 voidingStatement = new TinCan.Statement( 10257 { 10258 actor: actor, 10259 verb: { 10260 id: "http://adlnet.gov/expapi/verbs/voided" 10261 }, 10262 target: { 10263 objectType: "StatementRef", 10264 id: stmt 10265 } 10266 } 10267 ); 10268 10269 if (rsCount > 0) { 10270 /* 10271 if there is a callback that is a function then we need 10272 to wrap that function with a function that becomes 10273 the new callback that reduces a closure count of the 10274 requests that don't have allowFail set to true and 10275 when that number hits zero then the original callback 10276 is executed 10277 */ 10278 if (typeof callback === "function") { 10279 callbackWrapper = function (err, xhr) { 10280 var args; 10281 10282 self.log("voidStatement - callbackWrapper: " + rsCount); 10283 if (rsCount > 1) { 10284 rsCount -= 1; 10285 callbackResults.push( 10286 { 10287 err: err, 10288 xhr: xhr 10289 } 10290 ); 10291 } 10292 else if (rsCount === 1) { 10293 callbackResults.push( 10294 { 10295 err: err, 10296 xhr: xhr 10297 } 10298 ); 10299 args = [ 10300 callbackResults, 10301 voidingStatement 10302 ]; 10303 callback.apply(this, args); 10304 } 10305 else { 10306 self.log("voidStatement - unexpected record store count: " + rsCount); 10307 } 10308 }; 10309 } 10310 10311 for (i = 0; i < rsCount; i += 1) { 10312 lrs = this.recordStores[i]; 10313 10314 results.push( 10315 lrs.saveStatement(voidingStatement, { callback: callbackWrapper }) 10316 ); 10317 } 10318 } 10319 else { 10320 this.log("[warning] voidStatement: No LRSs added yet (statement not sent)"); 10321 if (typeof callback === "function") { 10322 callback.apply(this, [ null, voidingStatement ]); 10323 } 10324 } 10325 10326 return { 10327 statement: voidingStatement, 10328 results: results 10329 }; 10330 }, 10331 10332 /** 10333 Calls retrieveVoidedStatement on the first LRS, provide callback to make it asynchronous 10334 10335 @method getVoidedStatement 10336 @param {String} statement Statement ID to get 10337 @param {Function} [callback] Callback function to execute on completion 10338 @return {Array|Result} Array of results, or single result 10339 10340 TODO: make TinCan track voided statements it has seen in a local cache to be returned easily 10341 */ 10342 getVoidedStatement: function (stmtId, callback) { 10343 this.log("getVoidedStatement"); 10344 10345 var lrs; 10346 10347 if (this.recordStores.length > 0) { 10348 // 10349 // for statements (for now) we only need to read from the first LRS 10350 // in the future it may make sense to get all from all LRSes and 10351 // compare to remove duplicates or allow inspection of them for differences? 10352 // 10353 // TODO: make this the first non-allowFail LRS but for now it should 10354 // be good enough to make it the first since we know the LMS provided 10355 // LRS is the first 10356 // 10357 lrs = this.recordStores[0]; 10358
10359 return lrs.retrieveVoidedStatement(stmtId, { callback: callback }); 10360 } 10361 10362 this.log("[warning] getVoidedStatement: No LRSs added yet (statement not retrieved)"); 10363 }, 10364 10365 /** 10366 Calls saveStatements with list of prepared statements 10367 10368 @method sendStatements 10369 @param {Array} Array of statements to send 10370 @param {Function} Callback function to execute on completion 10371 */ 10372 sendStatements: function (stmts, callback) { 10373 this.log("sendStatements"); 10374 var self = this, 10375 lrs, 10376 statements = [], 10377 rsCount = this.recordStores.length, 10378 i, 10379 results = [], 10380 callbackWrapper, 10381 callbackResults = [] 10382 ; 10383 if (stmts.length === 0) { 10384 if (typeof callback === "function") { 10385 callback.apply(this, [ null, statements ]); 10386 } 10387 } 10388 else { 10389 for (i = 0; i < stmts.length; i += 1) { 10390 statements.push( 10391 this.prepareStatement(stmts[i]) 10392 ); 10393 } 10394 10395 if (rsCount > 0) { 10396 /* 10397 if there is a callback that is a function then we need 10398 to wrap that function with a function that becomes 10399 the new callback that reduces a closure count of the 10400 requests that don't have allowFail set to true and 10401 when that number hits zero then the original callback 10402 is executed 10403 */ 10404 10405 if (typeof callback === "function") { 10406 callbackWrapper = function (err, xhr) { 10407 var args; 10408 10409 self.log("sendStatements - callbackWrapper: " + rsCount); 10410 if (rsCount > 1) { 10411 rsCount -= 1; 10412 callbackResults.push( 10413 { 10414 err: err, 10415 xhr: xhr 10416 } 10417 ); 10418 } 10419 else if (rsCount === 1) { 10420 callbackResults.push( 10421 { 10422 err: err, 10423 xhr: xhr 10424 } 10425 ); 10426 args = [ 10427 callbackResults, 10428 statements 10429 ]; 10430 callback.apply(this, args); 10431 } 10432 else { 10433 self.log("sendStatements - unexpected record store count: " + rsCount); 10434 } 10435 }; 10436 } 10437 10438 for (i = 0; i < rsCount; i += 1) { 10439 lrs = this.recordStores[i]; 10440 10441 results.push( 10442 lrs.saveStatements(statements, { callback: callbackWrapper }) 10443 ); 10444 } 10445 } 10446 else { 10447 this.log("[warning] sendStatements: No LRSs added yet (statements not sent)"); 10448 if (typeof callback === "function") { 10449 callback.apply(this, [ null, statements ]); 10450 } 10451 } 10452 } 10453 10454 return { 10455 statements: statements, 10456 results: results 10457 }; 10458 }, 10459 10460 /** 10461 @method getStatements 10462 @param {Object} [cfg] Configuration for request 10463 @param {Boolean} [cfg.sendActor] Include default actor in query params 10464 @param {Boolean} [cfg.sendActivity] Include default activity in query params 10465 @param {Object} [cfg.params] Parameters used to filter. 10466 These are the same as those accepted by the 10467 <a href="TinCan.LRS.html#method_queryStatements">LRS.queryStatements</a> 10468 method. 10469 10470 @param {Function} [cfg.callback] Function to run at completion 10471 10472 TODO: support multiple LRSs and flag to use single 10473 */ 10474 getStatements: function (cfg) { 10475 this.log("getStatements"); 10476 var queryCfg = {}, 10477 lrs, 10478 params 10479 ; 10480 if (this.recordStores.length > 0) { 10481 //
10482 // for get (for now) we only get from one (as they should be the same) 10483 // but it may make sense to long term try to merge statements, perhaps 10484 // by using statementId as unique 10485 // 10486 // TODO: make this the first non-allowFail LRS but for now it should 10487 // be good enough to make it the first since we know the LMS provided 10488 // LRS is the first 10489 // 10490 lrs = this.recordStores[0]; 10491 10492 cfg = cfg || {}; 10493 10494 // TODO: need a clone function? 10495 params = cfg.params || {}; 10496 10497 if (cfg.sendActor && this.actor !== null) { 10498 if (lrs.version === "0.9" || lrs.version === "0.95") { 10499 params.actor = this.actor; 10500 } 10501 else { 10502 params.agent = this.actor; 10503 } 10504 } 10505 if (cfg.sendActivity && this.activity !== null) { 10506 if (lrs.version === "0.9" || lrs.version === "0.95") { 10507 params.target = this.activity; 10508 } 10509 else { 10510 params.activity = this.activity; 10511 } 10512 } 10513 if (typeof params.registration === "undefined" && this.registration !== null) { 10514 params.registration = this.registration; 10515 } 10516 10517 queryCfg = { 10518 params: params 10519 }; 10520 if (typeof cfg.callback !== "undefined") { 10521 queryCfg.callback = cfg.callback; 10522 } 10523 10524 return lrs.queryStatements(queryCfg); 10525 } 10526 10527 this.log("[warning] getStatements: No LRSs added yet (statements not read)"); 10528 }, 10529 10530 /** 10531 @method getState 10532 @param {String} key Key to retrieve from the state 10533 @param {Object} [cfg] Configuration for request 10534 @param {Object} [cfg.agent] Agent used in query, 10535 defaults to 'actor' property if empty 10536 @param {Object} [cfg.activity] Activity used in query, 10537 defaults to 'activity' property if empty 10538 @param {Object} [cfg.registration] Registration used in query, 10539 defaults to 'registration' property if empty 10540 @param {Function} [cfg.callback] Function to run with state 10541 */ 10542 getState: function (key, cfg) { 10543 this.log("getState"); 10544 var queryCfg, 10545 lrs 10546 ; 10547 10548 if (this.recordStores.length > 0) { 10549 // 10550 // for state (for now) we are only going to store to the first LRS 10551 // so only get from there too 10552 // 10553 // TODO: make this the first non-allowFail LRS but for now it should 10554 // be good enough to make it the first since we know the LMS provided 10555 // LRS is the first 10556 // 10557 lrs = this.recordStores[0]; 10558 10559 cfg = cfg || {}; 10560 10561 queryCfg = { 10562 agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor), 10563 activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity) 10564 }; 10565 if (typeof cfg.registration !== "undefined") { 10566 queryCfg.registration = cfg.registration; 10567 } 10568 else if (this.registration !== null) { 10569 queryCfg.registration = this.registration; 10570 } 10571 if (typeof cfg.callback !== "undefined") { 10572 queryCfg.callback = cfg.callback; 10573 } 10574 10575 return lrs.retrieveState(key, queryCfg); 10576 } 10577 10578 this.log("[warning] getState: No LRSs added yet (state not retrieved)"); 10579 }, 10580 10581 /** 10582 @method setState 10583 @param {String} key Key to store into the state 10584 @param {String|Object} val Value to store into the state, objects will be stringified to JSON 10585 @param {Object} [cfg] Configuration for request 10586 @param {Object} [cfg.agent] Agent used in query, 10587 defaults to 'actor' property if empty 10588 @param {Object} [cfg.activity] Activity used in query, 10589 defaults to 'activity' property if empty 10590 @param {Object} [cfg.registration] Registration used in query, 10591 defaults to 'registration' property if empty 10592 @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing state 10593 @param {String} [cfg.contentType] Content-Type to specify in headers 10594 @param {Boolean} [cfg.overwriteJSON] If the Content-Type is JSON, should a PUT be used? 10595 @param {Function} [cfg.callback] Function to run with state 10596 */ 10597 setState: function (key, val, cfg) { 10598 this.log("setState"); 10599 var queryCfg, 10600 lrs 10601 ; 10602 10603 if (this.recordStores.length > 0) { 10604 // 10605 // for state (for now) we are only going to store to the first LRS 10606 // so only get from there too 10607 // 10608 // TODO: make this the first non-allowFail LRS but for now it should 10609 // be good enough to make it the first since we know the LMS provided 10610 // LRS is the first 10611 // 10612 lrs = this.recordStores[0]; 10613 10614 cfg = cfg || {}; 10615 10616 queryCfg = { 10617 agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor), 10618 activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity) 10619 }; 10620 if (typeof cfg.registration !== "undefined") { 10621 queryCfg.registration = cfg.registration; 10622 } 10623 else if (this.registration !== null) { 10624 queryCfg.registration = this.registration; 10625 } 10626 if (typeof cfg.lastSHA1 !== "undefined") { 10627 queryCfg.lastSHA1 = cfg.lastSHA1; 10628 } 10629 if (typeof cfg.contentType !== "undefined") { 10630 queryCfg.contentType = cfg.contentType; 10631 if ((typeof cfg.overwriteJSON !== "undefined") && (! cfg.overwriteJSON) && (TinCan.Utils.isApplicationJSON(cfg.contentType))) { 10632 queryCfg.method = "POST"; 10633 } 10634 } 10635 if (typeof cfg.callback !== "undefined") { 10636 queryCfg.callback = cfg.callback; 10637 } 10638 10639 return lrs.saveState(key, val, queryCfg); 10640 } 10641 10642 this.log("[warning] setState: No LRSs added yet (state not saved)"); 10643 }, 10644 10645 /** 10646 @method deleteState 10647 @param {String|null} key Key to remove from the state, or null to clear all 10648 @param {Object} [cfg] Configuration for request 10649 @param {Object} [cfg.agent] Agent used in query, 10650 defaults to 'actor' property if empty 10651 @param {Object} [cfg.activity] Activity used in query, 10652 defaults to 'activity' property if empty 10653 @param {Object} [cfg.registration] Registration used in query, 10654 defaults to 'registration' property if empty 10655 @param {Function} [cfg.callback] Function to run with state 10656 */ 10657 deleteState: function (key, cfg) { 10658 this.log("deleteState"); 10659 var queryCfg, 10660 lrs 10661 ; 10662 10663 if (this.recordStores.length > 0) { 10664 // 10665 // for state (for now) we are only going to store to the first LRS 10666 // so only get from there too 10667 // 10668 // TODO: make this the first non-allowFail LRS but for now it should 10669 // be good enough to make it the first since we know the LMS provided 10670 // LRS is the first 10671 // 10672 lrs = this.recordStores[0]; 10673 10674 cfg = cfg || {}; 10675 10676 queryCfg = { 10677 agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor), 10678 activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity) 10679 }; 10680 if (typeof cfg.registration !== "undefined") { 10681 queryCfg.registration = cfg.registration; 10682 } 10683 else if (this.registration !== null) { 10684 queryCfg.registration = this.registration; 10685 } 10686 if (typeof cfg.callback !== "undefined") { 10687 queryCfg.callback = cfg.callback; 10688 } 10689 10690 return lrs.dropState(key, queryCfg); 10691 } 10692 10693 this.log("[warning] deleteState: No LRSs added yet (state not deleted)"); 10694 }, 10695 10696 /** 10697 @method getActivityProfile 10698 @param {String} key Key to retrieve from the profile 10699 @param {Object} [cfg] Configuration for request 10700 @param {Object} [cfg.activity] Activity used in query, 10701 defaults to 'activity' property if empty 10702 @param {Function} [cfg.callback] Function to run with activity profile 10703 */ 10704 getActivityProfile: function (key, cfg) { 10705 this.log("getActivityProfile"); 10706 var queryCfg, 10707 lrs 10708 ; 10709 10710 if (this.recordStores.length > 0) { 10711 // 10712 // for activity profiles (for now) we are only going to store to the first LRS 10713 // so only get from there too 10714 // 10715 // TODO: make this the first non-allowFail LRS but for now it should 10716 // be good enough to make it the first since we know the LMS provided 10717 // LRS is the first 10718 // 10719 lrs = this.recordStores[0]; 10720 10721 cfg = cfg || {}; 10722 10723 queryCfg = { 10724 activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity) 10725 }; 10726 if (typeof cfg.callback !== "undefined") { 10727 queryCfg.callback = cfg.callback; 10728 } 10729 10730 return lrs.retrieveActivityProfile(key, queryCfg); 10731 } 10732 10733 this.log("[warning] getActivityProfile: No LRSs added yet (activity profile not retrieved)"); 10734 }, 10735 10736 /** 10737 @method setActivityProfile 10738 @param {String}
10738 key Key to store into the activity profile 10739 @param {String|Object} val Value to store into the activity profile, objects will be stringified to JSON 10740 @param {Object} [cfg] Configuration for request 10741 @param {Object} [cfg.activity] Activity used in query, 10742 defaults to 'activity' property if empty 10743 @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile 10744 @param {String} [cfg.contentType] Content-Type to specify in headers 10745 @param {Boolean} [cfg.overwriteJSON] If the Content-Type is JSON, should a PUT be used? 10746 @param {Function} [cfg.callback] Function to run with activity profile 10747 */ 10748 setActivityProfile: function (key, val, cfg) { 10749 this.log("setActivityProfile"); 10750 var queryCfg, 10751 lrs 10752 ; 10753 10754 if (this.recordStores.length > 0) { 10755 // 10756 // for activity profile (for now) we are only going to store to the first LRS 10757 // so only get from there too 10758 // 10759 // TODO: make this the first non-allowFail LRS but for now it should 10760 // be good enough to make it the first since we know the LMS provided 10761 // LRS is the first 10762 // 10763 lrs = this.recordStores[0]; 10764 10765 cfg = cfg || {}; 10766 10767 queryCfg = { 10768 activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity) 10769 }; 10770 if (typeof cfg.callback !== "undefined") { 10771 queryCfg.callback = cfg.callback; 10772 } 10773 if (typeof cfg.lastSHA1 !== "undefined") { 10774 queryCfg.lastSHA1 = cfg.lastSHA1; 10775 } 10776 if (typeof cfg.contentType !== "undefined") { 10777 queryCfg.contentType = cfg.contentType; 10778 if ((typeof cfg.overwriteJSON !== "undefined") && (! cfg.overwriteJSON) && (TinCan.Utils.isApplicationJSON(cfg.contentType))) { 10779 queryCfg.method = "POST"; 10780 } 10781 } 10782 10783 return lrs.saveActivityProfile(key, val, queryCfg); 10784 } 10785 10786 this.log("[warning] setActivityProfile: No LRSs added yet (activity profile not saved)"); 10787 }, 10788 10789 /** 10790 @method deleteActivityProfile 10791 @param {String|null} key Key to remove from the activity profile, or null to clear all 10792 @param {Object} [cfg] Configuration for request 10793 @param {Object} [cfg.activity] Activity used in query, 10794 defaults to 'activity' property if empty 10795 @param {Function} [cfg.callback] Function to run with activity profile 10796 */ 10797 deleteActivityProfile: function (key, cfg) { 10798 this.log("deleteActivityProfile"); 10799 var queryCfg, 10800 lrs 10801 ; 10802 10803 if (this.recordStores.length > 0) { 10804 // 10805 // for activity profile (for now) we are only going to store to the first LRS 10806 // so only get from there too 10807 // 10808 // TODO: make this the first non-allowFail LRS but for now it should 10809 // be good enough to make it the first since we know the LMS provided 10810 // LRS is the first 10811 // 10812 lrs = this.recordStores[0]; 10813 10814 cfg = cfg || {}; 10815 10816 queryCfg = { 10817 activity: (typeof cfg.activity !== "undefined" ? cfg.activity : this.activity) 10818 }; 10819 if (typeof cfg.callback !== "undefined") { 10820 queryCfg.callback = cfg.callback; 10821 } 10822 10823 return lrs.dropActivityProfile(key, queryCfg); 10824 } 10825 10826 this.log("[warning] deleteActivityProfile: No LRSs added yet (activity profile not deleted)"); 10827 }, 10828 10829 /** 10830 @method getAgentProfile 10831 @param {String} key Key to retrieve from the profile 10832 @param {Object} [cfg] Configuration for request 10833 @param {Object} [cfg.agent] Agent used in query, 10834 defaults to 'actor' property if empty 10835 @param {Function} [cfg.callback] Function to run with agent profile 10836 */ 10837 getAgentProfile: function (key, cfg) { 10838 this.log("getAgentProfile"); 10839 var queryCfg, 10840 lrs 10841 ; 10842 10843 if (this.recordStores.length > 0) { 10844 // 10845 // for agent profiles (for now) we are only going to store to the first LRS 10846 // so only get from there too 10847 // 10848 // TODO: make this the first non-allowFail LRS but for now it should 10849 // be good enough to make it the first since we know the LMS provided 10850 // LRS is the first 10851 // 10852 lrs = this.recordStores[0]; 10853 10854 cfg = cfg || {}; 10855 10856 queryCfg = { 10857 agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor) 10858 }; 10859 if (typeof cfg.callback !== "undefined") { 10860 queryCfg.callback = cfg.callback; 10861 } 10862 10863 return lrs.retrieveAgentProfile(key, queryCfg); 10864 } 10865 10866 this.log("[warning] getAgentProfile: No LRSs added yet (agent profile not retrieved)"); 10867 }, 10868 10869 /** 10870 @method setAgentProfile 10871 @param {String}
10871 key Key to store into the agent profile 10872 @param {String|Object} val Value to store into the agent profile, objects will be stringified to JSON 10873 @param {Object} [cfg] Configuration for request 10874 @param {Object} [cfg.agent] Agent used in query, 10875 defaults to 'actor' property if empty 10876 @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile 10877 @param {String} [cfg.contentType] Content-Type to specify in headers 10878 @param {Boolean} [cfg.overwriteJSON] If the Content-Type is JSON, should a PUT be used? 10879 @param {Function} [cfg.callback] Function to run with agent profile 10880 */ 10881 setAgentProfile: function (key, val, cfg) { 10882 this.log("setAgentProfile"); 10883 var queryCfg, 10884 lrs 10885 ; 10886 10887 if (this.recordStores.length > 0) { 10888 // 10889 // for agent profile (for now) we are only going to store to the first LRS 10890 // so only get from there too 10891 // 10892 // TODO: make this the first non-allowFail LRS but for now it should 10893 // be good enough to make it the first since we know the LMS provided 10894 // LRS is the first 10895 // 10896 lrs = this.recordStores[0]; 10897 10898 cfg = cfg || {}; 10899 10900 queryCfg = { 10901 agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor) 10902 }; 10903 if (typeof cfg.callback !== "undefined") { 10904 queryCfg.callback = cfg.callback; 10905 } 10906 if (typeof cfg.lastSHA1 !== "undefined") { 10907 queryCfg.lastSHA1 = cfg.lastSHA1; 10908 } 10909 if (typeof cfg.contentType !== "undefined") { 10910 queryCfg.contentType = cfg.contentType; 10911 if ((typeof cfg.overwriteJSON !== "undefined") && (! cfg.overwriteJSON) && (TinCan.Utils.isApplicationJSON(cfg.contentType))) { 10912 queryCfg.method = "POST"; 10913 } 10914 } 10915 10916 return lrs.saveAgentProfile(key, val, queryCfg); 10917 } 10918 10919 this.log("[warning] setAgentProfile: No LRSs added yet (agent profile not saved)"); 10920 }, 10921 10922 /** 10923 @method deleteAgentProfile 10924 @param {String|null} key Key to remove from the agent profile, or null to clear all 10925 @param {Object} [cfg] Configuration for request 10926 @param {Object} [cfg.agent] Agent used in query, 10927 defaults to 'actor' property if empty 10928 @param {Function} [cfg.callback] Function to run with agent profile 10929 */ 10930 deleteAgentProfile: function (key, cfg) { 10931 this.log("deleteAgentProfile"); 10932 var queryCfg, 10933 lrs 10934 ; 10935 10936 if (this.recordStores.length > 0) { 10937 // 10938 // for agent profile (for now) we are only going to store to the first LRS 10939 // so only get from there too 10940 // 10941 // TODO: make this the first non-allowFail LRS but for now it should 10942 // be good enough to make it the first since we know the LMS provided 10943 // LRS is the first 10944 // 10945 lrs = this.recordStores[0]; 10946 10947 cfg = cfg || {}; 10948 10949 queryCfg = { 10950 agent: (typeof cfg.agent !== "undefined" ? cfg.agent : this.actor) 10951 }; 10952 if (typeof cfg.callback !== "undefined") { 10953 queryCfg.callback = cfg.callback; 10954 } 10955 10956 return lrs.dropAgentProfile(key, queryCfg); 10957 } 10958 10959 this.log("[warning] deleteAgentProfile: No LRSs added yet (agent profile not deleted)"); 10960 } 10961 }; 10962 10963 /** 10964 @property DEBUG 10965 @static 10966 @default false 10967 */ 10968 TinCan.DEBUG = false; 10969 10970 /** 10971 Turn on debug logging 10972 10973 @method enableDebug 10974 @static 10975 */ 10976 TinCan.enableDebug = function () { 10977 TinCan.DEBUG = true; 10978 }; 10979 10980 /** 10981 Turn off debug logging 10982 10983 @method disableDebug 10984 @static 10985 */ 10986 TinCan.disableDebug = function () { 10987 TinCan.DEBUG = false;
10988 }; 10989 10990 /** 10991 @method versions 10992 @return {Array} Array of supported version numbers 10993 @static 10994 */ 10995 TinCan.versions = function () { 10996 // newest first so we can use the first as the default 10997 return [ 10998 "1.0.2", 10999 "1.0.1", 11000 "1.0.0", 11001 "0.95", 11002 "0.9" 11003 ]; 11004 }; 11005 11006 /*global module*/ 11007 // Support the CommonJS method for exporting our single global 11008 if (typeof module === "object") { 11009 module.exports = TinCan; 11010 } 11011}()); 11012 11013/* 11014 Copyright 2012 Rustici Software 11015 11016 Licensed under the Apache License, Version 2.0 (the "License"); 11017 you may not use this file except in compliance with the License. 11018 You may obtain a copy of the License at 11019 11020 http://www.apache.org/licenses/LICENSE-2.0 11021 11022 Unless required by applicable law or agreed to in writing, software 11023 distributed under the License is distributed on an "AS IS" BASIS, 11024 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11025 See the License for the specific language governing permissions and 11026 limitations under the License. 11027*/ 11028 11029/** 11030TinCan client library 11031 11032@module TinCan 11033@submodule TinCan.Utils 11034**/ 11035(function () { 11036 "use strict"; 11037 11038 /** 11039 @class TinCan.Utils 11040 */ 11041 TinCan.Utils = { 11042 defaultEncoding: "utf8", 11043 11044 /** 11045 Generates a UUIDv4 compliant string that should be reasonably unique 11046 11047 @method getUUID 11048 @return {String} UUID 11049 @static 11050 11051 Excerpt from: http://www.broofa.com/Tools/Math.uuid.js (v1.4) 11052 http://www.broofa.com 11053 mailto:[email protected] 11054 Copyright (c) 2010 Robert Kieffer 11055 Dual licensed under the MIT and GPL licenses. 11056 */ 11057 getUUID: function () { 11058 /*jslint bitwise: true, eqeq: true */ 11059 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( 11060 /[xy]/g, 11061 function (c) { 11062 var r = Math.random() * 16|0, v = c == "x" ? r : (r&0x3|0x8); 11063 return v.toString(16); 11064 } 11065 ); 11066 }, 11067 11068 /** 11069 @method getISODateString 11070 @static 11071 @param {Date} date Date to stringify 11072 @return {String} ISO date String 11073 */ 11074 getISODateString: function (d) { 11075 function pad (val, n) { 11076 var padder, 11077 tempVal; 11078 if (typeof val === "undefined" || val === null) { 11079 val = 0; 11080 } 11081 if (typeof n === "undefined" || n === null) { 11082 n = 2; 11083 } 11084 padder = Math.pow(10, n-1); 11085 tempVal = val.toString(); 11086 11087 while (val < padder && padder > 1) { 11088 tempVal = "0" + tempVal; 11089 padder = padder / 10; 11090 } 11091 11092 return tempVal; 11093 } 11094 11095 return d.getUTCFullYear() + "-" + 11096 pad(d.getUTCMonth() + 1) + "-" + 11097 pad(d.getUTCDate()) + "T" + 11098 pad(d.getUTCHours()) + ":" + 11099 pad(d.getUTCMinutes()) + ":" + 11100 pad(d.getUTCSeconds()) + "." + 11101 pad(d.getUTCMilliseconds(), 3) + "Z"; 11102 }, 11103 11104 /** 11105 @method convertISO8601DurationToMilliseconds 11106 @static 11107 @param {String} ISO8601Duration Duration in ISO8601 format 11108 @return {Int} Duration in milliseconds 11109 11110 Note: does not handle input strings with years, months and days 11111 */ 11112 convertISO8601DurationToMilliseconds: function (ISO8601Duration) { 11113 var isValueNegative = (ISO8601Duration.indexOf("-") >= 0), 11114 indexOfT = ISO8601Duration.indexOf("T"), 11115 indexOfH = ISO8601Duration.indexOf("H"), 11116 indexOfM = ISO8601Duration.indexOf("M"), 11117 indexOfS = ISO8601Duration.indexOf("S"), 11118 hours, 11119 minutes, 11120 seconds, 11121 durationInMilliseconds; 11122 11123 if ((indexOfT === -1) || ((indexOfM !== -1) && (indexOfM < indexOfT)) || (ISO8601Duration.indexOf("D") !== -1) || (ISO8601Duration.indexOf("Y") !== -1)) { 11124 throw new Error("ISO 8601 timestamps including years, months and/or days are not currently supported"); 11125 } 11126 11127 if (indexOfH === -1) { 11128 indexOfH = indexOfT; 11129 hours = 0; 11130 } 11131 else { 11132 hours = parseInt(ISO8601Duration.slice(indexOfT + 1, indexOfH), 10); 11133 } 11134
11135 if (indexOfM === -1) { 11136 indexOfM = indexOfT; 11137 minutes = 0; 11138 } 11139 else { 11140 minutes = parseInt(ISO8601Duration.slice(indexOfH + 1, indexOfM), 10); 11141 } 11142 11143 seconds = parseFloat(ISO8601Duration.slice(indexOfM + 1, indexOfS)); 11144 11145 durationInMilliseconds = parseInt((((((hours * 60) + minutes) * 60) + seconds) * 1000), 10); 11146 if (isNaN(durationInMilliseconds)){ 11147 durationInMilliseconds = 0; 11148 } 11149 if (isValueNegative) { 11150 durationInMilliseconds = durationInMilliseconds * -1; 11151 } 11152 11153 return durationInMilliseconds; 11154 }, 11155 11156 /** 11157 @method convertMillisecondsToISO8601Duration 11158 @static 11159 @param {Int} inputMilliseconds Duration in milliseconds 11160 @return {String} Duration in ISO8601 format 11161 */ 11162 convertMillisecondsToISO8601Duration: function (inputMilliseconds) { 11163 var hours, 11164 minutes, 11165 seconds, 11166 i_inputMilliseconds = parseInt(inputMilliseconds, 10), 11167 i_inputCentiseconds, 11168 inputIsNegative = "", 11169 rtnStr = ""; 11170 11171 //round to nearest 0.01 seconds 11172 i_inputCentiseconds = Math.round(i_inputMilliseconds / 10); 11173 11174 if (i_inputCentiseconds < 0) { 11175 inputIsNegative = "-"; 11176 i_inputCentiseconds = i_inputCentiseconds * -1; 11177 } 11178 11179 hours = parseInt(((i_inputCentiseconds) / 360000), 10); 11180 minutes = parseInt((((i_inputCentiseconds) % 360000) / 6000), 10); 11181 seconds = (((i_inputCentiseconds) % 360000) % 6000) / 100; 11182 11183 rtnStr = inputIsNegative + "PT"; 11184 if (hours > 0) { 11185 rtnStr += hours + "H"; 11186 } 11187 11188 if (minutes > 0) { 11189 rtnStr += minutes + "M"; 11190 } 11191 11192 rtnStr += seconds + "S"; 11193 11194 return rtnStr; 11195 }, 11196 11197 /** 11198 @method getSHA1String 11199 @static 11200 @param {String} str Content to hash 11201 @return {String} SHA1 for contents 11202 */ 11203 getSHA1String: function (str) { 11204 /*global CryptoJS*/ 11205 11206 return CryptoJS.SHA1(str).toString(CryptoJS.enc.Hex); 11207 }, 11208 11209 /** 11210 @method getSHA256String 11211 @static 11212 @param {ArrayBuffer|String} content Content to hash 11213 @return {String} SHA256 for contents 11214 */ 11215 getSHA256String: function (content) { 11216 /*global CryptoJS*/ 11217 11218 if (Object.prototype.toString.call(content) === "[object ArrayBuffer]") { 11219 content = CryptoJS.lib.WordArray.create(content); 11220 } 11221 return CryptoJS.SHA256(content).toString(CryptoJS.enc.Hex); 11222 }, 11223 11224 /** 11225 @method getBase64String 11226 @static 11227 @param {String} str Content to encode 11228 @return {String} Base64 encoded contents 11229 */ 11230 getBase64String: function (str) { 11231 /*global CryptoJS*/ 11232 11233 return CryptoJS.enc.Base64.stringify( 11234 CryptoJS.enc.Latin1.parse(str) 11235 ); 11236 }, 11237 11238 /** 11239 Intended to be inherited by objects with properties that store 11240 display values in a language based "dictionary" 11241 11242 @method getLangDictionaryValue 11243 @param {String} prop Property name storing the dictionary 11244 @param {String} [lang] Language to return 11245 @return {String} 11246 */ 11247 getLangDictionaryValue: function (prop, lang) { 11248 var langDict = this[prop], 11249 key; 11250 11251 if (typeof lang !== "undefined" && typeof langDict[lang] !== "undefined") { 11252 return langDict[lang]; 11253 } 11254 if (typeof langDict.und !== "undefined") { 11255 return langDict.und; 11256 } 11257 if (typeof langDict["en-US"] !== "undefined") { 11258 return langDict["en-US"]; 11259 } 11260 for (key in langDict) { 11261 if (langDict.hasOwnProperty(key)) { 11262 return langDict[key]; 11263 } 11264 } 11265 11266 return ""; 11267 }, 11268 11269 /** 11270 @method parseURL 11271 @param {String} url 11272 @param {Object} [options] 11273 @param {Boolean} [options.allowRelative] Option to allow relative URLs 11274 @return {Object} Object of values 11275 @private 11276 */ 11277 parseURL: function (url, cfg) { 11278 // 11279 // see http://stackoverflow.com/a/21553982 11280 // and http://stackoverflow.com/a/2880929 11281 // 11282 var isRelative = url.charAt(0) === "/", 11283 _reURLInformation = [ 11284 "(/[^?#]*)", // pathname 11285 "(\\?[^#]*|)", // search 11286 "(#.*|)$" // hash 11287 ], 11288 reURLInformation, 11289 match, 11290 result, 11291 paramMatch, 11292 pl = /\+/g, // Regex for replacing addition symbol with a space 11293 search = /([^&=]+)=?([^&]*)/g, 11294 decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); }; 11295 11296 cfg = cfg || {}; 11297 11298 // 11299 // this method in an earlier version supported relative URLs, mostly to provide 11300 // support to the `LRS.moreStatements` method, that functionality was removed and 11301 // subsequently restored but with the addition of the option for allowing relative 11302 // URLs to be accepted which is the reason for the "helpful" exception message here 11303 // 11304 if (! isRelative) { 11305 // 11306 // not relative so make sure they have a scheme, host, etc. 11307 // 11308 _reURLInformation.unshift( 11309 "^(https?:)//", // scheme 11310 "(([^:/?#]*)(?::([0-9]+))?)" // host (hostname and port) 11311 ); 11312 11313 // 11314 // our regex requires there to be a '/' for the detection of the start 11315 // of the path, we can detect a '/' using indexOf beyond the part of the 11316 // scheme, since we've restricted scheme to 'http' or 'https' and because 11317 // a hostname is guaranteed to be there we can detect beyond the '://' 11318 // based on position, then tack on a trailing '/' because it can't be 11319 // part of the path 11320 // 11321 if (url.indexOf("/", 8) === -1) { 11322 url = url + "/"; 11323 } 11324 } 11325 else { 11326 // 11327 // relative so make sure they allow that explicitly 11328 // 11329 if (typeof cfg.allowRelative === "undefined" || ! cfg.allowRelative) { 11330 throw new Error("Refusing to parse relative URL without 'allowRelative' option"); 11331 } 11332 } 11333 11334 reURLInformation = new RegExp(_reURLInformation.join("")); 11335 match = url.match(reURLInformation); 11336 if (match === null) { 11337 throw new Error("Unable to parse URL regular expression did not match: '" + url + "'"); 11338 } 11339 11340 // 'path' is for backwards compatibility 11341 if (isRelative) { 11342 result = { 11343 protocol: null, 11344 host: null, 11345 hostname: null, 11346 port: null, 11347 path: null, 11348 pathname: match[1], 11349 search: match[2], 11350 hash: match[3], 11351 params: {} 11352 }; 11353 11354 result.path = result.pathname; 11355 } 11356 else { 11357 result = { 11358 protocol: match[1], 11359 host: match[2], 11360 hostname: match[3], 11361 port: match[4], 11362 pathname: match[5], 11363 search: match[6], 11364 hash: match[7], 11365 params: {} 11366 }; 11367 11368 result.path = result.protocol + "//" + result.host
11368+ result.pathname; 11369 } 11370 11371 if (result.search !== "") { 11372 // extra parens to let jshint know this is an expression 11373 while ((paramMatch = search.exec(result.search.substring(1)))) { 11374 result.params[decode(paramMatch[1])] = decode(paramMatch[2]); 11375 } 11376 } 11377 11378 return result; 11379 }, 11380 11381 /** 11382 @method getServerRoot 11383 @param {String} absoluteUrl 11384 @return {String} server root of url 11385 @private 11386 */ 11387 getServerRoot: function (absoluteUrl) { 11388 var urlParts = absoluteUrl.split("/"); 11389 return urlParts[0] + "//" + urlParts[2]; 11390 }, 11391 11392 /** 11393 @method getContentTypeFromHeader 11394 @static 11395 @param {String} header Content-Type header value 11396 @return {String} Primary value from Content-Type 11397 */ 11398 getContentTypeFromHeader: function (header) { 11399 return (String(header).split(";"))[0]; 11400 }, 11401 11402 /** 11403 @method isApplicationJSON 11404 @static 11405 @param {String} header Content-Type header value 11406 @return {Boolean} whether "application/json" was matched 11407 */ 11408 isApplicationJSON: function (header) { 11409 return TinCan.Utils.getContentTypeFromHeader(header).toLowerCase().indexOf("application/json") === 0; 11410 }, 11411 11412 /** 11413 @method stringToArrayBuffer 11414 @static 11415 @param {String} content String of content to convert to an ArrayBuffer 11416 @param {String} [encoding] Encoding to use for conversion 11417 @return {ArrayBuffer} Converted content 11418 */ 11419 stringToArrayBuffer: function () { 11420 TinCan.prototype.log("stringToArrayBuffer not overloaded - no environment loaded?"); 11421 }, 11422 11423 /** 11424 @method stringFromArrayBuffer 11425 @static 11426 @param {ArrayBuffer} content ArrayBuffer of content to convert to a String 11427 @param {String} [encoding] Encoding to use for conversion 11428 @return {String} Converted content 11429 */ 11430 stringFromArrayBuffer: function () { 11431 TinCan.prototype.log("stringFromArrayBuffer not overloaded - no environment loaded?"); 11432 } 11433 }; 11434}()); 11435 11436/* 11437 Copyright 2012-2013 Rustici Software 11438 11439 Licensed under the Apache License, Version 2.0 (the "License"); 11440 you may not use this file except in compliance with the License. 11441 You may obtain a copy of the License at 11442 11443 http://www.apache.org/licenses/LICENSE-2.0 11444 11445 Unless required by applicable law or agreed to in writing, software 11446 distributed under the License is distributed on an "AS IS" BASIS, 11447 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11448 See the License for the specific language governing permissions and 11449 limitations under the License. 11450*/ 11451 11452/** 11453TinCan client library 11454 11455@module TinCan 11456@submodule TinCan.LRS 11457**/ 11458(function () { 11459 "use strict"; 11460 /** 11461 @class TinCan.LRS 11462 @constructor 11463 */ 11464 var LRS = TinCan.LRS = function (cfg) { 11465 this.log("constructor"); 11466 11467 /** 11468 @property endpoint 11469 @type String 11470 */ 11471 this.endpoint = null; 11472 11473 /** 11474 @property version 11475 @type String 11476 */ 11477 this.version = null; 11478 11479 /** 11480 @property auth 11481 @type String 11482 */ 11483 this.auth = null; 11484 11485 /** 11486 @property allowFail 11487 @type Boolean 11488 @default true 11489 */ 11490 this.allowFail = true; 11491 11492 /** 11493 @property extended 11494 @type Object 11495 */ 11496 this.extended = null; 11497 11498 this.init(cfg); 11499 }; 11500 LRS.prototype = { 11501 /** 11502 @property LOG_SRC 11503 */ 11504 LOG_SRC: "LRS", 11505 11506 /** 11507 @method log 11508 */ 11509 log: TinCan.prototype.log, 11510 11511 /** 11512 @method init 11513 */ 11514 init: function (cfg) { 11515 this.log("init"); 11516 11517 var versions = TinCan.versions(), 11518 versionMatch = false, 11519 i 11520 ; 11521 11522 cfg = cfg || {}; 11523 11524 if (cfg.hasOwnProperty("alertOnRequestFailure")) { 11525 this.log("'alertOnRequestFailure' is deprecated (alerts have been removed) no need to set it now"); 11526 } 11527 11528 if (! cfg.hasOwnProperty("endpoint") || cfg.endpoint === null || cfg.endpoint === "") {
11529 this.log("[error] LRS invalid: no endpoint"); 11530 throw { 11531 code: 3, 11532 mesg: "LRS invalid: no endpoint" 11533 }; 11534 } 11535 11536 this.endpoint = String(cfg.endpoint); 11537 if (this.endpoint.slice(-1) !== "/") { 11538 this.log("adding trailing slash to endpoint"); 11539 this.endpoint += "/"; 11540 } 11541 11542 if (cfg.hasOwnProperty("allowFail")) { 11543 this.allowFail = cfg.allowFail; 11544 } 11545 11546 if (cfg.hasOwnProperty("auth")) { 11547 this.auth = cfg.auth; 11548 } 11549 else if (cfg.hasOwnProperty("username") && cfg.hasOwnProperty("password")) { 11550 this.auth = "Basic " + TinCan.Utils.getBase64String(cfg.username + ":" + cfg.password); 11551 } 11552 11553 if (cfg.hasOwnProperty("extended")) { 11554 this.extended = cfg.extended; 11555 } 11556 11557 // 11558 // provide a hook method that environments can override 11559 // to handle anything necessary in the initialization 11560 // process that is customized to them, such as cross domain 11561 // setup in browsers, default implementation is empty 11562 // 11563 // this hook must run prior to version detection so that 11564 // request handling can be set up before requesting the 11565 // LRS version via the /about resource 11566 // 11567 this._initByEnvironment(cfg); 11568 11569 if (typeof cfg.version !== "undefined") { 11570 this.log("version: " + cfg.version); 11571 for (i = 0; i < versions.length; i += 1) { 11572 if (versions[i] === cfg.version) { 11573 versionMatch = true; 11574 break; 11575 } 11576 } 11577 if (! versionMatch) { 11578 this.log("[error] LRS invalid: version not supported (" + cfg.version + ")"); 11579 throw { 11580 code: 5, 11581 mesg: "LRS invalid: version not supported (" + cfg.version + ")" 11582 }; 11583 } 11584 this.version = cfg.version; 11585 } 11586 else { 11587 // 11588 // assume max supported when not specified, 11589 // TODO: add detection of LRS from call to endpoint 11590 // 11591 this.version = versions[0]; 11592 } 11593 }, 11594 11595 /** 11596 Creates and returns a boundary for separating parts in 11597 requests where the statement has an attachment 11598 11599 @method _getBoundary 11600 @private 11601 */ 11602 _getBoundary: function () { 11603 return TinCan.Utils.getUUID().replace(/-/g, ""); 11604 }, 11605 11606 /** 11607 Method should be overloaded by an environment to do per 11608 environment specifics such that the LRS can make a call 11609 to set the version if not provided 11610 11611 @method _initByEnvironment 11612 @private 11613 */ 11614 _initByEnvironment: function () { 11615 this.log("_initByEnvironment not overloaded - no environment loaded?"); 11616 }, 11617 11618 /** 11619 Method should be overloaded by an environment to do per 11620 environment specifics for sending requests to the LRS 11621 11622 @method _makeRequest 11623 @private 11624 */ 11625 _makeRequest: function () { 11626 this.log("_makeRequest not overloaded - no environment loaded?"); 11627 }, 11628 11629 /** 11630 Method should be overloaded by an environment to do per 11631 environment specifics for building multipart request data 11632 11633 @method _getMultipartRequestData 11634 @private 11635 */ 11636 _getMultipartRequestData: function () { 11637 this.log("_getMultipartRequestData not overloaded - no environment loaded?"); 11638 }, 11639 11640 /** 11641 Method is overloaded by the browser environment in order to test c
11641onverting an 11642 HTTP request that is greater than a defined length 11643 11644 @method _IEModeConversion 11645 @private 11646 */ 11647 _IEModeConversion: function () { 11648 this.log("_IEModeConversion not overloaded - browser environment not loaded."); 11649 }, 11650 11651 _processGetStatementResult: function (xhr, params) { 11652 var boundary, 11653 parsedResponse, 11654 statement, 11655 attachmentMap = {}, 11656 i; 11657 11658 if (! params.attachments) { 11659 return TinCan.Statement.fromJSON(xhr.responseText); 11660 } 11661 11662 boundary = xhr.getResponseHeader("Content-Type").split("boundary=")[1]; 11663 11664 parsedResponse = this._parseMultipart(boundary, xhr.response); 11665 statement = JSON.parse(parsedResponse[0].body); 11666 for (i = 1; i < parsedResponse.length; i += 1) { 11667 attachmentMap[parsedResponse[i].headers["X-Experience-API-Hash"]] = parsedResponse[i].body; 11668 } 11669 11670 this._assignAttachmentContent([statement], attachmentMap); 11671 11672 return new TinCan.Statement(statement); 11673 }, 11674 11675 /** 11676 Method used to send a request via browser objects to the LRS 11677 11678 @method sendRequest 11679 @param {Object} cfg Configuration for request 11680 @param {String} cfg.url URL portion to add to endpoint 11681 @param {String} [cfg.method] GET, PUT, POST, etc. 11682 @param {Object} [cfg.params] Parameters to set on the querystring 11683 @param {String|ArrayBuffer} [cfg.data] Body content as a String or ArrayBuffer 11684 @param {Object} [cfg.headers] Additional headers to set in the request 11685 @param {Function} [cfg.callback] Function to run at completion 11686 @param {String|Null} cfg.callback.err If an error occurred, this parameter will contain the HTTP status code. 11687 If the operation succeeded, err will be null. 11688 @param {Object} cfg.callback.xhr XHR object 11689 @param {Boolean} [cfg.ignore404] Whether 404 status codes should be considered an error 11690 @param {Boolean} [cfg.expectMultipart] Whether to expect the response to be a multipart response 11691 @return {Object} XHR if called in a synchronous way (in other words no callback) 11692 */ 11693 sendRequest: function (cfg) { 11694 this.log("sendRequest"); 11695 var fullUrl = this.endpoint + cfg.url, 11696 headers = {}, 11697 prop 11698 ; 11699 11700 // respect absolute URLs passed in 11701 if (cfg.url.indexOf("http") === 0) { 11702 fullUrl = cfg.url; 11703 } 11704 11705 // add extended LMS-specified values to the params 11706 if (this.extended !== null) { 11707 cfg.params = cfg.params || {}; 11708 11709 for (prop in this.extended) { 11710 if (this.extended.hasOwnProperty(prop)) { 11711 // don't overwrite cfg.params values that have already been added to the request with our extended params 11712 if (! cfg.params.hasOwnProperty(prop)) { 11713 if (this.extended[prop] !== null) { 11714 cfg.params[prop] = this.extended[prop]; 11715 } 11716 } 11717 } 11718 } 11719 } 11720 11721 // consolidate headers 11722 headers.Authorization = this.auth; 11723 if (this.version !== "0.9") { 11724 headers["X-Experience-API-Version"] = this.version; 11725 } 11726 11727 for (prop in cfg.headers) { 11728 if (cfg.headers.hasOwnProperty(prop)) { 11729 headers[prop] = cfg.headers[prop]; 11730 } 11731 } 11732 11733 return this._makeRequest(fullUrl, headers, cfg); 11734 }, 11735 11736 /** 11737 Method used to determine the LRS version 11738 11739 @method about 11740 @param {Object} cfg Configuration object for the about request 11741 @param {Function} [cfg.callback] Callback to execute upon receiving a response 11742 @param {Object} [cfg.params] this is needed, but can be empty 11743 @return {Object} About which holds the version, or asyncrhonously calls a specified callback 11744 */ 11745 about: function (cfg) { 11746 this.log("about"); 11747 var requestCfg, 11748 requestResult, 11749 callbackWrapper; 11750 11751 cfg = cfg || {}; 11752 11753 requestCfg = { 11754 url: "about", 11755 method: "GET", 11756 params: {} 11757 }; 11758 if (typeof cfg.callback !== "undefined") { 11759 callbackWrapper = function (err, xhr) { 11760 var result = xhr; 11761 11762 if (err === null) { 11763 result = TinCan.About.fromJSON(xhr.responseText); 11764 } 11765 11766 cfg.callback(err, result); 11767 }; 11768 requestCfg.callback = callbackWrapper; 11769 } 11770 11771 requestResult = this.sendRequest(requestCfg); 11772 11773 if (callbackWrapper) { 11774 return; 11775 } 11776 11777 if (requestResult.err === null) { 11778 requestResult.xhr = TinCan.About.fromJSON(requestResult.xhr.responseText); 11779 } 11780 return requestResult; 11781 }, 11782 11783 /** 11784 Save a statement, when used from a browser sends to the endpoint using the RESTful interface. 11785 Use a callback to make the call asynchronous. 11786 11787 @method saveStatement 11788 @param {TinCan.Statement} statement to send 11789 @param {Object} [cfg] Configuration used when saving 11790 @param {Function} [cfg.callback] Callback to execute on completion 11791 */ 11792 saveStatement: function (stmt, cfg) { 11793 this.log("saveStatement"); 11794 var requestCfg = { 11795 url: "statements", 11796 headers: {} 11797 }, 11798 versionedStatement, 11799 requestAttachments = [], 11800 boundary, 11801 i; 11802 11803 cfg = cfg || {}; 11804 11805 try { 11806 versionedStatement = stmt.asVersion( this.version ); 11807 } 11808 catch (ex) {
11809 if (this.allowFail) { 11810 this.log("[warning] statement could not be serialized in version (" + this.version + "): " + ex); 11811 if (typeof cfg.callback !== "undefined") { 11812 cfg.callback(null, null); 11813 return; 11814 } 11815 return { 11816 err: null, 11817 xhr: null 11818 }; 11819 } 11820 11821 this.log("[error] statement could not be serialized in version (" + this.version + "): " + ex); 11822 if (typeof cfg.callback !== "undefined") { 11823 cfg.callback(ex, null); 11824 return; 11825 } 11826 return { 11827 err: ex, 11828 xhr: null 11829 }; 11830 } 11831 11832 if (versionedStatement.hasOwnProperty("attachments") && stmt.hasAttachmentWithContent()) { 11833 boundary = this._getBoundary(); 11834 11835 requestCfg.headers["Content-Type"] = "multipart/mixed; boundary=" + boundary; 11836 11837 for (i = 0; i < stmt.attachments.length; i += 1) { 11838 if (stmt.attachments[i].content !== null) { 11839 requestAttachments.push(stmt.attachments[i]); 11840 } 11841 } 11842 11843 try { 11844 requestCfg.data = this._getMultipartRequestData(boundary, versionedStatement, requestAttachments); 11845 } 11846 catch (ex) { 11847 if (this.allowFail) { 11848 this.log("[warning] multipart request data could not be created (attachments probably not supported): " + ex); 11849 if (typeof cfg.callback !== "undefined") { 11850 cfg.callback(null, null); 11851 return; 11852 } 11853 return { 11854 err: null, 11855 xhr: null 11856 }; 11857 } 11858 11859 this.log("[error] multipart request data could not be created (attachments probably not supported): " + ex); 11860 if (typeof cfg.callback !== "undefined") { 11861 cfg.callback(ex, null); 11862 return; 11863 } 11864 return { 11865 err: ex, 11866 xhr: null 11867 }; 11868 } 11869 } 11870 else { 11871 requestCfg.headers["Content-Type"] = "application/json"; 11872 requestCfg.data = JSON.stringify(versionedStatement); 11873 } 11874 if (stmt.id !== null) { 11875 requestCfg.method = "PUT"; 11876 requestCfg.params = { 11877 statementId: stmt.id 11878 }; 11879 } 11880 else { 11881 requestCfg.method = "POST"; 11882 } 11883 11884 if (typeof cfg.callback !== "undefined") { 11885 requestCfg.callback = cfg.callback; 11886 } 11887 11888 return this.sendRequest(requestCfg); 11889 }, 11890 11891 /** 11892 Retrieve a statement, when used from a browser sends to the endpoint using the RESTful interface. 11893 11894 @method retrieveStatement 11895 @param {String} ID of statement to retrieve 11896 @param {Object} [cfg] Configuration options 11897 @param {Object} [cfg.params] Query parameters 11898 @param {Boolean} [cfg.params.attachments] Include attachments in multipart response or don't (default: false) 11899 @param {Function} [cfg.callback] Callback to execute on completion 11900 @return {TinCan.Statement} Statement retrieved 11901 */ 11902 retrieveStatement: function (stmtId, cfg) { 11903 this.log("retrieveStatement"); 11904 var requestCfg, 11905 requestResult, 11906 callbackWrapper, 11907 lrs = this; 11908 11909 cfg = cfg || {}; 11910 cfg.params = cfg.params || {}; 11911 11912 requestCfg = { 11913 url: "statements", 11914 method: "GET", 11915 params: { 11916 statementId: stmtId 11917 } 11918 }; 11919 if (cfg.params.attachments) { 11920 requestCfg.params.attachments = true; 11921 requestCfg.expectMultipart = true; 11922 } 11923 if (typeof cfg.callback !== "undefined") { 11924 callbackWrapper = function (err, xhr) { 11925 var result = xhr; 11926 11927 if (err === null) { 11928 result = lrs._processGetStatementResult(xhr, cfg.params); 11929 } 11930 11931 cfg.callback(err, result); 11932 }; 11933 requestCfg.callback = callbackWrapper; 11934 } 11935 11936 requestResult = this.sendRequest(requestCfg); 11937 if (! callbackWrapper) { 11938 requestResult.statement = null; 11939 if (requestResult.err === null) { 11940 requestResult.statement = lrs._processGetStatementResult(requestResult.xhr, cfg.params); 11941 } 11942 } 11943 11944 return requestResult; 11945 }, 11946 11947 /** 11948 Retrieve a voided statement, when used from a browser sends to the endpoint using the RESTful interface. 11949 11950 @method retrieveVoidedStatement 11951 @param {String} ID of voided statement to retrieve 11952 @param {Object} [cfg] Configuration options 11953 @param {Object} [cfg.params] Query parameters 11954 @param {Boolean} [cfg.params.attachments] Include attachments in multipart response or don't (default: false) 11955 @param {Function} [cfg.callback] Callback to execute on completion 11956 @return {TinCan.Statement} Statement retrieved 11957 */ 11958 retrieveVoidedStatement: function (stmtId, cfg) { 11959 this.log("retrieveVoidedStatement"); 11960 var requestCfg, 11961 requestResult, 11962 callbackWrapper, 11963 lrs = this; 11964 11965 cfg = cfg || {}; 11966 cfg.params = cfg.params || {}; 11967 11968 requestCfg = { 11969 url: "statements", 11970 method: "GET", 11971 params: {} 11972 }; 11973 if (this.version === "0.9" || this.version === "0.95") { 11974 requestCfg.params.statementId = stmtId; 11975 } 11976 else { 11977 requestCfg.params.voidedStatementId = stmtId; 11978 if (cfg.params.attachments) { 11979 requestCfg.params.attachments = true; 11980 requestCfg.expectMultipart = true; 11981 } 11982 } 11983 11984 if (typeof cfg.callback !== "undefined") { 11985 callbackWrapper = function (err, xhr) { 11986 var result = xhr; 11987 11988 if (err === null) { 11989 result = lrs._processGetStatementResult(xhr, cfg.params); 11990 } 11991 11992 cfg.callback(err, result); 11993 }; 11994 requestCfg.callback = callbackWrapper; 11995 } 11996 11997 requestResult = this.sendRequest(requestCfg); 11998 if (! callbackWrapper) { 11999 requestResult.statement = null; 12000 if (requestResult.err === null) { 12001 requestResult.statement = lrs._processGetStatementResult(requestResult.xhr, cfg.params); 12002 } 12003 } 12004 12005 return requestResult; 12006 }, 12007 12008 /** 12009 Save a set of statements, when used from a browser sends to the endpoint using the RESTful interface. 12010 Use a callback to make the call asynchronous. 12011 12012 @method saveStatements 12013 @param {Array} Array of statements or objects convertable to statements 12014 @param {Object} [cfg] Configuration used when saving 12015 @param {Function} [cfg.callback] Callback to execute on completion 12016 */ 12017 saveStatements: function (stmts, cfg) { 12018 this.log("saveStatements"); 12019 var requestCfg = { 12020 url: "statements", 12021 method: "POST", 12022 headers: {} 12023 }, 12024 versionedStatement, 12025 versionedStatements = [], 12026 requestAttachments = [], 12027 boundary, 12028 i, 12029 j; 12030 12031 cfg = cfg || {}; 12032 12033 if (stmts.length === 0) { 12034 if (typeof cfg.callback !== "undefined") { 12035 cfg.callback(new Error("no statements"), null); 12036 return; 12037 } 12038 return { 12039 err: new Error("no statements"), 12040 xhr: null 12041 }; 12042 } 12043 12044 for (i = 0; i < stmts.length; i += 1) { 12045 try { 12046 versionedStatement = stmts[i].asVersion( this.version ); 12047 } 12048 catch (ex) {
12049 if (this.allowFail) { 12050 this.log("[warning] statement could not be serialized in version (" + this.version + "): " + ex); 12051 if (typeof cfg.callback !== "undefined") { 12052 cfg.callback(null, null); 12053 return; 12054 } 12055 return { 12056 err: null, 12057 xhr: null 12058 }; 12059 } 12060 12061 this.log("[error] statement could not be serialized in version (" + this.version + "): " + ex); 12062 if (typeof cfg.callback !== "undefined") { 12063 cfg.callback(ex, null); 12064 return; 12065 } 12066 return { 12067 err: ex, 12068 xhr: null 12069 }; 12070 } 12071 12072 if (stmts[i].hasAttachmentWithContent()) { 12073 for (j = 0; j < stmts[i].attachments.length; j += 1) { 12074 if (stmts[i].attachments[j].content !== null) { 12075 requestAttachments.push(stmts[i].attachments[j]); 12076 } 12077 } 12078 } 12079 12080 versionedStatements.push(versionedStatement); 12081 } 12082 12083 if (requestAttachments.length !== 0) { 12084 boundary = this._getBoundary(); 12085 12086 requestCfg.headers["Content-Type"] = "multipart/mixed; boundary=" + boundary; 12087 12088 try { 12089 requestCfg.data = this._getMultipartRequestData(boundary, versionedStatements, requestAttachments); 12090 } 12091 catch (ex) { 12092 if (this.allowFail) { 12093 this.log("[warning] multipart request data could not be created (attachments probably not supported): " + ex); 12094 if (typeof cfg.callback !== "undefined") { 12095 cfg.callback(null, null); 12096 return; 12097 } 12098 return { 12099 err: null, 12100 xhr: null 12101 }; 12102 } 12103 12104 this.log("[error] multipart request data could not be created (attachments probably not supported): " + ex); 12105 if (typeof cfg.callback !== "undefined") { 12106 cfg.callback(ex, null); 12107 return; 12108 } 12109 return { 12110 err: ex, 12111 xhr: null 12112 }; 12113 } 12114 } 12115 else { 12116 requestCfg.headers["Content-Type"] = "application/json"; 12117 requestCfg.data = JSON.stringify(versionedStatements); 12118 } 12119 12120 if (typeof cfg.callback !== "undefined") { 12121 requestCfg.callback = cfg.callback; 12122 } 12123 12124 return this.sendRequest(requestCfg); 12125 }, 12126 12127 /** 12128 Fetch a set of statements, when used from a browser sends to the endpoint using the 12129 RESTful interface. Use a callback to make the call asynchronous. 12130 12131 @method queryStatements 12132 @param {Object} [cfg] Configuration used to query 12133 @param {Object} [cfg.params] Query parameters 12134 @param {TinCan.Agent|TinCan.Group} [cfg.params.agent] Agent matches 'actor' or 'object' 12135 @param {TinCan.Verb|String} [cfg.params.verb] Verb (or verb ID) to query on 12136 @param {TinCan.Activity|String} [cfg.params.activity] Activity (or activity ID) to query on 12137 @param {String} [cfg.params.registration] Registration UUID 12138 @param {Boolean} [cfg.params.related_activities] Match related activities 12139 @param {Boolean} [cfg.params.related_agents] Match related agents 12140 @param {String} [cfg.params.since] Match statements stored since specified timestamp 12141 @param {String} [cfg.params.until] Match statements stored at or before specified timestamp 12142 @param {Integer} [cfg.params.limit] Number of results to retrieve 12143 @param {String} [cfg.params.format] One of "ids", "exact", "canonical" (default: "exact") 12144 @param {Boolean} [cfg.params.ascending] Return results in ascending order of stored time 12145 12146 @param {TinCan.Agent} [cfg.params.actor] (Removed in 1.0.0, use 'agent' instead) Agent matches 'actor' 12147 @param {TinCan.Activity|TinCan.Agent|TinCan.Statement} [cfg.params.target] (Removed in 1.0.0, use 'activity' or 'agent' instead) Activity, Agent, or Statement matches 'object' 12148 @param {TinCan.Agent} [cfg.params.instructor] (Removed in 1.0.0, use 'agent' + 'related_agents' instead) Agent matches 'context:instructor' 12149 @param {Boolean} [cfg.params.context] (Removed in 1.0.0, use 'activity' instead) When filtering on target, include statements with matching context 12150 @param {Boolean} [cfg.params.authoritative] (Removed in 1.0.0) Get authoritative results 12151 @param {Boolean} [cfg.params.sparse] (Removed in 1.0.0, use 'format' instead) Get sparse results 12152 12153 @param {Function} [cfg.callback] Callback to execute on completion 12154 @param {String|null} cfg.callback.err Error status or null if succcess 12155 @param {TinCan.StatementsResult|XHR} cfg.callback.response Receives a StatementsResult argument 12156 @return {Object} Request result 12157 */ 12158 queryStatements: function (cfg) { 12159 this.log("queryStatements"); 12160 var requestCfg, 12161 requestResult, 12162 callbackWrapper, 12163 lrs = this; 12164 12165 cfg = cfg || {}; 12166 cfg.params = cfg.params || {}; 12167 12168 // 12169 // if they misconfigured (possibly due to version mismatches) the 12170 // query then don't try to send a request at all, rather than give 12171 // them invalid results 12172 // 12173 try { 12174 requestCfg = this._queryStatementsRequestCfg(cfg); 12175 12176 if (cfg.params.attachments) { 12177 requestCfg.expectMultipart = true; 12178 } 12179 } 12180 catch (ex) { 12181 this.log("[error] Query statements failed - " + ex); 12182 if (typeof cfg.callback !== "undefined") { 12183 cfg.callback(ex, {}); 12184 } 12185 12186 return { 12187 err: ex,
12188 statementsResult: null 12189 }; 12190 } 12191 12192 if (typeof cfg.callback !== "undefined") { 12193 callbackWrapper = function (err, xhr) { 12194 var result = xhr, 12195 parsedResponse, 12196 boundary, 12197 statements, 12198 attachmentMap = {}, 12199 i; 12200 12201 if (err === null) { 12202 if (! cfg.params.attachments) { 12203 result = TinCan.StatementsResult.fromJSON(xhr.responseText); 12204 } 12205 else { 12206 boundary = xhr.getResponseHeader("Content-Type").split("boundary=")[1]; 12207 12208 parsedResponse = lrs._parseMultipart(boundary, xhr.response); 12209 statements = JSON.parse(parsedResponse[0].body); 12210 for (i = 1; i < parsedResponse.length; i += 1) { 12211 attachmentMap[parsedResponse[i].headers["X-Experience-API-Hash"]] = parsedResponse[i].body; 12212 } 12213 12214 lrs._assignAttachmentContent(statements.statements, attachmentMap); 12215 result = new TinCan.StatementsResult({ statements: statements.statements }); 12216 12217 for (i = 0; i < result.statements.length; i += 1) { 12218 if (! (result.statements[i] instanceof TinCan.Statement)) { 12219 result.statements[i] = new TinCan.Statement(result.statements[i]); 12220 } 12221 } 12222 } 12223 } 12224 12225 cfg.callback(err, result); 12226 }; 12227 requestCfg.callback = callbackWrapper; 12228 } 12229 12230 requestResult = this.sendRequest(requestCfg); 12231 requestResult.config = requestCfg; 12232 12233 if (! callbackWrapper) { 12234 requestResult.statementsResult = null; 12235 if (requestResult.err === null) { 12236 requestResult.statementsResult = TinCan.StatementsResult.fromJSON(requestResult.xhr.responseText); 12237 } 12238 } 12239 12240 return requestResult; 12241 }, 12242 12243 /** 12244 Build a request config object that can be passed to sendRequest() to make a query request 12245 12246 @method _queryStatementsRequestCfg 12247 @private 12248 @param {Object} [cfg] See configuration for {{#crossLink "TinCan.LRS/queryStatements"}}{{/crossLink}} 12249 @return {Object} Request configuration object 12250 */ 12251 _queryStatementsRequestCfg: function (cfg) { 12252 this.log("_queryStatementsRequestCfg"); 12253 var params = {}, 12254 returnCfg = { 12255 url: "statements", 12256 method: "GET", 12257 params: params 12258 }, 12259 jsonProps = [ 12260 "agent", 12261 "actor", 12262 "object", 12263 "instructor" 12264 ], 12265 idProps = [ 12266 "verb", 12267 "activity" 12268 ], 12269 valProps = [ 12270 "registration", 12271 "context", 12272 "since", 12273 "until", 12274 "limit", 12275 "authoritative", 12276 "sparse", 12277 "ascending", 12278 "related_activities", 12279 "related_agents", 12280 "format", 12281 "attachments" 12282 ], 12283 i, 12284 prop, 12285 // 12286 // list of parameters that are supported in all versions (supported by 12287 // this library) of the spec 12288 // 12289 universal = { 12290 verb: true, 12291 registration: true, 12292 since: true, 12293 until: true, 12294 limit: true, 12295 ascending: true 12296 }, 12297 // 12298 // future proofing here, "supported" is an object so that 12299 // in the future we can support a "deprecated" list to 12300 // throw warnings, hopefully the spec uses deprecation phases 12301 // for the removal of these things 12302 // 12303 compatibility = { 12304 "0.9": { 12305 supported: { 12306 actor: true, 12307 instructor: true, 12308 target: true, 12309 object: true, 12310 context: true, 12311 authoritative: true, 12312 sparse: true 12313 } 12314 }, 12315 "1.0.0": { 12316 supported: { 12317 agent: true, 12318 activity: true, 12319 related_activities: true, 12320 related_agents: true, 12321 format: true, 12322 attachments: true 12323 } 12324 } 12325 }; 12326 12327 compatibility["0.95"] = compatibility["0.9"]; 12328 compatibility["1.0.1"] = compatibility["1.0.0"]; 12329 compatibility["1.0.2"] = compatibility["1.0.0"]; 12330 12331 if (cfg.params.hasOwnProperty("target")) { 12332 cfg.params.object = cfg.params.target; 12333 } 12334 12335 // 12336 // check compatibility tables, either the configured parameter is in 12337 // the universal list or the specific version, if not then throw an 12338 // error which at least for .queryStatements will prevent the request 12339 // and potentially alert the user 12340 // 12341 for (prop in cfg.params) { 12342 if (cfg.params.hasOwnProperty(prop)) { 12343 if (typeof universal[prop] === "undefined" && typeof compatibility[this.version].supported[prop] === "undefined") { 12344 throw "Unrecognized query parameter configured: " + prop; 12345 } 12346 } 12347 } 12348 12349 // 12350 // getting here means that all parameters are valid for this version 12351 // to make handling the output formats easier 12352 // 12353 12354 for (i = 0; i < jsonProps.length; i += 1) { 12355 if (typeof cfg.params[jsonProps[i]] !== "undefined") { 12356 params[jsonProps[i]] = JSON.stringify(cfg.params[jsonProps[i]].asVersion(this.version)); 12357 } 12358 } 12359 12360 for (i = 0; i < idProps.length; i += 1) { 12361 if (typeof cfg.params[idProps[i]] !== "undefined") { 12362 if (typeof cfg.params[idProps[i]].id === "undefined") { 12363 params[idProps[i]] = cfg.params[idProps[i]]; 12364 } 12365 else { 12366 params[idProps[i]] = cfg.params[idProps[i]].id; 12367 } 12368 } 12369 } 12370 12371 for (i = 0; i < valProps.length; i += 1) { 12372 if (typeof cfg.params[valProps[i]] !== "undefined" && cfg.params[valProps[i]] !== null) { 12373 params[valProps[i]] = cfg.params[valProps[i]]; 12374 } 12375 } 12376 12377 return returnCfg; 12378 }, 12379 12380 /** 12381 Assigns attachment content to the correct attachment to create a StatementsResult object that is sent 12382 to the callback of queryStatements() 12383 12384 @method _assignAttachmentContent 12385 @private 12386 @param {Array} [stmts] Array of TinCan.Statement JSON objects 12387 @param {Object} [attachmentMap] Map of the content to place into its attachment 12388 @return {Array} Array of TinCan.Statement JSON objects with correctly assigned attachment content 12389 */ 12390 _assignAttachmentContent: function (stmts, attachmentMap) { 12391 var i, 12392 j; 12393 12394 for (i = 0; i < stmts.length; i += 1) { 12395 if (stmts[i].hasOwnProperty("attachments") && stmts[i].attachments !== null) { 12396 for (j = 0; j < stmts[i].attachments.length; j += 1) { 12397 if (attachmentMap.hasOwnProperty(stmts[i].attachments[j].sha2)) { 12398 stmts[i].attachments[j].content = attachmentMap[stmts[i].attachments[j].sha2]; 12399 } 12400 } 12401 } 12402 } 12403 }, 12404 12405 /** 12406 Parses the different sections of a multipart/mixed response 12407 12408 @method _parseMultipart 12409 @private 12410 @param {String} [boundary] Boundary used to mark off the sections of the response 12411 @param {ArrayBuffer} [response] Body of the response 12412 @return {Array} Array of objects containing the parsed headers and body of each part 12413 */ 12414 _parseMultipart: function (boundary, response) { 12415 /* global Uint8Array */ 12416 var __boundary = "--" + boundary, 12417 byteArray, 12418 bodyEncodedInString, 12419 fullBodyEnd, 12420 sliceStart, 12421 sliceEnd, 12422 headerStart, 12423 headerEnd, 12424 bodyStart, 12425 bodyEnd, 12426 headers, 12427 body, 12428 parts = [], 12429 CRLF = 2; 12430 12431 // 12432 // treating the reponse as a stream of bytes and assuming that headers 12433 // and related mime boundaries are all US-ASCII (which is a safe assumption) 12434 // allows us to treat the whole response as a string when looking for offsets 12435 // but then slice on the raw array buffer 12436 // 12437 byteArray = new Uint8Array(response); 12438 bodyEncodedInString = this.__uint8ToString(byteArray); 12439 12440 fullBodyEnd = bodyEncodedInString.indexOf(__boundary + "--"); 12441 12442 sliceStart = bodyEncodedInString.indexOf(__boundary); 12443 while (sliceStart !== -1) { 12444 sliceEnd = bodyEncodedInString.indexOf(__boundary, sliceStart + __boundary.length); 12445 12446 headerStart = sliceStart + __boundary.length + CRLF; 12447 headerEnd = bodyEncodedInString.indexOf("\r\n\r\n", sliceStart); 12448 bodyStart = headerEnd + CRLF + CRLF; 12449 bodyEnd = sliceEnd - 2; 12450 12451 headers = this._parseHeaders( 12452 this.__uint8ToString( 12453 new Uint8Array( response.slice(headerStart, headerEnd) ) 12454 ) 12455 ); 12456 body = response.slice(bodyStart, bodyEnd); 12457 12458 // 12459 // we know the first slice is the statement, and we know it is a string in UTF-8 (spec requirement) 12460 // 12461 if (parts.length === 0) { 12462 body = TinCan.Utils.stringFromArrayBuffer(body); 12463 } 12464 12465 parts.push( 12466 { 12467 headers: headers, 12468 body: body 12469 } 12470 ); 12471 12472 if (sliceEnd === fullBodyEnd) { 12473 sliceStart = -1; 12474 } 12475 else { 12476 sliceStart = sliceEnd; 12477 } 12478 } 12479 12480 return parts; 12481 }, 12482 12483 // 12484 // implemented as a function to avoid 'RangeError: Maximum call stack size exceeded' 12485 // when calling .fromCharCode on the full byteArray which results in a too long 12486 // argument list for large arrays 12487 // 12488 __uint8ToString: function (byteArray) { 12489 var result = "", 12490 len = byteArray.byteLength, 12491 i; 12492 12493 for (i = 0; i < len; i += 1) { 12494 result += String.fromCharCode(byteArray[i]); 12495 } 12496 return result; 12497 }, 12498 12499 /** 12500 Parses the headers of a multipart/mixed response section 12501 12502 @method _parseHeaders 12503 @private 12504 @param {String} [rawHeaders] String containing all the headers 12505 @return {Object} Map of the headers 12506 */ 12507 _parseHeaders: function (rawHeaders) { 12508 var headers = {}, 12509 headerList, 12510 key, 12511 h, 12512 i; 12513 12514 headerList = rawHeaders.split("\n"); 12515 for (i = 0; i < headerList.length; i += 1) { 12516 h = headerList[i].split(":", 2); 12517 12518 if (h[1] !== null) { 12519 headers[h[0]] = h[1].replace(/^\s+|\s+$/g, ""); 12520 12521 key = h[0]; 12522 } 12523 else { 12524 if (h[0].substring(0, 1) === "\t") { 12525 headers[h[0]] = h[1].replace(/^\s+|\s+$/g, ""); 12526 } 12527 } 12528 } 12529 12530 return headers; 12531 }, 12532 12533 /** 12534 Fetch more statements from a previous query, when used from a browser sends to the endpoint using the 12535 RESTful interface. Use a callback to make the call asynchronous. 12536 12537 @method moreStatements 12538 @param {Object} [cfg] Configuration used to query 12539 @param {String} [cfg.url] More URL 12540 @param {Function} [cfg.callback] Callback to execute on completion 12541 @param {String|null} cfg.callback.err Error status or null if succcess 12542 @param {TinCan.StatementsResult|XHR} cfg.callback.response Receives a StatementsResult argument 12543 @return {Object} Request result 12544 */ 12545 moreStatements: function (cfg) { 12546 this.log("moreStatements: " + cfg.url); 12547 var requestCfg, 12548 requestResult, 12549 callbackWrapper, 12550 parsedURL, 12551 serverRoot; 12552 12553 cfg = cfg || {}; 12554 12555 // to support our interface (to support IE) we need to break apart 12556 // the more URL query params so that the request can be made properly later 12557 parsedURL = TinCan.Utils.parseURL(cfg.url, { allowRelative: true }); 12558 12559 // Respect a more URL that is relative to either the server root 12560 // or endpoint (though only the former is allowed in the spec) 12561 serverRoot = TinCan.Utils.getServerRoot(this.endpoint); 12562 if (parsedURL.path.indexOf("/statements") === 0){ 12563 parsedURL.path = this.endpoint.replace(serverRoot, "") + parsedURL.path; 12564 this.log("converting non-standard more URL to " + parsedURL.path); 12565 } 12566 12567 // The more relative URL might not start with a slash, add it if not 12568 if (parsedURL.path.indexOf("/") !== 0) { 12569 parsedURL.path = "/" + parsedURL.path; 12570 } 12571 12572 requestCfg = { 12573 method: "GET", 12574 // For arbitrary more URLs to work, we need to make the URL absolute here 12575 url: serverRoot + parsedURL.path, 12576 params: parsedURL.params 12577 }; 12578 if (typeof cfg.callback !== "undefined") { 12579 callbackWrapper = function (err, xhr) { 12580 var result = xhr; 12581 12582 if (err === null) { 12583 result = TinCan.StatementsResult.fromJSON(xhr.responseText); 12584 } 12585 12586 cfg.callback(err, result); 12587 }; 12588 requestCfg.callback = callbackWrapper; 12589 } 12590 12591 requestResult = this.sendRequest(requestCfg); 12592 requestResult.config = requestCfg; 12593 12594 if (! callbackWrapper) { 12595 requestResult.statementsResult = null; 12596 if (requestResult.err === null) { 12597 requestResult.statementsResult = TinCan.StatementsResult.fromJSON(requestResult.xhr.responseText); 12598 } 12599 } 12600 12601 return requestResult; 12602 }, 12603 12604 /** 12605 Retrieve a state value, when used from a browser sends to the endpoint using the RESTful interface. 12606 12607 @method retrieveState 12608 @param {String} key Key of state to retrieve 12609 @param {Object} cfg Configuration options 12610 @param {TinCan.Activity} cfg.activity Activity in document identifier 12611 @param {TinCan.Agent} cfg.agent Agent in document identifier 12612
12612 @param {String} [cfg.registration] Registration 12613 @param {Function} [cfg.callback] Callback to execute on completion 12614 @param {Object|Null} cfg.callback.error 12615 @param {TinCan.State|null} cfg.callback.result null if state is 404 12616 @param {Object} [cfg.requestHeaders] Object containing additional headers to add to request 12617 @return {TinCan.State|Object} TinCan.State retrieved when synchronous, or result from sendRequest 12618 */ 12619 retrieveState: function (key, cfg) { 12620 this.log("retrieveState"); 12621 var requestParams = {}, 12622 requestCfg = {}, 12623 requestResult, 12624 callbackWrapper, 12625 requestHeaders, 12626 self = this; 12627 12628 requestHeaders = cfg.requestHeaders || {}; 12629 12630 requestParams = { 12631 stateId: key, 12632 activityId: cfg.activity.id 12633 }; 12634 if (this.version === "0.9") { 12635 requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 12636 } 12637 else { 12638 requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 12639 } 12640 if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) { 12641 if (this.version === "0.9") { 12642 requestParams.registrationId = cfg.registration; 12643 } 12644 else { 12645 requestParams.registration = cfg.registration; 12646 } 12647 } 12648 12649 requestCfg = { 12650 url: "activities/state", 12651 method: "GET", 12652 params: requestParams, 12653 ignore404: true, 12654 headers: requestHeaders 12655 }; 12656 12657 if (typeof cfg.callback !== "undefined") { 12658 callbackWrapper = function (err, xhr) { 12659 var result = xhr; 12660 12661 if (err === null) { 12662 if (xhr.status === 404) { 12663 result = null; 12664 } 12665 else { 12666 result = new TinCan.State( 12667 { 12668 id: key, 12669 contents: xhr.responseText 12670 } 12671 ); 12672 if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") { 12673 result.etag = xhr.getResponseHeader("ETag"); 12674 } 12675 else { 12676 // 12677 // either XHR didn't have getResponseHeader (probably cause it is an IE 12678 // XDomainRequest object which doesn't) or not populated by LRS so create 12679 // the hash ourselves 12680 // 12681 // the LRS is responsible for quoting the Etag value so we need to mimic 12682 // that behavior here as well 12683 // 12684 result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\""; 12685 } 12686 12687 if (typeof xhr.contentType !== "undefined") { 12688 // most likely an XDomainRequest which has .contentType, 12689 // for the ones that it supports 12690 result.contentType = xhr.contentType; 12691 } 12692 else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") { 12693 result.contentType = xhr.getResponseHeader("Content-Type"); 12694 } 12695 12696 if (TinCan.Utils.isApplicationJSON(result.contentType)) { 12697 try { 12698 result.contents = JSON.parse(result.contents); 12699 } catch (ex) { 12700 self.log("retrieveState - failed to deserialize JSON: " + ex); 12701 } 12702 } 12703 } 12704 } 12705 12706 cfg.callback(err, result); 12707 }; 12708 requestCfg.callback = callbackWrapper; 12709 } 12710 12711 requestResult = this.sendRequest(requestCfg); 12712 if (! callbackWrapper) { 12713 requestResult.state = null; 12714 if (requestResult.err === null && requestResult.xhr.status !== 404) { 12715 requestResult.state = new TinCan.State( 12716 { 12717 id: key, 12718 contents: requestResult.xhr.responseText 12719 } 12720 ); 12721 if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") { 12722 requestResult.state.etag = requestResult.xhr.getResponseHeader("ETag"); 12723 } 12724 else { 12725 // 12726 // either XHR didn't have getResponseHeader (probably cause it is an IE 12727 // XDomainRequest object which doesn't) or not populated by LRS so create 12728 // the hash ourselves 12729 // 12730 // the LRS is responsible for quoting the Etag value so we need to mimic 12731 // that behavior here as well 12732 // 12733 requestResult.state.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\""; 12734 } 12735 if (typeof requestResult.xhr.contentType !== "undefined") { 12736 // most likely an XDomainRequest which has .contentType 12737 // for the ones that it supports 12738 requestResult.state.contentType = requestResult.xhr.contentType; 12739 } 12740 else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") { 12741 requestResult.state.contentType = requestResult.xhr.getResponseHeader("Content-Type"); 12742 } 12743 if (TinCan.Utils.isApplicationJSON(requestResult.state.contentType)) { 12744 try { 12745 requestResult.state.contents = JSON.parse(requestResult.state.contents); 12746 } catch (ex) { 12747 this.log("retrieveState - failed to deserialize JSON: " + ex); 12748 } 12749 } 12750 } 12751 } 12752 12753 return requestResult; 12754 }, 12755 12756 /** 12757 Retrieve the list of IDs for a state, when used from a browser sends to the endpoint using the RESTful interface. 12758 12759 @method retrieveStateIds 12760 @param {Object} cfg Configuration options 12761 @param {TinCan.Activity} cfg.activity Activity in document identifier 12762 @param {TinCan.Agent} cfg.agent Agent in document identifier 12763 @param {String} [cfg.registration] Registration 12764 @param {Function} [cfg.callback] Callback to execute on completion 12765 @param {String} [cfg.since] Match activity profiles saved since given timestamp 12766 @param {Object}
12766 [cfg.requestHeaders] Optional object containing additional headers to add to request 12767 @return {Object} requestResult Request result 12768 */ 12769 retrieveStateIds: function (cfg) { 12770 this.log("retrieveStateIds"); 12771 var requestParams = {}, 12772 requestCfg, 12773 requestHeaders, 12774 requestResult, 12775 callbackWrapper; 12776 12777 cfg = cfg || {}; 12778 requestHeaders = cfg.requestHeaders || {}; 12779 12780 requestParams.activityId = cfg.activity.id; 12781 if (this.version === "0.9") { 12782 requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 12783 } 12784 else { 12785 requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 12786 } 12787 if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) { 12788 if (this.version === "0.9") { 12789 requestParams.registrationId = cfg.registration; 12790 } 12791 else { 12792 requestParams.registration = cfg.registration; 12793 } 12794 } 12795 12796 requestCfg = { 12797 url: "activities/state", 12798 method: "GET", 12799 params: requestParams, 12800 headers: requestHeaders, 12801 ignore404: true 12802 }; 12803 if (typeof cfg.callback !== "undefined") { 12804 callbackWrapper = function (err, xhr) { 12805 var result = xhr; 12806 12807 if (err !== null) { 12808 cfg.callback(err, result); 12809 return; 12810 } 12811 12812 if (xhr.status === 404) { 12813 result = []; 12814 } 12815 else { 12816 try { 12817 result = JSON.parse(xhr.responseText); 12818 } 12819 catch (ex) { 12820 err = "Response JSON parse error: " + ex; 12821 } 12822 } 12823 12824 cfg.callback(err, result); 12825 }; 12826 requestCfg.callback = callbackWrapper; 12827 } 12828 if (typeof cfg.since !== "undefined") { 12829 requestCfg.params.since = cfg.since; 12830 } 12831 12832 requestResult = this.sendRequest(requestCfg); 12833 if (! callbackWrapper) { 12834 requestResult.profileIds = null; 12835 if (requestResult.err !== null) { 12836 return requestResult; 12837 } 12838 12839 if (requestResult.xhr.status === 404) { 12840 requestResult.profileIds = []; 12841 } 12842 else { 12843 try { 12844 requestResult.profileIds = JSON.parse(requestResult.xhr.responseText); 12845 } 12846 catch (ex) { 12847 requestResult.err = "retrieveStateIds - JSON parse error: " + ex; 12848 } 12849 } 12850 } 12851 return requestResult; 12852 }, 12853 12854 /** 12855 Save a state value, when used from a browser sends to the endpoint using the RESTful interface. 12856 12857 @method saveState 12858 @param {String} key Key of state to save 12859 @param val Value to be stored 12860 @param {Object} cfg Configuration options 12861 @param {TinCan.Activity} cfg.activity Activity in document identifier 12862 @param {TinCan.Agent} cfg.agent Agent in document identifier 12863 @param {String} [cfg.registration] Registration 12864 @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing state 12865 @param {String} [cfg.contentType] Content-Type to specify in headers (defaults to 'application/octet-stream') 12866 @param {String} [cfg.method] Method to use. Default: PUT 12867 @param {Function} [cfg.callback] Callback to execute on completion 12868 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 12869 */ 12870 saveState: function (key, val, cfg) { 12871 this.log("saveState");
12872 var requestParams, 12873 requestCfg, 12874 requestHeaders; 12875 12876 requestHeaders = cfg.requestHeaders || {}; 12877 12878 if (typeof cfg.contentType === "undefined") { 12879 cfg.contentType = "application/octet-stream"; 12880 } 12881 requestHeaders["Content-Type"] = cfg.contentType; 12882 12883 if (typeof val === "object" && TinCan.Utils.isApplicationJSON(cfg.contentType)) { 12884 val = JSON.stringify(val); 12885 } 12886 12887 if (typeof cfg.method === "undefined" || cfg.method !== "POST") { 12888 cfg.method = "PUT"; 12889 } 12890 12891 requestParams = { 12892 stateId: key, 12893 activityId: cfg.activity.id 12894 }; 12895 if (this.version === "0.9") { 12896 requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 12897 } 12898 else { 12899 requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 12900 } 12901 if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) { 12902 if (this.version === "0.9") { 12903 requestParams.registrationId = cfg.registration; 12904 } 12905 else { 12906 requestParams.registration = cfg.registration; 12907 } 12908 } 12909 12910 requestCfg = { 12911 url: "activities/state", 12912 method: cfg.method, 12913 params: requestParams, 12914 data: val, 12915 headers: requestHeaders 12916 }; 12917 12918 if (typeof cfg.callback !== "undefined") { 12919 requestCfg.callback = cfg.callback; 12920 } 12921 if (typeof cfg.lastSHA1 !== "undefined" && cfg.lastSHA1 !== null) { 12922 requestCfg.headers["If-Match"] = cfg.lastSHA1; 12923 } 12924 12925 return this.sendRequest(requestCfg); 12926 }, 12927 12928 /** 12929 Drop a state value or all of the state, when used from a browser sends to the endpoint using the RESTful interface. 12930 12931 @method dropState 12932 @param {String|null} key Key of state to delete, or null for all 12933 @param {Object} cfg Configuration options 12934 @param {TinCan.Activity} cfg.activity Activity in document identifier 12935 @param {TinCan.Agent} cfg.agent Agent in document identifier 12936 @param {String} [cfg.registration] Registration 12937 @param {Function} [cfg.callback] Callback to execute on completion 12938 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 12939 */ 12940 dropState: function (key, cfg) { 12941 this.log("dropState"); 12942 var requestParams, 12943 requestCfg, 12944 requestHeaders; 12945 12946 requestHeaders = cfg.requestHeaders || {}; 12947 12948 requestParams = { 12949 activityId: cfg.activity.id 12950 }; 12951 if (this.version === "0.9") { 12952 requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 12953 } 12954 else { 12955 requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 12956 } 12957 if (key !== null) { 12958 requestParams.stateId = key; 12959 } 12960 if ((typeof cfg.registration !== "undefined") && (cfg.registration !== null)) { 12961 if (this.version === "0.9") { 12962 requestParams.registrationId = cfg.registration; 12963 } 12964 else { 12965 requestParams.registration = cfg.registration; 12966 } 12967 } 12968 12969 requestCfg = { 12970 url: "activities/state", 12971 method: "DELETE", 12972 params: requestParams, 12973 headers: requestHeaders 12974 }; 12975 12976 if (typeof cfg.callback !== "undefined") { 12977 requestCfg.callback = cfg.callback; 12978 } 12979 12980 return this.sendRequest(requestCfg); 12981 }, 12982 12983 /** 12984 Retrieve an activity, when used from a browser sends to the endpoint using the RESTful interface. 12985 12986 @method retrieveActivity 12987 @param {String} activityId id of the Activity to retrieve 12988 @param {Object} cfg Configuration options 12989 @param {Function} [cfg.callback] Callback to execute on completion 12990 @param {Object}
12990 [cfg.requestHeaders] Optional object containing additional headers to add to request 12991 @return {Object} Value retrieved 12992 */ 12993 retrieveActivity: function (activityId, cfg) { 12994 this.log("retrieveActivity"); 12995 var requestCfg = {}, 12996 requestResult, 12997 callbackWrapper, 12998 requestHeaders; 12999 13000 requestHeaders = cfg.requestHeaders || {}; 13001 13002 requestCfg = { 13003 url: "activities", 13004 method: "GET", 13005 params: { 13006 activityId: activityId 13007 }, 13008 ignore404: true, 13009 headers: requestHeaders 13010 }; 13011 13012 if (typeof cfg.callback !== "undefined") { 13013 callbackWrapper = function (err, xhr) { 13014 var result = xhr; 13015 13016 if (err === null) { 13017 // 13018 // a 404 really shouldn't happen because the LRS can dynamically 13019 // build the response based on what has been passed to it, but 13020 // don't have the client fail in the condition that it does, because 13021 // we can do the same thing 13022 // 13023 if (xhr.status === 404) { 13024 result = new TinCan.Activity( 13025 { 13026 id: activityId 13027 } 13028 ); 13029 } 13030 else { 13031 result = TinCan.Activity.fromJSON(xhr.responseText); 13032 } 13033 } 13034 13035 cfg.callback(err, result); 13036 }; 13037 requestCfg.callback = callbackWrapper; 13038 } 13039 13040 requestResult = this.sendRequest(requestCfg); 13041 if (! callbackWrapper) { 13042 requestResult.activity = null; 13043 if (requestResult.err === null) { 13044 if (requestResult.xhr.status === 404) { 13045 requestResult.activity = new TinCan.Activity( 13046 { 13047 id: activityId 13048 } 13049 ); 13050 } 13051 else { 13052 requestResult.activity = TinCan.Activity.fromJSON(requestResult.xhr.responseText); 13053 } 13054 } 13055 } 13056 13057 return requestResult; 13058 }, 13059 13060 /** 13061 Retrieve an activity profile value, when used from a browser sends to the endpoint using the RESTful interface. 13062 13063 @method retrieveActivityProfile 13064 @param {String} key Key of activity profile to retrieve 13065 @param {Object} cfg Configuration options 13066 @param {TinCan.Activity} cfg.activity Activity in document identifier 13067 @param {Function} [cfg.callback] Callback to execute on completion 13068 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 13069 @return {Object} Value retrieved 13070 */ 13071 retrieveActivityProfile: function (key, cfg) { 13072 this.log("retrieveActivityProfile"); 13073 var requestCfg = {}, 13074 requestResult, 13075 callbackWrapper, 13076 requestHeaders, 13077 self = this; 13078 13079 requestHeaders = cfg.requestHeaders || {}; 13080 13081 requestCfg = { 13082 url: "activities/profile", 13083 method: "GET", 13084 params: { 13085 profileId: key, 13086 activityId: cfg.activity.id 13087 }, 13088 ignore404: true, 13089 headers: requestHeaders 13090 }; 13091 13092 if (typeof cfg.callback !== "undefined") { 13093 callbackWrapper = function (err, xhr) { 13094 var result = xhr; 13095 13096 if (err === null) { 13097 if (xhr.status === 404) { 13098 result = null; 13099 } 13100 else { 13101 result = new TinCan.ActivityProfile( 13102 { 13103 id: key, 13104 activity: cfg.activity, 13105 contents: xhr.responseText 13106 } 13107 ); 13108 if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") { 13109 result.etag = xhr.getResponseHeader("ETag"); 13110 } 13111 else { 13112 // 13113 // either XHR didn't have getResponseHeader (probably cause it is an IE 13114 // XDomainRequest object which doesn't) or not populated by LRS so create 13115 // the hash ourselves 13116 // 13117 // the LRS is responsible for quoting the Etag value so we need to mimic 13118 // that behavior here as well 13119 // 13120 result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\""; 13121 } 13122 if (typeof xhr.contentType !== "undefined") { 13123 // most likely an XDomainRequest which has .contentType 13124 // for the ones that it supports 13125 result.contentType = xhr.contentType; 13126 } 13127 else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") { 13128 result.contentType = xhr.getResponseHeader("Content-Type"); 13129 } 13130 if (TinCan.Utils.isApplicationJSON(result.contentType)) { 13131 try { 13132 result.contents = JSON.parse(result.contents); 13133 } catch (ex) { 13134 self.log("retrieveActivityProfile - failed to deserialize JSON: " + ex); 13135 } 13136 } 13137 } 13138 } 13139 13140 cfg.callback(err, result); 13141 }; 13142 requestCfg.callback = callbackWrapper; 13143 } 13144 13145 requestResult = this.sendRequest(requestCfg); 13146 if (! callbackWrapper) { 13147 requestResult.profile = null; 13148 if (requestResult.err === null && requestResult.xhr.status !== 404) { 13149 requestResult.profile = new TinCan.ActivityProfile( 13150 { 13151 id: key, 13152 activity: cfg.activity, 13153 contents: requestResult.xhr.responseText 13154 } 13155 ); 13156 if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") { 13157 requestResult.profile.etag = requestResult.xhr.getResponseHeader("ETag"); 13158 } 13159 else { 13160 // 13161 // either XHR didn't have getResponseHeader (probably cause it is an IE 13162 // XDomainRequest object which doesn't) or not populated by LRS so create 13163 // the hash ourselves 13164 // 13165 // the LRS is responsible for quoting the Etag value so we need to mimic 13166 // that behavior here as well 13167 // 13168 requestResult.profile.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\""; 13169 } 13170 if (typeof requestResult.xhr.contentType !== "undefined") { 13171 // most likely an XDomainRequest which has .contentType 13172 // for the ones that it supports 13173 requestResult.profile.contentType = requestResult.xhr.contentType; 13174 } 13175 else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") { 13176 requestResult.profile.contentType = requestResult.xhr.getResponseHeader("Content-Type"); 13177 } 13178 if (TinCan.Utils.isApplicationJSON(requestResult.profile.contentType)) { 13179 try { 13180 requestResult.profile.contents = JSON.parse(requestResult.profile.contents); 13181 } catch (ex) { 13182 this.log("retrieveActivityProfile - failed to deserialize JSON: " + ex); 13183 } 13184 } 13185 } 13186 } 13187 13188 return requestResult; 13189 }, 13190 13191 /** 13192 Retrieve the list of IDs for an activity profile, when used from a browser sends to the endpoint using the RESTful interface. 13193 13194 @method retrieveActivityProfileIds 13195 @param {Object} cfg Configuration options 13196 @param {TinCan.Activity} cfg.activity Activity in document identifier 13197 @param {Function} [cfg.callback] Callback to execute on completion 13198 @param {String} [cfg.since] Match activity profiles saved since given timestamp 13199 @param {Object}
13199 [cfg.requestHeaders] Optional object containing additional headers to add to request 13200 @return {Array} List of ids for this Activity profile 13201 */ 13202 retrieveActivityProfileIds: function (cfg) { 13203 this.log("retrieveActivityProfileIds"); 13204 var requestCfg, 13205 requestHeaders, 13206 requestResult, 13207 callbackWrapper; 13208 13209 cfg = cfg || {}; 13210 requestHeaders = cfg.requestHeaders || {}; 13211 13212 requestCfg = { 13213 url: "activities/profile", 13214 method: "GET", 13215 params: { 13216 activityId: cfg.activity.id 13217 }, 13218 headers: requestHeaders, 13219 ignore404: true 13220 }; 13221 if (typeof cfg.callback !== "undefined") { 13222 callbackWrapper = function (err, xhr) { 13223 var result = xhr; 13224 13225 if (err !== null) { 13226 cfg.callback(err, result); 13227 return; 13228 } 13229 13230 if (xhr.status === 404) { 13231 result = []; 13232 } 13233 else { 13234 try { 13235 result = JSON.parse(xhr.responseText); 13236 } 13237 catch (ex) { 13238 err = "Response JSON parse error: " + ex; 13239 } 13240 } 13241 13242 cfg.callback(err, result); 13243 }; 13244 requestCfg.callback = callbackWrapper; 13245 } 13246 if (typeof cfg.since !== "undefined") { 13247 requestCfg.params.since = cfg.since; 13248 } 13249 13250 requestResult = this.sendRequest(requestCfg); 13251 if (! callbackWrapper) { 13252 requestResult.profileIds = null; 13253 if (requestResult.err !== null) { 13254 return requestResult; 13255 } 13256 13257 if (requestResult.xhr.status === 404) { 13258 requestResult.profileIds = []; 13259 } 13260 else { 13261 try { 13262 requestResult.profileIds = JSON.parse(requestResult.xhr.responseText); 13263 } 13264 catch (ex) { 13265 requestResult.err = "retrieveActivityProfileIds - JSON parse error: " + ex; 13266 } 13267 } 13268 } 13269 return requestResult; 13270 }, 13271 13272 /** 13273 Save an activity profile value, when used from a browser sends to the endpoint using the RESTful interface. 13274 13275 @method saveActivityProfile 13276 @param {String} key Key of activity profile to retrieve 13277 @param val Value to be stored 13278 @param {Object} cfg Configuration options 13279 @param {TinCan.Activity} cfg.activity Activity in document identifier 13280 @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile 13281 @param {String} [cfg.contentType] Content-Type to specify in headers (defaults to 'application/octet-stream') 13282 @param {String} [cfg.method] Method to use. Default: PUT 13283 @param {Function} [cfg.callback] Callback to execute on completion 13284 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 13285 */ 13286 saveActivityProfile: function (key, val, cfg) { 13287 this.log("saveActivityProfile"); 13288 var requestCfg, 13289 requestHeaders; 13290 13291 requestHeaders = cfg.requestHeaders || {}; 13292 13293 if (typeof cfg.contentType === "undefined") { 13294 cfg.contentType = "application/octet-stream"; 13295 } 13296 requestHeaders["Content-Type"] = cfg.contentType; 13297 13298 if (typeof cfg.method === "undefined" || cfg.method !== "POST") { 13299 cfg.method = "PUT"; 13300 } 13301 13302 if (typeof val === "object" && TinCan.Utils.isApplicationJSON(cfg.contentType)) { 13303 val = JSON.stringify(val); 13304 } 13305 13306 requestCfg = { 13307 url: "activities/profile", 13308 method: cfg.method, 13309 params: { 13310 profileId: key, 13311 activityId: cfg.activity.id 13312 }, 13313 data: val, 13314 headers: requestHeaders 13315 }; 13316 13317 if (typeof cfg.callback !== "undefined") { 13318 requestCfg.callback = cfg.callback; 13319 } 13320 if (typeof cfg.lastSHA1 !== "undefined" && cfg.lastSHA1 !== null) { 13321 requestCfg.headers["If-Match"] = cfg.lastSHA1; 13322 } 13323 else { 13324 requestCfg.headers["If-None-Match"] = "*"; 13325 } 13326 13327 return this.sendRequest(requestCfg); 13328 }, 13329 13330 /** 13331 Drop an activity profile value, when used from a browser sends to the endpoint using the RESTful interface. Full activity profile 13332 delete is not supported by the spec. 13333 13334 @method dropActivityProfile 13335 @param {String|null} key Key of activity profile to delete
13336 @param {Object} cfg Configuration options 13337 @param {TinCan.Activity} cfg.activity Activity in document identifier 13338 @param {Function} [cfg.callback] Callback to execute on completion 13339 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 13340 */ 13341 dropActivityProfile: function (key, cfg) { 13342 this.log("dropActivityProfile"); 13343 var requestParams, 13344 requestCfg, 13345 requestHeaders; 13346 13347 requestHeaders = cfg.requestHeaders || {}; 13348 13349 requestParams = { 13350 profileId: key, 13351 activityId: cfg.activity.id 13352 }; 13353 13354 requestCfg = { 13355 url: "activities/profile", 13356 method: "DELETE", 13357 params: requestParams, 13358 headers: requestHeaders 13359 }; 13360 13361 if (typeof cfg.callback !== "undefined") { 13362 requestCfg.callback = cfg.callback; 13363 } 13364 13365 return this.sendRequest(requestCfg); 13366 }, 13367 13368 /** 13369 Retrieve an agent profile value, when used from a browser sends to the endpoint using the RESTful interface. 13370 13371 @method retrieveAgentProfile 13372 @param {String} key Key of agent profile to retrieve 13373 @param {Object} cfg Configuration options 13374 @param {TinCan.Agent} cfg.agent Agent in document identifier 13375 @param {Function} [cfg.callback] Callback to execute on completion 13376 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 13377 @return {Object} Value retrieved 13378 */ 13379 retrieveAgentProfile: function (key, cfg) { 13380 this.log("retrieveAgentProfile"); 13381 var requestCfg = {}, 13382 requestResult, 13383 callbackWrapper, 13384 requestHeaders, 13385 self = this; 13386 13387 requestHeaders = cfg.requestHeaders || {}; 13388 13389 requestCfg = { 13390 method: "GET", 13391 params: { 13392 profileId: key 13393 }, 13394 ignore404: true, 13395 headers: requestHeaders 13396 }; 13397 13398 if (this.version === "0.9") { 13399 requestCfg.url = "actors/profile"; 13400 requestCfg.params.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 13401 } 13402 else { 13403 requestCfg.url = "agents/profile"; 13404 requestCfg.params.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 13405 } 13406 if (typeof cfg.callback !== "undefined") { 13407 callbackWrapper = function (err, xhr) { 13408 var result = xhr; 13409 13410 if (err === null) { 13411 if (xhr.status === 404) { 13412 result = null; 13413 } 13414 else { 13415 result = new TinCan.AgentProfile( 13416 { 13417 id: key, 13418 agent: cfg.agent, 13419 contents: xhr.responseText 13420 } 13421 ); 13422 if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("ETag") !== null && xhr.getResponseHeader("ETag") !== "") { 13423 result.etag = xhr.getResponseHeader("ETag"); 13424 } 13425 else { 13426 // 13427 // either XHR didn't have getResponseHeader (probably cause it is an IE 13428 // XDomainRequest object which doesn't) or not populated by LRS so create 13429 // the hash ourselves 13430 // 13431 // the LRS is responsible for quoting the Etag value so we need to mimic 13432 // that behavior here as well 13433 // 13434 result.etag = "\"" + TinCan.Utils.getSHA1String(xhr.responseText) + "\""; 13435 } 13436 if (typeof xhr.contentType !== "undefined") { 13437 // most likely an XDomainRequest which has .contentType 13438 // for the ones that it supports 13439 result.contentType = xhr.contentType; 13440 } 13441 else if (typeof xhr.getResponseHeader !== "undefined" && xhr.getResponseHeader("Content-Type") !== null && xhr.getResponseHeader("Content-Type") !== "") { 13442 result.contentType = xhr.getResponseHeader("Content-Type"); 13443 } 13444 if (TinCan.Utils.isApplicationJSON(result.contentType)) { 13445 try { 13446 result.contents = JSON.parse(result.contents); 13447 } catch (ex) { 13448 self.log("retrieveAgentProfile - failed to deserialize JSON: " + ex); 13449 } 13450 } 13451 } 13452 } 13453 13454 cfg.callback(err, result); 13455 }; 13456 requestCfg.callback = callbackWrapper; 13457 } 13458 13459 requestResult = this.sendRequest(requestCfg); 13460 if (! callbackWrapper) { 13461 requestResult.profile = null; 13462 if (requestResult.err === null && requestResult.xhr.status !== 404) { 13463 requestResult.profile = new TinCan.AgentProfile( 13464 { 13465 id: key, 13466 agent: cfg.agent, 13467 contents: requestResult.xhr.responseText 13468 } 13469 ); 13470 if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("ETag") !== null && requestResult.xhr.getResponseHeader("ETag") !== "") { 13471 requestResult.profile.etag = requestResult.xhr.getResponseHeader("ETag"); 13472 } 13473 else { 13474 // 13475 // either XHR didn't have getResponseHeader (probably cause it is an IE 13476 // XDomainRequest object which doesn't) or not populated by LRS so create 13477 // the hash ourselves 13478 // 13479 // the LRS is responsible for quoting the Etag value so we need to mimic 13480 // that behavior here as well 13481 // 13482 requestResult.profile.etag = "\"" + TinCan.Utils.getSHA1String(requestResult.xhr.responseText) + "\""; 13483 } 13484 if (typeof requestResult.xhr.contentType !== "undefined") { 13485 // most likely an XDomainRequest which has .contentType 13486 // for the ones that it supports 13487 requestResult.profile.contentType = requestResult.xhr.contentType; 13488 } 13489 else if (typeof requestResult.xhr.getResponseHeader !== "undefined" && requestResult.xhr.getResponseHeader("Content-Type") !== null && requestResult.xhr.getResponseHeader("Content-Type") !== "") { 13490 requestResult.profile.contentType = requestResult.xhr.getResponseHeader("Content-Type"); 13491 } 13492 if (TinCan.Utils.isApplicationJSON(requestResult.profile.contentType)) { 13493 try { 13494 requestResult.profile.contents = JSON.parse(requestResult.profile.contents); 13495 } catch (ex) { 13496 this.log("retrieveAgentProfile - failed to deserialize JSON: " + ex); 13497 } 13498 } 13499 } 13500 } 13501 13502 return requestResult; 13503 }, 13504 13505 /** 13506 Retrieve the list of profileIds for an agent profile, when used from a browser sends to the endpoint using the RESTful interface. 13507 13508 @method retrieveAgentProfileIds 13509 @param {Object} cfg Configuration options 13510 @param {TinCan.Agent} cfg.agent Agent in document identifier 13511 @param {Function} [cfg.callback] Callback to execute on completion 13512 @param {String} [cfg.since] Match activity profiles saved since given timestamp 13513 @param {Object}
13513 [cfg.requestHeaders] Optional object containing additional headers to add to request 13514 @return {Array} List of profileIds for this Agent 13515 13516 */ 13517 retrieveAgentProfileIds: function (cfg) { 13518 this.log("retrieveAgentProfileIds"); 13519 var requestParams = {}, 13520 requestCfg, 13521 requestHeaders, 13522 requestResult, 13523 callbackWrapper; 13524 13525 cfg = cfg || {}; 13526 requestHeaders = cfg.requestHeaders || {}; 13527 13528 requestCfg = { 13529 method: "GET", 13530 params: requestParams, 13531 headers: requestHeaders, 13532 ignore404: true 13533 }; 13534 13535 if (this.version === "0.9") { 13536 requestCfg.url = "actors/profile"; 13537 requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 13538 } 13539 else { 13540 requestCfg.url = "agents/profile"; 13541 requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 13542 } 13543 if (typeof cfg.callback !== "undefined") { 13544 callbackWrapper = function (err, xhr) { 13545 var result = xhr; 13546 13547 if (err !== null) { 13548 cfg.callback(err, result); 13549 return; 13550 } 13551 13552 if (xhr.status === 404) { 13553 result = []; 13554 } 13555 else { 13556 try { 13557 result = JSON.parse(xhr.responseText); 13558 } 13559 catch (ex) { 13560 err = "Response JSON parse error: " + ex; 13561 } 13562 } 13563 13564 cfg.callback(err, result); 13565 }; 13566 requestCfg.callback = callbackWrapper; 13567 } 13568 if (typeof cfg.since !== "undefined") { 13569 requestCfg.params.since = cfg.since; 13570 } 13571 13572 requestResult = this.sendRequest(requestCfg); 13573 if (! callbackWrapper) { 13574 requestResult.profileIds = null; 13575 if (requestResult.err !== null) { 13576 return requestResult; 13577 } 13578 13579 if (requestResult.xhr.status === 404) { 13580 requestResult.profileIds = []; 13581 } 13582 else { 13583 try { 13584 requestResult.profileIds = JSON.parse(requestResult.xhr.responseText); 13585 } 13586 catch (ex) { 13587 requestResult.err = "retrieveAgentProfileIds - JSON parse error: " + ex; 13588 } 13589 } 13590 } 13591 return requestResult; 13592 }, 13593 13594 /** 13595 Save an agent profile value, when used from a browser sends to the endpoint using the RESTful interface. 13596 13597 @method saveAgentProfile 13598 @param {String} key Key of agent profile to retrieve 13599 @param val Value to be stored 13600 @param {Object} cfg Configuration options 13601 @param {TinCan.Agent} cfg.agent Agent in document identifier 13602 @param {String} [cfg.lastSHA1] SHA1 of the previously seen existing profile 13603 @param {String} [cfg.contentType] Content-Type to specify in headers (defaults to 'application/octet-stream') 13604 @param {String} [cfg.method] Method to use. Default: PUT 13605 @param {Function} [cfg.callback] Callback to execute on completion 13606 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 13607 */ 13608 saveAgentProfile: function (key, val, cfg) { 13609 this.log("saveAgentProfile"); 13610 var requestCfg, 13611 requestHeaders; 13612 13613 requestHeaders = cfg.requestHeaders || {}; 13614 13615 if (typeof cfg.contentType === "undefined") { 13616 cfg.contentType = "application/octet-stream"; 13617 } 13618 requestHeaders["Content-Type"] = cfg.contentType; 13619 13620 if (typeof cfg.method === "undefined" || cfg.method !== "POST") { 13621 cfg.method = "PUT"; 13622 } 13623 13624 if (typeof val === "object" && TinCan.Utils.isApplicationJSON(cfg.contentType)) { 13625 val = JSON.stringify(val); 13626 } 13627 13628 requestCfg = { 13629 method: cfg.method, 13630 params: { 13631 profileId: key 13632 }, 13633 data: val, 13634 headers: requestHeaders 13635 }; 13636 13637 if (this.version === "0.9") { 13638 requestCfg.url = "actors/profile"; 13639 requestCfg.params.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 13640 } 13641 else { 13642 requestCfg.url = "agents/profile"; 13643 requestCfg.params.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 13644 } 13645 if (typeof cfg.callback !== "undefined") { 13646 requestCfg.callback = cfg.callback; 13647 } 13648 if (typeof cfg.lastSHA1 !== "undefined" && cfg.lastSHA1 !== null) { 13649 requestCfg.headers["If-Match"] = cfg.lastSHA1; 13650 } 13651 else { 13652 requestCfg.headers["If-None-Match"] = "*"; 13653 } 13654 13655 return this.sendRequest(requestCfg); 13656 }, 13657 13658 /** 13659 Drop an agent profile value, when used from a browser sends to the endpoint using the RESTful interface. Full agent profile 13660 delete is not supported by the spec. 13661 13662 @method dropAgentProfile 13663 @param {String|null} key Key of agent profile to delete
13664 @param {Object} cfg Configuration options 13665 @param {TinCan.Agent} cfg.agent Agent in document identifier 13666 @param {Function} [cfg.callback] Callback to execute on completion 13667 @param {Object} [cfg.requestHeaders] Optional object containing additional headers to add to request 13668 */ 13669 dropAgentProfile: function (key, cfg) { 13670 this.log("dropAgentProfile"); 13671 var requestParams, 13672 requestCfg, 13673 requestHeaders; 13674 13675 requestHeaders = cfg.requestHeaders || {}; 13676 13677 requestParams = { 13678 profileId: key 13679 }; 13680 requestCfg = { 13681 method: "DELETE", 13682 params: requestParams, 13683 headers: requestHeaders 13684 }; 13685 13686 if (this.version === "0.9") { 13687 requestCfg.url = "actors/profile"; 13688 requestParams.actor = JSON.stringify(cfg.agent.asVersion(this.version)); 13689 } 13690 else { 13691 requestCfg.url = "agents/profile"; 13692 requestParams.agent = JSON.stringify(cfg.agent.asVersion(this.version)); 13693 } 13694 if (typeof cfg.callback !== "undefined") { 13695 requestCfg.callback = cfg.callback; 13696 } 13697 13698 return this.sendRequest(requestCfg); 13699 } 13700 }; 13701 13702 /** 13703 Allows client code to determine whether their environment supports synchronous xhr handling 13704 @static this is a static property, set by the environment 13705 */ 13706 LRS.syncEnabled = null; 13707}()); 13708 13709/* 13710 Copyright 2012 Rustici Software 13711 13712 Licensed under the Apache License, Version 2.0 (the "License"); 13713 you may not use this file except in compliance with the License. 13714 You may obtain a copy of the License at 13715 13716 http://www.apache.org/licenses/LICENSE-2.0 13717 13718 Unless required by applicable law or agreed to in writing, software 13719 distributed under the License is distributed on an "AS IS" BASIS, 13720 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13721 See the License for the specific language governing permissions and 13722 limitations under the License. 13723*/ 13724 13725/** 13726TinCan client library 13727 13728@module TinCan 13729@submodule TinCan.AgentAccount 13730**/ 13731(function () { 13732 "use strict"; 13733 13734 /** 13735 @class TinCan.AgentAccount 13736 @constructor 13737 */ 13738 var AgentAccount = TinCan.AgentAccount = function (cfg) { 13739 this.log("constructor"); 13740 13741 /** 13742 @property homePage 13743 @type String 13744 */ 13745 this.homePage = null; 13746 13747 /** 13748 @property name 13749 @type String 13750 */ 13751 this.name = null; 13752 13753 this.init(cfg); 13754 }; 13755 AgentAccount.prototype = { 13756 /** 13757 @property LOG_SRC 13758 */ 13759 LOG_SRC: "AgentAccount", 13760 13761 /** 13762 @method log 13763 */ 13764 log: TinCan.prototype.log, 13765 13766 /** 13767 @method init 13768 @param {Object} [options] Configuration used to initialize 13769 */ 13770 init: function (cfg) { 13771 this.log("init"); 13772 var i, 13773 directProps = [ 13774 "name", 13775 "homePage" 13776 ]; 13777 13778 cfg = cfg || {}; 13779 13780 // handle .9 name changes 13781 if (typeof cfg.accountServiceHomePage !== "undefined") { 13782 cfg.homePage = cfg.accountServiceHomePage; 13783 } 13784 if (typeof cfg.accountName !== "undefined") { 13785 cfg.name = cfg.accountName; 13786 } 13787 13788 for (i = 0; i < directProps.length; i += 1) { 13789 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 13790 this[directProps[i]] = cfg[directProps[i]]; 13791 } 13792 } 13793 }, 13794 13795 toString: function () { 13796 this.log("toString"); 13797 var result = ""; 13798 13799 if (this.name !== null || this.homePage !== null) { 13800 result += this.name !== null ? this.name : "-"; 13801 result += ":"; 13802 result += this.homePage !== null ? this.homePage : "-"; 13803 } 13804 else { 13805 result = "AgentAccount: unidentified"; 13806 } 13807 13808 return result; 13809 }, 13810 13811 /** 13812 @method asVersion 13813 @param {String}
13813 [version] Version to return (defaults to newest supported) 13814 */ 13815 asVersion: function (version) { 13816 this.log("asVersion: " + version); 13817 var result = {}; 13818 13819 version = version || TinCan.versions()[0]; 13820 13821 if (version === "0.9") { 13822 result.accountName = this.name; 13823 result.accountServiceHomePage = this.homePage; 13824 } else { 13825 result.name = this.name; 13826 result.homePage = this.homePage; 13827 } 13828 13829 return result; 13830 } 13831 }; 13832 13833 /** 13834 @method fromJSON 13835 @return {Object} AgentAccount 13836 @static 13837 */ 13838 AgentAccount.fromJSON = function (acctJSON) { 13839 AgentAccount.prototype.log("fromJSON"); 13840 var _acct = JSON.parse(acctJSON); 13841 13842 return new AgentAccount(_acct); 13843 }; 13844}()); 13845 13846/* 13847 Copyright 2012 Rustici Software 13848 13849 Licensed under the Apache License, Version 2.0 (the "License"); 13850 you may not use this file except in compliance with the License. 13851 You may obtain a copy of the License at 13852 13853 http://www.apache.org/licenses/LICENSE-2.0 13854 13855 Unless required by applicable law or agreed to in writing, software 13856 distributed under the License is distributed on an "AS IS" BASIS, 13857 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13858 See the License for the specific language governing permissions and 13859 limitations under the License. 13860*/ 13861 13862/** 13863TinCan client library 13864 13865@module TinCan 13866@submodule TinCan.Agent 13867**/ 13868(function () { 13869 "use strict"; 13870 13871 /** 13872 @class TinCan.Agent 13873 @constructor 13874 */ 13875 var Agent = TinCan.Agent = function (cfg) { 13876 this.log("constructor"); 13877 13878 /** 13879 @property name 13880 @type String 13881 */ 13882 this.name = null; 13883 13884 /** 13885 @property mbox 13886 @type String 13887 */ 13888 this.mbox = null; 13889 13890 /** 13891 @property mbox_sha1sum 13892 @type String 13893 */ 13894 this.mbox_sha1sum = null; 13895 13896 /** 13897 @property openid 13898 @type String 13899 */ 13900 this.openid = null; 13901 13902 /** 13903 @property account 13904 @type TinCan.AgentAccount 13905 */ 13906 this.account = null; 13907 13908 /** 13909 @property degraded 13910 @type Boolean 13911 @default false 13912 */ 13913 this.degraded = false; 13914 13915 this.init(cfg); 13916 }; 13917 Agent.prototype = { 13918 /** 13919 @property objectType 13920 @type String 13921 @default Agent 13922 */ 13923 objectType: "Agent", 13924 13925 /** 13926 @property LOG_SRC 13927 */ 13928 LOG_SRC: "Agent", 13929 13930 /** 13931 @method log 13932 */ 13933 log: TinCan.prototype.log, 13934 13935 /** 13936 @method init 13937 @param {Object} [options] Configuration used to initialize 13938 */ 13939 init: function (cfg) { 13940 this.log("init"); 13941 var i, 13942 directProps = [ 13943 "name", 13944 "mbox", 13945 "mbox_sha1sum", 13946 "openid" 13947 ], 13948 val 13949 ; 13950 13951 cfg = cfg || {}; 13952 13953 // handle .9 split names and array properties into single interface 13954 if (typeof cfg.lastName !== "undefined" || typeof cfg.firstName !== "undefined") { 13955 cfg.name = ""; 13956 if (typeof cfg.firstName !== "undefined" && cfg.firstName.length > 0) { 13957 cfg.name = cfg.firstName[0]; 13958 if (cfg.firstName.length > 1) { 13959 this.degraded = true; 13960 } 13961 } 13962 13963 if (cfg.name !== "") { 13964 cfg.name += " "; 13965 } 13966 13967 if (typeof cfg.lastName !== "undefined" && cfg.lastName.length > 0) { 13968 cfg.name += cfg.lastName[0]; 13969 if (cfg.lastName.length > 1) { 13970 this.degraded = true; 13971 } 13972 } 13973 } else if (typeof cfg.familyName !== "undefined" || typeof cfg.givenName !== "undefined") { 13974 cfg.name = ""; 13975 if (typeof cfg.givenName !== "undefined" && cfg.givenName.length > 0) { 13976 cfg.name = cfg.givenName[0]; 13977 if (cfg.givenName.length > 1) { 13978 this.degraded = true; 13979 } 13980 } 13981 13982 if (cfg.name !== "") { 13983 cfg.name += " ";
13984 } 13985 13986 if (typeof cfg.familyName !== "undefined" && cfg.familyName.length > 0) { 13987 cfg.name += cfg.familyName[0]; 13988 if (cfg.familyName.length > 1) { 13989 this.degraded = true; 13990 } 13991 } 13992 } 13993 13994 if (typeof cfg.name === "object" && cfg.name !== null) { 13995 if (cfg.name.length > 1) { 13996 this.degraded = true; 13997 } 13998 cfg.name = cfg.name[0]; 13999 } 14000 if (typeof cfg.mbox === "object" && cfg.mbox !== null) { 14001 if (cfg.mbox.length > 1) { 14002 this.degraded = true; 14003 } 14004 cfg.mbox = cfg.mbox[0]; 14005 } 14006 if (typeof cfg.mbox_sha1sum === "object" && cfg.mbox_sha1sum !== null) { 14007 if (cfg.mbox_sha1sum.length > 1) { 14008 this.degraded = true; 14009 } 14010 cfg.mbox_sha1sum = cfg.mbox_sha1sum[0]; 14011 } 14012 if (typeof cfg.openid === "object" && cfg.openid !== null) { 14013 if (cfg.openid.length > 1) { 14014 this.degraded = true; 14015 } 14016 cfg.openid = cfg.openid[0]; 14017 } 14018 if (typeof cfg.account === "object" && cfg.account !== null && typeof cfg.account.homePage === "undefined" && typeof cfg.account.name === "undefined") { 14019 if (cfg.account.length === 0) { 14020 delete cfg.account; 14021 } 14022 else { 14023 if (cfg.account.length > 1) { 14024 this.degraded = true; 14025 } 14026 cfg.account = cfg.account[0]; 14027 } 14028 } 14029 14030 if (cfg.hasOwnProperty("account")) { 14031 if (cfg.account instanceof TinCan.AgentAccount) { 14032 this.account = cfg.account; 14033 } 14034 else { 14035 this.account = new TinCan.AgentAccount (cfg.account); 14036 } 14037 } 14038 14039 for (i = 0; i < directProps.length; i += 1) { 14040 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 14041 val = cfg[directProps[i]]; 14042 if (directProps[i] === "mbox" && val.indexOf("mailto:") === -1) { 14043 val = "mailto:" + val; 14044 } 14045 this[directProps[i]] = val; 14046 } 14047 } 14048 }, 14049 14050 toString: function () { 14051 this.log("toString"); 14052 14053 if (this.name !== null) { 14054 return this.name; 14055 } 14056 if (this.mbox !== null) { 14057 return this.mbox.replace("mailto:", ""); 14058 } 14059 if (this.mbox_sha1sum !== null) { 14060 return this.mbox_sha1sum; 14061 } 14062 if (this.openid !== null) { 14063 return this.openid; 14064 } 14065 if (this.account !== null) { 14066 return this.account.toString(); 14067 } 14068 14069 return this.objectType + ": unidentified"; 14070 }, 14071 14072 /** 14073 While a TinCan.Agent instance can store more than one inverse functional identifier 14074 this method will always only output one to be compliant with the statement sending 14075 specification. Order of preference is: mbox, mbox_sha1sum, openid, account 14076 14077 @method asVersion 14078 @param {String} [version] Version to return (defaults to newest supported) 14079 */ 14080 asVersion: function (version) { 14081 this.log("asVersion: " + version); 14082 var result = { 14083 objectType: this.objectType 14084 }; 14085 14086 version = version || TinCan.versions()[0]; 14087 14088 if (version === "0.9") { 14089 if (this.mbox !== null) { 14090 result.mbox = [ this.mbox ]; 14091 } 14092 else if (this.mbox_sha1sum !== null) { 14093 result.mbox_sha1sum = [ this.mbox_sha1sum ]; 14094 } 14095 else if (this.openid !== null) { 14096 result.openid = [ this.openid ]; 14097 } 14098 else if (this.account !== null) { 14099 result.account = [ this.account.asVersion(version) ]; 14100 } 14101 14102 if (this.name !== null) { 14103 result.name = [ this.name ]; 14104 } 14105 } else { 14106 if (this.mbox !== null) { 14107 result.mbox = this.mbox; 14108 } 14109 else if (this.mbox_sha1sum !== null) { 14110 result.mbox_sha1sum = this.mbox_sha1sum; 14111 } 14112 else if (this.openid !== null) { 14113 result.openid = this.openid; 14114 } 14115 else if (this.account !== null) { 14116 result.account = this.account.asVersion(version); 14117 } 14118 14119 if (this.name !== null) { 14120 result.name = this.name; 14121 } 14122 } 14123 14124 return result; 14125 } 14126 }; 14127 14128 /** 14129 @method fromJSON 14130 @return {Object} Agent 14131 @static 14132 */ 14133 Agent.fromJSON = function (agentJSON) { 14134 Agent.prototype.log("fromJSON"); 14135 var _agent = JSON.parse(agentJSON); 14136 14137 return new Agent(_agent); 14138 }; 14139}()); 14140 14141/* 14142 Copyright 2012 Rustici Software 14143 14144 Licensed under the Apache License, Version 2.0 (the "License");
14145 you may not use this file except in compliance with the License. 14146 You may obtain a copy of the License at 14147 14148 http://www.apache.org/licenses/LICENSE-2.0 14149 14150 Unless required by applicable law or agreed to in writing, software 14151 distributed under the License is distributed on an "AS IS" BASIS, 14152 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14153 See the License for the specific language governing permissions and 14154 limitations under the License. 14155*/ 14156 14157/** 14158TinCan client library 14159 14160@module TinCan 14161@submodule TinCan.Group 14162**/ 14163(function () { 14164 "use strict"; 14165 14166 /** 14167 @class TinCan.Group 14168 @constructor 14169 */ 14170 var Group = TinCan.Group = function (cfg) { 14171 this.log("constructor"); 14172 14173 /** 14174 @property name 14175 @type String 14176 */ 14177 this.name = null; 14178 14179 /** 14180 @property mbox 14181 @type String 14182 */ 14183 this.mbox = null; 14184 14185 /** 14186 @property mbox_sha1sum 14187 @type String 14188 */ 14189 this.mbox_sha1sum = null; 14190 14191 /** 14192 @property openid 14193 @type String 14194 */ 14195 this.openid = null; 14196 14197 /** 14198 @property account 14199 @type TinCan.AgentAccount 14200 */ 14201 this.account = null; 14202 14203 /** 14204 @property member 14205 @type Array 14206 */ 14207 this.member = []; 14208 14209 this.init(cfg); 14210 }; 14211 Group.prototype = { 14212 /** 14213 @property objectType 14214 @type String 14215 @default "Group" 14216 @static 14217 */ 14218 objectType: "Group", 14219 14220 /** 14221 @property LOG_SRC 14222 */ 14223 LOG_SRC: "Group", 14224 14225 /** 14226 @method log 14227 */ 14228 log: TinCan.prototype.log, 14229 14230 /** 14231 @method init 14232 @param {Object} [options] Configuration used to initialize 14233 */ 14234 init: function (cfg) { 14235 this.log("init"); 14236 var i; 14237 14238 cfg = cfg || {}; 14239 14240 TinCan.Agent.prototype.init.call(this, cfg); 14241 14242 if (typeof cfg.member !== "undefined") { 14243 for (i = 0; i < cfg.member.length; i += 1) { 14244 if (cfg.member[i] instanceof TinCan.Agent) { 14245 this.member.push(cfg.member[i]); 14246 } 14247 else { 14248 this.member.push(new TinCan.Agent (cfg.member[i])); 14249 } 14250 } 14251 } 14252 }, 14253 14254 toString: function (lang) { 14255 this.log("toString"); 14256 14257 var result = TinCan.Agent.prototype.toString.call(this, lang); 14258 if (result !== this.objectType + ": unidentified") { 14259 result = this.objectType + ": " + result; 14260 } 14261 14262 return result; 14263 }, 14264 14265 /** 14266 @method asVersion 14267 @param {String} [version] Version to return (defaults to newest supported) 14268 */ 14269 asVersion: function (version) { 14270 this.log("asVersion: " + version); 14271 var result, 14272 i 14273 ; 14274 14275 version = version || TinCan.versions()[0]; 14276 14277 result = TinCan.Agent.prototype.asVersion.call(this, version); 14278 14279 if (this.member.length > 0) { 14280 result.member = []; 14281 for (i = 0; i < this.member.length; i += 1) { 14282 result.member.push(this.member[i].asVersion(version)); 14283 } 14284 } 14285 14286 return result; 14287 } 14288 }; 14289 14290 /** 14291 @method fromJSON 14292 @return {Object} Group 14293 @static 14294 */ 14295 Group.fromJSON = function (groupJSON) { 14296 Group.prototype.log("fromJSON"); 14297 var _group = JSON.parse(groupJSON); 14298 14299 return new Group(_group); 14300 }; 14301}()); 14302 14303/* 14304 Copyright 2012 Rustici Software 14305 14306 Licensed under the Apache License, Version 2.0 (the "License"); 14307 you may not use this file except in compliance with the License. 14308 You may obtain a copy of the License at 14309 14310 http://www.apache.org/licenses/LICENSE-2.0 14311 14312 Unless required by applicable law or agreed to in writing, software 14313 distributed under the License is distributed on an "AS IS" BASIS, 14314 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14315 See the License for the specific language governing permissions and 14316 limitations under the License. 14317*/ 14318 14319/** 14320TinCan client library 14321 14322@module TinCan 14323@submodule TinCan.Verb 14324*/ 14325(function () { 14326 "use strict"; 14327 14328 // 14329 // this represents the full set of verb values that were 14330 // allowed by the .9 spec version, if an object is created with one of 14331 // the short forms it will be upconverted to the matching long form, 14332 // for local storage and use and if an object is needed in .9 version 14333 // consequently down converted 14334 // 14335 // hopefully this list will never grow (or change) and only the exact 14336 // ADL compatible URLs should be matched 14337 // 14338 var _downConvertMap = { 14339 "http://adlnet.gov/expapi/verbs/experienced": "experienced", 14340 "http://adlnet.gov/expapi/verbs/attended": "attended", 14341 "http://adlnet.gov/expapi/verbs/attempted": "attempted", 14342 "http://adlnet.gov/expapi/verbs/completed": "completed", 14343 "http://adlnet.gov/expapi/verbs/passed": "passed", 14344 "http://adlnet.gov/expapi/verbs/failed": "failed", 14345 "http://adlnet.gov/expapi/verbs/answered": "answered", 14346 "http://adlnet.gov/expapi/verbs/interacted": "interacted", 14347 "http://adlnet.gov/expapi/verbs/imported": "imported", 14348 "http://adlnet.gov/expapi/verbs/created": "created", 14349 "http://adlnet.gov/expapi/verbs/shared": "shared", 14350 "http://adlnet.gov/expapi/verbs/voided": "voided" 14351 }, 14352 14353 /** 14354 @class TinCan.Verb 14355 @constructor 14356 */ 14357 Verb = TinCan.Verb = function (cfg) { 14358 this.log("constructor"); 14359 14360 /** 14361 @property id
14362 @type String 14363 */ 14364 this.id = null; 14365 14366 /** 14367 @property display 14368 @type Object 14369 */ 14370 this.display = null; 14371 14372 this.init(cfg); 14373 }; 14374 Verb.prototype = { 14375 /** 14376 @property LOG_SRC 14377 */ 14378 LOG_SRC: "Verb", 14379 14380 /** 14381 @method log 14382 */ 14383 log: TinCan.prototype.log, 14384 14385 /** 14386 @method init 14387 @param {Object} [options] Configuration used to initialize 14388 */ 14389 init: function (cfg) { 14390 this.log("init"); 14391 var i, 14392 directProps = [ 14393 "id", 14394 "display" 14395 ], 14396 prop 14397 ; 14398 14399 if (typeof cfg === "string") { 14400 this.id = cfg; 14401 this.display = { 14402 und: this.id 14403 }; 14404 14405 //If simple string like "attempted" was passed in (0.9 verbs), 14406 //upconvert the ID to the 0.95 ADL version 14407 for (prop in _downConvertMap) { 14408 if (_downConvertMap.hasOwnProperty(prop) && _downConvertMap[prop] === cfg) { 14409 this.id = prop; 14410 break; 14411 } 14412 } 14413 } 14414 else { 14415 cfg = cfg || {}; 14416 14417 for (i = 0; i < directProps.length; i += 1) { 14418 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 14419 this[directProps[i]] = cfg[directProps[i]]; 14420 } 14421 } 14422 14423 if (this.display === null && typeof _downConvertMap[this.id] !== "undefined") { 14424 this.display = { 14425 "und": _downConvertMap[this.id] 14426 }; 14427 } 14428 } 14429 }, 14430 14431 /** 14432 @method toString 14433 @return {String} String representation of the verb 14434 */ 14435 toString: function (lang) { 14436 this.log("toString"); 14437 14438 if (this.display !== null) { 14439 return this.getLangDictionaryValue("display", lang); 14440 } 14441 14442 return this.id; 14443 }, 14444 14445 /** 14446 @method asVersion 14447 @param {String} [version] Version to return (defaults to newest supported) 14448 */ 14449 asVersion: function (version) { 14450 this.log("asVersion"); 14451 var result; 14452 14453 version = version || TinCan.versions()[0]; 14454 14455 if (version === "0.9") { 14456 result = _downConvertMap[this.id]; 14457 } 14458 else { 14459 result = { 14460 id: this.id 14461 }; 14462 if (this.display !== null) { 14463 result.display = this.display; 14464 } 14465 } 14466 14467 return result; 14468 }, 14469 14470 /** 14471 See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}} 14472 14473 @method getLangDictionaryValue 14474 */ 14475 getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue 14476 }; 14477 14478 /** 14479 @method fromJSON 14480 @param {String} verbJSON String of JSON representing the verb 14481 @return {Object} Verb 14482 @static 14483 */ 14484 Verb.fromJSON = function (verbJSON) { 14485 Verb.prototype.log("fromJSON"); 14486 var _verb = JSON.parse(verbJSON); 14487 14488 return new Verb(_verb); 14489 }; 14490}()); 14491 14492/* 14493 Copyright 2012 Rustici Software 14494 14495 Licensed under the Apache License, Version 2.0 (the "License"); 14496 you may not use this file except in compliance with the License. 14497 You may obtain a copy of the License at 14498 14499 http://www.apache.org/licenses/LICENSE-2.0 14500 14501 Unless required by applicable law or agreed to in writing, software 14502 distributed under the License is distributed on an "AS IS" BASIS, 14503 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14504 See the License for the specific language governing permissions and 14505 limitations under the License. 14506*/ 14507 14508/** 14509TinCan client library 14510 14511@module TinCan 14512@submodule TinCan.Result 14513**/ 14514(function () { 14515 "use strict"; 14516 14517 /** 14518 @class TinCan.Result 14519 @constructor 14520 */ 14521 var Result = TinCan.Result = function (cfg) { 14522 this.log("constructor"); 14523 14524 /** 14525 @property score 14526 @type TinCan.Score|null 14527 */ 14528 this.score = null; 14529 14530 /** 14531 @property success 14532 @type Boolean|null 14533 */ 14534 this.success = null; 14535 14536 /** 14537 @property completion 14538 @type Boolean|null 14539 */ 14540 this.completion = null; 14541 14542 /** 14543 @property duration
14544 @type String|null 14545 */ 14546 this.duration = null; 14547 14548 /** 14549 @property response 14550 @type String|null 14551 */ 14552 this.response = null; 14553 14554 /** 14555 @property extensions 14556 @type Object|null 14557 */ 14558 this.extensions = null; 14559 14560 this.init(cfg); 14561 }; 14562 Result.prototype = { 14563 /** 14564 @property LOG_SRC 14565 */ 14566 LOG_SRC: "Result", 14567 14568 /** 14569 @method log 14570 */ 14571 log: TinCan.prototype.log, 14572 14573 /** 14574 @method init 14575 @param {Object} [options] Configuration used to initialize 14576 */ 14577 init: function (cfg) { 14578 this.log("init"); 14579 14580 var i, 14581 directProps = [ 14582 "completion", 14583 "duration", 14584 "extensions", 14585 "response", 14586 "success" 14587 ] 14588 ; 14589 14590 cfg = cfg || {}; 14591 14592 if (cfg.hasOwnProperty("score") && cfg.score !== null) { 14593 if (cfg.score instanceof TinCan.Score) { 14594 this.score = cfg.score; 14595 } 14596 else { 14597 this.score = new TinCan.Score (cfg.score); 14598 } 14599 } 14600 14601 for (i = 0; i < directProps.length; i += 1) { 14602 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 14603 this[directProps[i]] = cfg[directProps[i]]; 14604 } 14605 } 14606 14607 // 0.9 used a string, store it internally as a bool 14608 if (this.completion === "Completed") { 14609 this.completion = true; 14610 } 14611 }, 14612 14613 /** 14614 @method asVersion 14615 @param {String} [version] Version to return (defaults to newest supported) 14616 */ 14617 asVersion: function (version) { 14618 this.log("asVersion"); 14619 var result = {}, 14620 optionalDirectProps = [ 14621 "success", 14622 "duration", 14623 "response", 14624 "extensions" 14625 ], 14626 optionalObjProps = [ 14627 "score" 14628 ], 14629 i; 14630 14631 version = version || TinCan.versions()[0]; 14632 14633 for (i = 0; i < optionalDirectProps.length; i += 1) { 14634 if (this[optionalDirectProps[i]] !== null) { 14635 result[optionalDirectProps[i]] = this[optionalDirectProps[i]]; 14636 } 14637 } 14638 for (i = 0; i < optionalObjProps.length; i += 1) { 14639 if (this[optionalObjProps[i]] !== null) { 14640 result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version); 14641 } 14642 } 14643 if (this.completion !== null) { 14644 if (version === "0.9") { 14645 if (this.completion) { 14646 result.completion = "Completed"; 14647 } 14648 } 14649 else { 14650 result.completion = this.completion; 14651 } 14652 } 14653 14654 return result; 14655 } 14656 }; 14657 14658 /** 14659 @method fromJSON 14660 @return {Object} Result 14661 @static 14662 */ 14663 Result.fromJSON = function (resultJSON) { 14664 Result.prototype.log("fromJSON"); 14665 var _result = JSON.parse(resultJSON); 14666 14667 return new Result(_result); 14668 }; 14669}()); 14670 14671/* 14672 Copyright 2012 Rustici Software 14673 14674 Licensed under the Apache License, Version 2.0 (the "License"); 14675 you may not use this file except in compliance with the License. 14676 You may obtain a copy of the License at 14677 14678 http://www.apache.org/licenses/LICENSE-2.0 14679 14680 Unless required by applicable law or agreed to in writing, software 14681 distributed under the License is distributed on an "AS IS" BASIS, 14682 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14683 See the License for the specific language governing permissions and 14684 limitations under the License. 14685*/ 14686 14687/** 14688TinCan client library 14689 14690@module TinCan 14691@submodule TinCan.Score 14692**/ 14693(function () { 14694 "use strict"; 14695 14696 /** 14697 @class TinCan.Score 14698 @constructor 14699 */ 14700 var Score = TinCan.Score = function (cfg) { 14701 this.log("constructor"); 14702 14703 /** 14704 @property scaled
14705 @type String 14706 */ 14707 this.scaled = null; 14708 14709 /** 14710 @property raw 14711 @type String 14712 */ 14713 this.raw = null; 14714 14715 /** 14716 @property min 14717 @type String 14718 */ 14719 this.min = null; 14720 14721 /** 14722 @property max 14723 @type String 14724 */ 14725 this.max = null; 14726 14727 this.init(cfg); 14728 }; 14729 Score.prototype = { 14730 /** 14731 @property LOG_SRC 14732 */ 14733 LOG_SRC: "Score", 14734 14735 /** 14736 @method log 14737 */ 14738 log: TinCan.prototype.log, 14739 14740 /** 14741 @method init 14742 @param {Object} [options] Configuration used to initialize 14743 */ 14744 init: function (cfg) { 14745 this.log("init"); 14746 14747 var i, 14748 directProps = [ 14749 "scaled", 14750 "raw", 14751 "min", 14752 "max" 14753 ] 14754 ; 14755 14756 cfg = cfg || {}; 14757 14758 for (i = 0; i < directProps.length; i += 1) { 14759 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 14760 this[directProps[i]] = cfg[directProps[i]]; 14761 } 14762 } 14763 }, 14764 14765 /** 14766 @method asVersion 14767 @param {String} [version] Version to return (defaults to newest supported) 14768 */ 14769 asVersion: function (version) { 14770 this.log("asVersion"); 14771 var result = {}, 14772 optionalDirectProps = [ 14773 "scaled", 14774 "raw", 14775 "min", 14776 "max" 14777 ], 14778 i; 14779 14780 version = version || TinCan.versions()[0]; 14781 14782 for (i = 0; i < optionalDirectProps.length; i += 1) { 14783 if (this[optionalDirectProps[i]] !== null) { 14784 result[optionalDirectProps[i]] = this[optionalDirectProps[i]]; 14785 } 14786 } 14787 14788 return result; 14789 } 14790 }; 14791 14792 /** 14793 @method fromJSON 14794 @return {Object} Score 14795 @static 14796 */ 14797 Score.fromJSON = function (scoreJSON) { 14798 Score.prototype.log("fromJSON"); 14799 var _score = JSON.parse(scoreJSON); 14800 14801 return new Score(_score); 14802 }; 14803}()); 14804 14805/* 14806 Copyright 2012 Rustici Software 14807 14808 Licensed under the Apache License, Version 2.0 (the "License"); 14809 you may not use this file except in compliance with the License. 14810 You may obtain a copy of the License at 14811 14812 http://www.apache.org/licenses/LICENSE-2.0 14813 14814 Unless required by applicable law or agreed to in writing, software 14815 distributed under the License is distributed on an "AS IS" BASIS, 14816 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14817 See the License for the specific language governing permissions and 14818 limitations under the License. 14819*/ 14820 14821/** 14822TinCan client library 14823 14824@module TinCan 14825@submodule TinCan.InteractionComponent 14826**/ 14827(function () { 14828 "use strict"; 14829 14830 /** 14831 @class TinCan.InteractionComponent 14832 @constructor 14833 */ 14834 var InteractionComponent = TinCan.InteractionComponent = function (cfg) { 14835 this.log("constructor"); 14836 14837 /** 14838 @property id 14839 @type String 14840 */ 14841 this.id = null; 14842 14843 /** 14844 @property description 14845 @type Object 14846 */ 14847 this.description = null; 14848 14849 this.init(cfg); 14850 }; 14851 InteractionComponent.prototype = { 14852 /** 14853 @property LOG_SRC 14854 */ 14855 LOG_SRC: "InteractionComponent", 14856 14857 /** 14858 @method log 14859 */ 14860 log: TinCan.prototype.log, 14861 14862 /** 14863 @method init 14864 @param {Object} [options] Configuration used to initialize 14865 */ 14866 init: function (cfg) { 14867 this.log("init"); 14868 var i, 14869 directProps = [ 14870 "id", 14871 "description" 14872 ] 14873 ; 14874 14875 cfg = cfg || {}; 14876 14877 for (i = 0; i < directProps.length; i += 1) { 14878 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 14879 this[directProps[i]] = cfg[directProps[i]]; 14880 } 14881 } 14882 }, 14883 14884 /** 14885 @method asVersion 14886 @param {String}
14886 [version] Version to return (defaults to newest supported) 14887 */ 14888 asVersion: function (version) { 14889 this.log("asVersion"); 14890 var result = { 14891 id: this.id 14892 }, 14893 optionalDirectProps = [ 14894 "description" 14895 ], 14896 i, 14897 prop; 14898 14899 version = version || TinCan.versions()[0]; 14900 14901 for (i = 0; i < optionalDirectProps.length; i += 1) { 14902 prop = optionalDirectProps[i]; 14903 if (this[prop] !== null) { 14904 result[prop] = this[prop]; 14905 } 14906 } 14907 14908 return result; 14909 }, 14910 14911 /** 14912 See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}} 14913 14914 @method getLangDictionaryValue 14915 */ 14916 getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue 14917 }; 14918 14919 /** 14920 @method fromJSON 14921 @return {Object} InteractionComponent 14922 @static 14923 */ 14924 InteractionComponent.fromJSON = function (icJSON) { 14925 InteractionComponent.prototype.log("fromJSON"); 14926 var _ic = JSON.parse(icJSON); 14927 14928 return new InteractionComponent(_ic); 14929 }; 14930}()); 14931 14932/* 14933 Copyright 2012 Rustici Software 14934 14935 Licensed under the Apache License, Version 2.0 (the "License"); 14936 you may not use this file except in compliance with the License. 14937 You may obtain a copy of the License at 14938 14939 http://www.apache.org/licenses/LICENSE-2.0 14940 14941 Unless required by applicable law or agreed to in writing, software 14942 distributed under the License is distributed on an "AS IS" BASIS, 14943 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14944 See the License for the specific language governing permissions and 14945 limitations under the License. 14946*/ 14947 14948/** 14949TinCan client library 14950 14951@module TinCan 14952@submodule TinCan.ActivityDefinition 14953**/ 14954(function () { 14955 "use strict"; 14956 14957 // 14958 // this represents the full set of activity definition types that were 14959 // allowed by the .9 spec version, if an object is created with one of 14960 // the short forms it will be upconverted to the matching long form, 14961 // for local storage and use and if an object is needed in .9 version 14962 // consequently down converted 14963 // 14964 // hopefully this list will never grow (or change) and only the exact 14965 // ADL compatible URLs should be matched 14966 // 14967 var _downConvertMap = { 14968 "http://adlnet.gov/expapi/activities/course": "course", 14969 "http://adlnet.gov/expapi/activities/module": "module", 14970 "http://adlnet.gov/expapi/activities/meeting": "meeting", 14971 "http://adlnet.gov/expapi/activities/media": "media", 14972 "http://adlnet.gov/expapi/activities/performance": "performance", 14973 "http://adlnet.gov/expapi/activities/simulation": "simulation", 14974 "http://adlnet.gov/expapi/activities/assessment": "assessment", 14975 "http://adlnet.gov/expapi/activities/interaction": "interaction", 14976 "http://adlnet.gov/expapi/activities/cmi.interaction": "cmi.interaction", 14977 "http://adlnet.gov/expapi/activities/question": "question", 14978 "http://adlnet.gov/expapi/activities/objective": "objective", 14979 "http://adlnet.gov/expapi/activities/link": "link" 14980 }, 14981 14982 /** 14983 @class TinCan.ActivityDefinition 14984 @constructor 14985 */ 14986 ActivityDefinition = TinCan.ActivityDefinition = function (cfg) { 14987 this.log("constructor"); 14988 14989 /** 14990 @property name 14991 @type Object 14992 */ 14993 this.name = null; 14994 14995 /** 14996 @property description 14997 @type Object 14998 */ 14999 this.description = null; 15000 15001 /** 15002 @property type 15003 @type String 15004 */ 15005 this.type = null; 15006 15007 /** 15008 @property moreInfo 15009 @type String 15010 */ 15011 this.moreInfo = null; 15012 15013 /** 15014 @property extensions 15015 @type Object 15016 */ 15017 this.extensions = null; 15018 15019 /** 15020 @property interactionType 15021 @type String 15022 */ 15023 this.interactionType = null; 15024 15025 /** 15026 @property correctResponsesPattern 15027 @type Array 15028 */ 15029 this.correctResponsesPattern = null; 15030 15031 /** 15032 @property choices 15033 @type Array 15034 */ 15035 this.choices = null; 15036 15037 /** 15038 @property scale 15039 @type Array 15040 */ 15041 this.scale = null; 15042 15043 /** 15044 @property source 15045 @type Array 15046 */ 15047 this.source = null; 15048 15049 /** 15050 @property target 15051 @type Array 15052 */ 15053 this.target = null; 15054 15055 /** 15056 @property steps 15057 @type Array 15058 */ 15059 this.steps = null; 15060 15061 this.init(cfg); 15062 }; 15063 ActivityDefinition.prototype = { 15064 /** 15065 @property LOG_SRC 15066 */ 15067 LOG_SRC: "ActivityDefinition", 15068 15069 /** 15070 @method log 15071 */ 15072 log: TinCan.prototype.log, 15073 15074 /** 15075 @method init 15076 @param {Object} [options] Configuration used to initialize 15077 */ 15078 init: function (cfg) { 15079 this.log("init"); 15080 15081 var i, 15082 j, 15083 prop, 15084 directProps = [ 15085 "name", 15086 "description", 15087 "moreInfo", 15088 "extensions", 15089 "correctResponsesPattern" 15090 ], 15091 interactionComponentProps = [] 15092 ; 15093 15094 cfg = cfg || {}; 15095 15096 if (cfg.hasOwnProperty("type") && cfg.type !== null) { 15097 // TODO: verify type is URI? 15098 for (prop in _downConvertMap) { 15099 if (_downConvertMap.hasOwnProperty(prop) && _downConvertMap[prop] === cfg.type) { 15100 cfg.type = _downConvertMap[prop]; 15101 } 15102 } 15103 this.type = cfg.type; 15104 } 15105 15106 if (cfg.hasOwnProperty("interactionType") && cfg.interactionType !== null) { 15107 // TODO: verify interaction type in acceptable set? 15108 this.interactionType = cfg.interactionType; 15109 if (cfg.interactionType === "choice" || cfg.interactionType === "sequencing") { 15110 interactionComponentProps.push("choices"); 15111 } 15112 else if (cfg.interactionType === "likert") { 15113 interactionComponentProps.push("scale"); 15114 } 15115 else if (cfg.interactionType === "matching") { 15116 interactionComponentProps.push("source"); 15117 interactionComponentProps.push("target"); 15118 } 15119 else if (cfg.interactionType === "performance") { 15120 interactionComponentProps.push("steps"); 15121 } 15122 15123 if (interactionComponentProps.length > 0) { 15124 for (i = 0; i < interactionComponentProps.length; i += 1) { 15125 prop = interactionComponentProps[i]; 15126 if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) { 15127 this[prop] = []; 15128 for (j = 0; j < cfg[prop].length; j += 1) { 15129 if (cfg[prop][j] instanceof TinCan.InteractionComponent) { 15130 this[prop].push(cfg[prop][j]); 15131 } else { 15132 this[prop].push( 15133 new TinCan.InteractionComponent ( 15134 cfg[prop][j] 15135 ) 15136 ); 15137 } 15138 } 15139 } 15140 } 15141 } 15142 } 15143 15144 for (i = 0; i < directProps.length; i += 1) { 15145 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 15146 this[directProps[i]] = cfg[directProps[i]]; 15147 } 15148 } 15149 }, 15150 15151 /** 15152 @method toString 15153 @return {String} String representation of the definition 15154 */ 15155 toString: function (lang) { 15156 this.log("toString"); 15157 15158 if (this.name !== null) { 15159 return this.getLangDictionaryValue("name", lang); 15160 } 15161 15162 if (this.description !== null) { 15163 return this.getLangDictionaryValue("description", lang); 15164 } 15165 15166 return ""; 15167 }, 15168 15169 /** 15170 @method asVersion 15171 @param {String}
15171 [version] Version to return (defaults to newest supported) 15172 */ 15173 asVersion: function (version) { 15174 this.log("asVersion"); 15175 var result = {}, 15176 directProps = [ 15177 "name", 15178 "description", 15179 "interactionType", 15180 "correctResponsesPattern", 15181 "extensions" 15182 ], 15183 interactionComponentProps = [ 15184 "choices", 15185 "scale", 15186 "source", 15187 "target", 15188 "steps" 15189 ], 15190 i, 15191 j, 15192 prop 15193 ; 15194 15195 version = version || TinCan.versions()[0]; 15196 15197 if (this.type !== null) { 15198 if (version === "0.9") { 15199 result.type = _downConvertMap[this.type]; 15200 } 15201 else { 15202 result.type = this.type; 15203 } 15204 } 15205 15206 for (i = 0; i < directProps.length; i += 1) { 15207 prop = directProps[i]; 15208 if (this[prop] !== null) { 15209 result[prop] = this[prop]; 15210 } 15211 } 15212 15213 for (i = 0; i < interactionComponentProps.length; i += 1) { 15214 prop = interactionComponentProps[i]; 15215 if (this[prop] !== null) { 15216 result[prop] = []; 15217 for (j = 0; j < this[prop].length; j += 1) { 15218 result[prop].push( 15219 this[prop][j].asVersion(version) 15220 ); 15221 } 15222 } 15223 } 15224 15225 if (version.indexOf("0.9") !== 0) { 15226 if (this.moreInfo !== null) { 15227 result.moreInfo = this.moreInfo; 15228 } 15229 } 15230 15231 return result; 15232 }, 15233 15234 /** 15235 See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}} 15236 15237 @method getLangDictionaryValue 15238 */ 15239 getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue 15240 }; 15241 15242 /** 15243 @method fromJSON 15244 @return {Object} ActivityDefinition 15245 @static 15246 */ 15247 ActivityDefinition.fromJSON = function (definitionJSON) { 15248 ActivityDefinition.prototype.log("fromJSON"); 15249 var _definition = JSON.parse(definitionJSON); 15250 15251 return new ActivityDefinition(_definition); 15252 }; 15253}()); 15254 15255/* 15256 Copyright 2012 Rustici Software 15257 15258 Licensed under the Apache License, Version 2.0 (the "License"); 15259 you may not use this file except in compliance with the License. 15260 You may obtain a copy of the License at 15261 15262 http://www.apache.org/licenses/LICENSE-2.0 15263 15264 Unless required by applicable law or agreed to in writing, software 15265 distributed under the License is distributed on an "AS IS" BASIS, 15266 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15267 See the License for the specific language governing permissions and 15268 limitations under the License. 15269*/ 15270 15271/** 15272TinCan client library 15273 15274@module TinCan 15275@submodule TinCan.Activity 15276**/ 15277(function () { 15278 "use strict"; 15279 15280 /** 15281 @class TinCan.Activity 15282 @constructor 15283 */ 15284 var Activity = TinCan.Activity = function (cfg) { 15285 this.log("constructor"); 15286 15287 /** 15288 @property objectType 15289 @type String 15290 @default Activity 15291 */ 15292 this.objectType = "Activity"; 15293 15294 /** 15295 @property id 15296 @type String 15297 */ 15298 this.id = null; 15299 15300 /** 15301 @property definition 15302 @type TinCan.ActivityDefinition 15303 */ 15304 this.definition = null; 15305 15306 this.init(cfg); 15307 }; 15308 Activity.prototype = { 15309 /** 15310 @property LOG_SRC 15311 */ 15312 LOG_SRC: "Activity", 15313 15314 /** 15315 @method log 15316 */ 15317 log: TinCan.prototype.log, 15318 15319 /** 15320 @method init 15321 @param {Object} [options] Configuration used to initialize 15322 */ 15323 init: function (cfg) { 15324 this.log("init"); 15325 15326 var i, 15327 directProps = [ 15328 "id" 15329 ] 15330 ; 15331 15332 cfg = cfg || {}; 15333 15334 if (cfg.hasOwnProperty("definition")) { 15335 if (cfg.definition instanceof TinCan.ActivityDefinition) { 15336 this.definition = cfg.definition; 15337 } else { 15338 this.definition = new TinCan.ActivityDefinition (cfg.definition); 15339 } 15340 } 15341 15342 for (i = 0; i < directProps.length; i += 1) { 15343 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 15344 this[directProps[i]] = cfg[directProps[i]]; 15345 } 15346 } 15347 }, 15348 15349 /** 15350 @method toString 15351 @return {String} String representation of the activity 15352 */ 15353 toString: function (lang) { 15354 this.log("toString"); 15355 var defString = ""; 15356 15357 if (this.definition !== null) { 15358 defString = this.definition.toString(lang); 15359 if (defString !== "") { 15360 return defString; 15361 } 15362 } 15363 15364 if (this.id !== null) { 15365 return this.id; 15366 } 15367 15368 return "Activity: unidentified"; 15369 }, 15370 15371 /** 15372 @method asVersion 15373 @param {String}
15373 [version] Version to return (defaults to newest supported) 15374 */ 15375 asVersion: function (version) { 15376 this.log("asVersion"); 15377 var result = { 15378 id: this.id, 15379 objectType: this.objectType 15380 }; 15381 15382 version = version || TinCan.versions()[0]; 15383 15384 if (this.definition !== null) { 15385 result.definition = this.definition.asVersion(version); 15386 } 15387 15388 return result; 15389 } 15390 }; 15391 15392 /** 15393 @method fromJSON 15394 @return {Object} Activity 15395 @static 15396 */ 15397 Activity.fromJSON = function (activityJSON) { 15398 Activity.prototype.log("fromJSON"); 15399 var _activity = JSON.parse(activityJSON); 15400 15401 return new Activity(_activity); 15402 }; 15403}()); 15404 15405/* 15406 Copyright 2013 Rustici Software 15407 15408 Licensed under the Apache License, Version 2.0 (the "License"); 15409 you may not use this file except in compliance with the License. 15410 You may obtain a copy of the License at 15411 15412 http://www.apache.org/licenses/LICENSE-2.0 15413 15414 Unless required by applicable law or agreed to in writing, software 15415 distributed under the License is distributed on an "AS IS" BASIS, 15416 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15417 See the License for the specific language governing permissions and 15418 limitations under the License. 15419*/ 15420 15421/** 15422TinCan client library 15423 15424@module TinCan
15425@submodule TinCan.ContextActivities 15426**/ 15427(function () { 15428 "use strict"; 15429 15430 /** 15431 @class TinCan.ContextActivities 15432 @constructor 15433 */ 15434 var ContextActivities = TinCan.ContextActivities = function (cfg) { 15435 this.log("constructor"); 15436 15437 /** 15438 @property category 15439 @type Array 15440 */ 15441 this.category = null; 15442 15443 /** 15444 @property parent 15445 @type Array 15446 */ 15447 this.parent = null; 15448 15449 /** 15450 @property grouping 15451 @type Array 15452 */ 15453 this.grouping = null; 15454 15455 /** 15456 @property other 15457 @type Array 15458 */ 15459 this.other = null; 15460 15461 this.init(cfg); 15462 }; 15463 ContextActivities.prototype = { 15464 /** 15465 @property LOG_SRC 15466 */ 15467 LOG_SRC: "ContextActivities", 15468 15469 /** 15470 @method log 15471 */ 15472 log: TinCan.prototype.log, 15473 15474 /** 15475 @method init 15476 @param {Object} [options] Configuration used to initialize 15477 */ 15478 init: function (cfg) { 15479 this.log("init"); 15480 15481 var i, 15482 j, 15483 objProps = [ 15484 "category", 15485 "parent", 15486 "grouping", 15487 "other" 15488 ], 15489 prop, 15490 val 15491 ; 15492 15493 cfg = cfg || {}; 15494 15495 for (i = 0; i < objProps.length; i += 1) { 15496 prop = objProps[i]; 15497 if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) { 15498 if (Object.prototype.toString.call(cfg[prop]) === "[object Array]") { 15499 for (j = 0; j < cfg[prop].length; j += 1) { 15500 this.add(prop, cfg[prop][j]); 15501 } 15502 } 15503 else { 15504 val = cfg[prop]; 15505 15506 this.add(prop, val); 15507 } 15508 } 15509 } 15510 }, 15511 15512 /** 15513 @method add 15514 @param String key Property to add value to one of "category", "parent", "grouping", "other" 15515 @return Number index where the value was added 15516 */ 15517 add: function (key, val) { 15518 if (key !== "category" && key !== "parent" && key !== "grouping" && key !== "other") { 15519 return; 15520 } 15521 15522 if (this[key] === null) { 15523 this[key] = []; 15524 } 15525 15526 if (! (val instanceof TinCan.Activity)) { 15527 val = typeof val === "string" ? { id: val } : val; 15528 val = new TinCan.Activity (val); 15529 } 15530 15531 this[key].push(val); 15532 15533 return this[key].length - 1; 15534 }, 15535 15536 /** 15537 @method asVersion 15538 @param {String} [version] Version to return (defaults to newest supported) 15539 */ 15540 asVersion: function (version) { 15541 this.log("asVersion"); 15542 var result = {}, 15543 optionalObjProps = [ 15544 "parent", 15545 "grouping", 15546 "other" 15547 ], 15548 i, 15549 j; 15550 15551 version = version || TinCan.versions()[0]; 15552 15553 for (i = 0; i < optionalObjProps.length; i += 1) { 15554 if (this[optionalObjProps[i]] !== null && this[optionalObjProps[i]].length > 0) { 15555 if (version === "0.9" || version === "0.95") { 15556 if (this[optionalObjProps[i]].length > 1) { 15557 // TODO: exception? 15558 this.log("[warning] version does not support multiple values in: " + optionalObjProps[i]); 15559 } 15560 15561 result[optionalObjProps[i]] = this[optionalObjProps[i]][0].asVersion(version); 15562 } 15563 else { 15564 result[optionalObjProps[i]] = []; 15565 for (j = 0; j < this[optionalObjProps[i]].length; j += 1) { 15566 result[optionalObjProps[i]].push( 15567 this[optionalObjProps[i]][j].asVersion(version) 15568 ); 15569 } 15570 } 15571 } 15572 } 15573 if (this.category !== null && this.category.length > 0) { 15574 if (version === "0.9" || version === "0.95") { 15575 this.log("[error] version does not support the 'category' property: " + version); 15576 throw new Error(version + " does not support the 'category' property"); 15577 } 15578 else { 15579 result.category = []; 15580 for (i = 0; i < this.category.length; i += 1) { 15581 result.category.push(this.category[i].asVersion(version)); 15582 } 15583 } 15584 } 15585 15586 return result; 15587 } 15588 }; 15589 15590 /** 15591 @method fromJSON 15592 @return {Object} ContextActivities 15593 @static 15594 */ 15595 ContextActivities.fromJSON = function (contextActivitiesJSON) { 15596 ContextActivities.prototype.log("fromJSON"); 15597 var _contextActivities = JSON.parse(contextActivitiesJSON); 15598 15599 return new ContextActivities(_contextActivities); 15600 }; 15601}()); 15602 15603/* 15604 Copyright 2012 Rustici Software 15605 15606 Licensed under the Apache License, Version 2.0 (the "License");
15607 you may not use this file except in compliance with the License. 15608 You may obtain a copy of the License at 15609 15610 http://www.apache.org/licenses/LICENSE-2.0 15611 15612 Unless required by applicable law or agreed to in writing, software 15613 distributed under the License is distributed on an "AS IS" BASIS, 15614 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15615 See the License for the specific language governing permissions and 15616 limitations under the License. 15617*/ 15618 15619/** 15620TinCan client library 15621 15622@module TinCan 15623@submodule TinCan.Context 15624**/ 15625(function () { 15626 "use strict"; 15627 15628 /** 15629 @class TinCan.Context 15630 @constructor 15631 */ 15632 var Context = TinCan.Context = function (cfg) { 15633 this.log("constructor"); 15634 15635 /** 15636 @property registration 15637 @type String|null 15638 */ 15639 this.registration = null; 15640 15641 /** 15642 @property instructor 15643 @type TinCan.Agent|TinCan.Group|null 15644 */ 15645 this.instructor = null; 15646 15647 /** 15648 @property team 15649 @type TinCan.Agent|TinCan.Group|null 15650 */ 15651 this.team = null; 15652 15653 /** 15654 @property contextActivities 15655 @type ContextActivities|null 15656 */ 15657 this.contextActivities = null; 15658 15659 /** 15660 @property revision 15661 @type String|null 15662 */ 15663 this.revision = null; 15664 15665 /** 15666 @property platform 15667 @type Object|null 15668 */ 15669 this.platform = null; 15670 15671 /** 15672 @property language 15673 @type String|null 15674 */ 15675 this.language = null; 15676 15677 /** 15678 @property statement 15679 @type StatementRef|null 15680 */ 15681 this.statement = null; 15682 15683 /** 15684 @property extensions 15685 @type String 15686 */ 15687 this.extensions = null; 15688 15689 this.init(cfg); 15690 }; 15691 Context.prototype = { 15692 /** 15693 @property LOG_SRC 15694 */ 15695 LOG_SRC: "Context", 15696 15697 /** 15698 @method log 15699 */ 15700 log: TinCan.prototype.log, 15701 15702 /** 15703 @method init 15704 @param {Object} [options] Configuration used to initialize 15705 */ 15706 init: function (cfg) { 15707 this.log("init"); 15708 15709 var i, 15710 directProps = [ 15711 "registration", 15712 "revision", 15713 "platform", 15714 "language", 15715 "extensions" 15716 ], 15717 agentGroupProps = [ 15718 "instructor", 15719 "team" 15720 ], 15721 prop, 15722 val 15723 ; 15724 15725 cfg = cfg || {}; 15726 15727 for (i = 0; i < directProps.length; i += 1) { 15728 prop = directProps[i]; 15729 if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) { 15730 this[prop] = cfg[prop]; 15731 } 15732 } 15733 for (i = 0; i < agentGroupProps.length; i += 1) { 15734 prop = agentGroupProps[i]; 15735 if (cfg.hasOwnProperty(prop) && cfg[prop] !== null) { 15736 val = cfg[prop]; 15737 15738 if (typeof val.objectType === "undefined" || val.objectType === "Person") { 15739 val.objectType = "Agent"; 15740 } 15741 15742 if (val.objectType === "Agent" && ! (val instanceof TinCan.Agent)) { 15743 val = new TinCan.Agent (val); 15744 } else if (val.objectType === "Group" && ! (val instanceof TinCan.Group)) { 15745 val = new TinCan.Group (val); 15746 } 15747 15748 this[prop] = val; 15749 } 15750 } 15751 15752 if (cfg.hasOwnProperty("contextActivities") && cfg.contextActivities !== null) { 15753 if (cfg.contextActivities instanceof TinCan.ContextActivities) { 15754 this.contextActivities = cfg.contextActivities; 15755 } 15756 else { 15757 this.contextActivities = new TinCan.ContextActivities(cfg.contextActivities); 15758 } 15759 } 15760 15761 if (cfg.hasOwnProperty("statement") && cfg.statement !== null) { 15762 if (cfg.statement instanceof TinCan.StatementRef) { 15763 this.statement = cfg.statement; 15764 } 15765 else if (cfg.statement instanceof TinCan.SubStatement) { 15766 this.statement = cfg.statement; 15767 } 15768 else if (cfg.statement.objectType === "StatementRef") { 15769 this.statement = new TinCan.StatementRef(cfg.statement); 15770 } 15771 else if (cfg.statement.objectType === "SubStatement") {
15772 this.statement = new TinCan.SubStatement(cfg.statement); 15773 } 15774 else { 15775 this.log("Unable to parse statement.context.statement property."); 15776 } 15777 } 15778 }, 15779 15780 /** 15781 @method asVersion 15782 @param {String} [version] Version to return (defaults to newest supported) 15783 */ 15784 asVersion: function (version) { 15785 this.log("asVersion"); 15786 var result = {}, 15787 optionalDirectProps = [ 15788 "registration", 15789 "revision", 15790 "platform", 15791 "language", 15792 "extensions" 15793 ], 15794 optionalObjProps = [ 15795 "instructor", 15796 "team", 15797 "contextActivities", 15798 "statement" 15799 ], 15800 i; 15801 15802 version = version || TinCan.versions()[0]; 15803 15804 if (this.statement instanceof TinCan.SubStatement && version !== "0.9" && version !== "0.95") { 15805 this.log("[error] version does not support SubStatements in the 'statement' property: " + version); 15806 throw new Error(version + " does not support SubStatements in the 'statement' property"); 15807 } 15808 15809 for (i = 0; i < optionalDirectProps.length; i += 1) { 15810 if (this[optionalDirectProps[i]] !== null) { 15811 result[optionalDirectProps[i]] = this[optionalDirectProps[i]]; 15812 } 15813 } 15814 for (i = 0; i < optionalObjProps.length; i += 1) { 15815 if (this[optionalObjProps[i]] !== null) { 15816 result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version); 15817 } 15818 } 15819 15820 return result; 15821 } 15822 }; 15823 15824 /** 15825 @method fromJSON 15826 @return {Object} Context 15827 @static 15828 */ 15829 Context.fromJSON = function (contextJSON) { 15830 Context.prototype.log("fromJSON"); 15831 var _context = JSON.parse(contextJSON); 15832 15833 return new Context(_context); 15834 }; 15835}()); 15836 15837/* 15838 Copyright 2012 Rustici Software 15839 15840 Licensed under the Apache License, Version 2.0 (the "License"); 15841 you may not use this file except in compliance with the License. 15842 You may obtain a copy of the License at 15843 15844 http://www.apache.org/licenses/LICENSE-2.0 15845 15846 Unless required by applicable law or agreed to in writing, software 15847 distributed under the License is distributed on an "AS IS" BASIS, 15848 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15849 See the License for the specific language governing permissions and 15850 limitations under the License. 15851*/ 15852 15853/** 15854TinCan client library 15855 15856@module TinCan 15857@submodule TinCan.StatementRef 15858**/ 15859(function () { 15860 "use strict"; 15861 15862 /** 15863 @class TinCan.StatementRef 15864 @constructor 15865 @param {Object} [cfg] Configuration used to initialize. 15866 @param {Object} [cfg.id] ID of statement to reference 15867 **/ 15868 var StatementRef = TinCan.StatementRef = function (cfg) { 15869 this.log("constructor"); 15870 15871 /** 15872 @property id 15873 @type String 15874 */ 15875 this.id = null; 15876 15877 this.init(cfg); 15878 }; 15879 15880 StatementRef.prototype = { 15881 /** 15882 @property objectType 15883 @type String 15884 @default Agent 15885 */ 15886 objectType: "StatementRef", 15887 15888 /** 15889 @property LOG_SRC 15890 */ 15891 LOG_SRC: "StatementRef", 15892 15893 /** 15894 @method log 15895 */ 15896 log: TinCan.prototype.log, 15897 15898 /** 15899 @method init 15900 @param {Object} [options] Configuration used to initialize (see constructor) 15901 */ 15902 init: function (cfg) { 15903 this.log("init"); 15904 var i, 15905 directProps = [ 15906 "id" 15907 ]; 15908 15909 cfg = cfg || {}; 15910 15911 for (i = 0; i < directProps.length; i += 1) { 15912 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 15913 this[directProps[i]] = cfg[directProps[i]]; 15914 } 15915 } 15916 }, 15917 15918 /** 15919 @method toString 15920 @return {String} String representation of the statement 15921 */ 15922 toString: function () { 15923 this.log("toString"); 15924 return this.id; 15925 }, 15926 15927 /** 15928 @method asVersion 15929 @param {String}
15929 [version] Version to return (defaults to newest supported) 15930 */ 15931 asVersion: function (version) { 15932 this.log("asVersion"); 15933 var result = { 15934 objectType: this.objectType, 15935 id: this.id 15936 }; 15937 15938 if (version === "0.9") { 15939 result.objectType = "Statement"; 15940 } 15941 15942 return result; 15943 } 15944 }; 15945 15946 /** 15947 @method fromJSON 15948 @return {Object} StatementRef 15949 @static 15950 */ 15951 StatementRef.fromJSON = function (stRefJSON) { 15952 StatementRef.prototype.log("fromJSON"); 15953 var _stRef = JSON.parse(stRefJSON); 15954 15955 return new StatementRef(_stRef); 15956 }; 15957}()); 15958 15959/* 15960 Copyright 2012 Rustici Software 15961 15962 Licensed under the Apache License, Version 2.0 (the "License"); 15963 you may not use this file except in compliance with the License. 15964 You may obtain a copy of the License at 15965 15966 http://www.apache.org/licenses/LICENSE-2.0 15967 15968 Unless required by applicable law or agreed to in writing, software 15969 distributed under the License is distributed on an "AS IS" BASIS, 15970 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15971 See the License for the specific language governing permissions and 15972 limitations under the License. 15973*/ 15974 15975/** 15976TinCan client library 15977 15978@module TinCan 15979@submodule TinCan.SubStatement 15980**/ 15981(function () { 15982 "use strict"; 15983 15984 /** 15985 @class TinCan.SubStatement 15986 @constructor 15987 @param {Object} [cfg] Configuration used to initialize. 15988 @param {TinCan.Agent} [cfg.actor] Actor of statement 15989 @param {TinCan.Verb} [cfg.verb] Verb of statement 15990 @param {TinCan.Activity|TinCan.Agent} [cfg.object] Alias for 'target' 15991 @param {TinCan.Activity|TinCan.Agent} [cfg.target] Object of statement 15992 @param {TinCan.Result} [cfg.result] Statement Result 15993 @param {TinCan.Context} [cfg.context] Statement Context 15994 **/ 15995 var SubStatement = TinCan.SubStatement = function (cfg) { 15996 this.log("constructor"); 15997 15998 /** 15999 @property actor 16000 @type Object 16001 */ 16002 this.actor = null; 16003 16004 /** 16005 @property verb 16006 @type Object 16007 */ 16008 this.verb = null; 16009 16010 /** 16011 @property target 16012 @type Object 16013 */ 16014 this.target = null; 16015 16016 /** 16017 @property result 16018 @type Object 16019 */ 16020 this.result = null; 16021 16022 /** 16023 @property context 16024 @type Object 16025 */ 16026 this.context = null; 16027 16028 /** 16029 @property timestamp 16030 @type Date 16031 */ 16032 this.timestamp = null; 16033 16034 this.init(cfg); 16035 }; 16036 16037 SubStatement.prototype = { 16038 /** 16039 @property objectType 16040 @type String 16041 @default Agent 16042 */ 16043 objectType: "SubStatement", 16044 16045 /** 16046 @property LOG_SRC 16047 */ 16048 LOG_SRC: "SubStatement", 16049 16050 /** 16051 @method log 16052 */ 16053 log: TinCan.prototype.log, 16054 16055 /** 16056 @method init 16057 @param {Object} [options] Configuration used to initialize (see constructor) 16058 */ 16059 init: function (cfg) { 16060 this.log("init"); 16061 var i, 16062 directProps = [ 16063 "timestamp" 16064 ]; 16065 16066 cfg = cfg || {}; 16067 16068 if (cfg.hasOwnProperty("object")) { 16069 cfg.target = cfg.object; 16070 } 16071 16072 if (cfg.hasOwnProperty("actor")) { 16073 if (typeof cfg.actor.objectType === "undefined" || cfg.actor.objectType === "Person") { 16074 cfg.actor.objectType = "Agent"; 16075 } 16076 16077 if (cfg.actor.objectType === "Agent") { 16078 if (cfg.actor instanceof TinCan.Agent) { 16079 this.actor = cfg.actor; 16080 } else { 16081 this.actor = new TinCan.Agent (cfg.actor); 16082 } 16083 } else if (cfg.actor.objectType === "Group") { 16084 if (cfg.actor instanceof TinCan.Group) { 16085 this.actor = cfg.actor; 16086 } else { 16087 this.actor = new TinCan.Group (cfg.actor); 16088 } 16089 } 16090 } 16091 if (cfg.hasOwnProperty("verb")) { 16092 if (cfg.verb instanceof TinCan.Verb) { 16093 this.verb = cfg.verb; 16094 } else { 16095 this.verb = new TinCan.Verb (cfg.verb); 16096 } 16097 } 16098 if (cfg.hasOwnProperty("target")) { 16099 if (cfg.target instanceof TinCan.Activity || 16100 cfg.target instanceof TinCan.Agent || 16101 cfg.target instanceof TinCan.Group || 16102 cfg.target instanceof TinCan.SubStatement || 16103 cfg.target instanceof TinCan.StatementRef 16104 ) { 16105 this.target = cfg.target; 16106 } else { 16107 if (typeof cfg.target.objectType === "undefined") { 16108 cfg.target.objectType = "Activity"; 16109 } 16110 16111 if (cfg.target.objectType === "Activity") { 16112 this.target = new TinCan.Activity (cfg.target); 16113 } else if (cfg.target.objectType === "Agent") { 16114 this.target = new TinCan.Agent (cfg.target); 16115 } else if (cfg.target.objectType === "Group") { 16116 this.target = new TinCan.Group (cfg.target); 16117 } else if (cfg.target.objectType === "SubStatement") { 16118 this.target = new TinCan.SubStatement (cfg.target); 16119 } else if (cfg.target.objectType === "StatementRef") { 16120 this.target = new TinCan.StatementRef (cfg.target); 16121 } else { 16122 this.log("Unrecognized target type: " + cfg.target.objectType); 16123 } 16124 } 16125 } 16126 if (cfg.hasOwnProperty("result")) { 16127 if (cfg.result instanceof TinCan.Result) { 16128 this.result = cfg.result; 16129 } else { 16130 this.result = new TinCan.Result (cfg.result); 16131 } 16132 } 16133 if (cfg.hasOwnProperty("context")) {
16134 if (cfg.context instanceof TinCan.Context) { 16135 this.context = cfg.context; 16136 } else { 16137 this.context = new TinCan.Context (cfg.context); 16138 } 16139 } 16140 16141 for (i = 0; i < directProps.length; i += 1) { 16142 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 16143 this[directProps[i]] = cfg[directProps[i]]; 16144 } 16145 } 16146 }, 16147 16148 /** 16149 @method toString 16150 @return {String} String representation of the statement 16151 */ 16152 toString: function (lang) { 16153 this.log("toString"); 16154 return (this.actor !== null ? this.actor.toString(lang) : "") + 16155 " " + 16156 (this.verb !== null ? this.verb.toString(lang) : "") + 16157 " " + 16158 (this.target !== null ? this.target.toString(lang) : ""); 16159 }, 16160 16161 /** 16162 @method asVersion 16163 @param {String} [version] Version to return (defaults to newest supported) 16164 */ 16165 asVersion: function (version) { 16166 this.log("asVersion"); 16167 var result, 16168 optionalDirectProps = [ 16169 "timestamp" 16170 ], 16171 optionalObjProps = [ 16172 "actor", 16173 "verb", 16174 "result", 16175 "context" 16176 ], 16177 i; 16178 16179 result = { 16180 objectType: this.objectType 16181 }; 16182 version = version || TinCan.versions()[0]; 16183 16184 for (i = 0; i < optionalDirectProps.length; i += 1) { 16185 if (this[optionalDirectProps[i]] !== null) { 16186 result[optionalDirectProps[i]] = this[optionalDirectProps[i]]; 16187 } 16188 } 16189 for (i = 0; i < optionalObjProps.length; i += 1) { 16190 if (this[optionalObjProps[i]] !== null) { 16191 result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version); 16192 } 16193 } 16194 if (this.target !== null) { 16195 result.object = this.target.asVersion(version); 16196 } 16197 16198 if (version === "0.9") { 16199 result.objectType = "Statement"; 16200 } 16201 16202 return result; 16203 } 16204 }; 16205 16206 /** 16207 @method fromJSON 16208 @return {Object} SubStatement 16209 @static 16210 */ 16211 SubStatement.fromJSON = function (subStJSON) { 16212 SubStatement.prototype.log("fromJSON"); 16213 var _subSt = JSON.parse(subStJSON); 16214 16215 return new SubStatement(_subSt); 16216 }; 16217}()); 16218 16219/* 16220 Copyright 2012-3 Rustici Software 16221 16222 Licensed under the Apache License, Version 2.0 (the "License"); 16223 you may not use this file except in compliance with the License. 16224 You may obtain a copy of the License at 16225 16226 http://www.apache.org/licenses/LICENSE-2.0 16227 16228 Unless required by applicable law or agreed to in writing, software 16229 distributed under the License is distributed on an "AS IS" BASIS, 16230 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16231 See the License for the specific language governing permissions and 16232 limitations under the License. 16233*/ 16234 16235/** 16236TinCan client library 16237 16238@module TinCan 16239@submodule TinCan.Statement 16240**/ 16241(function () { 16242 "use strict"; 16243 16244 /** 16245 @class TinCan.Statement 16246 @constructor 16247 @param {Object} [cfg] Values to set in properties 16248 @param {String} [cfg.id] Statement ID (UUID) 16249 @param {TinCan.Agent} [cfg.actor] Actor of statement 16250 @param {TinCan.Verb} [cfg.verb] Verb of statement 16251 @param {TinCan.Activity|TinCan.Agent|TinCan.Group|TinCan.StatementRef|TinCan.SubStatement} [cfg.object] Alias for 'target' 16252 @param {TinCan.Activity|TinCan.Agent|TinCan.Group|TinCan.StatementRef|TinCan.SubStatement} [cfg.target] Object of statement 16253 @param {TinCan.Result} [cfg.result] Statement Result 16254 @param {TinCan.Context} [cfg.context] Statement Context 16255 @param {TinCan.Agent} [cfg.authority] Statement Authority 16256 @param {TinCan.Attachment} [cfg.attachments] Statement Attachments 16257 @param {String} [cfg.timestamp] ISO8601 Date/time value 16258 @param {String} [cfg.stored] ISO8601 Date/time value 16259 @param {String} [cfg.version] Version of the statement (post 0.95) 16260 @param {Object} [initCfg] Configuration of initialization process 16261 @param {Integer} [initCfg.storeOriginal] Whether to store a JSON stringified version 16262 of the original options object, pass number of space
16262s used for indent 16263 @param {Boolean} [initCfg.doStamp] Whether to automatically set the 'id' and 'timestamp' 16264 properties (default: true) 16265 **/ 16266 var Statement = TinCan.Statement = function (cfg, initCfg) { 16267 this.log("constructor"); 16268 16269 // check for true value for API backwards compat 16270 if (typeof initCfg === "number") { 16271 initCfg = { 16272 storeOriginal: initCfg 16273 }; 16274 } else { 16275 initCfg = initCfg || {}; 16276 } 16277 if (typeof initCfg.storeOriginal === "undefined") { 16278 initCfg.storeOriginal = null; 16279 } 16280 if (typeof initCfg.doStamp === "undefined") { 16281 initCfg.doStamp = true; 16282 } 16283 16284 /** 16285 @property id 16286 @type String 16287 */ 16288 this.id = null; 16289 16290 /** 16291 @property actor 16292 @type TinCan.Agent|TinCan.Group|null 16293 */ 16294 this.actor = null; 16295 16296 /** 16297 @property verb 16298 @type TinCan.Verb|null 16299 */ 16300 this.verb = null; 16301 16302 /** 16303 @property target 16304 @type TinCan.Activity|TinCan.Agent|TinCan.Group|TinCan.StatementRef|TinCan.SubStatement|null 16305 */ 16306 this.target = null; 16307 16308 /** 16309 @property result 16310 @type Object 16311 */ 16312 this.result = null; 16313 16314 /** 16315 @property context 16316 @type Object 16317 */ 16318 this.context = null; 16319 16320 /** 16321 @property timestamp 16322 @type String 16323 */ 16324 this.timestamp = null; 16325 16326 /** 16327 @property stored 16328 @type String 16329 */ 16330 this.stored = null; 16331 16332 /** 16333 @property authority 16334 @type TinCan.Agent|null 16335 */ 16336 this.authority = null; 16337 16338 /** 16339 @property attachments 16340 @type Array of TinCan.Attachment 16341 */ 16342 this.attachments = null; 16343 16344 /** 16345 @property version 16346 @type String 16347 */ 16348 this.version = null; 16349 16350 /** 16351 @property degraded 16352 @type Boolean 16353 @default false 16354 */ 16355 this.degraded = false; 16356 16357 /** 16358 @property voided 16359 @type Boolean 16360 @default null 16361 @deprecated 16362 */ 16363 this.voided = null; 16364 16365 /** 16366 @property inProgress 16367 @type Boolean 16368 @deprecated 16369 */ 16370 this.inProgress = null; 16371 16372 /** 16373 @property originalJSON 16374 @type String 16375 */ 16376 this.originalJSON = null; 16377 16378 this.init(cfg, initCfg); 16379 }; 16380 16381 Statement.prototype = { 16382 /** 16383 @property LOG_SRC 16384 */ 16385 LOG_SRC: "Statement", 16386 16387 /** 16388 @method log 16389 */ 16390 log: TinCan.prototype.log, 16391 16392 /** 16393 @method init 16394 @param {Object} [properties] Configuration used to set properties (see constructor) 16395 @param {Object} [cfg] Configuration used to initialize (see constructor) 16396 */ 16397 init: function (cfg, initCfg) { 16398 this.log("init"); 16399 var i, 16400 directProps = [ 16401 "id", 16402 "stored", 16403 "timestamp", 16404 "version", 16405 "inProgress", 16406 "voided" 16407 ]; 16408 16409 cfg = cfg || {}; 16410 16411 if (initCfg.storeOriginal) { 16412 this.originalJSON = JSON.stringify(cfg, null, initCfg.storeOriginal); 16413 } 16414 16415 if (cfg.hasOwnProperty("object")) { 16416 cfg.target = cfg.object; 16417 } 16418 16419 if (cfg.hasOwnProperty("actor")) { 16420 if (typeof cfg.actor.objectType === "undefined" || cfg.actor.objectType === "Person") { 16421 cfg.actor.objectType = "Agent"; 16422 } 16423 16424 if (cfg.actor.objectType === "Agent") { 16425 if (cfg.actor instanceof TinCan.Agent) { 16426 this.actor = cfg.actor; 16427 } else { 16428 this.actor = new TinCan.Agent (cfg.actor); 16429 } 16430 } else if (cfg.actor.objectType === "Group") { 16431 if (cfg.actor instanceof TinCan.Group) { 16432 this.actor = cfg.actor; 16433 } else { 16434 this.actor = new TinCan.Group (cfg.actor); 16435 } 16436 } 16437 } 16438 if (cfg.hasOwnProperty("authority")) { 16439 if (typeof cfg.authority.objectType === "undefined" || cfg.authority.objectType === "Person") { 16440 cfg.authority.objectType = "Agent"; 16441 } 16442 16443 if (cfg.authority.objectType === "Agent") { 16444 if (cfg.authority instanceof TinCan.Agent) { 16445 this.authority = cfg.authority; 16446 } else { 16447 this.authority = new TinCan.Agent (cfg.authority); 16448 } 16449 } else if (cfg.authority.objectType === "Group") { 16450 if (cfg.actor instanceof TinCan.Group) { 16451 this.authority = cfg.authority; 16452 } else { 16453 this.authority = new TinCan.Group (cfg.authority); 16454 } 16455 } 16456 } 16457 if (cfg.hasOwnProperty("verb")) { 16458 if (cfg.verb instanceof TinCan.Verb) { 16459 this.verb = cfg.verb; 16460 } else { 16461 this.verb = new TinCan.Verb (cfg.verb); 16462 } 16463 } 16464 if (cfg.hasOwnProperty("target")) { 16465 if (cfg.target instanceof TinCan.Activity || 16466 cfg.target instanceof TinCan.Agent || 16467 cfg.target instanceof TinCan.Group || 16468 cfg.target instanceof TinCan.SubStatement || 16469 cfg.target instanceof TinCan.StatementRef 16470 ) { 16471 this.target = cfg.target; 16472 } else { 16473 if (typeof cfg.target.objectType === "undefined") { 16474 cfg.target.objectType = "Activity"; 16475 } 16476 16477 if (cfg.target.objectType === "Activity") { 16478 this.target = new TinCan.Activity (cfg.target); 16479 } else if (cfg.target.objectType === "Agent") { 16480 this.target = new TinCan.Agent (cfg.target); 16481 } else if (cfg.target.objectType === "Group") { 16482 this.target = new TinCan.Group (cfg.target); 16483 } else if (cfg.target.objectType === "SubStatement") { 16484 this.target = new TinCan.SubStatement (cfg.target); 16485 } else if (cfg.target.objectType === "StatementRef") { 16486 this.target = new TinCan.StatementRef (cfg.target); 16487 } else { 16488 this.log("Unrecognized target type: " + cfg.target.objectType); 16489 } 16490 } 16491 } 16492 if (cfg.hasOwnProperty("result")) { 16493 if (cfg.result instanceof TinCan.Result) { 16494 this.result = cfg.result; 16495 } else { 16496 this.result = new TinCan.Result (cfg.result); 16497 } 16498 } 16499 if (cfg.hasOwnProperty("context")) {
16500 if (cfg.context instanceof TinCan.Context) { 16501 this.context = cfg.context; 16502 } else { 16503 this.context = new TinCan.Context (cfg.context); 16504 } 16505 } 16506 if (cfg.hasOwnProperty("attachments") && cfg.attachments !== null) { 16507 this.attachments = []; 16508 for (i = 0; i < cfg.attachments.length; i += 1) { 16509 if (! (cfg.attachments[i] instanceof TinCan.Attachment)) { 16510 this.attachments.push(new TinCan.Attachment (cfg.attachments[i])); 16511 } 16512 else { 16513 this.attachments.push(cfg.attachments[i]); 16514 } 16515 } 16516 } 16517 16518 for (i = 0; i < directProps.length; i += 1) { 16519 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 16520 this[directProps[i]] = cfg[directProps[i]]; 16521 } 16522 } 16523 16524 if (initCfg.doStamp) { 16525 this.stamp(); 16526 } 16527 }, 16528 16529 /** 16530 @method toString 16531 @return {String} String representation of the statement 16532 */ 16533 toString: function (lang) { 16534 this.log("toString"); 16535 return (this.actor !== null ? this.actor.toString(lang) : "") + 16536 " " + 16537 (this.verb !== null ? this.verb.toString(lang) : "") + 16538 " " + 16539 (this.target !== null ? this.target.toString(lang) : ""); 16540 }, 16541 16542 /** 16543 @method asVersion 16544 @param {String} [version] Version to return (defaults to newest supported) 16545 */ 16546 asVersion: function (version) { 16547 this.log("asVersion"); 16548 var result = {}, 16549 optionalDirectProps = [ 16550 "id", 16551 "timestamp" 16552 ], 16553 optionalObjProps = [ 16554 "actor", 16555 "verb", 16556 "result", 16557 "context", 16558 "authority" 16559 ], 16560 i; 16561 16562 version = version || TinCan.versions()[0]; 16563 16564 for (i = 0; i < optionalDirectProps.length; i += 1) { 16565 if (this[optionalDirectProps[i]] !== null) { 16566 result[optionalDirectProps[i]] = this[optionalDirectProps[i]]; 16567 } 16568 } 16569 for (i = 0; i < optionalObjProps.length; i += 1) { 16570 if (this[optionalObjProps[i]] !== null) { 16571 result[optionalObjProps[i]] = this[optionalObjProps[i]].asVersion(version); 16572 } 16573 } 16574 if (this.target !== null) { 16575 result.object = this.target.asVersion(version); 16576 } 16577 16578 if (version === "0.9" || version === "0.95") { 16579 if (this.voided !== null) { 16580 result.voided = this.voided; 16581 } 16582 } 16583 if (version === "0.9" && this.inProgress !== null) { 16584 result.inProgress = this.inProgress; 16585 } 16586 if (this.attachments !== null) { 16587 if (! (version === "0.9" || version === "0.95")) { 16588 result.attachments = []; 16589 for (i = 0; i < this.attachments.length; i += 1) { 16590 if (this.attachments[i] instanceof TinCan.Attachment) { 16591 result.attachments.push(this.attachments[i].asVersion(version)); 16592 } 16593 else { 16594 result.attachments.push(new TinCan.Attachment(this.attachments[i]).asVersion(version)); 16595 } 16596 } 16597 } 16598 } 16599 16600 return result; 16601 }, 16602 16603 /** 16604 Sets 'id' and 'timestamp' properties if not already set 16605 16606 @method stamp 16607 */ 16608 stamp: function () { 16609 this.log("stamp"); 16610 if (this.id === null) { 16611 this.id = TinCan.Utils.getUUID(); 16612 } 16613 if (this.timestamp === null) { 16614 this.timestamp = TinCan.Utils.getISODateString(new Date()); 16615 } 16616 }, 16617 16618 /** 16619 Checks if the Statement has at least one attachment with content 16620 16621 @method hasAttachmentsWithContent 16622 */ 16623 hasAttachmentWithContent: function () { 16624 this.log("hasAttachmentWithContent"); 16625 var i; 16626 16627 if (this.attachments === null) { 16628 return false;
16629 } 16630 16631 for (i = 0; i < this.attachments.length; i += 1) { 16632 if (this.attachments[i].content !== null) { 16633 return true; 16634 } 16635 } 16636 16637 return false; 16638 } 16639 }; 16640 16641 /** 16642 @method fromJSON 16643 @return {Object} Statement 16644 @static 16645 */ 16646 Statement.fromJSON = function (stJSON) { 16647 Statement.prototype.log("fromJSON"); 16648 var _st = JSON.parse(stJSON); 16649 16650 return new Statement(_st); 16651 }; 16652}()); 16653 16654/* 16655 Copyright 2012 Rustici Software 16656 16657 Licensed under the Apache License, Version 2.0 (the "License"); 16658 you may not use this file except in compliance with the License. 16659 You may obtain a copy of the License at 16660 16661 http://www.apache.org/licenses/LICENSE-2.0 16662 16663 Unless required by applicable law or agreed to in writing, software 16664 distributed under the License is distributed on an "AS IS" BASIS, 16665 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16666 See the License for the specific language governing permissions and 16667 limitations under the License. 16668*/ 16669 16670/** 16671TinCan client library 16672 16673@module TinCan 16674@submodule TinCan.StatementsResult 16675**/ 16676(function () { 16677 "use strict"; 16678 16679 /** 16680 @class TinCan.StatementsResult 16681 @constructor 16682 @param {Object} options Configuration used to initialize. 16683 @param {Array} options.statements Actor of statement 16684 @param {String} options.more URL to fetch more data 16685 **/ 16686 var StatementsResult = TinCan.StatementsResult = function (cfg) { 16687 this.log("constructor"); 16688 16689 /** 16690 @property statements 16691 @type Array 16692 */ 16693 this.statements = null; 16694 16695 /** 16696 @property more 16697 @type String 16698 */ 16699 this.more = null; 16700 16701 this.init(cfg); 16702 }; 16703 16704 StatementsResult.prototype = { 16705 /** 16706 @property LOG_SRC 16707 */ 16708 LOG_SRC: "StatementsResult", 16709 16710 /** 16711 @method log 16712 */ 16713 log: TinCan.prototype.log, 16714 16715 /** 16716 @method init 16717 @param {Object} [options] Configuration used to initialize 16718 */ 16719 init: function (cfg) { 16720 this.log("init"); 16721 16722 cfg = cfg || {}; 16723 16724 if (cfg.hasOwnProperty("statements")) { 16725 this.statements = cfg.statements; 16726 } 16727 if (cfg.hasOwnProperty("more")) { 16728 this.more = cfg.more; 16729 } 16730 } 16731 }; 16732 16733 /** 16734 @method fromJSON 16735 @return {Object} Agent 16736 @static 16737 */ 16738 StatementsResult.fromJSON = function (resultJSON) { 16739 StatementsResult.prototype.log("fromJSON"); 16740 var _result, 16741 stmts = [], 16742 stmt, 16743 i 16744 ; 16745 16746 try { 16747 _result = JSON.parse(resultJSON); 16748 } catch (parseError) { 16749 StatementsResult.prototype.log("fromJSON - JSON.parse error: " + parseError); 16750 } 16751 16752 if (_result) { 16753 for (i = 0; i < _result.statements.length; i += 1) { 16754 try { 16755 stmt = new TinCan.Statement (_result.statements[i], 4); 16756 } catch (error) { 16757 StatementsResult.prototype.log("fromJSON - statement instantiation failed: " + error + " (" + JSON.stringify(_result.statements[i]) + ")"); 16758 16759 stmt = new TinCan.Statement ( 16760 { 16761 id: _result.statements[i].id 16762 }, 16763 4 16764 ); 16765 } 16766 16767 stmts.push(stmt); 16768 } 16769 _result.statements = stmts; 16770 } 16771 16772 return new StatementsResult (_result); 16773 }; 16774}()); 16775 16776/* 16777 Copyright 2012 Rustici Software 16778 16779 Licensed under the Apache License, Version 2.0 (the "License"); 16780 you may not use this file except in compliance with the License. 16781 You may obtain a copy of the License at 16782 16783 http://www.apache.org/licenses/LICENSE-2.0 16784 16785 Unless required by applicable law or agreed to in writing, software 16786 distributed under the License is distributed on an "AS IS" BASIS, 16787 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16788 See the License for the specific language governing permissions and 16789 limitations under the License. 16790*/ 16791 16792/** 16793TinCan client library 16794 16795@module TinCan 16796@submodule TinCan.State 16797**/ 16798(function () { 16799 "use strict"; 16800 16801 /** 16802 @class TinCan.State 16803 @constructor 16804 */ 16805 var State = TinCan.State = function (cfg) { 16806 this.log("constructor"); 16807 16808 /** 16809 @property id
16810 @type String 16811 */ 16812 this.id = null; 16813 16814 /** 16815 @property updated 16816 @type Boolean 16817 */ 16818 this.updated = null; 16819 16820 /** 16821 @property contents 16822 @type String 16823 */ 16824 this.contents = null; 16825 16826 /** 16827 @property etag 16828 @type String 16829 */ 16830 this.etag = null; 16831 16832 /** 16833 @property contentType 16834 @type String 16835 */ 16836 this.contentType = null; 16837 16838 this.init(cfg); 16839 }; 16840 State.prototype = { 16841 /** 16842 @property LOG_SRC 16843 */ 16844 LOG_SRC: "State", 16845 16846 /** 16847 @method log 16848 */ 16849 log: TinCan.prototype.log, 16850 16851 /** 16852 @method init 16853 @param {Object} [options] Configuration used to initialize 16854 */ 16855 init: function (cfg) { 16856 this.log("init"); 16857 var i, 16858 directProps = [ 16859 "id", 16860 "contents", 16861 "etag", 16862 "contentType" 16863 ]; 16864 16865 cfg = cfg || {}; 16866 16867 for (i = 0; i < directProps.length; i += 1) { 16868 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 16869 this[directProps[i]] = cfg[directProps[i]]; 16870 } 16871 } 16872 16873 this.updated = false; 16874 } 16875 }; 16876 16877 /** 16878 @method fromJSON 16879 @return {Object} State 16880 @static 16881 */ 16882 State.fromJSON = function (stateJSON) { 16883 State.prototype.log("fromJSON"); 16884 var _state = JSON.parse(stateJSON); 16885 16886 return new State(_state); 16887 }; 16888}()); 16889 16890/* 16891 Copyright 2012 Rustici Software 16892 16893 Licensed under the Apache License, Version 2.0 (the "License"); 16894 you may not use this file except in compliance with the License. 16895 You may obtain a copy of the License at 16896 16897 http://www.apache.org/licenses/LICENSE-2.0 16898 16899 Unless required by applicable law or agreed to in writing, software 16900 distributed under the License is distributed on an "AS IS" BASIS, 16901 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16902 See the License for the specific language governing permissions and 16903 limitations under the License. 16904*/ 16905 16906/** 16907TinCan client library 16908 16909@module TinCan 16910@submodule TinCan.ActivityProfile 16911**/ 16912(function () { 16913 "use strict"; 16914 16915 /** 16916 @class TinCan.ActivityProfile 16917 @constructor 16918 */ 16919 var ActivityProfile = TinCan.ActivityProfile = function (cfg) { 16920 this.log("constructor"); 16921 16922 /** 16923 @property id 16924 @type String 16925 */ 16926 this.id = null; 16927 16928 /** 16929 @property activity 16930 @type TinCan.Activity 16931 */ 16932 this.activity = null; 16933 16934 /** 16935 @property updated 16936 @type String 16937 */ 16938 this.updated = null; 16939 16940 /** 16941 @property contents 16942 @type String 16943 */ 16944 this.contents = null; 16945 16946 /** 16947 SHA1 of contents as provided by the server during last fetch, 16948 this should be passed through to saveActivityProfile 16949 16950 @property etag 16951 @type String 16952 */ 16953 this.etag = null; 16954 16955 /** 16956 @property contentType 16957 @type String 16958 */ 16959 this.contentType = null; 16960 16961 this.init(cfg); 16962 }; 16963 ActivityProfile.prototype = { 16964 /** 16965 @property LOG_SRC 16966 */ 16967 LOG_SRC: "ActivityProfile", 16968 16969 /** 16970 @method log 16971 */ 16972 log: TinCan.prototype.log, 16973 16974 /** 16975 @method init 16976 @param {Object} [options] Configuration used to initialize 16977 */ 16978 init: function (cfg) { 16979 this.log("init"); 16980 var i, 16981 directProps = [ 16982 "id", 16983 "contents", 16984 "etag", 16985 "contentType" 16986 ]; 16987 16988 cfg = cfg || {}; 16989 16990 if (cfg.hasOwnProperty("activity")) { 16991 if (cfg.activity instanceof TinCan.Activity) { 16992 this.activity = cfg.activity; 16993 } 16994 else { 16995 this.activity = new TinCan.Activity (cfg.activity); 16996 } 16997 } 16998 16999 for (i = 0; i < directProps.length; i += 1) { 17000 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 17001 this[directProps[i]] = cfg[directProps[i]]; 17002 } 17003 } 17004 17005 this.updated = false;
17006 } 17007 }; 17008 17009 /** 17010 @method fromJSON 17011 @return {Object} ActivityProfile 17012 @static 17013 */ 17014 ActivityProfile.fromJSON = function (stateJSON) { 17015 ActivityProfile.prototype.log("fromJSON"); 17016 var _state = JSON.parse(stateJSON); 17017 17018 return new ActivityProfile(_state); 17019 }; 17020}()); 17021 17022/* 17023 Copyright 2013 Rustici Software 17024 17025 Licensed under the Apache License, Version 2.0 (the "License"); 17026 you may not use this file except in compliance with the License. 17027 You may obtain a copy of the License at 17028 17029 http://www.apache.org/licenses/LICENSE-2.0 17030 17031 Unless required by applicable law or agreed to in writing, software 17032 distributed under the License is distributed on an "AS IS" BASIS, 17033 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17034 See the License for the specific language governing permissions and 17035 limitations under the License. 17036*/ 17037 17038/** 17039TinCan client library 17040 17041@module TinCan 17042@submodule TinCan.AgentProfile 17043**/ 17044(function () { 17045 "use strict"; 17046 17047 /** 17048 @class TinCan.AgentProfile 17049 @constructor 17050 */ 17051 var AgentProfile = TinCan.AgentProfile = function (cfg) { 17052 this.log("constructor"); 17053 17054 /** 17055 @property id 17056 @type String 17057 */ 17058 this.id = null; 17059 17060 /** 17061 @property agent 17062 @type TinCan.Agent 17063 */ 17064 this.agent = null; 17065 17066 /** 17067 @property updated 17068 @type String 17069 */ 17070 this.updated = null; 17071 17072 /** 17073 @property contents 17074 @type String 17075 */ 17076 this.contents = null; 17077 17078 /** 17079 SHA1 of contents as provided by the server during last fetch, 17080 this should be passed through to saveAgentProfile 17081 17082 @property etag 17083 @type String 17084 */ 17085 this.etag = null; 17086 17087 /** 17088 @property contentType 17089 @type String 17090 */ 17091 this.contentType = null; 17092 17093 this.init(cfg); 17094 }; 17095 AgentProfile.prototype = { 17096 /** 17097 @property LOG_SRC 17098 */ 17099 LOG_SRC: "AgentProfile", 17100 17101 /** 17102 @method log 17103 */ 17104 log: TinCan.prototype.log, 17105 17106 /** 17107 @method init 17108 @param {Object} [options] Configuration used to initialize 17109 */ 17110 init: function (cfg) { 17111 this.log("init"); 17112 var i, 17113 directProps = [ 17114 "id", 17115 "contents", 17116 "etag", 17117 "contentType" 17118 ]; 17119 17120 cfg = cfg || {}; 17121 17122 if (cfg.hasOwnProperty("agent")) { 17123 if (cfg.agent instanceof TinCan.Agent) { 17124 this.agent = cfg.agent; 17125 } 17126 else { 17127 this.agent = new TinCan.Agent (cfg.agent); 17128 } 17129 } 17130 17131 for (i = 0; i < directProps.length; i += 1) { 17132 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 17133 this[directProps[i]] = cfg[directProps[i]]; 17134 } 17135 } 17136 17137 this.updated = false; 17138 } 17139 }; 17140 17141 /** 17142 @method fromJSON 17143 @return {Object} AgentProfile 17144 @static 17145 */ 17146 AgentProfile.fromJSON = function (stateJSON) { 17147 AgentProfile.prototype.log("fromJSON"); 17148 var _state = JSON.parse(stateJSON); 17149 17150 return new AgentProfile(_state); 17151 }; 17152}()); 17153 17154/* 17155 Copyright 2014 Rustici Software 17156 17157 Licensed under the Apache License, Version 2.0 (the "License"); 17158 you may not use this file except in compliance with the License. 17159 You may obtain a copy of the License at 17160 17161 http://www.apache.org/licenses/LICENSE-2.0 17162 17163 Unless required by applicable law or agreed to in writing, software 17164 distributed under the License is distributed on an "AS IS" BASIS, 17165 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17166 See the License for the specific language governing permissions and 17167 limitations under the License. 17168*/ 17169 17170/** 17171TinCan client library 17172 17173@module TinCan 17174@submodule TinCan.About 17175**/ 17176(function () { 17177 "use strict"; 17178 17179 /** 17180 @class TinCan.About 17181 @constructor 17182 */ 17183 var About = TinCan.About = function (cfg) { 17184 this.log("constructor"); 17185 17186 /** 17187 @property version 17188 @type {String[]} 17189 */ 17190 this.version = null; 17191 17192 this.init(cfg); 17193 }; 17194 About.prototype = { 17195 /** 17196 @property LOG_SRC 17197 */ 17198 LOG_SRC: "About", 17199 17200 /** 17201 @method log 17202 */ 17203 log: TinCan.prototype.log, 17204 17205 /** 17206 @method init 17207 @param {Object} [options] Configuration used to initialize 17208 */ 17209 init: function (cfg) { 17210 this.log("init"); 17211 var i, 17212 directProps = [ 17213 "version" 17214 ]; 17215 17216 cfg = cfg || {}; 17217 17218 for (i = 0; i < directProps.length; i += 1) { 17219 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 17220 this[directProps[i]] = cfg[directProps[i]]; 17221 } 17222 } 17223 } 17224 }; 17225 17226 /** 17227 @method fromJSON 17228 @return {Object} About 17229 @static 17230 */ 17231 About.fromJSON = function (aboutJSON) { 17232 About.prototype.log("fromJSON"); 17233 var _about = JSON.parse(aboutJSON); 17234 17235 return new About(_about); 17236 }; 17237}()); 17238 17239/* 17240 Copyright 2016 Rustici Software 17241 17242 Licensed under the Apache License, Version 2.0 (the "License");
17243 you may not use this file except in compliance with the License. 17244 You may obtain a copy of the License at 17245 17246 http://www.apache.org/licenses/LICENSE-2.0 17247 17248 Unless required by applicable law or agreed to in writing, software 17249 distributed under the License is distributed on an "AS IS" BASIS, 17250 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17251 See the License for the specific language governing permissions and 17252 limitations under the License. 17253*/ 17254 17255/** 17256TinCan client library 17257 17258@module TinCan 17259@submodule TinCan.Attachment 17260**/ 17261(function () { 17262 "use strict"; 17263 17264 /** 17265 @class TinCan.Attachment 17266 @constructor 17267 */ 17268 var Attachment = TinCan.Attachment = function (cfg) { 17269 this.log("constructor"); 17270 17271 /** 17272 @property usageType 17273 @type String 17274 */ 17275 this.usageType = null; 17276 17277 /** 17278 @property display 17279 @type Object 17280 */ 17281 this.display = null; 17282 17283 /** 17284 @property contentType 17285 @type String 17286 */ 17287 this.contentType = null; 17288 17289 /** 17290 @property length 17291 @type int 17292 */ 17293 this.length = null; 17294 17295 /** 17296 @property sha2 17297 @type String 17298 */ 17299 this.sha2 = null; 17300 17301 /** 17302 @property description 17303 @type Object 17304 */ 17305 this.description = null; 17306 17307 /** 17308 @property fileUrl 17309 @type String 17310 */ 17311 this.fileUrl = null; 17312 17313 /** 17314 @property content 17315 @type ArrayBuffer 17316 */ 17317 this.content = null; 17318 17319 this.init(cfg); 17320 }; 17321 Attachment.prototype = { 17322 /** 17323 @property LOG_SRC 17324 */ 17325 LOG_SRC: "Attachment", 17326 17327 /** 17328 @method log 17329 */ 17330 log: TinCan.prototype.log, 17331 17332 /** 17333 @method init 17334 @param {Object} [options] Configuration used to initialize 17335 */ 17336 init: function (cfg) { 17337 this.log("init"); 17338 var i, 17339 directProps = [ 17340 "contentType", 17341 "length", 17342 "sha2", 17343 "usageType", 17344 "display", 17345 "description", 17346 "fileUrl" 17347 ] 17348 ; 17349 17350 cfg = cfg || {}; 17351 17352 for (i = 0; i < directProps.length; i += 1) { 17353 if (cfg.hasOwnProperty(directProps[i]) && cfg[directProps[i]] !== null) { 17354 this[directProps[i]] = cfg[directProps[i]]; 17355 } 17356 } 17357 17358 if (cfg.hasOwnProperty("content") && cfg.content !== null) { 17359 if (typeof cfg.content === "string") { 17360 this.setContentFromString(cfg.content); 17361 } 17362 else { 17363 this.setContent(cfg.content); 17364 } 17365 } 17366 }, 17367 17368 /** 17369 @method asVersion 17370 @param {String} [version] Version to return (defaults to newest supported) 17371 */ 17372 asVersion: function (version) { 17373 this.log("asVersion"); 17374 var result; 17375 17376 version = version || TinCan.versions()[0]; 17377 17378 if (version === "0.9" || version === "0.95") { 17379 result = null; 17380 } 17381 else { 17382 result = { 17383 contentType: this.contentType, 17384 display: this.display, 17385 length: this.length, 17386 sha2: this.sha2, 17387 usageType: this.usageType 17388 }; 17389 17390 if (this.fileUrl !== null) { 17391 result.fileUrl = this.fileUrl; 17392 } 17393 if (this.description !== null) { 17394 result.description = this.description; 17395 } 17396 } 17397 17398 return result; 17399 }, 17400 17401 /** 17402 See {{#crossLink "TinCan.Utils/getLangDictionaryValue"}}{{/crossLink}} 17403 17404 @method getLangDictionaryValue 17405 */ 17406 getLangDictionaryValue: TinCan.Utils.getLangDictionaryValue, 17407 17408 /** 17409 @method setContent 17410 @param {ArrayBuffer} content Sets content from ArrayBuffer 17411 */ 17412 setContent: function (content) { 17413 this.content = content; 17414 this.length = content.byteLength; 17415 this.sha2 = TinCan.Utils.getSHA256String(content); 17416 }, 17417 17418 /** 17419 @method setContentFromString 17420 @param {String} content Sets the content property of the attachment from a string 17421 */ 17422 setContentFromString: function (content) { 17423 var _content = content; 17424 17425 _content = TinCan.Utils.stringToArrayBuffer(content); 17426 17427 this.setContent(_content); 17428 }, 17429 17430 /** 17431 @method getContentAsString 17432 @return {String}
17432 Value of content property as a string 17433 */ 17434 getContentAsString: function () { 17435 return TinCan.Utils.stringFromArrayBuffer(this.content); 17436 } 17437 }; 17438 17439 /** 17440 @method fromJSON 17441 @return {Object} Attachment 17442 @static 17443 */ 17444 Attachment.fromJSON = function (attachmentJSON) { 17445 Attachment.prototype.log("fromJSON"); 17446 var _attachment = JSON.parse(attachmentJSON); 17447 17448 return new Attachment(_attachment); 17449 }; 17450 17451 Attachment._defaultEncoding = "utf-8"; 17452}()); 17453 17454/* 17455 Copyright 2012-2013 Rustici Software 17456 17457 Licensed under the Apache License, Version 2.0 (the "License"); 17458 you may not use this file except in compliance with the License. 17459 You may obtain a copy of the License at 17460 17461 http://www.apache.org/licenses/LICENSE-2.0 17462 17463 Unless required by applicable law or agreed to in writing, software 17464 distributed under the License is distributed on an "AS IS" BASIS, 17465 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17466 See the License for the specific language governing permissions and 17467 limitations under the License. 17468*/ 17469 17470/** 17471TinCan client library 17472 17473@module TinCan 17474@submodule TinCan.Environment.Browser 17475**/ 17476(function () { 17477 /* globals window, XMLHttpRequest, XDomainRequest, Blob */ 17478 "use strict"; 17479 var LOG_SRC = "Environment.Browser", 17480 requestComplete, 17481 __IEModeConversion, 17482 nativeRequest, 17483 xdrRequest, 17484 __createJSONSegment, 17485 __createAttachmentSegment, 17486 __delay, 17487 env = {}, 17488 log = TinCan.prototype.log; 17489 17490 if (typeof window === "undefined") { 17491 log("'window' not defined", LOG_SRC); 17492 return; 17493 } 17494 17495 /* Shims for browsers not supporting our needs, mainly IE */ 17496 17497 // 17498 // Make Date.now safe for IE < 9 17499 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/now 17500 // 17501 if (!Date.now) { 17502 Date.now = function () { 17503 return +(new Date ()); 17504 }; 17505 } 17506 17507 // 17508 // Add .forEach implementation for supporting our string encoding polyfill 17509 // imported from js-polyfills to avoid bringing in the whole es5 shim 17510 // for now, a rewrite probably moves all shims out of the main build or at 17511 // least Environment file and leverages more of them 17512 // 17513 17514 // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] ) 17515 // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach 17516 if (!Array.prototype.forEach) { 17517 /* jshint freeze:false,bitwise:false */ 17518 Array.prototype.forEach = function (fun /*, thisp */) { 17519 if (this === void 0 || this === null) { throw new TypeError(); } 17520 17521 var t = Object(this); 17522 var len = t.length >>> 0; 17523 if (typeof fun !== "function") { throw new TypeError(); } 17524 17525 var thisp = arguments[1], i; 17526 for (i = 0; i < len; i += 1) { 17527 if (i in t) { 17528 fun.call(thisp, t[i], i, t); 17529 } 17530 } 17531 }; 17532 } 17533 17534 /* Detect CORS and XDR support */ 17535 env.hasCORS = false; 17536 env.useXDR = false; 17537 17538 if (typeof XMLHttpRequest !== "undefined" && typeof (new XMLHttpRequest()).withCredentials !== "undefined") { 17539 env.hasCORS = true; 17540 } 17541 else if (typeof XDomainRequest !== "undefined") { 17542 env.hasCORS = true; 17543 env.useXDR = true; 17544 } 17545 17546 // TODO: should we have our own internal "Request" object 17547 // that replaces the need for "control"? 17548 17549 // 17550 // Setup request callback 17551 // 17552 requestComplete = function (xhr, cfg, control) { 17553 log("requestComplete: " + control.finished + ", xhr.status: " + xhr.status, LOG_SRC); 17554 var requestCompleteResult, 17555 notFoundOk, 17556 httpStatus; 17557 17558 // 17559 // XDomainRequest doesn't give us a way to get the status, 17560 // so allow passing in a forged one 17561 // 17562 if (typeof xhr.status === "undefined") { 17563 httpStatus = control.fakeStatus; 17564 } 17565 else { 17566 // 17567 // older versions of IE don't properly handle 204 status codes 17568 // so correct when receiving a 1223 to be 204 locally 17569 // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request 17570 // 17571 httpStatus = (xhr.status === 1223) ? 204 : xhr.status; 17572 } 17573 17574 if (! control.finished) { 17575 // may be in sync or async mode, using XMLHttpRequest or IE XDomainRequest, onreadystatechange or 17576 // onload or both might fire depending upon browser, just covering all bases with event hooks and 17577 // using 'finished' flag to avoid triggering events multiple times 17578 control.finished = true; 17579 17580 notFoundOk = (cfg.ignore404 && httpStatus === 404); 17581 if ((httpStatus >= 200 && httpStatus < 400) || notFoundOk) { 17582 if (cfg.callback) { 17583 cfg.callback(null, xhr); 17584 } 17585 else { 17586 requestCompleteResult = { 17587 err: null, 17588 xhr: xhr 17589 }; 17590 return requestCompleteResult; 17591 } 17592 } 17593 else { 17594 requestCompleteResult = { 17595 err: httpStatus, 17596 xhr: xhr 17597 }; 17598 if (httpStatus === 0) { 17599 log("[warning] There was a problem communicating with the Learning Record Store. Aborted, offline, or invalid CORS endpoint (" + httpStatus + ")", LOG_SRC); 17600 } 17601 else { 17602 log("[warning] There was a problem communicating with the Learning Record Store. (" + httpStatus + " | " + xhr.responseText+ ")", LOG_SRC); 17603 } 17604 if (cfg.callback) { 17605 cfg.callback(httpStatus, xhr); 17606 } 17607 return requestCompleteResult; 17608 } 17609 } 17610 else { 17611 return requestCompleteResult; 17612 } 17613 }; 17614 17615 // 17616 // Converts an HTTP request cfg of above a set length (//MAX_REQUEST_LENGTH) to a post 17617 // request cfg, with the original request as the form data. 17618 // 17619 __IEModeConversion = function (fullUrl, headers, pairs, cfg) { 17620 var prop; 17621 17622 // 'pairs' already holds the original cfg params, now needs headers and data 17623 // from the original cfg to add as the form data to the POST request 17624 for (prop in headers) { 17625 if (headers.hasOwnProperty(prop)) { 17626 pairs.push(prop + "=" + encodeURIComponent(headers[prop])); 17627 } 17628 } 17629 17630 if (typeof cfg.data !== "undefined") { 17631 pairs.push("content=" + encodeURIComponent(cfg.data)); 17632 } 17633 17634 // the Authorization and xAPI version headers need to still be present, but 17635 // the content type must exist and be of type application/x-www-form-urlencoded
17636 headers["Content-Type"] = "application/x-www-form-urlencoded"; 17637 fullUrl += "?method=" + cfg.method; 17638 cfg.method = "POST"; 17639 cfg.params = {}; 17640 if (pairs.length > 0) { 17641 cfg.data = pairs.join("&"); 17642 } 17643 return fullUrl; 17644 }; 17645 17646 // 17647 // one of the two of these is stuffed into the LRS' instance 17648 // as ._makeRequest 17649 // 17650 nativeRequest = function (fullUrl, headers, cfg) { 17651 /*global ActiveXObject*/ 17652 log("sendRequest using XMLHttpRequest", LOG_SRC); 17653 var self = this, 17654 xhr, 17655 prop, 17656 pairs = [], 17657 data, 17658 control = { 17659 finished: false, 17660 fakeStatus: null 17661 }, 17662 async = typeof cfg.callback !== "undefined", 17663 fullRequest = fullUrl, 17664 err, 17665 MAX_REQUEST_LENGTH = 2048 17666 ; 17667 log("sendRequest using XMLHttpRequest - async: " + async, LOG_SRC); 17668 17669 for (prop in cfg.params) { 17670 if (cfg.params.hasOwnProperty(prop)) { 17671 pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop])); 17672 } 17673 } 17674 17675 if (pairs.length > 0) { 17676 fullRequest += "?" + pairs.join("&"); 17677 } 17678 17679 if (fullRequest.length >= MAX_REQUEST_LENGTH) { 17680 if (typeof cfg.method === "undefined") { 17681 err = new Error("method must not be undefined for an IE Mode Request conversion"); 17682 if (typeof cfg.callback !== "undefined") { 17683 cfg.callback(err, null); 17684 } 17685 return { 17686 err: err, 17687 xhr: null 17688 }; 17689 } 17690 17691 fullUrl = __IEModeConversion(fullUrl, headers, pairs, cfg); 17692 } 17693 else { 17694 fullUrl = fullRequest; 17695 } 17696 17697 if (typeof XMLHttpRequest !== "undefined") { 17698 xhr = new XMLHttpRequest(); 17699 } 17700 else { 17701 // 17702 // IE6 implements XMLHttpRequest through ActiveX control 17703 // http://blogs.msdn.com/b/ie/archive/2006/01/23/516393.aspx 17704 // 17705 xhr = new ActiveXObject("Microsoft.XMLHTTP"); 17706 17707 if (cfg.expectMultipart) { 17708 err = new Error("Attachment support not available"); 17709 if (typeof cfg.callback !== "undefined") { 17710 cfg.callback(err, null); 17711 } 17712 return { 17713 err: err, 17714 xhr: null 17715 }; 17716 } 17717 } 17718 17719 xhr.open(cfg.method, fullUrl, async); 17720 17721 // 17722 // setting the .responseType before .open was causing IE to fail 17723 // with a StateError, so moved it to here 17724 // 17725 if (cfg.expectMultipart) { 17726 xhr.responseType = "arraybuffer"; 17727 } 17728 17729 for (prop in headers) { 17730 if (headers.hasOwnProperty(prop)) { 17731 xhr.setRequestHeader(prop, headers[prop]); 17732 } 17733 } 17734 17735 data = cfg.data; 17736 17737 if (async) { 17738 xhr.onreadystatechange = function () { 17739 log("xhr.onreadystatechange - xhr.readyState: " + xhr.readyState, LOG_SRC); 17740 if (xhr.readyState === 4) { 17741 requestComplete.call(self, xhr, cfg, control); 17742 } 17743 }; 17744 } 17745 17746 // 17747 // research indicates that IE is known to just throw exceptions 17748 // on .send and it seems everyone pretty much just ignores them 17749 // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549 17750 // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97) 17751 // 17752 try { 17753 xhr.send(data); 17754 } 17755 catch (ex) { 17756 log("sendRequest caught send exception: " + ex, LOG_SRC); 17757 } 17758 17759 if (async) { 17760 // 17761 // for async requests give them the XHR object directly 17762 // as the return value, the actual stuff they should be 17763 // caring about is params to the callback, for sync 17764 // requests they got the return value above 17765 // 17766 return xhr; 17767 } 17768 17769 return requestComplete.call(this, xhr, cfg, control); 17770 }; 17771 xdrRequest = function (fullUrl, headers, cfg) { 17772 log("sendRequest using XDomainRequest", LOG_SRC); 17773 var self = this, 17774 xhr, 17775 pairs = [], 17776 data, 17777 prop, 17778 until, 17779 control = { 17780 finished: false, 17781 fakeStatus: null 17782 }, 17783 err; 17784 17785 if (cfg.expectMultipart) { 17786 err = new Error("Attachment support not available"); 17787 if (typeof cfg.callback !== "undefined") { 17788 cfg.callback(err, null); 17789 } 17790 return { 17791 err: err, 17792 xhr: null 17793 }; 17794 } 17795 17796 // method has to go on querystring, and nothing else, 17797 // and the actual method is then always POST 17798 fullUrl += "?method=" + cfg.method; 17799 17800 // params end up in the body 17801 for (prop in cfg.params) { 17802 if (cfg.params.hasOwnProperty(prop)) { 17803 pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop])); 17804 } 17805 } 17806 17807 // headers go into form data 17808 for (prop in headers) { 17809 if (headers.hasOwnProperty(prop)) { 17810 pairs.push(prop + "=" + encodeURIComponent(headers[prop])); 17811 } 17812 } 17813 17814 // the original data is repackaged as "content" form var 17815 if (typeof cfg.data !== "undefined") { 17816 pairs.push("content=" + encodeURIComponent(cfg.data)); 17817 } 17818 17819 data = pairs.join("&"); 17820 17821 xhr = new XDomainRequest (); 17822 xhr.open("POST", fullUrl); 17823 17824 if (! cfg.callback) { 17825 xhr.onload = function () { 17826 control.fakeStatus = 200; 17827 }; 17828 xhr.onerror = function () { 17829 control.fakeStatus = 400; 17830 }; 17831 xhr.ontimeout = function () { 17832 control.fakeStatus = 0; 17833 }; 17834 } 17835 else { 17836 xhr.onload = function () { 17837 control.fakeStatus = 200; 17838 requestComplete.call(self, xhr, cfg, control); 17839 }; 17840 xhr.onerror = function () { 17841 control.fakeStatus = 400; 17842 requestComplete.call(self, xhr, cfg, control); 17843 }; 17844 xhr.ontimeout = function () { 17845 control.fakeStatus = 0; 17846 requestComplete.call(self, xhr, cfg, control); 17847 }; 17848 } 17849 17850 // IE likes to randomly abort requests when some handlers 17851 // aren't defined, so define them with no-ops, see: 17852 // 17853 // http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/ 17854 // http://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified 17855 // 17856 xhr.onprogress = function () {}; 17857 xhr.timeout = 0; 17858 17859 // 17860 // research indicates that IE is known to just throw exceptions 17861 // on .send and it seems everyone pretty much just ignores them 17862 // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549 17863 // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97) 17864 // 17865 try { 17866 xhr.send(data); 17867 } 17868 catch (ex) { 17869 log("sendRequest caught send exception: " + ex, LOG_SRC); 17870 } 17871 17872 if (! cfg.callback) { 17873 // synchronous call in IE, with no synchronous mode available 17874 until = 10000 + Date.now(); 17875 log("sendRequest - until: " + until + ", finished: " + control.finished, LOG_SRC); 17876 17877 while (Date.now() < until && control.fakeStatus === null) { 17878 //log("calling __delay", LOG_SRC); 17879 __delay(); 17880 } 17881 return requestComplete.call(self, xhr, cfg, control); 17882 } 17883 17884 // 17885 // for async requests give them the XHR object directly 17886 // as the return value, the actual stuff they should be 17887 // caring about is params to the callback, for sync 17888 // requests they got the return value above 17889 // 17890 return xhr; 17891 }; 17892 17893 // 17894 // Override LRS' init method to set up our request handling 17895 // capabilities 17896 // 17897 TinCan.LRS.prototype._initByEnvironment = function (cfg) { 17898 /*jslint regexp: true, laxbreak: true */ 17899 /* globals location */ 17900 log("_initByEnvironment", LOG_SRC); 17901 var urlParts, 17902 schemeMatches, 17903 locationPort, 17904 isXD 17905 ; 17906 17907 cfg = cfg || {}; 17908 17909 // 17910 // default to native request mode 17911 // 17912 this._makeRequest = nativeRequest; 17913 17914 // 17915 // overload LRS ._IEModeConversion to be able to test this method, 17916 // which only applies in a browser setting 17917 // 17918 this._IEModeConversion = __IEModeConversion; 17919 17920 urlParts = this.endpoint.toLowerCase().match(/([A-Za-z]+:)\/\/([^:\/]+):?(\d+)?(\/.*)?$/); 17921 if (urlParts === null) {
17922 log("[error] LRS invalid: failed to divide URL parts", LOG_SRC); 17923 throw { 17924 code: 4, 17925 mesg: "LRS invalid: failed to divide URL parts" 17926 }; 17927 } 17928 17929 // 17930 // determine whether this is a cross domain request, 17931 // whether our browser has CORS support at all, and then 17932 // if it does then if we are in IE with XDR only check that 17933 // the schemes match to see if we should be able to talk to 17934 // the LRS 17935 // 17936 locationPort = location.port; 17937 schemeMatches = location.protocol.toLowerCase() === urlParts[1]; 17938 17939 // 17940 // normalize the location.port cause it appears to be "" when 80/443 17941 // but our endpoint may have provided it 17942 // 17943 if (locationPort === "") { 17944 locationPort = (location.protocol.toLowerCase() === "http:" ? "80" : (location.protocol.toLowerCase() === "https:" ? "443" : "")); 17945 } 17946 17947 isXD = ( 17948 // is same scheme? 17949 ! schemeMatches 17950 17951 // is same host? 17952 || location.hostname.toLowerCase() !== urlParts[2] 17953 17954 // is same port? 17955 || locationPort !== ( 17956 (urlParts[3] !== null && typeof urlParts[3] !== "undefined" && urlParts[3] !== "") ? urlParts[3] : (urlParts[1] === "http:" ? "80" : (urlParts[1] === "https:" ? "443" : "")) 17957 ) 17958 ); 17959 if (isXD) { 17960 if (env.hasCORS) { 17961 if (env.useXDR && schemeMatches) { 17962 this._makeRequest = xdrRequest; 17963 } 17964 else if (env.useXDR && ! schemeMatches) { 17965 if (cfg.allowFail) { 17966 log("[warning] LRS invalid: cross domain request for differing scheme in IE with XDR (allowed to fail)", LOG_SRC); 17967 } 17968 else { 17969 log("[error] LRS invalid: cross domain request for differing scheme in IE with XDR", LOG_SRC); 17970 throw { 17971 code: 2, 17972 mesg: "LRS invalid: cross domain request for differing scheme in IE with XDR" 17973 }; 17974 } 17975 } 17976 } 17977 else { 17978 if (cfg.allowFail) { 17979 log("[warning] LRS invalid: cross domain requests not supported in this browser (allowed to fail)", LOG_SRC); 17980 } 17981 else { 17982 log("[error] LRS invalid: cross domain requests not supported in this browser", LOG_SRC); 17983 throw { 17984 code: 1, 17985 mesg: "LRS invalid: cross domain requests not supported in this browser" 17986 }; 17987 } 17988 } 17989 } 17990 }; 17991 17992 /** 17993 Non-environment safe method used to create a delay to give impression 17994 of synchronous response (for IE, shocker) 17995 17996 @method __delay 17997 @private 17998 */ 17999 __delay = function () { 18000 // 18001 // use a synchronous request to the current location to allow the browser 18002 // to yield to the asynchronous request's events but still block in the 18003 // outer loop to make it seem synchronous to the end user 18004 // 18005 // removing this made the while loop too tight to allow the asynchronous 18006 // events through to get handled so that the response was correctly handled 18007 // 18008 var xhr = new XMLHttpRequest (), 18009 url = window.location + "?forcenocache=" + TinCan.Utils.getUUID() 18010 ; 18011 xhr.open("GET", url, false); 18012 xhr.send(null); 18013 }; 18014 18015 // 18016 // Synchronous xhr handling is accepted in the browser environment 18017 // 18018 TinCan.LRS.syncEnabled = true; 18019 18020 TinCan.LRS.prototype._getMultipartRequestData = function (boundary, jsonContent, requestAttachments) { 18021 var parts = [], 18022 i; 18023 18024 parts.push( 18025 __createJSONSegment( 18026 boundary, 18027 jsonContent 18028 ) 18029 ); 18030 for (i = 0; i < requestAttachments.length; i += 1) { 18031 if (requestAttachments[i].content !== null) { 18032 parts.push( 18033 __createAttachmentSegment( 18034 boundary, 18035 requestAttachments[i].content, 18036 requestAttachments[i].sha2, 18037 requestAttachments[i].contentType 18038 ) 18039 ); 18040 } 18041 } 18042 parts.push("\r\n--" + boundary + "--\r\n"); 18043 18044 return new Blob(parts); 18045 }; 18046 18047 __createJSONSegment = function (boundary, jsonContent) { 18048 var content = [ 18049 "--" + boundary, 18050 "Content-Type: application/json", 18051 "", 18052 JSON.stringify(jsonContent) 18053 ].join("\r\n"); 18054 18055 content += "\r\n"; 18056 18057 return content; 18058 }; 18059 18060 __createAttachmentSegment = function (boundary, content, sha2, contentType) { 18061 var blobParts = [], 18062 header = [ 18063 "--" + boundary, 18064 "Content-Type: " + contentType, 18065 "Content-Transfer-Encoding: binary",
18066 "X-Experience-API-Hash: " + sha2 18067 ].join("\r\n"); 18068 18069 header += "\r\n\r\n"; 18070 18071 blobParts.push(header); 18072 blobParts.push(content); 18073 18074 return new Blob(blobParts); 18075 }; 18076 18077 TinCan.Utils.stringToArrayBuffer = function (content, encoding) { 18078 /* global TextEncoder */ 18079 var encoder; 18080 18081 if (! encoding) { 18082 encoding = TinCan.Utils.defaultEncoding; 18083 } 18084 18085 encoder = new TextEncoder(encoding); 18086 18087 return encoder.encode(content).buffer; 18088 }; 18089 18090 TinCan.Utils.stringFromArrayBuffer = function (content, encoding) { 18091 /* global TextDecoder */ 18092 var decoder; 18093 18094 if (! encoding) { 18095 encoding = TinCan.Utils.defaultEncoding; 18096 } 18097 18098 decoder = new TextDecoder(encoding); 18099 18100 return decoder.decode(content); 18101 }; 18102}()); 18103
vendor: 30,509 bytes, lines 18104-18951
18104/* 18105 Copyright (c) 2010, Linden Research, Inc. 18106 Copyright (c) 2014, Joshua Bell 18107 18108 Permission is hereby granted, free of charge, to any person obtaining a copy 18109 of this software and associated documentation files (the "Software"), to deal 18110 in the Software without restriction, including without limitation the rights 18111 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18112 copies of the Software, and to permit persons to whom the Software is 18113 furnished to do so, subject to the following conditions: 18114 18115 The above copyright notice and this permission notice shall be included in 18116 all copies or substantial portions of the Software. 18117 18118 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18119 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18120 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18121 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18122 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18123 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18124 THE SOFTWARE. 18125 $/LicenseInfo$ 18126 */ 18127 18128// Original can be found at: 18129// https://bitbucket.org/lindenlab/llsd 18130// Modifications by Joshua Bell [email protected] 18131// https://github.com/inexorabletash/polyfill 18132 18133// ES3/ES5 implementation of the Krhonos Typed Array Specification 18134// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ 18135// Date: 2011-02-01 18136// 18137// Variations: 18138// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) 18139// * Gradually migrating structure from Khronos spec to ES2015 spec 18140// 18141// Caveats: 18142// * Beyond 10000 or so entries, polyfilled array accessors (ta[0], 18143// etc) become memory-prohibitive, so array creation will fail. Set 18144// self.TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS=true to disable 18145// creation of accessors. Your code will need to use the 18146// non-standard get()/set() instead, and will need to add those to 18147// native arrays for interop. 18148(function(global) { 18149 'use strict'; 18150 var undefined = (void 0); // Paranoia 18151 18152 // Beyond this value, index getters/setters (i.e. array[0], array[1]) are so slow to 18153 // create, and consume so much memory, that the browser appears frozen. 18154 var MAX_ARRAY_LENGTH = 1e5; 18155 18156 // Approximations of internal ECMAScript conversion functions 18157 function Type(v) { 18158 switch(typeof v) { 18159 case 'undefined': return 'undefined'; 18160 case 'boolean': return 'boolean'; 18161 case 'number': return 'number'; 18162 case 'string': return 'string'; 18163 default: return v === null ? 'null' : 'object'; 18164 } 18165 } 18166 18167 // Class returns internal [[Class]] property, used to avoid cross-frame instanceof issues: 18168 function Class(v) { return Object.prototype.toString.call(v).replace(/^\[object *|\]$/g, ''); } 18169 function IsCallable(o) { return typeof o === 'function'; } 18170 function ToObject(v) { 18171 if (v === null || v === undefined) throw TypeError(); 18172 return Object(v); 18173 } 18174 function ToInt32(v) { return v >> 0; } 18175 function ToUint32(v) { return v >>> 0; } 18176 18177 // Snapshot intrinsics 18178 var LN2 = Math.LN2, 18179 abs = Math.abs, 18180 floor = Math.floor, 18181 log = Math.log, 18182 max = Math.max, 18183 min = Math.min, 18184 pow = Math.pow, 18185 round = Math.round; 18186 18187 // emulate ES5 getter/setter API using legacy APIs 18188 // http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx 18189 // (second clause tests for Object.defineProperty() in IE<9 that only supports extending DOM prototypes, but 18190 // note that IE<9 does not support __defineGetter__ or __defineSetter__ so it just renders the method harmless) 18191 18192 (function() { 18193 var orig = Object.defineProperty; 18194 var dom_only = !(function(){try{return Object.defineProperty({},'x',{});}catch(_){return false;}}()); 18195 18196 if (!orig || dom_only) { 18197 Object.defineProperty = function (o, prop, desc) { 18198 // In IE8 try built-in implementation for defining properties on DOM prototypes. 18199 if (orig) 18200 try { return orig(o, prop, desc); } catch (_) {} 18201 if (o !== Object(o)) 18202 throw TypeError('Object.defineProperty called on non-object'); 18203 if (Object.prototype.__defineGetter__ && ('get' in desc)) 18204 Object.prototype.__defineGetter__.call(o, prop, desc.get); 18205 if (Object.prototype.__defineSetter__ && ('set' in desc)) 18206 Object.prototype.__defineSetter__.call(o, prop, desc.set); 18207 if ('value' in desc) 18208 o[prop] = desc.value; 18209 return o; 18210 }; 18211 } 18212 }()); 18213 18214 // ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value) 18215 // for index in 0 ... obj.length 18216 function makeArrayAccessors(obj) { 18217 if ('TYPED_ARRAY_POLYFILL_NO_ARRAY_ACCESSORS' in global) 18218 return; 18219 18220 if (obj.length > MAX_ARRAY_LENGTH) throw RangeError('Array too large for polyfill'); 18221 18222 function makeArrayAccessor(index) { 18223 Object.defineProperty(obj, index, { 18224 'get': function() { return obj._getter(index); }, 18225 'set': function(v) { obj._setter(index, v); }, 18226 enumerable: true, 18227 configurable: false 18228 }); 18229 } 18230 18231 var i; 18232 for (i = 0; i < obj.length; i += 1) { 18233 makeArrayAccessor(i); 18234 } 18235 } 18236 18237 // Internal conversion functions: 18238 // pack<Type>() - take a number (interpreted as Type), output a byte array 18239 // unpack<Type>() - take a byte array, output a Type-like number 18240 18241 function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; } 18242 function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; } 18243 18244 function packI8(n) { return [n & 0xff]; } 18245 function unpackI8(bytes) { return as_signed(bytes[0], 8); } 18246 18247 function packU8(n) { return [n & 0xff]; } 18248 function unpackU8(bytes) { return as_unsigned(bytes[0], 8); } 18249 18250 function packU8Clamped(n) { n = round(Number(n)); return [n < 0 ? 0 : n > 0xff ? 0xff : n & 0xff]; } 18251 18252 function packI16(n) { return [n & 0xff, (n >> 8) & 0xff]; } 18253 function unpackI16(bytes) { return as_signed(bytes[1] << 8 | bytes[0], 16); } 18254 18255 function packU16(n) { return [n & 0xff, (n >> 8) & 0xff]; } 18256 function unpackU16(bytes) { return as_unsigned(bytes[1] << 8 | bytes[0], 16); } 18257 18258 function packI32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; } 18259 function unpackI32(bytes) { return as_signed(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); } 18260 18261 function packU32(n) { return [n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]; } 18262 function unpackU32(bytes) { return as_unsigned(bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0], 32); } 18263 18264 function packIEEE754(v, ebits, fbits) { 18265 18266 var bias = (1 << (ebits - 1)) - 1; 18267 18268 function roundToEven(n) { 18269 var w = floor(n), f = n - w; 18270 if (f < 0.5) 18271 return w; 18272 if (f > 0.5) 18273 return w + 1; 18274 return w % 2 ? w + 1 : w; 18275 } 18276 18277 // Compute sign, exponent, fraction 18278 var s, e, f; 18279 if (v !== v) { 18280 // NaN 18281 // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping 18282 e = (1 << ebits) - 1; f = pow(2, fbits - 1); s = 0; 18283 } else if (v === Infinity || v === -Infinity) { 18284 e = (1 << ebits) - 1; f = 0; s = (v < 0) ? 1 : 0; 18285 } else if (v === 0) { 18286 e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0; 18287 } else { 18288 s = v < 0; 18289 v = abs(v); 18290 18291 if (v >= pow(2, 1 - bias)) { 18292 // Normalized 18293 e = min(floor(log(v) / LN2), 1023); 18294 var significand = v / pow(2, e); 18295 if (significand < 1) { 18296 e -= 1; 18297 significand *= 2; 18298 } 18299 if (significand >= 2) { 18300 e += 1; 18301 significand /= 2; 18302 } 18303 var d = pow(2, fbits); 18304 f = roundToEven(significand * d) - d; 18305 e += bias; 18306 if (f / d >= 1) { 18307 e += 1; 18308 f = 0; 18309 } 18310 if (e > 2 * bias) { 18311 // Overflow 18312 e = (1 << ebits) - 1; 18313 f = 0; 18314 } 18315 } else { 18316 // Denormalized 18317 e = 0; 18318 f = roundToEven(v / pow(2, 1 - bias - fbits)); 18319 } 18320 } 18321 18322 // Pack sign, exponent, fraction 18323 var bits = [], i; 18324 for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = floor(f / 2); } 18325 for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = floor(e / 2); } 18326 bits.push(s ? 1 : 0); 18327 bits.reverse(); 18328 var str = bits.join(''); 18329 18330 // Bits to bytes 18331 var bytes = []; 18332 while (str.length) { 18333 bytes.unshift(parseInt(str.substring(0, 8), 2)); 18334 str = str.substring(8); 18335 } 18336 return bytes; 18337 } 18338 18339 function unpackIEEE754(bytes, ebits, fbits) { 18340 // Bytes to bits 18341 var bits = [], i, j, b, str, 18342 bias, s, e, f; 18343 18344 for (i = 0; i < bytes.length; ++i) { 18345 b = bytes[i]; 18346 for (j = 8; j; j -= 1) { 18347 bits.push(b % 2 ? 1 : 0); b = b >> 1; 18348 } 18349 } 18350 bits.reverse(); 18351 str = bits.join(''); 18352 18353 // Unpack sign, exponent, fraction 18354 bias = (1 << (ebits - 1)) - 1; 18355 s = parseInt(str.substring(0, 1), 2) ? -1 : 1; 18356 e = parseInt(str.substring(1, 1 + ebits), 2); 18357 f = parseInt(str.substring(1 + ebits), 2); 18358 18359 // Produce number 18360 if (e === (1 << ebits) - 1) { 18361 return f !== 0 ? NaN : s * Infinity; 18362 } else if (e > 0) { 18363 // Normalized 18364 return s * pow(2, e - bias) * (1 + f / pow(2, fbits)); 18365 } else if (f !== 0) { 18366 // Denormalized 18367 return s * pow(2, -(bias - 1)) * (f / pow(2, fbits)); 18368 } else { 18369 return s < 0 ? -0 : 0; 18370 } 18371 } 18372 18373 function unpackF64(b) { return unpackIEEE754(b, 11, 52); } 18374 function packF64(v) { return packIEEE754(v, 11, 52); } 18375 function unpackF32(b) { return unpackIEEE754(b, 8, 23); } 18376 function packF32(v) { return packIEEE754(v, 8, 23); } 18377 18378 // 18379 // 3 The ArrayBuffer Type 18380 // 18381 18382 (function() { 18383 18384 function ArrayBuffer(length) { 18385 length = ToInt32(length); 18386 if (length < 0) throw RangeError('ArrayBuffer size is not a small enough positive integer.'); 18387 Object.defineProperty(this, 'byteLength', {value: length}); 18388 Object.defineProperty(this, '_bytes', {value: Array(length)}); 18389 18390 for (var i = 0; i < length; i += 1) 18391 this._bytes[i] = 0; 18392 } 18393 18394 global.ArrayBuffer = global.ArrayBuffer || ArrayBuffer; 18395 18396 // 18397 // 5 The Typed Array View Types 18398 // 18399 18400 function $TypedArray$() { 18401 18402 // %TypedArray% ( length ) 18403 if (!arguments.length || typeof arguments[0] !== 'object') { 18404 return (function(length) { 18405 length = ToInt32(length); 18406 if (length < 0) throw RangeError('length is not a small enough positive integer.'); 18407 Object.defineProperty(this, 'length', {value: length}); 18408 Object.defineProperty(this, 'byteLength', {value: length * this.BYTES_PER_ELEMENT}); 18409 Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(this.byteLength)}); 18410 Object.defineProperty(this, 'byteOffset', {value: 0}); 18411 18412 }).apply(this, arguments); 18413 } 18414 18415 // %TypedArray% ( typedArray ) 18416 if (arguments.length >= 1 && 18417 Type(arguments[0]) === 'object' && 18418 arguments[0] instanceof $TypedArray$) { 18419 return (function(typedArray){ 18420 if (this.constructor !== typedArray.constructor) throw TypeError(); 18421 18422 var byteLength = typedArray.length * this.BYTES_PER_ELEMENT; 18423 Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); 18424 Object.defineProperty(this, 'byteLength', {value: byteLength}); 18425 Object.defineProperty(this, 'byteOffset', {value: 0}); 18426 Object.defineProperty(this, 'length', {value: typedArray.length}); 18427 18428 for (var i = 0; i < this.length; i += 1) 18429 this._setter(i, typedArray._getter(i)); 18430 18431 }).apply(this, arguments); 18432 } 18433 18434 // %TypedArray% ( array ) 18435 if (arguments.length >= 1 && 18436 Type(arguments[0]) === 'object' && 18437 !(arguments[0] instanceof $TypedArray$) && 18438 !(arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { 18439 return (function(array) { 18440 18441 var byteLength = array.length * this.BYTES_PER_ELEMENT; 18442 Object.defineProperty(this, 'buffer', {value: new ArrayBuffer(byteLength)}); 18443 Object.defineProperty(this, 'byteLength', {value: byteLength}); 18444 Object.defineProperty(this, 'byteOffset', {value: 0}); 18445 Object.defineProperty(this, 'length', {value: array.length}); 18446 18447 for (var i = 0; i < this.length; i += 1) { 18448 var s = array[i]; 18449 this._setter(i, Number(s)); 18450 } 18451 }).apply(this, arguments); 18452 } 18453 18454 // %TypedArray% ( buffer, byteOffset=0, length=undefined ) 18455 if (arguments.length >= 1 && 18456 Type(arguments[0]) === 'object' && 18457 (arguments[0] instanceof ArrayBuffer || Class(arguments[0]) === 'ArrayBuffer')) { 18458 return (function(buffer, byteOffset, length) { 18459 18460 byteOffset = ToUint32(byteOffset); 18461 if (byteOffset > buffer.byteLength) 18462 throw RangeError('byteOffset out of range'); 18463 18464 // The given byteOffset must be a multiple of the element 18465 // size of the specific type, otherwise an exception is raised. 18466 if (byteOffset % this.BYTES_PER_ELEMENT) 18467 throw RangeError('buffer length minus the byteOffset is not a multiple of the element size.'); 18468 18469 if (length === undefined) { 18470 var byteLength = buffer.byteLength - byteOffset; 18471 if (byteLength % this.BYTES_PER_ELEMENT) 18472 throw RangeError('length of buffer minus byteOffset not a multiple of the element size'); 18473 length = byteLength / this.BYTES_PER_ELEMENT; 18474 18475 } else { 18476 length = ToUint32(length); 18477 byteLength = length * this.BYTES_PER_ELEMENT; 18478 } 18479 18480 if ((byteOffset + byteLength) > buffer.byteLength) 18481 throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); 18482 18483 Object.defineProperty(this, 'buffer', {value: buffer}); 18484 Object.defineProperty(this, 'byteLength', {value: byteLength}); 18485 Object.defineProperty(this, 'byteOffset', {value: byteOffset}); 18486 Object.defineProperty(this, 'length', {value: length}); 18487 18488 }).apply(this, arguments); 18489 } 18490 18491 // %TypedArray% ( all other argument combinations ) 18492 throw TypeError(); 18493 } 18494 18495 // Properties of the %TypedArray Instrinsic Object 18496 18497 // %TypedArray%.from ( source , mapfn=undefined, thisArg=undefined ) 18498 Object.defineProperty($TypedArray$, 'from', {value: function(iterable) { 18499 return new this(iterable); 18500 }}); 18501 18502 // %TypedArray%.of ( ...items ) 18503 Object.defineProperty($TypedArray$, 'of', {value: function(/*...items*/) { 18504 return new this(arguments); 18505 }}); 18506 18507 // %TypedArray%.prototype 18508 var $TypedArrayPrototype$ = {}; 18509 $TypedArray$.prototype = $TypedArrayPrototype$; 18510 18511 // WebIDL: getter type (unsigned long index); 18512 Object.defineProperty($TypedArray$.prototype, '_getter', {value: function(index) { 18513 if (arguments.length < 1) throw SyntaxError('Not enough arguments'); 18514 18515 index = ToUint32(index); 18516 if (index >= this.length) 18517 return undefined; 18518 18519 var bytes = [], i, o; 18520 for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; 18521 i < this.BYTES_PER_ELEMENT; 18522 i += 1, o += 1) { 18523 bytes.push(this.buffer._bytes[o]); 18524 } 18525 return this._unpack(bytes); 18526 }}); 18527 18528 // NONSTANDARD: convenience alias for getter: type get(unsigned long index); 18529 Object.defineProperty($TypedArray$.prototype, 'get', {value: $TypedArray$.prototype._getter}); 18530 18531 // WebIDL: setter void (unsigned long index, type value); 18532 Object.defineProperty($TypedArray$.prototype, '_setter', {value: function(index, value) { 18533 if (arguments.length < 2) throw SyntaxError('Not enough arguments'); 18534 18535 index = ToUint32(index); 18536 if (index >= this.length) 18537 return; 18538 18539 var bytes = this._pack(value), i, o; 18540 for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT; 18541 i < this.BYTES_PER_ELEMENT; 18542 i += 1, o += 1) { 18543 this.buffer._bytes[o] = bytes[i]; 18544 } 18545 }}); 18546 18547 // get %TypedArray%.prototype.buffer 18548 // get %TypedArray%.prototype.byteLength 18549 // get %TypedArray%.prototype.byteOffset 18550 // -- applied directly to the object in the constructor 18551 18552 // %TypedArray%.prototype.constructor 18553 Object.defineProperty($TypedArray$.prototype, 'constructor', {value: $TypedArray$}); 18554 18555 // %TypedArray%.prototype.copyWithin (target, start, end = this.length ) 18556 Object.defineProperty($TypedArray$.prototype, 'copyWithin', {value: function(target, start) { 18557 var end = arguments[2]; 18558 18559 var o = ToObject(this); 18560 var lenVal = o.length; 18561 var len = ToUint32(lenVal); 18562 len = max(len, 0); 18563 var relativeTarget = ToInt32(target); 18564 var to; 18565 if (relativeTarget < 0) 18566 to = max(len + relativeTarget, 0); 18567 else 18568 to = min(relativeTarget, len); 18569 var relativeStart = ToInt32(start); 18570 var from; 18571 if (relativeStart < 0) 18572 from = max(len + relativeStart, 0); 18573 else 18574 from = min(relativeStart, len); 18575 var relativeEnd; 18576 if (end === undefined) 18577 relativeEnd = len; 18578 else 18579 relativeEnd = ToInt32(end); 18580 var final; 18581 if (relativeEnd < 0) 18582 final = max(len + relativeEnd, 0); 18583 else 18584 final = min(relativeEnd, len); 18585 var count = min(final - from, len - to); 18586 var direction; 18587 if (from < to && to < from + count) { 18588 direction = -1; 18589 from = from + count - 1; 18590 to = to + count - 1; 18591 } else { 18592 direction = 1; 18593 } 18594 while (count > 0) { 18595 o._setter(to, o._getter(from)); 18596 from = from + direction; 18597 to = to + direction; 18598 count = count - 1; 18599 } 18600 return o; 18601 }}); 18602 18603 // %TypedArray%.prototype.entries ( ) 18604 // -- defined in es6.js to shim browsers w/ native TypedArrays 18605 18606 // %TypedArray%.prototype.every ( callbackfn, thisArg = undefined ) 18607 Object.defineProperty($TypedArray$.prototype, 'every', {value: function(callbackfn) { 18608 if (this === undefined || this === null) throw TypeError(); 18609 var t = Object(this); 18610 var len = ToUint32(t.length); 18611 if (!IsCallable(callbackfn)) throw TypeError(); 18612 var thisArg = arguments[1]; 18613 for (var i = 0; i < len; i++) { 18614 if (!callbackfn.call(thisArg, t._getter(i), i, t)) 18615 return false; 18616 } 18617 return true; 18618 }}); 18619 18620 // %TypedArray%.prototype.fill (value, start = 0, end = this.length ) 18621 Object.defineProperty($TypedArray$.prototype, 'fill', {value: function(value) { 18622 var start = arguments[1], 18623 end = arguments[2]; 18624 18625 var o = ToObject(this); 18626 var lenVal = o.length; 18627 var len = ToUint32(lenVal); 18628 len = max(len, 0); 18629 var relativeStart = ToInt32(start); 18630 var k; 18631 if (relativeStart < 0) 18632 k = max((len + relativeStart), 0); 18633 else 18634 k = min(relativeStart, len); 18635 var relativeEnd; 18636 if (end === undefined) 18637 relativeEnd = len; 18638 else 18639 relativeEnd = ToInt32(end); 18640 var final; 18641 if (relativeEnd < 0) 18642 final = max((len + relativeEnd), 0); 18643 else 18644 final = min(relativeEnd, len); 18645 while (k < final) { 18646 o._setter(k, value); 18647 k += 1; 18648 } 18649 return o; 18650 }}); 18651 18652 // %TypedArray%.prototype.filter ( callbackfn, thisArg = undefined ) 18653 Object.defineProperty($TypedArray$.prototype, 'filter', {value: function(callbackfn) { 18654 if (this === undefined || this === null) throw TypeError(); 18655 var t = Object(this); 18656 var len = ToUint32(t.length); 18657 if (!IsCallable(callbackfn)) throw TypeError(); 18658 var res = []; 18659 var thisp = arguments[1]; 18660 for (var i = 0; i < len; i++) { 18661 var val = t._getter(i); // in case fun mutates this 18662 if (callbackfn.call(thisp, val, i, t)) 18663 res.push(val); 18664 } 18665 return new this.constructor(res); 18666 }}); 18667 18668 // %TypedArray%.prototype.find (predicate, thisArg = undefined) 18669 Object.defineProperty($TypedArray$.prototype, 'find', {value: function(predicate) { 18670 var o = ToObject(this); 18671 var lenValue = o.length; 18672 var len = ToUint32(lenValue); 18673 if (!IsCallable(predicate)) throw TypeError(); 18674 var t = arguments.length > 1 ? arguments[1] : undefined; 18675 var k = 0; 18676 while (k < len) { 18677 var kValue = o._getter(k); 18678 var testResult = predicate.call(t, kValue, k, o); 18679 if (Boolean(testResult)) 18680 return kValue; 18681 ++k; 18682 } 18683 return undefined; 18684 }}); 18685 18686 // %TypedArray%.prototype.findIndex ( predicate, thisArg = undefined ) 18687 Object.defineProperty($TypedArray$.prototype, 'findIndex', {value: function(predicate) { 18688 var o = ToObject(this); 18689 var lenValue = o.length; 18690 var len = ToUint32(lenValue); 18691 if (!IsCallable(predicate)) throw TypeError(); 18692 var t = arguments.length > 1 ? arguments[1] : undefined; 18693 var k = 0; 18694 while (k < len) { 18695 var kValue = o._getter(k); 18696 var testResult = predicate.call(t, kValue, k, o); 18697 if (Boolean(testResult)) 18698 return k; 18699 ++k; 18700 } 18701 return -1; 18702 }}); 18703 18704 // %TypedArray%.prototype.forEach ( callbackfn, thisArg = undefined ) 18705 Object.defineProperty($TypedArray$.prototype, 'forEach', {value: function(callbackfn) { 18706 if (this === undefined || this === null) throw TypeError(); 18707 var t = Object(this); 18708 var len = ToUint32(t.length); 18709 if (!IsCallable(callbackfn)) throw TypeError(); 18710 var thisp = arguments[1]; 18711 for (var i = 0; i < len; i++) 18712 callbackfn.call(thisp, t._getter(i), i, t); 18713 }}); 18714 18715 // %TypedArray%.prototype.indexOf (searchElement, fromIndex = 0 ) 18716 Object.defineProperty($TypedArray$.prototype, 'indexOf', {value: function(searchElement) { 18717 if (this === undefined || this === null) throw TypeError(); 18718 var t = Object(this); 18719 var len = ToUint32(t.length); 18720 if (len === 0) return -1; 18721 var n = 0; 18722 if (arguments.length > 0) { 18723 n = Number(arguments[1]); 18724 if (n !== n) { 18725 n = 0; 18726 } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 18727 n = (n > 0 || -1) * floor(abs(n)); 18728 } 18729 } 18730 if (n >= len) return -1; 18731 var k = n >= 0 ? n : max(len - abs(n), 0); 18732 for (; k < len; k++) { 18733 if (t._getter(k) === searchElement) { 18734 return k; 18735 } 18736 } 18737 return -1; 18738 }}); 18739 18740 // %TypedArray%.prototype.join ( separator ) 18741 Object.defineProperty($TypedArray$.prototype, 'join', {value: function(separator) { 18742 if (this === undefined || this === null) throw TypeError(); 18743 var t = Object(this); 18744 var len = ToUint32(t.length); 18745 var tmp = Array(len); 18746 for (var i = 0; i < len; ++i) 18747 tmp[i] = t._getter(i); 18748 return tmp.join(separator === undefined ? ',' : separator); // Hack for IE7 18749 }}); 18750 18751 // %TypedArray%.prototype.keys ( ) 18752 // -- defined in es6.js to shim browsers w/ native TypedArrays 18753 18754 // %TypedArray%.prototype.lastIndexOf ( searchElement, fromIndex = this.length-1 ) 18755 Object.defineProperty($TypedArray$.prototype, 'lastIndexOf', {value: function(searchElement) { 18756 if (this === undefined || this === null) throw TypeError(); 18757 var t = Object(this); 18758 var len = ToUint32(t.length); 18759 if (len === 0) return -1; 18760 var n = len; 18761 if (arguments.length > 1) { 18762 n = Number(arguments[1]); 18763 if (n !== n) { 18764 n = 0; 18765 } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { 18766 n = (n > 0 || -1) * floor(abs(n)); 18767 } 18768 } 18769 var k = n >= 0 ? min(n, len - 1) : len - abs(n); 18770 for (; k >= 0; k--) { 18771 if (t._getter(k) === searchElement) 18772 return k; 18773 } 18774 return -1; 18775 }}); 18776 18777 // get %TypedArray%.prototype.length 18778 // -- applied directly to the object in the constructor 18779 18780 // %TypedArray%.prototype.map ( callbackfn, thisArg = undefined ) 18781 Object.defineProperty($TypedArray$.prototype, 'map', {value: function(callbackfn) { 18782 if (this === undefined || this === null) throw TypeError(); 18783 var t = Object(this); 18784 var len = ToUint32(t.length); 18785 if (!IsCallable(callbackfn)) throw TypeError(); 18786 var res = []; res.length = len; 18787 var thisp = arguments[1]; 18788 for (var i = 0; i < len; i++) 18789 res[i] = callbackfn.call(thisp, t._getter(i), i, t); 18790 return new this.constructor(res); 18791 }}); 18792 18793 // %TypedArray%.prototype.reduce ( callbackfn [, initialValue] ) 18794 Object.defineProperty($TypedArray$.prototype, 'reduce', {value: function(callbackfn) { 18795 if (this === undefined || this === null) throw TypeError(); 18796 var t = Object(this); 18797 var len = ToUint32(t.length); 18798 if (!IsCallable(callbackfn)) throw TypeError(); 18799 // no value to return if no initial value and an empty array 18800 if (len === 0 && arguments.length === 1) throw TypeError(); 18801 var k = 0; 18802 var accumulator; 18803 if (arguments.length >= 2) { 18804 accumulator = arguments[1]; 18805 } else { 18806 accumulator = t._getter(k++); 18807 } 18808 while (k < len) { 18809 accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); 18810 k++; 18811 } 18812 return accumulator; 18813 }}); 18814 18815 // %TypedArray%.prototype.reduceRight ( callbackfn [, initialValue] ) 18816 Object.defineProperty($TypedArray$.prototype, 'reduceRight', {value: function(callbackfn) { 18817 if (this === undefined || this === null) throw TypeError(); 18818 var t = Object(this); 18819 var len = ToUint32(t.length); 18820 if (!IsCallable(callbackfn)) throw TypeError(); 18821 // no value to return if no initial value, empty array 18822 if (len === 0 && arguments.length === 1) throw TypeError(); 18823 var k = len - 1; 18824 var accumulator; 18825 if (arguments.length >= 2) { 18826 accumulator = arguments[1]; 18827 } else { 18828 accumulator = t._getter(k--); 18829 } 18830 while (k >= 0) { 18831 accumulator = callbackfn.call(undefined, accumulator, t._getter(k), k, t); 18832 k--; 18833 } 18834 return accumulator; 18835 }}); 18836 18837 // %TypedArray%.prototype.reverse ( ) 18838 Object.defineProperty($TypedArray$.prototype, 'reverse', {value: function() { 18839 if (this === undefined || this === null) throw TypeError(); 18840 var t = Object(this); 18841 var len = ToUint32(t.length); 18842 var half = floor(len / 2); 18843 for (var i = 0, j = len - 1; i < half; ++i, --j) { 18844 var tmp = t._getter(i); 18845 t._setter(i, t._getter(j)); 18846 t._setter(j, tmp); 18847 } 18848 return t; 18849 }}); 18850 18851 // %TypedArray%.prototype.set(array, offset = 0 ) 18852 // %TypedArray%.prototype.set(typedArray, offset = 0 ) 18853 // WebIDL: void set(TypedArray array, optional unsigned long offset); 18854 // WebIDL: void set(sequence<type> array, optional unsigned long offset); 18855 Object.defineProperty($TypedArray$.prototype, 'set', {value: function(index, value) { 18856 if (arguments.length < 1) throw SyntaxError('Not enough arguments'); 18857 var array, sequence, offset, len, 18858 i, s, d, 18859 byteOffset, byteLength, tmp; 18860 18861 if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) { 18862 // void set(TypedArray array, optional unsigned long offset); 18863 array = arguments[0]; 18864 offset = ToUint32(arguments[1]); 18865 18866 if (offset + array.length > this.length) { 18867 throw RangeError('Offset plus length of array is out of range'); 18868 } 18869 18870 byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT; 18871 byteLength = array.length * this.BYTES_PER_ELEMENT; 18872 18873 if (array.buffer === this.buffer) { 18874 tmp = []; 18875 for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) { 18876 tmp[i] = array.buffer._bytes[s]; 18877 } 18878 for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) { 18879 this.buffer._bytes[d] = tmp[i]; 18880 } 18881 } else { 18882 for (i = 0, s = array.byteOffset, d = byteOffset; 18883 i < byteLength; i += 1, s += 1, d += 1) { 18884 this.buffer._bytes[d] = array.buffer._bytes[s]; 18885 } 18886 } 18887 } else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') { 18888 // void set(sequence<type> array, optional unsigned long offset); 18889 sequence = arguments[0]; 18890 len = ToUint32(sequence.length); 18891 offset = ToUint32(arguments[1]); 18892 18893 if (offset + len > this.length) { 18894 throw RangeError('Offset plus length of array is out of range'); 18895 } 18896 18897 for (i = 0; i < len; i += 1) { 18898 s = sequence[i]; 18899 this._setter(offset + i, Number(s)); 18900 } 18901 } else { 18902 throw TypeError('Unexpected argument type(s)'); 18903 } 18904 }}); 18905 18906 // %TypedArray%.prototype.slice ( start, end ) 18907 Object.defineProperty($TypedArray$.prototype, 'slice', {value: function(start, end) { 18908 var o = ToObject(this); 18909 var lenVal = o.length; 18910 var len = ToUint32(lenVal); 18911 var relativeStart = ToInt32(start); 18912 var k = (relativeStart < 0) ? max(len + relativeStart, 0) : min(relativeStart, len); 18913 var relativeEnd = (end === undefined) ? len : ToInt32(end); 18914 var final = (relativeEnd < 0) ? max(len + relativeEnd, 0) : min(relativeEnd, len); 18915 var count = final - k; 18916 var c = o.constructor; 18917 var a = new c(count); 18918 var n = 0; 18919 while (k < final) { 18920 var kValue = o._getter(k); 18921 a._setter(n, kValue); 18922 ++k; 18923 ++n; 18924 } 18925 return a; 18926 }}); 18927 18928 // %TypedArray%.prototype.some ( callbackfn, thisArg = undefined ) 18929 Object.defineProperty($TypedArray$.prototype, 'some', {value: function(callbackfn) { 18930 if (this === undefined || this === null) throw TypeError(); 18931 var t = Object(this); 18932 var len = ToUint32(t.length); 18933 if (!IsCallable(callbackfn)) throw TypeError(); 18934 var thisp = arguments[1]; 18935 for (var i = 0; i < len; i++) { 18936 if (callbackfn.call(thisp, t._getter(i), i, t)) { 18937 return true; 18938 } 18939 } 18940 return false; 18941 }}); 18942 18943 // %TypedArray%.prototype.sort ( comparefn ) 18944 Object.defineProperty($TypedArray$.prototype, 'sort', {value: function(comparefn) { 18945 if (this === undefined || this === null) throw TypeError(); 18946 var t = Object(this); 18947 var len = ToUint32(t.length); 18948 var tmp = Array(len); 18949 for (var i = 0; i < len; ++i) 18950 tmp[i] = t._getter(i); 18951
18951if (comparefn) tmp.sort(comparefn); else tmp.sort(); // Hack for IE8/9
vendor: 8,088 bytes, lines 18951-19151
18951 18952 for (i = 0; i < len; ++i) 18953 t._setter(i, tmp[i]); 18954 return t; 18955 }}); 18956 18957 // %TypedArray%.prototype.subarray(begin = 0, end = this.length ) 18958 // WebIDL: TypedArray subarray(long begin, optional long end); 18959 Object.defineProperty($TypedArray$.prototype, 'subarray', {value: function(start, end) { 18960 function clamp(v, min, max) { return v < min ? min : v > max ? max : v; } 18961 18962 start = ToInt32(start); 18963 end = ToInt32(end); 18964 18965 if (arguments.length < 1) { start = 0; } 18966 if (arguments.length < 2) { end = this.length; } 18967 18968 if (start < 0) { start = this.length + start; } 18969 if (end < 0) { end = this.length + end; } 18970 18971 start = clamp(start, 0, this.length); 18972 end = clamp(end, 0, this.length); 18973 18974 var len = end - start; 18975 if (len < 0) { 18976 len = 0; 18977 } 18978 18979 return new this.constructor( 18980 this.buffer, this.byteOffset + start * this.BYTES_PER_ELEMENT, len); 18981 }}); 18982 18983 // %TypedArray%.prototype.toLocaleString ( ) 18984 // %TypedArray%.prototype.toString ( ) 18985 // %TypedArray%.prototype.values ( ) 18986 // %TypedArray%.prototype [ @@iterator ] ( ) 18987 // get %TypedArray%.prototype [ @@toStringTag ] 18988 // -- defined in es6.js to shim browsers w/ native TypedArrays 18989 18990 function makeTypedArray(elementSize, pack, unpack) { 18991 // Each TypedArray type requires a distinct constructor instance with 18992 // identical logic, which this produces. 18993 var TypedArray = function() { 18994 Object.defineProperty(this, 'constructor', {value: TypedArray}); 18995 $TypedArray$.apply(this, arguments); 18996 makeArrayAccessors(this); 18997 }; 18998 if ('__proto__' in TypedArray) { 18999 TypedArray.__proto__ = $TypedArray$; 19000 } else { 19001 TypedArray.from = $TypedArray$.from; 19002 TypedArray.of = $TypedArray$.of; 19003 } 19004 19005 TypedArray.BYTES_PER_ELEMENT = elementSize; 19006 19007 var TypedArrayPrototype = function() {}; 19008 TypedArrayPrototype.prototype = $TypedArrayPrototype$; 19009 19010 TypedArray.prototype = new TypedArrayPrototype(); 19011 19012 Object.defineProperty(TypedArray.prototype, 'BYTES_PER_ELEMENT', {value: elementSize}); 19013 Object.defineProperty(TypedArray.prototype, '_pack', {value: pack}); 19014 Object.defineProperty(TypedArray.prototype, '_unpack', {value: unpack}); 19015 19016 return TypedArray; 19017 } 19018 19019 var Int8Array = makeTypedArray(1, packI8, unpackI8); 19020 var Uint8Array = makeTypedArray(1, packU8, unpackU8); 19021 var Uint8ClampedArray = makeTypedArray(1, packU8Clamped, unpackU8); 19022 var Int16Array = makeTypedArray(2, packI16, unpackI16); 19023 var Uint16Array = makeTypedArray(2, packU16, unpackU16); 19024 var Int32Array = makeTypedArray(4, packI32, unpackI32); 19025 var Uint32Array = makeTypedArray(4, packU32, unpackU32); 19026 var Float32Array = makeTypedArray(4, packF32, unpackF32); 19027 var Float64Array = makeTypedArray(8, packF64, unpackF64); 19028 19029 global.Int8Array = global.Int8Array || Int8Array; 19030 global.Uint8Array = global.Uint8Array || Uint8Array; 19031 global.Uint8ClampedArray = global.Uint8ClampedArray || Uint8ClampedArray; 19032 global.Int16Array = global.Int16Array || Int16Array; 19033 global.Uint16Array = global.Uint16Array || Uint16Array; 19034 global.Int32Array = global.Int32Array || Int32Array; 19035 global.Uint32Array = global.Uint32Array || Uint32Array; 19036 global.Float32Array = global.Float32Array || Float32Array; 19037 global.Float64Array = global.Float64Array || Float64Array; 19038 }()); 19039 19040 // 19041 // 6 The DataView View Type 19042 // 19043 19044 (function() { 19045 function r(array, index) { 19046 return IsCallable(array.get) ? array.get(index) : array[index]; 19047 } 19048 19049 var IS_BIG_ENDIAN = (function() { 19050 var u16array = new Uint16Array([0x1234]), 19051 u8array = new Uint8Array(u16array.buffer); 19052 return r(u8array, 0) === 0x12; 19053 }()); 19054 19055 // DataView(buffer, byteOffset=0, byteLength=undefined) 19056 // WebIDL: Constructor(ArrayBuffer buffer, 19057 // optional unsigned long byteOffset, 19058 // optional unsigned long byteLength) 19059 function DataView(buffer, byteOffset, byteLength) { 19060 if (!(buffer instanceof ArrayBuffer || Class(buffer) === 'ArrayBuffer')) throw TypeError(); 19061 19062 byteOffset = ToUint32(byteOffset); 19063 if (byteOffset > buffer.byteLength) 19064 throw RangeError('byteOffset out of range'); 19065 19066 if (byteLength === undefined) 19067 byteLength = buffer.byteLength - byteOffset; 19068 else 19069 byteLength = ToUint32(byteLength); 19070 19071 if ((byteOffset + byteLength) > buffer.byteLength) 19072 throw RangeError('byteOffset and length reference an area beyond the end of the buffer'); 19073 19074 Object.defineProperty(this, 'buffer', {value: buffer}); 19075 Object.defineProperty(this, 'byteLength', {value: byteLength}); 19076 Object.defineProperty(this, 'byteOffset', {value: byteOffset}); 19077 }; 19078 19079 // get DataView.prototype.buffer 19080 // get DataView.prototype.byteLength 19081 // get DataView.prototype.byteOffset 19082 // -- applied directly to instances by the constructor 19083 19084 function makeGetter(arrayType) { 19085 return function GetViewValue(byteOffset, littleEndian) { 19086 byteOffset = ToUint32(byteOffset); 19087 19088 if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) 19089 throw RangeError('Array index out of range'); 19090 19091 byteOffset += this.byteOffset; 19092 19093 var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT), 19094 bytes = []; 19095 for (var i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) 19096 bytes.push(r(uint8Array, i)); 19097 19098 if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) 19099 bytes.reverse(); 19100 19101 return r(new arrayType(new Uint8Array(bytes).buffer), 0); 19102 }; 19103 } 19104 19105 Object.defineProperty(DataView.prototype, 'getUint8', {value: makeGetter(Uint8Array)}); 19106 Object.defineProperty(DataView.prototype, 'getInt8', {value: makeGetter(Int8Array)}); 19107 Object.defineProperty(DataView.prototype, 'getUint16', {value: makeGetter(Uint16Array)}); 19108 Object.defineProperty(DataView.prototype, 'getInt16', {value: makeGetter(Int16Array)}); 19109 Object.defineProperty(DataView.prototype, 'getUint32', {value: makeGetter(Uint32Array)}); 19110 Object.defineProperty(DataView.prototype, 'getInt32', {value: makeGetter(Int32Array)}); 19111 Object.defineProperty(DataView.prototype, 'getFloat32', {value: makeGetter(Float32Array)}); 19112 Object.defineProperty(DataView.prototype, 'getFloat64', {value: makeGetter(Float64Array)}); 19113 19114 function makeSetter(arrayType) { 19115 return function SetViewValue(byteOffset, value, littleEndian) { 19116 byteOffset = ToUint32(byteOffset); 19117 if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) 19118 throw RangeError('Array index out of range'); 19119 19120 // Get bytes 19121 var typeArray = new arrayType([value]), 19122 byteArray = new Uint8Array(typeArray.buffer), 19123 bytes = [], i, byteView; 19124 19125 for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) 19126 bytes.push(r(byteArray, i)); 19127 19128 // Flip if necessary 19129 if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) 19130 bytes.reverse(); 19131 19132 // Write them 19133 byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT); 19134 byteView.set(bytes); 19135 }; 19136 } 19137 19138 Object.defineProperty(DataView.prototype, 'setUint8', {value: makeSetter(Uint8Array)}); 19139 Object.defineProperty(DataView.prototype, 'setInt8', {value: makeSetter(Int8Array)}); 19140 Object.defineProperty(DataView.prototype, 'setUint16', {value: makeSetter(Uint16Array)}); 19141 Object.defineProperty(DataView.prototype, 'setInt16', {value: makeSetter(Int16Array)}); 19142 Object.defineProperty(DataView.prototype, 'setUint32', {value: makeSetter(Uint32Array)}); 19143 Object.defineProperty(DataView.prototype, 'setInt32', {value: makeSetter(Int32Array)}); 19144 Object.defineProperty(DataView.prototype, 'setFloat32', {value: makeSetter(Float32Array)}); 19145 Object.defineProperty(DataView.prototype, 'setFloat64', {value: makeSetter(Float64Array)}); 19146 19147 global.DataView = global.DataView || DataView; 19148 19149 }()); 19150 19151}(self));
19152 19153// https://github.com/ttaubert/node-arraybuffer-slice 19154// (c) 2014 Tim Taubert <[email protected]> 19155// arraybuffer-slice may be freely distributed under the MIT license. 19156 19157(function (undefined) { 19158 "use strict"; 19159 19160 function clamp(val, length) { 19161 val = (val|0) || 0; 19162 19163 if (val < 0) { 19164 return Math.max(val + length, 0); 19165 } 19166 19167 return Math.min(val, length); 19168 } 19169 19170 if (!ArrayBuffer.prototype.slice) { 19171 ArrayBuffer.prototype.slice = function (from, to) { 19172 var length = this.byteLength; 19173 var begin = clamp(from, length); 19174 var end = length; 19175 19176 if (to !== undefined) { 19177 end = clamp(to, length); 19178 } 19179 19180 if (begin > end) { 19181 return new ArrayBuffer(0); 19182 } 19183 19184 var num = end - begin; 19185 var target = new ArrayBuffer(num); 19186 var targetArray = new Uint8Array(target); 19187 19188 var sourceArray = new Uint8Array(this, begin, num); 19189 targetArray.set(sourceArray); 19190 19191 return target; 19192 }; 19193 } 19194})(); 19195 19196// This is free and unencumbered software released into the public domain. 19197// See LICENSE.md for more information. 19198 19199// If we're in node require encoding-indexes and attach it to the global. 19200/** 19201 * @fileoverview Global |this| required for resolving indexes in node. 19202 * @suppress {globalThis} 19203 */ 19204if (typeof module !== "undefined" && module.exports) { 19205 this["encoding-indexes"] = 19206 require("./encoding-indexes.js")["encoding-indexes"]; 19207} 19208 19209(function(global) { 19210 'use strict'; 19211 19212 // 19213 // Utilities 19214 // 19215 19216 /** 19217 * @param {number} a The number to test. 19218 * @param {number} min The minimum value in the range, inclusive. 19219 * @param {number} max The maximum value in the range, inclusive. 19220 * @return {boolean} True if a >= min and a <= max. 19221 */ 19222 function inRange(a, min, max) { 19223 return min <= a && a <= max; 19224 } 19225 19226 /** 19227 * @param {!Array.<*>} array The array to check. 19228 * @param {*} item The item to look for in the array. 19229 * @return {boolean} True if the item appears in the array. 19230 */ 19231 function includes(array, item) { 19232 return array.indexOf(item) !== -1; 19233 } 19234 19235 var floor = Math.floor; 19236 19237 /** 19238 * @param {*} o 19239 * @return {Object} 19240 */ 19241 function ToDictionary(o) { 19242 if (o === undefined) return {}; 19243 if (o === Object(o)) return o; 19244 throw TypeError('Could not convert argument to dictionary'); 19245 } 19246 19247 /** 19248 * @param {string} string Input string of UTF-16 code units. 19249 * @return {!Array.<number>} Code points. 19250 */ 19251 function stringToCodePoints(string) { 19252 // https://heycam.github.io/webidl/#dfn-obtain-unicode 19253 19254 // 1. Let S be the DOMString value. 19255 var s = String(string); 19256 19257 // 2. Let n be the length of S. 19258 var n = s.length; 19259 19260 // 3. Initialize i to 0. 19261 var i = 0; 19262 19263 // 4. Initialize U to be an empty sequence of Unicode characters. 19264 var u = []; 19265 19266 // 5. While i < n: 19267 while (i < n) { 19268 19269 // 1. Let c be the code unit in S at index i. 19270 var c = s.charCodeAt(i); 19271 19272 // 2. Depending on the value of c: 19273 19274 // c < 0xD800 or c > 0xDFFF 19275 if (c < 0xD800 || c > 0xDFFF) { 19276 // Append to U the Unicode character with code point c. 19277 u.push(c); 19278 } 19279 19280 // 0xDC00 ⤠c ⤠0xDFFF 19281 else if (0xDC00 <= c && c <= 0xDFFF) { 19282 // Append to U a U+FFFD REPLACEMENT CHARACTER. 19283 u.push(0xFFFD); 19284 } 19285 19286 // 0xD800 ⤠c ⤠0xDBFF 19287 else if (0xD800 <= c && c <= 0xDBFF) { 19288 // 1. If i = nâ1, then append to U a U+FFFD REPLACEMENT 19289 // CHARACTER. 19290 if (i === n - 1) { 19291 u.push(0xFFFD); 19292 } 19293 // 2. Otherwise, i < nâ1: 19294 else { 19295 // 1. Let d be the code unit in S at index i+1. 19296 var d = string.charCodeAt(i + 1); 19297 19298 // 2. If 0xDC00 ⤠d ⤠0xDFFF, then: 19299 if (0xDC00 <= d && d <= 0xDFFF) { 19300 // 1. Let a be c & 0x3FF. 19301 var a = c & 0x3FF; 19302 19303 // 2. Let b be d & 0x3FF. 19304 var b = d & 0x3FF; 19305 19306 // 3. Append to U the Unicode character with code point 19307 // 2^16+2^10*a+b. 19308 u.push(0x10000 + (a << 10) + b); 19309 19310 // 4. Set i to i+1. 19311 i += 1; 19312 } 19313 19314 // 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a 19315 // U+FFFD REPLACEMENT CHARACTER. 19316 else { 19317 u.push(0xFFFD); 19318 } 19319 } 19320 } 19321 19322 // 3. Set i to i+1. 19323 i += 1; 19324 } 19325 19326 // 6. Return U. 19327 return u; 19328 } 19329 19330 /** 19331 * @param {!Array.<number>} code_points Array of code points. 19332 * @return {string} string String of UTF-16 code units. 19333 */ 19334 function codePointsToString(code_points) { 19335 var s = ''; 19336 for (var i = 0; i < code_points.length; ++i) { 19337 var cp = code_points[i]; 19338 if (cp <= 0xFFFF) { 19339 s += String.fromCharCode(cp); 19340 } else { 19341 cp -= 0x10000; 19342 s += String.fromCharCode((cp >> 10) + 0xD800, 19343 (cp & 0x3FF) + 0xDC00); 19344 } 19345 } 19346 return s; 19347 } 19348 19349 19350 // 19351 // Implementation of Encoding specification 19352 // https://encoding.spec.whatwg.org/ 19353 // 19354 19355 // 19356 // 4. Terminology 19357 // 19358 19359 /** 19360 * An ASCII byte is a byte in the range 0x00 to 0x7F, inclusive. 19361 * @param {number} a The number to test. 19362 * @return {boolean} True if a is in the range 0x00 to 0x7F, inclusive. 19363 */ 19364 function isASCIIByte(a) {
19365 return 0x00 <= a && a <= 0x7F; 19366 } 19367 19368 /** 19369 * An ASCII code point is a code point in the range U+0000 to 19370 * U+007F, inclusive. 19371 */ 19372 var isASCIICodePoint = isASCIIByte; 19373 19374 19375 /** 19376 * End-of-stream is a special token that signifies no more tokens 19377 * are in the stream. 19378 * @const 19379 */ var end_of_stream = -1; 19380 19381 /** 19382 * A stream represents an ordered sequence of tokens. 19383 * 19384 * @constructor 19385 * @param {!(Array.<number>|Uint8Array)} tokens Array of tokens that provide 19386 * the stream. 19387 */ 19388 function Stream(tokens) { 19389 /** @type {!Array.<number>} */ 19390 this.tokens = [].slice.call(tokens); 19391 // Reversed as push/pop is more efficient than shift/unshift. 19392 this.tokens.reverse(); 19393 } 19394 19395 Stream.prototype = { 19396 /** 19397 * @return {boolean} True if end-of-stream has been hit. 19398 */ 19399 endOfStream: function() { 19400 return !this.tokens.length; 19401 }, 19402 19403 /** 19404 * When a token is read from a stream, the first token in the 19405 * stream must be returned and subsequently removed, and 19406 * end-of-stream must be returned otherwise. 19407 * 19408 * @return {number} Get the next token from the stream, or 19409 * end_of_stream. 19410 */ 19411 read: function() { 19412 if (!this.tokens.length) 19413 return end_of_stream; 19414 return this.tokens.pop(); 19415 }, 19416 19417 /** 19418 * When one or more tokens are prepended to a stream, those tokens 19419 * must be inserted, in given order, before the first token in the 19420 * stream. 19421 * 19422 * @param {(number|!Array.<number>)} token The token(s) to prepend to the 19423 * stream. 19424 */ 19425 prepend: function(token) { 19426 if (Array.isArray(token)) { 19427 var tokens = /**@type {!Array.<number>}*/(token); 19428 while (tokens.length) 19429 this.tokens.push(tokens.pop()); 19430 } else { 19431 this.tokens.push(token); 19432 } 19433 }, 19434 19435 /** 19436 * When one or more tokens are pushed to a stream, those tokens 19437 * must be inserted, in given order, after the last token in the 19438 * stream. 19439 * 19440 * @param {(number|!Array.<number>)} token The tokens(s) to push to the 19441 * stream. 19442 */ 19443 push: function(token) { 19444 if (Array.isArray(token)) { 19445 var tokens = /**@type {!Array.<number>}*/(token); 19446 while (tokens.length) 19447 this.tokens.unshift(tokens.shift()); 19448 } else { 19449 this.tokens.unshift(token); 19450 } 19451 } 19452 }; 19453 19454 // 19455 // 5. Encodings 19456 // 19457 19458 // 5.1 Encoders and decoders 19459 19460 /** @const */ 19461 var finished = -1; 19462 19463 /** 19464 * @param {boolean} fatal If true, decoding errors raise an exception. 19465 * @param {number=} opt_code_point Override the standard fallback code point. 19466 * @return {number} The code point to insert on a decoding error. 19467 */ 19468 function decoderError(fatal, opt_code_point) { 19469 if (fatal) 19470 throw TypeError('Decoder error'); 19471 return opt_code_point || 0xFFFD; 19472 } 19473 19474 /** 19475 * @param {number} code_point The code point that could not be encoded. 19476 * @return {number} Always throws, no value is actually returned. 19477 */ 19478 function encoderError(code_point) { 19479 throw TypeError('The code point ' + code_point + ' could not be encoded.'); 19480 } 19481 19482 /** @interface */ 19483 function Decoder() {} 19484 Decoder.prototype = { 19485 /** 19486 * @param {Stream} stream The stream of bytes being decoded. 19487 * @param {number} bite The next byte read from the stream. 19488 * @return {?(number|!Array.<number>)} The next code point(s) 19489 * decoded, or null if not enough data exists in the input 19490 * stream to decode a complete code point, or |finished|. 19491 */ 19492 handler: function(stream, bite) {} 19493 }; 19494 19495 /** @interface */ 19496 function Encoder() {} 19497 Encoder.prototype = { 19498 /** 19499 * @param {Stream} stream The stream of code points being encoded. 19500 * @param {number} code_point Next code point read from the stream. 19501 * @return {(number|!Array.<number>)} Byte(s) to emit, or |finished|. 19502 */ 19503 handler: function(stream, code_point) {} 19504 }; 19505 19506 // 5.2 Names and labels 19507 19508 // TODO: Define @typedef for Encoding: {name:string,labels:Array.<string>} 19509 // https://github.com/google/closure-compiler/issues/247 19510 19511 /** 19512 * @param {string} label The encoding label. 19513 * @return {?{name:string,labels:Array.<string>}} 19514 */ 19515 function getEncoding(label) { 19516 // 1. Remove any leading and trailing ASCII whitespace from label. 19517 label = String(label).trim().toLowerCase(); 19518 19519 // 2. If label is an ASCII case-insensitive match for any of the 19520 // labels listed in the table below, return the corresponding 19521 // encoding, and failure otherwise. 19522 if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) { 19523 return label_to_encoding[label]; 19524 } 19525 return null; 19526 } 19527 19528 /** 19529 * Encodings table: https://encoding.spec.whatwg.org/encodings.json 19530 * @const
19531 * @type {!Array.<{ 19532 * heading: string, 19533 * encodings: Array.<{name:string,labels:Array.<string>}> 19534 * }>} 19535 */ 19536 var encodings = [ 19537 { 19538 "encodings": [ 19539 { 19540 "labels": [ 19541 "unicode-1-1-utf-8", 19542 "utf-8", 19543 "utf8" 19544 ], 19545 "name": "UTF-8" 19546 } 19547 ], 19548 "heading": "The Encoding" 19549 }, 19550 { 19551 "encodings": [ 19552 { 19553 "labels": [ 19554 "866", 19555 "cp866", 19556 "csibm866", 19557 "ibm866" 19558 ], 19559 "name": "IBM866" 19560 }, 19561 { 19562 "labels": [ 19563 "csisolatin2", 19564 "iso-8859-2", 19565 "iso-ir-101", 19566 "iso8859-2", 19567 "iso88592", 19568 "iso_8859-2", 19569 "iso_8859-2:1987", 19570 "l2", 19571 "latin2" 19572 ], 19573 "name": "ISO-8859-2" 19574 }, 19575 { 19576 "labels": [ 19577 "csisolatin3", 19578 "iso-8859-3", 19579 "iso-ir-109", 19580 "iso8859-3", 19581 "iso88593", 19582 "iso_8859-3", 19583 "iso_8859-3:1988", 19584 "l3", 19585 "latin3" 19586 ], 19587 "name": "ISO-8859-3" 19588 }, 19589 { 19590 "labels": [ 19591 "csisolatin4", 19592 "iso-8859-4", 19593 "iso-ir-110", 19594 "iso8859-4", 19595 "iso88594", 19596 "iso_8859-4", 19597 "iso_8859-4:1988", 19598 "l4", 19599 "latin4" 19600 ], 19601 "name": "ISO-8859-4" 19602 }, 19603 { 19604 "labels": [ 19605 "csisolatincyrillic", 19606 "cyrillic", 19607 "iso-8859-5", 19608 "iso-ir-144", 19609 "iso8859-5", 19610 "iso88595", 19611 "iso_8859-5", 19612 "iso_8859-5:1988" 19613 ], 19614 "name": "ISO-8859-5" 19615 }, 19616 { 19617 "labels": [ 19618 "arabic", 19619 "asmo-708", 19620 "csiso88596e", 19621 "csiso88596i", 19622 "csisolatinarabic", 19623 "ecma-114", 19624 "iso-8859-6", 19625 "iso-8859-6-e", 19626 "iso-8859-6-i", 19627 "iso-ir-127", 19628 "iso8859-6", 19629 "iso88596", 19630 "iso_8859-6", 19631 "iso_8859-6:1987" 19632 ], 19633 "name": "ISO-8859-6" 19634 }, 19635 { 19636 "labels": [ 19637 "csisolatingreek", 19638 "ecma-118", 19639 "elot_928", 19640 "greek", 19641 "greek8", 19642 "iso-8859-7", 19643 "iso-ir-126", 19644 "iso8859-7", 19645 "iso88597", 19646 "iso_8859-7", 19647 "iso_8859-7:1987", 19648 "sun_eu_greek" 19649 ], 19650 "name": "ISO-8859-7" 19651 }, 19652 { 19653 "labels": [ 19654 "csiso88598e", 19655 "csisolatinhebrew", 19656 "hebrew", 19657 "iso-8859-8", 19658 "iso-8859-8-e", 19659 "iso-ir-138", 19660 "iso8859-8", 19661 "iso88598", 19662 "iso_8859-8", 19663 "iso_8859-8:1988", 19664 "visual" 19665 ], 19666 "name": "ISO-8859-8" 19667 }, 19668 { 19669 "labels": [ 19670 "csiso88598i", 19671 "iso-8859-8-i", 19672 "logical" 19673 ], 19674 "name": "ISO-8859-8-I" 19675 }, 19676 { 19677 "labels": [ 19678 "csisolatin6", 19679 "iso-8859-10", 19680 "iso-ir-157", 19681 "iso8859-10", 19682 "iso885910", 19683 "l6", 19684 "latin6" 19685 ], 19686 "name": "ISO-8859-10" 19687 }, 19688 { 19689 "labels": [ 19690 "iso-8859-13", 19691 "iso8859-13", 19692 "iso885913" 19693 ], 19694 "name": "ISO-8859-13" 19695 }, 19696 { 19697 "labels": [ 19698 "iso-8859-14", 19699 "iso8859-14", 19700 "iso885914" 19701 ], 19702 "name": "ISO-8859-14" 19703 }, 19704 { 19705 "labels": [ 19706 "csisolatin9", 19707 "iso-8859-15", 19708 "iso8859-15", 19709 "iso885915", 19710 "iso_8859-15", 19711 "l9" 19712 ], 19713 "name": "ISO-8859-15" 19714 }, 19715 { 19716 "labels": [ 19717 "iso-8859-16" 19718 ], 19719 "name": "ISO-8859-16" 19720 }, 19721 { 19722 "labels": [ 19723 "cskoi8r", 19724 "koi", 19725 "koi8", 19726 "koi8-r", 19727 "koi8_r" 19728 ], 19729 "name": "KOI8-R" 19730 }, 19731 { 19732 "labels": [ 19733 "koi8-ru", 19734 "koi8-u" 19735 ], 19736 "name": "KOI8-U" 19737 }, 19738 { 19739 "labels": [ 19740 "csmacintosh", 19741 "mac", 19742 "macintosh", 19743 "x-mac-roman" 19744 ], 19745 "name": "macintosh" 19746 }, 19747 { 19748 "labels": [ 19749 "dos-874", 19750 "iso-8859-11", 19751 "iso8859-11", 19752 "iso885911", 19753 "tis-620", 19754 "windows-874" 19755 ], 19756 "name": "windows-874" 19757 }, 19758 { 19759 "labels": [ 19760 "cp1250", 19761 "windows-1250", 19762 "x-cp1250" 19763 ], 19764 "name": "windows-1250" 19765 }, 19766 { 19767 "labels": [ 19768 "cp1251", 19769 "windows-1251", 19770 "x-cp1251" 19771 ], 19772 "name": "windows-1251" 19773 }, 19774 { 19775 "labels": [ 19776 "ansi_x3.4-1968", 19777 "ascii", 19778 "cp1252", 19779 "cp819", 19780 "csisolatin1", 19781 "ibm819", 19782 "iso-8859-1", 19783 "iso-ir-100", 19784 "iso8859-1", 19785 "iso88591", 19786 "iso_8859-1", 19787 "iso_8859-1:1987", 19788 "l1", 19789 "latin1", 19790 "us-ascii", 19791 "windows-1252", 19792 "x-cp1252" 19793 ], 19794 "name": "windows-1252" 19795 }, 19796 { 19797 "labels": [ 19798 "cp1253", 19799 "windows-1253", 19800 "x-cp1253" 19801 ], 19802 "name": "windows-1253" 19803 }, 19804 { 19805 "labels": [ 19806 "cp1254", 19807 "csisolatin5", 19808 "iso-8859-9", 19809 "iso-ir-148", 19810 "iso8859-9", 19811 "iso88599", 19812 "iso_8859-9", 19813 "iso_8859-9:1989", 19814 "l5", 19815 "latin5", 19816 "windows-1254", 19817 "x-cp1254" 19818 ], 19819 "name": "windows-1254" 19820 }, 19821 { 19822 "labels": [ 19823 "cp1255", 19824 "windows-1255", 19825 "x-cp1255" 19826 ], 19827 "name": "windows-1255" 19828 }, 19829 { 19830 "labels": [ 19831 "cp1256", 19832 "windows-1256", 19833 "x-cp1256" 19834 ], 19835 "name": "windows-1256" 19836 }, 19837 { 19838 "labels": [ 19839 "cp1257", 19840 "windows-1257", 19841 "x-cp1257" 19842 ], 19843 "name": "windows-1257" 19844 }, 19845 { 19846 "labels": [ 19847 "cp1258", 19848 "windows-1258", 19849 "x-cp1258" 19850 ], 19851 "name": "windows-1258" 19852 }, 19853 { 19854 "labels": [ 19855 "x-mac-cyrillic", 19856 "x-mac-ukrainian" 19857 ], 19858 "name": "x-mac-cyrillic" 19859 } 19860 ], 19861 "heading": "Legacy single-byte encodings" 19862 }, 19863 { 19864 "encodings": [ 19865 { 19866 "labels": [ 19867 "chinese", 19868 "csgb2312", 19869 "csiso58gb231280", 19870 "gb2312", 19871 "gb_2312", 19872 "gb_2312-80", 19873 "gbk", 19874 "iso-ir-58", 19875 "x-gbk" 19876 ], 19877 "name": "GBK" 19878 }, 19879 { 19880 "labels": [ 19881 "gb18030" 19882 ], 19883 "name": "gb18030" 19884 } 19885 ], 19886 "heading": "Legacy multi-byte Chinese (simplified) encodings" 19887 }, 19888 { 19889 "encodings": [ 19890 { 19891 "labels": [ 19892 "big5", 19893 "big5-hkscs", 19894 "cn-big5", 19895 "csbig5", 19896 "x-x-big5" 19897 ], 19898 "name": "Big5" 19899 } 19900 ], 19901 "heading": "Legacy multi-byte Chinese (traditional) encodings" 19902 }, 19903 { 19904 "encodings": [ 19905 { 19906 "labels": [ 19907 "cseucpkdfmtjapanese", 19908 "euc-jp", 19909 "x-euc-jp" 19910 ], 19911 "name": "EUC-JP" 19912 }, 19913 { 19914 "labels": [ 19915 "csiso2022jp", 19916 "iso-2022-jp" 19917 ], 19918 "name": "ISO-2022-JP" 19919 }, 19920 { 19921 "labels": [ 19922 "csshiftjis", 19923 "ms932", 19924 "ms_kanji", 19925 "shift-jis", 19926 "shift_jis", 19927 "sjis", 19928 "windows-31j", 19929 "x-sjis" 19930 ], 19931 "name": "Shift_JIS" 19932 } 19933 ], 19934 "heading": "Legacy multi-byte Japanese encodings" 19935 }, 19936 { 19937 "encodings": [ 19938 { 19939 "labels": [ 19940 "cseuckr", 19941 "csksc56011987", 19942 "euc-kr", 19943 "iso-ir-149", 19944 "korean", 19945 "ks_c_5601-1987", 19946 "ks_c_5601-1989", 19947 "ksc5601", 19948 "ksc_5601", 19949 "windows-949" 19950 ], 19951 "name": "EUC-KR" 19952 } 19953 ], 19954 "heading": "Legacy multi-byte Korean encodings" 19955 }, 19956 { 19957 "encodings": [ 19958 { 19959 "labels": [ 19960 "csiso2022kr", 19961 "hz-gb-2312", 19962 "iso-2022-cn", 19963 "iso-2022-cn-ext", 19964 "iso-2022-kr" 19965 ], 19966 "name": "replacement" 19967 }, 19968 { 19969 "labels": [ 19970 "utf-16be" 19971 ], 19972 "name": "UTF-16BE" 19973 }, 19974 { 19975 "labels": [ 19976 "utf-16", 19977 "utf-16le" 19978 ], 19979 "name": "UTF-16LE" 19980 }, 19981 { 19982 "labels": [
19983 "x-user-defined" 19984 ], 19985 "name": "x-user-defined" 19986 } 19987 ], 19988 "heading": "Legacy miscellaneous encodings" 19989 } 19990 ]; 19991 19992 // Label to encoding registry. 19993 /** @type {Object.<string,{name:string,labels:Array.<string>}>} */ 19994 var label_to_encoding = {}; 19995 encodings.forEach(function(category) { 19996 category.encodings.forEach(function(encoding) { 19997 encoding.labels.forEach(function(label) { 19998 label_to_encoding[label] = encoding; 19999 }); 20000 }); 20001 }); 20002 20003 // Registry of of encoder/decoder factories, by encoding name. 20004 /** @type {Object.<string, function({fatal:boolean}): Encoder>} */ 20005 var encoders = {}; 20006 /** @type {Object.<string, function({fatal:boolean}): Decoder>} */ 20007 var decoders = {}; 20008 20009 // 20010 // 6. Indexes 20011 // 20012 20013 /** 20014 * @param {number} pointer The |pointer| to search for. 20015 * @param {(!Array.<?number>|undefined)} index The |index| to search within. 20016 * @return {?number} The code point corresponding to |pointer| in |index|, 20017 * or null if |code point| is not in |index|. 20018 */ 20019 function indexCodePointFor(pointer, index) { 20020 if (!index) return null; 20021 return index[pointer] || null; 20022 } 20023 20024 /** 20025 * @param {number} code_point The |code point| to search for. 20026 * @param {!Array.<?number>} index The |index| to search within. 20027 * @return {?number} The first pointer corresponding to |code point| in 20028 * |index|, or null if |code point| is not in |index|. 20029 */ 20030 function indexPointerFor(code_point, index) { 20031 var pointer = index.indexOf(code_point); 20032 return pointer === -1 ? null : pointer; 20033 } 20034 20035 /** 20036 * @param {string} name Name of the index. 20037 * @return {(!Array.<number>|!Array.<Array.<number>>)} 20038 * */ 20039 function index(name) { 20040 if (!('encoding-indexes' in global)) { 20041 throw Error("Indexes missing." + 20042 " Did you forget to include encoding-indexes.js?"); 20043 } 20044 return global['encoding-indexes'][name]; 20045 } 20046 20047 /** 20048 * @param {number} pointer The |pointer| to search for in the gb18030 index. 20049 * @return {?number} The code point corresponding to |pointer| in |index|, 20050 * or null if |code point| is not in the gb18030 index. 20051 */ 20052 function indexGB18030RangesCodePointFor(pointer) { 20053 // 1. If pointer is greater than 39419 and less than 189000, or 20054 // pointer is greater than 1237575, return null. 20055 if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575)) 20056 return null; 20057 20058 // 2. If pointer is 7457, return code point U+E7C7. 20059 if (pointer === 7457) return 0xE7C7; 20060 20061 // 3. Let offset be the last pointer in index gb18030 ranges that 20062 // is equal to or less than pointer and let code point offset be 20063 // its corresponding code point. 20064 var offset = 0; 20065 var code_point_offset = 0; 20066 var idx = index('gb18030'); 20067 var i; 20068 for (i = 0; i < idx.length; ++i) { 20069 /** @type {!Array.<number>} */ 20070 var entry = idx[i]; 20071 if (entry[0] <= pointer) { 20072 offset = entry[0]; 20073 code_point_offset = entry[1]; 20074 } else { 20075 break; 20076 } 20077 } 20078 20079 // 4. Return a code point whose value is code point offset + 20080 // pointer â offset. 20081 return code_point_offset + pointer - offset; 20082 } 20083 20084 /** 20085 * @param {number} code_point The |code point| to locate in the gb18030 index. 20086 * @return {number} The first pointer corresponding to |code point| in the 20087 * gb18030 index. 20088 */ 20089 function indexGB18030RangesPointerFor(code_point) { 20090 // 1. If code point is U+E7C7, return pointer 7457. 20091 if (code_point === 0xE7C7) return 7457; 20092 20093 // 2. Let offset be the last code point in index gb18030 ranges 20094 // that is equal to or less than code point and let pointer offset 20095 // be its corresponding pointer. 20096 var offset = 0; 20097 var pointer_offset = 0; 20098 var idx = index('gb18030'); 20099 var i; 20100 for (i = 0; i < idx.length; ++i) { 20101 /** @type {!Array.<number>} */ 20102 var entry = idx[i]; 20103 if (entry[1] <= code_point) { 20104 offset = entry[1]; 20105 pointer_offset = entry[0]; 20106 } else { 20107 break; 20108 } 20109 } 20110 20111 // 3. Return a pointer whose value is pointer offset + code point 20112 // â offset. 20113 return pointer_offset + code_point - offset; 20114 } 20115 20116 /** 20117 * @param {number} code_point The |code_point| to search for in the shift_jis 20118 * index. 20119 * @return {?number} The code point corresponding to |pointer| in |index|, 20120 * or null if |code point| is not in the shift_jis index. 20121 */ 20122 function indexShiftJISPointerFor(code_point) { 20123 // 1. Let index be index jis0208 excluding all pointers in the 20124 // range 8272 to 8835. 20125 var pointer = indexPointerFor(code_point, index('jis0208')); 20126 if (pointer === null || inRange(pointer, 8272, 8835)) 20127 return null; 20128 20129 // 2. Return the index pointer for code point in index. 20130 return pointer; 20131 } 20132 20133 /** 20134 * @param {number} code_point The |code_point| to search for in the big5 20135 * index. 20136 * @return {?number} The code point corresponding to |pointer| in |index|, 20137 * or null if |code point| is not in the big5 index. 20138 */ 20139 function indexBig5PointerFor(code_point) { 20140 20141 // 1. Let index be index big5. 20142 var index_ = index('big5'); 20143 20144 // 2. If code point is U+2550, U+255E, U+2561, U+256A, U+5341, or 20145 // U+5345, return the last pointer corresponding to code point in 20146 // index. 20147 if (code_point === 0x2550 || code_point === 0x255E || 20148 code_point === 0x2561 || code_point === 0x256A || 20149 code_point === 0x5341 || code_point === 0x5345) { 20150 return index_.lastIndexOf(code_point); 20151 } 20152 20153 // 3. Return the index pointer for code point in index. 20154 return indexPointerFor(code_point, index_); 20155 } 20156 20157 // 20158 // 8. API 20159 // 20160 20161 /** @const */ var DEFAULT_ENCODING = 'utf-8'; 20162 20163 // 8.1 Interface TextDecoder 20164 20165 /** 20166 * @constructor 20167 * @param {string=} label The label of the encoding; 20168 * defaults to 'utf-8'. 20169 * @param {Object=} options 20170 */ 20171 function TextDecoder(label, options) { 20172 // Web IDL conventions 20173 if (!(this instanceof TextDecoder)) 20174 throw TypeError('Called as a function. Did you forget \'new\'?'); 20175 label = label !== undefined ? String(label) : DEFAULT_ENCODING; 20176 options = ToDictionary(options); 20177 20178 // A TextDecoder object has an associated encoding, decoder, 20179 // stream, ignore BOM flag (initially unset), BOM seen flag 20180 // (initially unset), error mode (initially replacement), and do 20181 // not flush flag (initially unset). 20182 20183 /** @private */ 20184 this._encoding = null; 20185 /** @private @type {?Decoder} */ 20186 this._decoder = null; 20187 /** @private @type {boolean} */ 20188 this._ignoreBOM = false;
20189 /** @private @type {boolean} */ 20190 this._BOMseen = false; 20191 /** @private @type {string} */ 20192 this._error_mode = 'replacement'; 20193 /** @private @type {boolean} */ 20194 this._do_not_flush = false; 20195 20196 20197 // 1. Let encoding be the result of getting an encoding from 20198 // label. 20199 var encoding = getEncoding(label); 20200 20201 // 2. If encoding is failure or replacement, throw a RangeError. 20202 if (encoding === null || encoding.name === 'replacement') 20203 throw RangeError('Unknown encoding: ' + label); 20204 if (!decoders[encoding.name]) { 20205 throw Error('Decoder not present.' + 20206 ' Did you forget to include encoding-indexes.js?'); 20207 } 20208 20209 // 3. Let dec be a new TextDecoder object. 20210 var dec = this; 20211 20212 // 4. Set dec's encoding to encoding. 20213 dec._encoding = encoding; 20214 20215 // 5. If options's fatal member is true, set dec's error mode to 20216 // fatal. 20217 if (Boolean(options['fatal'])) 20218 dec._error_mode = 'fatal'; 20219 20220 // 6. If options's ignoreBOM member is true, set dec's ignore BOM 20221 // flag. 20222 if (Boolean(options['ignoreBOM'])) 20223 dec._ignoreBOM = true; 20224 20225 // For pre-ES5 runtimes: 20226 if (!Object.defineProperty) { 20227 this.encoding = dec._encoding.name.toLowerCase(); 20228 this.fatal = dec._error_mode === 'fatal'; 20229 this.ignoreBOM = dec._ignoreBOM; 20230 } 20231 20232 // 7. Return dec. 20233 return dec; 20234 } 20235 20236 if (Object.defineProperty) { 20237 // The encoding attribute's getter must return encoding's name. 20238 Object.defineProperty(TextDecoder.prototype, 'encoding', { 20239 /** @this {TextDecoder} */ 20240 get: function() { return this._encoding.name.toLowerCase(); } 20241 }); 20242 20243 // The fatal attribute's getter must return true if error mode 20244 // is fatal, and false otherwise. 20245 Object.defineProperty(TextDecoder.prototype, 'fatal', { 20246 /** @this {TextDecoder} */ 20247 get: function() { return this._error_mode === 'fatal'; } 20248 }); 20249 20250 // The ignoreBOM attribute's getter must return true if ignore 20251 // BOM flag is set, and false otherwise. 20252 Object.defineProperty(TextDecoder.prototype, 'ignoreBOM', { 20253 /** @this {TextDecoder} */ 20254 get: function() { return this._ignoreBOM; } 20255 }); 20256 } 20257 20258 /** 20259 * @param {BufferSource=} input The buffer of bytes to decode. 20260 * @param {Object=} options 20261 * @return {string} The decoded string. 20262 */ 20263 TextDecoder.prototype.decode = function decode(input, options) { 20264 var bytes; 20265 if (typeof input === 'object' && input instanceof ArrayBuffer) { 20266 bytes = new Uint8Array(input); 20267 } else if (typeof input === 'object' && 'buffer' in input && 20268 input.buffer instanceof ArrayBuffer) { 20269 bytes = new Uint8Array(input.buffer, 20270 input.byteOffset, 20271 input.byteLength); 20272 } else { 20273 bytes = new Uint8Array(0); 20274 } 20275 20276 options = ToDictionary(options); 20277 20278 // 1. If the do not flush flag is unset, set decoder to a new 20279 // encoding's decoder, set stream to a new stream, and unset the 20280 // BOM seen flag. 20281 if (!this._do_not_flush) { 20282 this._decoder = decoders[this._encoding.name]({ 20283 fatal: this._error_mode === 'fatal'}); 20284 this._BOMseen = false; 20285 } 20286 20287 // 2. If options's stream is true, set the do not flush flag, and 20288 // unset the do not flush flag otherwise. 20289 this._do_not_flush = Boolean(options['stream']); 20290 20291 // 3. If input is given, push a copy of input to stream. 20292 // TODO: Align with spec algorithm - maintain stream on instance. 20293 var input_stream = new Stream(bytes); 20294 20295 // 4. Let output be a new stream. 20296 var output = []; 20297 20298 /** @type {?(number|!Array.<number>)} */ 20299 var result; 20300 20301 // 5. While true: 20302 while (true) { 20303 // 1. Let token be the result of reading from stream. 20304 var token = input_stream.read(); 20305 20306 // 2. If token is end-of-stream and the do not flush flag is 20307 // set, return output, serialized. 20308 // TODO: Align with spec algorithm. 20309 if (token === end_of_stream) 20310 break; 20311 20312 // 3. Otherwise, run these subsubsteps: 20313 20314 // 1. Let result be the result of processing token for decoder, 20315 // stream, output, and error mode. 20316 result = this._decoder.handler(input_stream, token); 20317 20318 // 2. If result is finished, return output, serialized. 20319 if (result === finished) 20320 break; 20321 20322 if (result !== null) { 20323 if (Array.isArray(result)) 20324 output.push.apply(output, /**@type {!Array.<number>}*/(result)); 20325 else 20326 output.push(result); 20327 } 20328 20329 // 3. Otherwise, if result is error, throw a TypeError. 20330 // (Thrown in handler) 20331 20332 // 4. Otherwise, do nothing. 20333 } 20334 // TODO: Align with spec algorithm. 20335 if (!this._do_not_flush) { 20336 do { 20337 result = this._decoder.handler(input_stream, input_stream.read()); 20338 if (result === finished) 20339 break; 20340 if (result === null) 20341 continue; 20342 if (Array.isArray(result)) 20343 output.push.apply(output, /**@type {!Array.<number>}*/(result)); 20344 else 20345 output.push(result); 20346 } while (!input_stream.endOfStream()); 20347 this._decoder = null; 20348 } 20349 20350 // A TextDecoder object also has an associated serialize stream 20351 // algorithm... 20352 /** 20353 * @param {!Array.<number>} stream 20354 * @return {string} 20355 * @this {TextDecoder} 20356 */ 20357 function serializeStream(stream) { 20358 // 1. Let token be the result of reading from stream. 20359 // (Done in-place on array, rather than as a stream) 20360 20361 // 2. If encoding is UTF-8, UTF-16BE, or UTF-16LE, and ignore 20362 // BOM flag and BOM seen flag are unset, run these subsubsteps: 20363 if (includes(['UTF-8', 'UTF-16LE', 'UTF-16BE'], this._encoding.name) && 20364 !this._ignoreBOM && !this._BOMseen) { 20365 if (stream.length > 0 && stream[0] === 0xFEFF) { 20366 // 1. If token is U+FEFF, set BOM seen flag. 20367 this._BOMseen = true; 20368 stream.shift(); 20369 } else if (stream.length > 0) { 20370 // 2. Otherwise, if token is not end-of-stream, set BOM seen
20371 // flag and append token to stream. 20372 this._BOMseen = true; 20373 } else { 20374 // 3. Otherwise, if token is not end-of-stream, append token 20375 // to output. 20376 // (no-op) 20377 } 20378 } 20379 // 4. Otherwise, return output. 20380 return codePointsToString(stream); 20381 } 20382 20383 return serializeStream.call(this, output); 20384 }; 20385 20386 // 8.2 Interface TextEncoder 20387 20388 /** 20389 * @constructor 20390 * @param {string=} label The label of the encoding. NONSTANDARD. 20391 * @param {Object=} options NONSTANDARD. 20392 */ 20393 function TextEncoder(label, options) { 20394 // Web IDL conventions 20395 if (!(this instanceof TextEncoder)) 20396 throw TypeError('Called as a function. Did you forget \'new\'?'); 20397 options = ToDictionary(options); 20398 20399 // A TextEncoder object has an associated encoding and encoder. 20400 20401 /** @private */ 20402 this._encoding = null; 20403 /** @private @type {?Encoder} */ 20404 this._encoder = null; 20405 20406 // Non-standard 20407 /** @private @type {boolean} */ 20408 this._do_not_flush = false; 20409 /** @private @type {string} */ 20410 this._fatal = Boolean(options['fatal']) ? 'fatal' : 'replacement'; 20411 20412 // 1. Let enc be a new TextEncoder object. 20413 var enc = this; 20414 20415 // 2. Set enc's encoding to UTF-8's encoder. 20416 if (Boolean(options['NONSTANDARD_allowLegacyEncoding'])) { 20417 // NONSTANDARD behavior. 20418 label = label !== undefined ? String(label) : DEFAULT_ENCODING; 20419 var encoding = getEncoding(label); 20420 if (encoding === null || encoding.name === 'replacement') 20421 throw RangeError('Unknown encoding: ' + label); 20422 if (!encoders[encoding.name]) { 20423 throw Error('Encoder not present.' + 20424 ' Did you forget to include encoding-indexes.js?'); 20425 } 20426 enc._encoding = encoding; 20427 } else { 20428 // Standard behavior. 20429 enc._encoding = getEncoding('utf-8'); 20430 20431 if (label !== undefined && 'console' in global) { 20432 console.warn('TextEncoder constructor called with encoding label, ' 20433 + 'which is ignored.'); 20434 } 20435 } 20436 20437 // For pre-ES5 runtimes: 20438 if (!Object.defineProperty) 20439 this.encoding = enc._encoding.name.toLowerCase(); 20440 20441 // 3. Return enc. 20442 return enc; 20443 } 20444 20445 if (Object.defineProperty) { 20446 // The encoding attribute's getter must return encoding's name. 20447 Object.defineProperty(TextEncoder.prototype, 'encoding', { 20448 /** @this {TextEncoder} */ 20449 get: function() { return this._encoding.name.toLowerCase(); } 20450 }); 20451 } 20452 20453 /** 20454 * @param {string=} opt_string The string to encode. 20455 * @param {Object=} options 20456 * @return {!Uint8Array} Encoded bytes, as a Uint8Array. 20457 */ 20458 TextEncoder.prototype.encode = function encode(opt_string, options) { 20459 opt_string = opt_string ? String(opt_string) : ''; 20460 options = ToDictionary(options); 20461 20462 // NOTE: This option is nonstandard. None of the encodings 20463 // permitted for encoding (i.e. UTF-8, UTF-16) are stateful when 20464 // the input is a USVString so streaming is not necessary. 20465 if (!this._do_not_flush) 20466 this._encoder = encoders[this._encoding.name]({ 20467 fatal: this._fatal === 'fatal'}); 20468 this._do_not_flush = Boolean(options['stream']); 20469 20470 // 1. Convert input to a stream. 20471 var input = new Stream(stringToCodePoints(opt_string)); 20472 20473 // 2. Let output be a new stream 20474 var output = []; 20475 20476 /** @type {?(number|!Array.<number>)} */ 20477 var result; 20478 // 3. While true, run these substeps: 20479 while (true) { 20480 // 1. Let token be the result of reading from input. 20481 var token = input.read(); 20482 if (token === end_of_stream) 20483 break; 20484 // 2. Let result be the result of processing token for encoder, 20485 // input, output. 20486 result = this._encoder.handler(input, token); 20487 if (result === finished) 20488 break; 20489 if (Array.isArray(result)) 20490 output.push.apply(output, /**@type {!Array.<number>}*/(result)); 20491 else 20492 output.push(result); 20493 } 20494 // TODO: Align with spec algorithm. 20495 if (!this._do_not_flush) { 20496 while (true) { 20497 result = this._encoder.handler(input, input.read()); 20498 if (result === finished) 20499 break; 20500 if (Array.isArray(result)) 20501 output.push.apply(output, /**@type {!Array.<number>}*/(result)); 20502 else 20503 output.push(result); 20504 } 20505 this._encoder = null; 20506 } 20507 // 3. If result is finished, convert output into a byte sequence, 20508 // and then return a Uint8Array object wrapping an ArrayBuffer 20509 // containing output. 20510 return new Uint8Array(output); 20511 }; 20512 20513 20514 // 20515 // 9. The encoding 20516 // 20517 20518 // 9.1 utf-8 20519 20520 // 9.1.1 utf-8 decoder 20521 /** 20522 * @constructor 20523 * @implements {Decoder} 20524 * @param {{fatal: boolean}} options 20525 */ 20526 function UTF8Decoder(options) { 20527 var fatal = options.fatal; 20528 20529 // utf-8's decoder's has an associated utf-8 code point, utf-8 20530 // bytes seen, and utf-8 bytes needed (all initially 0), a utf-8 20531 // lower boundary (initially 0x80), and a utf-8 upper boundary 20532 // (initially 0xBF). 20533 var /** @type {number} */ utf8_code_point = 0, 20534 /** @type {number} */ utf8_bytes_seen = 0, 20535 /** @type {number}
20535 */ utf8_bytes_needed = 0, 20536 /** @type {number} */ utf8_lower_boundary = 0x80, 20537 /** @type {number} */ utf8_upper_boundary = 0xBF; 20538 20539 /** 20540 * @param {Stream} stream The stream of bytes being decoded. 20541 * @param {number} bite The next byte read from the stream. 20542 * @return {?(number|!Array.<number>)} The next code point(s) 20543 * decoded, or null if not enough data exists in the input 20544 * stream to decode a complete code point. 20545 */ 20546 this.handler = function(stream, bite) { 20547 // 1. If byte is end-of-stream and utf-8 bytes needed is not 0, 20548 // set utf-8 bytes needed to 0 and return error. 20549 if (bite === end_of_stream && utf8_bytes_needed !== 0) { 20550 utf8_bytes_needed = 0; 20551 return decoderError(fatal); 20552 } 20553 20554 // 2. If byte is end-of-stream, return finished. 20555 if (bite === end_of_stream) 20556 return finished; 20557 20558 // 3. If utf-8 bytes needed is 0, based on byte: 20559 if (utf8_bytes_needed === 0) { 20560 20561 // 0x00 to 0x7F 20562 if (inRange(bite, 0x00, 0x7F)) { 20563 // Return a code point whose value is byte. 20564 return bite; 20565 } 20566 20567 // 0xC2 to 0xDF 20568 if (inRange(bite, 0xC2, 0xDF)) { 20569 // Set utf-8 bytes needed to 1 and utf-8 code point to byte 20570 // â 0xC0. 20571 utf8_bytes_needed = 1; 20572 utf8_code_point = bite - 0xC0; 20573 } 20574 20575 // 0xE0 to 0xEF 20576 else if (inRange(bite, 0xE0, 0xEF)) { 20577 // 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0. 20578 if (bite === 0xE0) 20579 utf8_lower_boundary = 0xA0; 20580 // 2. If byte is 0xED, set utf-8 upper boundary to 0x9F. 20581 if (bite === 0xED) 20582 utf8_upper_boundary = 0x9F; 20583 // 3. Set utf-8 bytes needed to 2 and utf-8 code point to 20584 // byte â 0xE0. 20585 utf8_bytes_needed = 2; 20586 utf8_code_point = bite - 0xE0; 20587 } 20588 20589 // 0xF0 to 0xF4 20590 else if (inRange(bite, 0xF0, 0xF4)) { 20591 // 1. If byte is 0xF0, set utf-8 lower boundary to 0x90. 20592 if (bite === 0xF0) 20593 utf8_lower_boundary = 0x90; 20594 // 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F. 20595 if (bite === 0xF4) 20596 utf8_upper_boundary = 0x8F; 20597 // 3. Set utf-8 bytes needed to 3 and utf-8 code point to 20598 // byte â 0xF0. 20599 utf8_bytes_needed = 3; 20600 utf8_code_point = bite - 0xF0; 20601 } 20602 20603 // Otherwise 20604 else { 20605 // Return error. 20606 return decoderError(fatal); 20607 } 20608 20609 // Then (byte is in the range 0xC2 to 0xF4, inclusive) set 20610 // utf-8 code point to utf-8 code point << (6 Ã utf-8 bytes 20611 // needed) and return continue. 20612 utf8_code_point = utf8_code_point << (6 * utf8_bytes_needed); 20613 return null; 20614 } 20615 20616 // 4. If byte is not in the range utf-8 lower boundary to utf-8 20617 // upper boundary, inclusive, run these substeps: 20618 if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) { 20619 20620 // 1. Set utf-8 code point, utf-8 bytes needed, and utf-8 20621 // bytes seen to 0, set utf-8 lower boundary to 0x80, and set 20622 // utf-8 upper boundary to 0xBF. 20623 utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; 20624 utf8_lower_boundary = 0x80; 20625 utf8_upper_boundary = 0xBF; 20626 20627 // 2. Prepend byte to stream. 20628 stream.prepend(bite); 20629 20630 // 3. Return error. 20631 return decoderError(fatal); 20632 } 20633 20634 // 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary 20635 // to 0xBF. 20636 utf8_lower_boundary = 0x80; 20637 utf8_upper_boundary = 0xBF; 20638 20639 // 6. Increase utf-8 bytes seen by one and set utf-8 code point 20640 // to utf-8 code point + (byte â 0x80) << (6 Ã (utf-8 bytes 20641 // needed â utf-8 bytes seen)). 20642 utf8_bytes_seen += 1; 20643 utf8_code_point += (bite - 0x80) << (6 * (utf8_bytes_needed - 20644 utf8_bytes_seen)); 20645 20646 // 7. If utf-8 bytes seen is not equal to utf-8 bytes needed, 20647 // continue. 20648 if (utf8_bytes_seen !== utf8_bytes_needed) 20649 return null; 20650 20651 // 8. Let code point be utf-8 code point. 20652 var code_point = utf8_code_point; 20653 20654 // 9. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes 20655 // seen to 0.
20656 utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0; 20657 20658 // 10. Return a code point whose value is code point. 20659 return code_point; 20660 }; 20661 } 20662 20663 // 9.1.2 utf-8 encoder 20664 /** 20665 * @constructor 20666 * @implements {Encoder} 20667 * @param {{fatal: boolean}} options 20668 */ 20669 function UTF8Encoder(options) { 20670 var fatal = options.fatal; 20671 /** 20672 * @param {Stream} stream Input stream. 20673 * @param {number} code_point Next code point read from the stream. 20674 * @return {(number|!Array.<number>)} Byte(s) to emit. 20675 */ 20676 this.handler = function(stream, code_point) { 20677 // 1. If code point is end-of-stream, return finished. 20678 if (code_point === end_of_stream) 20679 return finished; 20680 20681 // 2. If code point is in the range U+0000 to U+007F, return a 20682 // byte whose value is code point. 20683 if (inRange(code_point, 0x0000, 0x007f)) 20684 return code_point; 20685 20686 // 3. Set count and offset based on the range code point is in: 20687 var count, offset; 20688 // U+0080 to U+07FF, inclusive: 20689 if (inRange(code_point, 0x0080, 0x07FF)) { 20690 // 1 and 0xC0 20691 count = 1; 20692 offset = 0xC0; 20693 } 20694 // U+0800 to U+FFFF, inclusive: 20695 else if (inRange(code_point, 0x0800, 0xFFFF)) { 20696 // 2 and 0xE0 20697 count = 2; 20698 offset = 0xE0; 20699 } 20700 // U+10000 to U+10FFFF, inclusive: 20701 else if (inRange(code_point, 0x10000, 0x10FFFF)) { 20702 // 3 and 0xF0 20703 count = 3; 20704 offset = 0xF0; 20705 } 20706 20707 // 4.Let bytes be a byte sequence whose first byte is (code 20708 // point >> (6 Ã count)) + offset. 20709 var bytes = [(code_point >> (6 * count)) + offset]; 20710 20711 // 5. Run these substeps while count is greater than 0: 20712 while (count > 0) { 20713 20714 // 1. Set temp to code point >> (6 Ã (count â 1)). 20715 var temp = code_point >> (6 * (count - 1)); 20716 20717 // 2. Append to bytes 0x80 | (temp & 0x3F). 20718 bytes.push(0x80 | (temp & 0x3F)); 20719 20720 // 3. Decrease count by one. 20721 count -= 1; 20722 } 20723 20724 // 6. Return bytes bytes, in order. 20725 return bytes; 20726 }; 20727 } 20728 20729 /** @param {{fatal: boolean}} options */ 20730 encoders['UTF-8'] = function(options) { 20731 return new UTF8Encoder(options); 20732 }; 20733 /** @param {{fatal: boolean}} options */ 20734 decoders['UTF-8'] = function(options) { 20735 return new UTF8Decoder(options); 20736 }; 20737 20738 // 20739 // 10. Legacy single-byte encodings 20740 // 20741 20742 // 10.1 single-byte decoder 20743 /** 20744 * @constructor 20745 * @implements {Decoder} 20746 * @param {!Array.<number>} index The encoding index. 20747 * @param {{fatal: boolean}} options 20748 */ 20749 function SingleByteDecoder(index, options) { 20750 var fatal = options.fatal; 20751 /** 20752 * @param {Stream} stream The stream of bytes being decoded. 20753 * @param {number} bite The next byte read from the stream. 20754 * @return {?(number|!Array.<number>)} The next code point(s) 20755 * decoded, or null if not enough data exists in the input 20756 * stream to decode a complete code point. 20757 */ 20758 this.handler = function(stream, bite) { 20759 // 1. If byte is end-of-stream, return finished. 20760 if (bite === end_of_stream) 20761 return finished; 20762 20763 // 2. If byte is an ASCII byte, return a code point whose value 20764 // is byte. 20765 if (isASCIIByte(bite)) 20766 return bite; 20767 20768 // 3. Let code point be the index code point for byte â 0x80 in 20769 // index single-byte. 20770 var code_point = index[bite - 0x80]; 20771 20772 // 4. If code point is null, return error. 20773 if (code_point === null) 20774 return decoderError(fatal); 20775 20776 // 5. Return a code point whose value is code point. 20777 return code_point; 20778 }; 20779 } 20780 20781 // 10.2 single-byte encoder 20782 /** 20783 * @constructor 20784 * @implements {Encoder} 20785 * @param {!Array.<?number>} index The encoding index. 20786 * @param {{fatal: boolean}} options 20787 */ 20788 function SingleByteEncoder(index, options) { 20789 var fatal = options.fatal; 20790 /** 20791 * @param {Stream} stream Input stream. 20792 * @param {number} code_point Next code point read from the stream. 20793 * @return {(number|!Array.<number>)} Byte(s) to emit. 20794 */ 20795 this.handler = function(stream, code_point) { 20796 // 1. If code point is end-of-stream, return finished. 20797 if (code_point === end_of_stream) 20798 return finished; 20799 20800 // 2. If code point is an ASCII code point, return a byte whose 20801 // value is code point. 20802 if (isASCIICodePoint(code_point)) 20803 return code_point; 20804 20805 // 3. Let pointer be the index pointer for code point in index 20806 // single-byte. 20807 var pointer = indexPointerFor(code_point, index); 20808 20809 // 4. If pointer is null, return error with code point. 20810 if (pointer === null) 20811 encoderError(code_point); 20812 20813 // 5. Return a byte whose value is pointer + 0x80. 20814 return pointer + 0x80; 20815 }; 20816 } 20817 20818 (function() { 20819 if (!('encoding-indexes' in global)) 20820 return;
20821 encodings.forEach(function(category) { 20822 if (category.heading !== 'Legacy single-byte encodings') 20823 return; 20824 category.encodings.forEach(function(encoding) { 20825 var name = encoding.name; 20826 var idx = index(name.toLowerCase()); 20827 /** @param {{fatal: boolean}} options */ 20828 decoders[name] = function(options) { 20829 return new SingleByteDecoder(idx, options); 20830 }; 20831 /** @param {{fatal: boolean}} options */ 20832 encoders[name] = function(options) { 20833 return new SingleByteEncoder(idx, options); 20834 }; 20835 }); 20836 }); 20837 }()); 20838 20839 // 20840 // 11. Legacy multi-byte Chinese (simplified) encodings 20841 // 20842 20843 // 11.1 gbk 20844 20845 // 11.1.1 gbk decoder 20846 // gbk's decoder is gb18030's decoder. 20847 /** @param {{fatal: boolean}} options */ 20848 decoders['GBK'] = function(options) { 20849 return new GB18030Decoder(options); 20850 }; 20851 20852 // 11.1.2 gbk encoder 20853 // gbk's encoder is gb18030's encoder with its gbk flag set. 20854 /** @param {{fatal: boolean}} options */ 20855 encoders['GBK'] = function(options) { 20856 return new GB18030Encoder(options, true); 20857 }; 20858 20859 // 11.2 gb18030 20860 20861 // 11.2.1 gb18030 decoder 20862 /** 20863 * @constructor 20864 * @implements {Decoder} 20865 * @param {{fatal: boolean}} options 20866 */ 20867 function GB18030Decoder(options) { 20868 var fatal = options.fatal; 20869 // gb18030's decoder has an associated gb18030 first, gb18030 20870 // second, and gb18030 third (all initially 0x00). 20871 var /** @type {number} */ gb18030_first = 0x00, 20872 /** @type {number} */ gb18030_second = 0x00, 20873 /** @type {number} */ gb18030_third = 0x00; 20874 /** 20875 * @param {Stream} stream The stream of bytes being decoded. 20876 * @param {number} bite The next byte read from the stream. 20877 * @return {?(number|!Array.<number>)} The next code point(s) 20878 * decoded, or null if not enough data exists in the input 20879 * stream to decode a complete code point. 20880 */ 20881 this.handler = function(stream, bite) { 20882 // 1. If byte is end-of-stream and gb18030 first, gb18030 20883 // second, and gb18030 third are 0x00, return finished. 20884 if (bite === end_of_stream && gb18030_first === 0x00 && 20885 gb18030_second === 0x00 && gb18030_third === 0x00) { 20886 return finished; 20887 } 20888 // 2. If byte is end-of-stream, and gb18030 first, gb18030 20889 // second, or gb18030 third is not 0x00, set gb18030 first, 20890 // gb18030 second, and gb18030 third to 0x00, and return error. 20891 if (bite === end_of_stream && 20892 (gb18030_first !== 0x00 || gb18030_second !== 0x00 || 20893 gb18030_third !== 0x00)) { 20894 gb18030_first = 0x00; 20895 gb18030_second = 0x00; 20896 gb18030_third = 0x00; 20897 decoderError(fatal); 20898 } 20899 var code_point; 20900 // 3. If gb18030 third is not 0x00, run these substeps: 20901 if (gb18030_third !== 0x00) { 20902 // 1. Let code point be null. 20903 code_point = null; 20904 // 2. If byte is in the range 0x30 to 0x39, set code point to
20905 // the index gb18030 ranges code point for (((gb18030 first â 20906 // 0x81) Ã 10 + gb18030 second â 0x30) Ã 126 + gb18030 third â 20907 // 0x81) Ã 10 + byte â 0x30. 20908 if (inRange(bite, 0x30, 0x39)) { 20909 code_point = indexGB18030RangesCodePointFor( 20910 (((gb18030_first - 0x81) * 10 + (gb18030_second - 0x30)) * 126 + 20911 (gb18030_third - 0x81)) * 10 + bite - 0x30); 20912 } 20913 20914 // 3. Let buffer be a byte sequence consisting of gb18030 20915 // second, gb18030 third, and byte, in order. 20916 var buffer = [gb18030_second, gb18030_third, bite]; 20917 20918 // 4. Set gb18030 first, gb18030 second, and gb18030 third to 20919 // 0x00. 20920 gb18030_first = 0x00; 20921 gb18030_second = 0x00; 20922 gb18030_third = 0x00; 20923 20924 // 5. If code point is null, prepend buffer to stream and 20925 // return error. 20926 if (code_point === null) { 20927 stream.prepend(buffer); 20928 return decoderError(fatal); 20929 } 20930 20931 // 6. Return a code point whose value is code point. 20932 return code_point; 20933 } 20934 20935 // 4. If gb18030 second is not 0x00, run these substeps: 20936 if (gb18030_second !== 0x00) { 20937 20938 // 1. If byte is in the range 0x81 to 0xFE, set gb18030 third 20939 // to byte and return continue. 20940 if (inRange(bite, 0x81, 0xFE)) { 20941 gb18030_third = bite; 20942 return null; 20943 } 20944 20945 // 2. Prepend gb18030 second followed by byte to stream, set 20946 // gb18030 first and gb18030 second to 0x00, and return error. 20947 stream.prepend([gb18030_second, bite]); 20948 gb18030_first = 0x00; 20949 gb18030_second = 0x00; 20950 return decoderError(fatal); 20951 } 20952 20953 // 5. If gb18030 first is not 0x00, run these substeps: 20954 if (gb18030_first !== 0x00) { 20955 20956 // 1. If byte is in the range 0x30 to 0x39, set gb18030 second 20957 // to byte and return continue. 20958 if (inRange(bite, 0x30, 0x39)) { 20959 gb18030_second = bite; 20960 return null; 20961 } 20962 20963 // 2. Let lead be gb18030 first, let pointer be null, and set 20964 // gb18030 first to 0x00. 20965 var lead = gb18030_first; 20966 var pointer = null; 20967 gb18030_first = 0x00; 20968 20969 // 3. Let offset be 0x40 if byte is less than 0x7F and 0x41 20970 // otherwise. 20971 var offset = bite < 0x7F ? 0x40 : 0x41; 20972 20973 // 4. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFE, 20974 // set pointer to (lead â 0x81) Ã 190 + (byte â offset). 20975 if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE)) 20976 pointer = (lead - 0x81) * 190 + (bite - offset); 20977 20978 // 5. Let code point be null if pointer is null and the index 20979 // code point for pointer in index gb18030 otherwise. 20980 code_point = pointer === null ? null : 20981 indexCodePointFor(pointer, index('gb18030')); 20982 20983 // 6. If code point is null and byte is an ASCII byte, prepend 20984 // byte to stream. 20985 if (code_point === null && isASCIIByte(bite)) 20986 stream.prepend(bite); 20987 20988 // 7. If code point is null, return error. 20989 if (code_point === null) 20990 return decoderError(fatal); 20991 20992 // 8. Return a code point whose value is code point. 20993 return code_point; 20994 } 20995 20996 // 6. If byte is an ASCII byte, return a code point whose value 20997 // is byte. 20998 if (isASCIIByte(bite)) 20999 return bite; 21000 21001 // 7. If byte is 0x80, return code point U+20AC. 21002 if (bite === 0x80) 21003 return 0x20AC; 21004 21005 // 8. If byte is in the range 0x81 to 0xFE, set gb18030 first to 21006 // byte and return continue. 21007 if (inRange(bite, 0x81, 0xFE)) { 21008 gb18030_first = bite; 21009 return null; 21010 } 21011 21012 // 9. Return error. 21013 return decoderError(fatal); 21014 }; 21015 } 21016 21017 // 11.2.2 gb18030 encoder 21018 /** 21019 * @constructor 21020 * @implements {Encoder} 21021 * @param {{fatal: boolean}} options 21022 * @param {boolean=} gbk_flag 21023 */ 21024 function GB18030Encoder(options, gbk_flag) { 21025 var fatal = options.fatal; 21026 // gb18030's decoder has an associated gbk flag (initially unset). 21027 /** 21028 * @param {Stream} stream Input stream. 21029 * @param {number} code_point Next code point read from the stream. 21030 * @return {(number|!Array.<number>)} Byte(s) to emit. 21031 */ 21032 this.handler = function(stream, code_point) { 21033 // 1. If code point is end-of-stream, return finished. 21034 if (code_point === end_of_stream) 21035 return finished; 21036 21037 // 2. If code point is an ASCII code point, return a byte whose 21038 // value is code point. 21039 if (isASCIICodePoint(code_point)) 21040 return code_point; 21041 21042 // 3. If code point is U+E5E5, return error with code point. 21043 if (code_point === 0xE5E5) 21044 return encoderError(code_point); 21045 21046 // 4. If the gbk flag is set and code point is U+20AC, return 21047 // byte 0x80. 21048 if (gbk_flag && code_point === 0x20AC) 21049 return 0x80; 21050 21051 // 5. Let pointer be the index pointer for code point in index 21052 // gb18030. 21053 var pointer = indexPointerFor(code_point, index('gb18030')); 21054 21055 // 6. If pointer is not null, run these substeps: 21056 if (pointer !== null) { 21057 21058 // 1. Let lead be floor(pointer / 190) + 0x81. 21059 var lead = floor(pointer / 190) + 0x81; 21060 21061 // 2. Let trail be pointer % 190. 21062 var trail = pointer % 190; 21063 21064 // 3. Let offset be 0x40 if trail is less than 0x3F and 0x41 otherwise. 21065 var offset = trail < 0x3F ? 0x40 : 0x41; 21066 21067 // 4. Return two bytes whose values are lead and trail + offset. 21068 return [lead, trail + offset]; 21069 } 21070 21071 // 7. If gbk flag is set, return error with code point. 21072 if (gbk_flag) 21073 return encoderError(code_point); 21074 21075 // 8. Set pointer to the index gb18030 ranges pointer for code 21076 // point. 21077 pointer = indexGB18030RangesPointerFor(code_point); 21078 21079 // 9. Let byte1 be floor(pointer / 10 / 126 / 10). 21080 var byte1 = floor(pointer / 10 / 126 / 10); 21081 21082 // 10. Set pointer to pointer â byte1 Ã 10 Ã 126 Ã 10. 21083 pointer = pointer - byte1 * 10 * 126 * 10; 21084 21085 // 11. Let byte2 be floor(pointer / 10 / 126). 21086 var byte2 = floor(pointer / 10 / 126); 21087 21088 // 12. Set pointer to pointer â byte2 Ã 10 Ã 126. 21089 pointer = pointer - byte2 * 10 * 126; 21090 21091 // 13. Let byte3 be floor(pointer / 10). 21092 var byte3 = floor(pointer / 10); 21093 21094 // 14. Let byte4 be pointer â byte3 Ã 10. 21095 var byte4 = pointer - byte3 * 10; 21096 21097 // 15. Return four bytes whose values are byte1 + 0x81, byte2 + 21098 // 0x30, byte3 + 0x81, byte4 + 0x30. 21099 return [byte1 + 0x81, 21100 byte2 + 0x30, 21101 byte3 + 0x81, 21102 byte4 + 0x30]; 21103 }; 21104 } 21105 21106 /** @param {{fatal: boolean}} options */ 21107 encoders['gb18030'] = function(options) { 21108 return new GB18030Encoder(options); 21109 }; 21110 /** @param {{fatal: boolean}} options */ 21111 decoders['gb18030'] = function(options) { 21112 return new GB18030Decoder(options); 21113 }; 21114 21115 21116 // 21117 // 12. Legacy multi-byte Chinese (traditional) encodings 21118 // 21119 21120 // 12.1 big5 21121 21122 // 12.1.1 big5 decoder 21123 /** 21124 * @constructor 21125 * @implements {Decoder} 21126 * @param {{fatal: boolean}} options 21127 */ 21128 function Big5Decoder(options) { 21129 var fatal = options.fatal; 21130 // big5's decoder has an associated big5 lead (initially 0x00). 21131 var /** @type {number} */ big5_lead = 0x00; 21132 21133 /** 21134 * @param {Stream} stream The stream of bytes being decoded. 21135 * @param {number} bite The next byte read from the stream. 21136 * @return {?(number|!Array.<number>)} The next code point(s) 21137 * decoded, or null if not enough data exists in the input 21138 * stream to decode a complete code point. 21139 */ 21140 this.handler = function(stream, bite) { 21141 // 1. If byte is end-of-stream and big5 lead is not 0x00, set 21142 // big5 lead to 0x00 and return error. 21143 if (bite === end_of_stream && big5_lead !== 0x00) { 21144 big5_lead = 0x00; 21145 return decoderError(fatal); 21146 } 21147 21148 // 2. If byte is end-of-stream and big5 lead is 0x00, return 21149 // finished. 21150 if (bite === end_of_stream && big5_lead === 0x00) 21151 return finished; 21152 21153 // 3. If big5 lead is not 0x00, let lead be big5 lead, let 21154 // pointer be null, set big5 lead to 0x00, and then run these 21155 // substeps: 21156 if (big5_lead !== 0x00) { 21157 var lead = big5_lead; 21158 var pointer = null; 21159 big5_lead = 0x00; 21160 21161 // 1. Let offset be 0x40 if byte is less than 0x7F and 0x62 21162 // otherwise. 21163 var offset = bite < 0x7F ? 0x40 : 0x62; 21164 21165 // 2. If byte is in the range 0x40 to 0x7E or 0xA1 to 0xFE, 21166 // set pointer to (lead â 0x81) Ã 157 + (byte â offset). 21167 if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE)) 21168 pointer = (lead - 0x81) * 157 + (bite - offset); 21169 21170 // 3. If there is a row in the table below whose first column 21171 // is pointer, return the two code points listed in its second 21172 // column 21173 // Pointer | Code points 21174 // --------+-------------- 21175 // 1133 | U+00CA U+0304 21176 // 1135 | U+00CA U+030C 21177 // 1164 | U+00EA U+0304 21178 // 1166 | U+00EA U+030C 21179 switch (pointer) { 21180 case 1133: return [0x00CA, 0x0304]; 21181 case 1135: return [0x00CA, 0x030C]; 21182 case 1164: return [0x00EA, 0x0304]; 21183 case 1166: return [0x00EA, 0x030C]; 21184 } 21185 21186 // 4. Let code point be null if pointer is null and the index 21187 // code point for pointer in index big5 otherwise. 21188 var code_point = (pointer === null) ? null : 21189 indexCodePointFor(pointer, index('big5')); 21190 21191 // 5. If code point is null and byte is an ASCII byte, prepend 21192 // byte to stream. 21193 if (code_point === null && isASCIIByte(bite)) 21194 stream.prepend(bite); 21195 21196 // 6. If code point is null, return error. 21197 if (code_point === null) 21198 return decoderError(fatal); 21199 21200 // 7. Return a code point whose value is code point. 21201 return code_point; 21202 } 21203 21204 // 4. If byte is an ASCII byte, return a code point whose value 21205 // is byte. 21206 if (isASCIIByte(bite)) 21207 return bite; 21208 21209 // 5. If byte is in the range 0x81 to 0xFE, set big5 lead to 21210 // byte and return continue. 21211 if (inRange(bite, 0x81, 0xFE)) { 21212 big5_lead = bite; 21213 return null; 21214 } 21215 21216 // 6. Return error. 21217 return decoderError(fatal); 21218 }; 21219 } 21220 21221 // 12.1.2 big5 encoder 21222 /** 21223 * @constructor 21224 * @implements {Encoder} 21225 * @param {{fatal: boolean}} options 21226 */ 21227 function Big5Encoder(options) { 21228 var fatal = options.fatal; 21229 /** 21230 * @param {Stream} stream Input stream. 21231 * @param {number} code_point Next code point read from the stream. 21232 * @return {(number|!Array.<number>)} Byte(s) to emit. 21233 */ 21234 this.handler = function(stream, code_point) { 21235 // 1. If code point is end-of-stream, return finished. 21236 if (code_point === end_of_stream) 21237 return finished; 21238 21239 // 2. If code point is an ASCII code point, return a byte whose 21240 // value is code point. 21241 if (isASCIICodePoint(code_point)) 21242 return code_point; 21243 21244 // 3. Let pointer be the index big5 pointer for code point. 21245 var pointer = indexBig5PointerFor(code_point); 21246 21247 // 4. If pointer is null, return error with code point. 21248 if (pointer === null) 21249 return encoderError(code_point); 21250 21251 // 5. Let lead be floor(pointer / 157) + 0x81. 21252 var lead = floor(pointer / 157) + 0x81; 21253 21254 // 6. If lead is less than 0xA1, return error with code point. 21255 if (lead < 0xA1) 21256 return encoderError(code_point); 21257 21258 // 7. Let trail be pointer % 157. 21259 var trail = pointer % 157; 21260 21261 // 8. Let offset be 0x40 if trail is less than 0x3F and 0x62 21262 // otherwise. 21263 var offset = trail < 0x3F ? 0x40 : 0x62; 21264 21265 // Return two bytes whose values are lead and trail + offset. 21266 return [lead, trail + offset]; 21267 }; 21268 } 21269 21270 /** @param {{fatal: boolean}} options */ 21271 encoders['Big5'] = function(options) { 21272 return new Big5Encoder(options); 21273 }; 21274 /** @param {{fatal: boolean}} options */ 21275 decoders['Big5'] = function(options) { 21276 return new Big5Decoder(options); 21277 }; 21278 21279 21280 // 21281 // 13. Legacy multi-byte Japanese encodings 21282 // 21283 21284 // 13.1 euc-jp 21285 21286 // 13.1.1 euc-jp decoder 21287 /** 21288 * @constructor 21289 * @implements {Decoder} 21290 * @param {{fatal: boolean}} options 21291 */ 21292 function EUCJPDecoder(options) { 21293 var fatal = options.fatal; 21294 21295 // euc-jp's decoder has an associated euc-jp jis0212 flag 21296 // (initially unset) and euc-jp lead (initially 0x00). 21297 var /** @type {boolean} */ eucjp_jis0212_flag = false, 21298 /** @type {number} */ eucjp_lead = 0x00; 21299 21300 /** 21301 * @param {Stream} stream The stream of bytes being decoded. 21302 * @param {number} bite The next byte read from the stream. 21303 * @return {?(number|!Array.<number>)} The next code point(s) 21304 * decoded, or null if not enough data exists in the input 21305 * stream to decode a complete code point. 21306 */ 21307 this.handler = function(stream, bite) { 21308 // 1. If byte is end-of-stream and euc-jp lead is not 0x00, set 21309 // euc-jp lead to 0x00, and return error. 21310 if (bite === end_of_stream && eucjp_lead !== 0x00) { 21311 eucjp_lead = 0x00; 21312 return decoderError(fatal); 21313 } 21314 21315 // 2. If byte is end-of-stream and euc-jp lead is 0x00, return 21316 // finished. 21317 if (bite === end_of_stream && eucjp_lead === 0x00) 21318 return finished; 21319 21320 // 3. If euc-jp lead is 0x8E and byte is in the range 0xA1 to 21321 // 0xDF, set euc-jp lead to 0x00 and return a code point whose 21322 // value is 0xFF61 + byte â 0xA1. 21323 if (eucjp_lead === 0x8E && inRange(bite, 0xA1, 0xDF)) { 21324 eucjp_lead = 0x00; 21325 return 0xFF61 + bite - 0xA1; 21326 } 21327 21328 // 4. If euc-jp lead is 0x8F and byte is in the range 0xA1 to 21329 // 0xFE, set the euc-jp jis0212 flag, set euc-jp lead to byte, 21330 // and return continue. 21331 if (eucjp_lead === 0x8F && inRange(bite, 0xA1, 0xFE)) { 21332 eucjp_jis0212_flag = true; 21333 eucjp_lead = bite; 21334 return null; 21335 } 21336 21337 // 5. If euc-jp lead is not 0x00, let lead be euc-jp lead, set 21338 // euc-jp lead to 0x00, and run these substeps: 21339 if (eucjp_lead !== 0x00) { 21340 var lead = eucjp_lead; 21341 eucjp_lead = 0x00; 21342 21343 // 1. Let code point be null. 21344 var code_point = null; 21345 21346 // 2. If lead and byte are both in the range 0xA1 to 0xFE, set 21347 // code point to the index code point for (lead â 0xA1) Ã 94 + 21348 // byte â 0xA1 in index jis0208 if the euc-jp jis0212 flag is 21349 // unset and in index jis0212 otherwise. 21350 if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) { 21351 code_point = indexCodePointFor( 21352 (lead - 0xA1) * 94 + (bite - 0xA1), 21353 index(!eucjp_jis0212_flag ? 'jis0208' : 'jis0212')); 21354 } 21355 21356 // 3. Unset the euc-jp jis0212 flag. 21357 eucjp_jis0212_flag = false;
21358 21359 // 4. If byte is not in the range 0xA1 to 0xFE, prepend byte 21360 // to stream. 21361 if (!inRange(bite, 0xA1, 0xFE)) 21362 stream.prepend(bite); 21363 21364 // 5. If code point is null, return error. 21365 if (code_point === null) 21366 return decoderError(fatal); 21367 21368 // 6. Return a code point whose value is code point. 21369 return code_point; 21370 } 21371 21372 // 6. If byte is an ASCII byte, return a code point whose value 21373 // is byte. 21374 if (isASCIIByte(bite)) 21375 return bite; 21376 21377 // 7. If byte is 0x8E, 0x8F, or in the range 0xA1 to 0xFE, set 21378 // euc-jp lead to byte and return continue. 21379 if (bite === 0x8E || bite === 0x8F || inRange(bite, 0xA1, 0xFE)) { 21380 eucjp_lead = bite; 21381 return null; 21382 } 21383 21384 // 8. Return error. 21385 return decoderError(fatal); 21386 }; 21387 } 21388 21389 // 13.1.2 euc-jp encoder 21390 /** 21391 * @constructor 21392 * @implements {Encoder} 21393 * @param {{fatal: boolean}} options 21394 */ 21395 function EUCJPEncoder(options) { 21396 var fatal = options.fatal; 21397 /** 21398 * @param {Stream} stream Input stream. 21399 * @param {number} code_point Next code point read from the stream. 21400 * @return {(number|!Array.<number>)} Byte(s) to emit. 21401 */ 21402 this.handler = function(stream, code_point) { 21403 // 1. If code point is end-of-stream, return finished. 21404 if (code_point === end_of_stream) 21405 return finished; 21406 21407 // 2. If code point is an ASCII code point, return a byte whose 21408 // value is code point. 21409 if (isASCIICodePoint(code_point)) 21410 return code_point; 21411 21412 // 3. If code point is U+00A5, return byte 0x5C. 21413 if (code_point === 0x00A5) 21414 return 0x5C; 21415 21416 // 4. If code point is U+203E, return byte 0x7E. 21417 if (code_point === 0x203E) 21418 return 0x7E; 21419 21420 // 5. If code point is in the range U+FF61 to U+FF9F, return two 21421 // bytes whose values are 0x8E and code point â 0xFF61 + 0xA1. 21422 if (inRange(code_point, 0xFF61, 0xFF9F)) 21423 return [0x8E, code_point - 0xFF61 + 0xA1]; 21424 21425 // 6. If code point is U+2212, set it to U+FF0D. 21426 if (code_point === 0x2212) 21427 code_point = 0xFF0D; 21428 21429 // 7. Let pointer be the index pointer for code point in index 21430 // jis0208. 21431 var pointer = indexPointerFor(code_point, index('jis0208')); 21432 21433 // 8. If pointer is null, return error with code point. 21434 if (pointer === null) 21435 return encoderError(code_point); 21436 21437 // 9. Let lead be floor(pointer / 94) + 0xA1. 21438 var lead = floor(pointer / 94) + 0xA1; 21439 21440 // 10. Let trail be pointer % 94 + 0xA1. 21441 var trail = pointer % 94 + 0xA1; 21442 21443 // 11. Return two bytes whose values are lead and trail. 21444 return [lead, trail]; 21445 }; 21446 } 21447 21448 /** @param {{fatal: boolean}} options */ 21449 encoders['EUC-JP'] = function(options) { 21450 return new EUCJPEncoder(options); 21451 }; 21452 /** @param {{fatal: boolean}} options */ 21453 decoders['EUC-JP'] = function(options) { 21454 return new EUCJPDecoder(options); 21455 }; 21456 21457 // 13.2 iso-2022-jp 21458 21459 // 13.2.1 iso-2022-jp decoder 21460 /** 21461 * @constructor 21462 * @implements {Decoder} 21463 * @param {{fatal: boolean}} options 21464 */ 21465 function ISO2022JPDecoder(options) { 21466 var fatal = options.fatal; 21467 /** @enum */ 21468 var states = { 21469 ASCII: 0, 21470 Roman: 1, 21471 Katakana: 2, 21472 LeadByte: 3, 21473 TrailByte: 4, 21474 EscapeStart: 5, 21475 Escape: 6 21476 }; 21477 // iso-2022-jp's decoder has an associated iso-2022-jp decoder 21478 // state (initially ASCII), iso-2022-jp decoder output state 21479 // (initially ASCII), iso-2022-jp lead (initially 0x00), and 21480 // iso-2022-jp output flag (initially unset). 21481 var /** @type {number} */ iso2022jp_decoder_state = states.ASCII, 21482 /** @type {number} */ iso2022jp_decoder_output_state = states.ASCII, 21483 /** @type {number} */ iso2022jp_lead = 0x00, 21484 /** @type {boolean} */ iso2022jp_output_flag = false; 21485 /** 21486 * @param {Stream} stream The stream of bytes being decoded. 21487 * @param {number} bite The next byte read from the stream. 21488 * @return {?(number|!Array.<number>)} The next code point(s) 21489 * decoded, or null if not enough data exists in the input 21490 * stream to decode a complete code point. 21491 */ 21492 this.handler = function(stream, bite) { 21493 // switching on iso-2022-jp decoder state: 21494 switch (iso2022jp_decoder_state) { 21495 default: 21496 case states.ASCII: 21497 // ASCII 21498 // Based on byte: 21499 21500 // 0x1B 21501 if (bite === 0x1B) { 21502 // Set iso-2022-jp decoder state to escape start and return 21503 // continue.
21504 iso2022jp_decoder_state = states.EscapeStart; 21505 return null; 21506 } 21507 21508 // 0x00 to 0x7F, excluding 0x0E, 0x0F, and 0x1B 21509 if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E 21510 && bite !== 0x0F && bite !== 0x1B) { 21511 // Unset the iso-2022-jp output flag and return a code point 21512 // whose value is byte. 21513 iso2022jp_output_flag = false; 21514 return bite; 21515 } 21516 21517 // end-of-stream 21518 if (bite === end_of_stream) { 21519 // Return finished. 21520 return finished; 21521 } 21522 21523 // Otherwise 21524 // Unset the iso-2022-jp output flag and return error. 21525 iso2022jp_output_flag = false; 21526 return decoderError(fatal); 21527 21528 case states.Roman: 21529 // Roman 21530 // Based on byte: 21531 21532 // 0x1B 21533 if (bite === 0x1B) { 21534 // Set iso-2022-jp decoder state to escape start and return 21535 // continue. 21536 iso2022jp_decoder_state = states.EscapeStart; 21537 return null; 21538 } 21539 21540 // 0x5C 21541 if (bite === 0x5C) { 21542 // Unset the iso-2022-jp output flag and return code point 21543 // U+00A5. 21544 iso2022jp_output_flag = false; 21545 return 0x00A5; 21546 } 21547 21548 // 0x7E 21549 if (bite === 0x7E) { 21550 // Unset the iso-2022-jp output flag and return code point 21551 // U+203E. 21552 iso2022jp_output_flag = false; 21553 return 0x203E; 21554 } 21555 21556 // 0x00 to 0x7F, excluding 0x0E, 0x0F, 0x1B, 0x5C, and 0x7E 21557 if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E && bite !== 0x0F 21558 && bite !== 0x1B && bite !== 0x5C && bite !== 0x7E) { 21559 // Unset the iso-2022-jp output flag and return a code point 21560 // whose value is byte. 21561 iso2022jp_output_flag = false; 21562 return bite; 21563 } 21564 21565 // end-of-stream 21566 if (bite === end_of_stream) { 21567 // Return finished. 21568 return finished; 21569 } 21570 21571 // Otherwise 21572 // Unset the iso-2022-jp output flag and return error. 21573 iso2022jp_output_flag = false; 21574 return decoderError(fatal); 21575 21576 case states.Katakana: 21577 // Katakana 21578 // Based on byte: 21579 21580 // 0x1B 21581 if (bite === 0x1B) { 21582 // Set iso-2022-jp decoder state to escape start and return 21583 // continue. 21584 iso2022jp_decoder_state = states.EscapeStart; 21585 return null; 21586 } 21587 21588 // 0x21 to 0x5F 21589 if (inRange(bite, 0x21, 0x5F)) { 21590 // Unset the iso-2022-jp output flag and return a code point 21591 // whose value is 0xFF61 + byte â 0x21. 21592 iso2022jp_output_flag = false; 21593 return 0xFF61 + bite - 0x21; 21594 } 21595 21596 // end-of-stream 21597 if (bite === end_of_stream) { 21598 // Return finished. 21599 return finished; 21600 } 21601 21602 // Otherwise 21603 // Unset the iso-2022-jp output flag and return error. 21604 iso2022jp_output_flag = false; 21605 return decoderError(fatal); 21606 21607 case states.LeadByte: 21608 // Lead byte 21609 // Based on byte: 21610 21611 // 0x1B 21612 if (bite === 0x1B) { 21613 // Set iso-2022-jp decoder state to escape start and return 21614 // continue. 21615 iso2022jp_decoder_state = states.EscapeStart; 21616 return null; 21617 } 21618 21619 // 0x21 to 0x7E 21620 if (inRange(bite, 0x21, 0x7E)) { 21621 // Unset the iso-2022-jp output flag, set iso-2022-jp lead 21622 // to byte, iso-2022-jp decoder state to trail byte, and 21623 // return continue. 21624 iso2022jp_output_flag = false; 21625 iso2022jp_lead = bite; 21626 iso2022jp_decoder_state = states.TrailByte; 21627 return null; 21628 } 21629 21630 // end-of-stream 21631 if (bite === end_of_stream) { 21632 // Return finished. 21633 return finished; 21634 } 21635 21636 // Otherwise 21637 // Unset the iso-2022-jp output flag and return error. 21638 iso2022jp_output_flag = false; 21639 return decoderError(fatal); 21640 21641 case states.TrailByte: 21642 // Trail byte 21643 // Based on byte: 21644 21645 // 0x1B 21646 if (bite === 0x1B) { 21647 // Set iso-2022-jp decoder state to escape start and return 21648 // continue.
21649 iso2022jp_decoder_state = states.EscapeStart; 21650 return decoderError(fatal); 21651 } 21652 21653 // 0x21 to 0x7E 21654 if (inRange(bite, 0x21, 0x7E)) { 21655 // 1. Set the iso-2022-jp decoder state to lead byte. 21656 iso2022jp_decoder_state = states.LeadByte; 21657 21658 // 2. Let pointer be (iso-2022-jp lead â 0x21) Ã 94 + byte â 0x21. 21659 var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21; 21660 21661 // 3. Let code point be the index code point for pointer in 21662 // index jis0208. 21663 var code_point = indexCodePointFor(pointer, index('jis0208')); 21664 21665 // 4. If code point is null, return error. 21666 if (code_point === null) 21667 return decoderError(fatal); 21668 21669 // 5. Return a code point whose value is code point. 21670 return code_point; 21671 } 21672 21673 // end-of-stream 21674 if (bite === end_of_stream) { 21675 // Set the iso-2022-jp decoder state to lead byte, prepend 21676 // byte to stream, and return error. 21677 iso2022jp_decoder_state = states.LeadByte; 21678 stream.prepend(bite); 21679 return decoderError(fatal); 21680 } 21681 21682 // Otherwise 21683 // Set iso-2022-jp decoder state to lead byte and return 21684 // error. 21685 iso2022jp_decoder_state = states.LeadByte; 21686 return decoderError(fatal); 21687 21688 case states.EscapeStart: 21689 // Escape start 21690 21691 // 1. If byte is either 0x24 or 0x28, set iso-2022-jp lead to 21692 // byte, iso-2022-jp decoder state to escape, and return 21693 // continue. 21694 if (bite === 0x24 || bite === 0x28) { 21695 iso2022jp_lead = bite; 21696 iso2022jp_decoder_state = states.Escape; 21697 return null; 21698 } 21699 21700 // 2. Prepend byte to stream. 21701 stream.prepend(bite); 21702 21703 // 3. Unset the iso-2022-jp output flag, set iso-2022-jp 21704 // decoder state to iso-2022-jp decoder output state, and 21705 // return error. 21706 iso2022jp_output_flag = false; 21707 iso2022jp_decoder_state = iso2022jp_decoder_output_state; 21708 return decoderError(fatal); 21709 21710 case states.Escape: 21711 // Escape 21712 21713 // 1. Let lead be iso-2022-jp lead and set iso-2022-jp lead to 21714 // 0x00. 21715 var lead = iso2022jp_lead; 21716 iso2022jp_lead = 0x00; 21717 21718 // 2. Let state be null. 21719 var state = null; 21720 21721 // 3. If lead is 0x28 and byte is 0x42, set state to ASCII. 21722 if (lead === 0x28 && bite === 0x42) 21723 state = states.ASCII; 21724 21725 // 4. If lead is 0x28 and byte is 0x4A, set state to Roman. 21726 if (lead === 0x28 && bite === 0x4A) 21727 state = states.Roman; 21728 21729 // 5. If lead is 0x28 and byte is 0x49, set state to Katakana. 21730 if (lead === 0x28 && bite === 0x49) 21731 state = states.Katakana; 21732 21733 // 6. If lead is 0x24 and byte is either 0x40 or 0x42, set 21734 // state to lead byte. 21735 if (lead === 0x24 && (bite === 0x40 || bite === 0x42)) 21736 state = states.LeadByte; 21737 21738 // 7. If state is non-null, run these substeps: 21739 if (state !== null) { 21740 // 1. Set iso-2022-jp decoder state and iso-2022-jp decoder 21741 // output state to states. 21742 iso2022jp_decoder_state = iso2022jp_decoder_state = state; 21743 21744 // 2. Let output flag be the iso-2022-jp output flag. 21745 var output_flag = iso2022jp_output_flag; 21746 21747 // 3. Set the iso-2022-jp output flag. 21748 iso2022jp_output_flag = true; 21749 21750 // 4. Return continue, if output flag is unset, and error 21751 // otherwise. 21752 return !output_flag ? null : decoderError(fatal); 21753 } 21754 21755 // 8. Prepend lead and byte to stream. 21756 stream.prepend([lead, bite]); 21757 21758 // 9. Unset the iso-2022-jp output flag, set iso-2022-jp 21759 // decoder state to iso-2022-jp decoder output state and 21760 // return error. 21761 iso2022jp_output_flag = false; 21762 iso2022jp_decoder_state = iso2022jp_decoder_output_state; 21763 return decoderError(fatal); 21764 } 21765 }; 21766 } 21767 21768 // 13.2.2 iso-2022-jp encoder 21769 /** 21770 * @constructor 21771 * @implements {Encoder} 21772 * @param {{fatal: boolean}} options 21773 */ 21774 function ISO2022JPEncoder(options) { 21775 var fatal = options.fatal; 21776 // iso-2022-jp's encoder has an associated iso-2022-jp encoder 21777 // state which is one of ASCII, Roman, and jis0208 (initially 21778 // ASCII). 21779 /** @enum */ 21780 var states = { 21781 ASCII: 0, 21782 Roman: 1, 21783 jis0208: 2 21784 }; 21785 var /** @type {number} */ iso2022jp_state = states.ASCII; 21786 /** 21787 * @param {Stream} stream Input stream. 21788 * @param {number} code_point Next code point read from the stream. 21789 * @return {(number|!Array.<number>)} Byte(s) to emit. 21790 */ 21791 this.handler = function(stream, code_point) { 21792 // 1. If code point is end-of-stream and iso-2022-jp encoder 21793 // state is not ASCII, prepend code point to stream, set 21794 // iso-2022-jp encoder state to ASCII, and return three bytes 21795 // 0x1B 0x28 0x42. 21796 if (code_point === end_of_stream && 21797 iso2022jp_state !== states.ASCII) { 21798 stream.prepend(code_point); 21799 iso2022jp_state = states.ASCII; 21800 return [0x1B, 0x28, 0x42]; 21801 } 21802 21803 // 2. If code point is end-of-stream and iso-2022-jp encoder 21804 // state is ASCII, return finished. 21805 if (code_point === end_of_stream && iso2022jp_state === states.ASCII) 21806 return finished; 21807 21808 // 3. If ISO-2022-JP encoder state is ASCII or Roman, and code 21809 // point is U+000E, U+000F, or U+001B, return error with U+FFFD. 21810 if ((iso2022jp_state === states.ASCII || 21811 iso2022jp_state === states.Roman) && 21812 (code_point === 0x000E || code_point === 0x000F || 21813 code_point === 0x001B)) { 21814 return encoderError(0xFFFD); 21815 } 21816 21817 // 4. If iso-2022-jp encoder state is ASCII and code point is an 21818 // ASCII code point, return a byte whose value is code point. 21819 if (iso2022jp_state === states.ASCII && 21820 isASCIICodePoint(code_point)) 21821 return code_point; 21822 21823 // 5. If iso-2022-jp encoder state is Roman and code point is an 21824 // ASCII code point, excluding U+005C and U+007E, or is U+00A5 21825 // or U+203E, run these substeps: 21826 if (iso2022jp_state === states.Roman && 21827 ((isASCIICodePoint(code_point) && 21828 code_point !== 0x005C && code_point !== 0x007E) ||
21829 (code_point == 0x00A5 || code_point == 0x203E))) { 21830 21831 // 1. If code point is an ASCII code point, return a byte 21832 // whose value is code point. 21833 if (isASCIICodePoint(code_point)) 21834 return code_point; 21835 21836 // 2. If code point is U+00A5, return byte 0x5C. 21837 if (code_point === 0x00A5) 21838 return 0x5C; 21839 21840 // 3. If code point is U+203E, return byte 0x7E. 21841 if (code_point === 0x203E) 21842 return 0x7E; 21843 } 21844 21845 // 6. If code point is an ASCII code point, and iso-2022-jp 21846 // encoder state is not ASCII, prepend code point to stream, set 21847 // iso-2022-jp encoder state to ASCII, and return three bytes 21848 // 0x1B 0x28 0x42. 21849 if (isASCIICodePoint(code_point) && 21850 iso2022jp_state !== states.ASCII) { 21851 stream.prepend(code_point); 21852 iso2022jp_state = states.ASCII; 21853 return [0x1B, 0x28, 0x42]; 21854 } 21855 21856 // 7. If code point is either U+00A5 or U+203E, and iso-2022-jp 21857 // encoder state is not Roman, prepend code point to stream, set 21858 // iso-2022-jp encoder state to Roman, and return three bytes 21859 // 0x1B 0x28 0x4A. 21860 if ((code_point === 0x00A5 || code_point === 0x203E) && 21861 iso2022jp_state !== states.Roman) { 21862 stream.prepend(code_point); 21863 iso2022jp_state = states.Roman; 21864 return [0x1B, 0x28, 0x4A]; 21865 } 21866 21867 // 8. If code point is U+2212, set it to U+FF0D. 21868 if (code_point === 0x2212) 21869 code_point = 0xFF0D; 21870 21871 // 9. Let pointer be the index pointer for code point in index 21872 // jis0208. 21873 var pointer = indexPointerFor(code_point, index('jis0208')); 21874 21875 // 10. If pointer is null, return error with code point. 21876 if (pointer === null) 21877 return encoderError(code_point); 21878 21879 // 11. If iso-2022-jp encoder state is not jis0208, prepend code 21880 // point to stream, set iso-2022-jp encoder state to jis0208, 21881 // and return three bytes 0x1B 0x24 0x42. 21882 if (iso2022jp_state !== states.jis0208) { 21883 stream.prepend(code_point); 21884 iso2022jp_state = states.jis0208; 21885 return [0x1B, 0x24, 0x42]; 21886 } 21887 21888 // 12. Let lead be floor(pointer / 94) + 0x21. 21889 var lead = floor(pointer / 94) + 0x21; 21890 21891 // 13. Let trail be pointer % 94 + 0x21. 21892 var trail = pointer % 94 + 0x21; 21893 21894 // 14. Return two bytes whose values are lead and trail. 21895 return [lead, trail]; 21896 }; 21897 } 21898 21899 /** @param {{fatal: boolean}} options */ 21900 encoders['ISO-2022-JP'] = function(options) { 21901 return new ISO2022JPEncoder(options); 21902 }; 21903 /** @param {{fatal: boolean}} options */ 21904 decoders['ISO-2022-JP'] = function(options) { 21905 return new ISO2022JPDecoder(options); 21906 }; 21907 21908 // 13.3 shift_jis 21909 21910 // 13.3.1 shift_jis decoder 21911 /** 21912 * @constructor 21913 * @implements {Decoder} 21914 * @param {{fatal: boolean}} options 21915 */ 21916 function ShiftJISDecoder(options) { 21917 var fatal = options.fatal; 21918 // shift_jis's decoder has an associated shift_jis lead (initially 21919 // 0x00). 21920 var /** @type {number} */ shiftjis_lead = 0x00; 21921 /** 21922 * @param {Stream} stream The stream of bytes being decoded. 21923 * @param {number} bite The next byte read from the stream. 21924 * @return {?(number|!Array.<number>)} The next code point(s) 21925 * decoded, or null if not enough data exists in the input 21926 * stream to decode a complete code point. 21927 */ 21928 this.handler = function(stream, bite) { 21929 // 1. If byte is end-of-stream and shift_jis lead is not 0x00, 21930 // set shift_jis lead to 0x00 and return error. 21931 if (bite === end_of_stream && shiftjis_lead !== 0x00) { 21932 shiftjis_lead = 0x00; 21933 return decoderError(fatal); 21934 } 21935 21936 // 2. If byte is end-of-stream and shift_jis lead is 0x00, 21937 // return finished. 21938 if (bite === end_of_stream && shiftjis_lead === 0x00) 21939 return finished; 21940 21941 // 3. If shift_jis lead is not 0x00, let lead be shift_jis lead, 21942 // let pointer be null, set shift_jis lead to 0x00, and then run 21943 // these substeps: 21944 if (shiftjis_lead !== 0x00) { 21945 var lead = shiftjis_lead; 21946 var pointer = null; 21947 shiftjis_lead = 0x00; 21948 21949 // 1. Let offset be 0x40, if byte is less than 0x7F, and 0x41 21950 // otherwise. 21951 var offset = (bite < 0x7F) ? 0x40 : 0x41; 21952 21953 // 2. Let lead offset be 0x81, if lead is less than 0xA0, and 21954 // 0xC1 otherwise. 21955 var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1; 21956 21957 // 3. If byte is in the range 0x40 to 0x7E or 0x80 to 0xFC, 21958 // set pointer to (lead â lead offset) Ã 188 + byte â offset. 21959 if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC)) 21960 pointer = (lead - lead_offset) * 188 + bite - offset; 21961 21962 // 4. Let code point be null, if pointer is null, and the 21963 // index code point for pointer in index jis0208 otherwise. 21964 var code_point = (pointer === null) ? null : 21965 indexCodePointFor(pointer, index('jis0208')); 21966 21967 // 5. If code point is null and pointer is in the range 8836 21968 // to 10528, return a code point whose value is 0xE000 + 21969 // pointer â 8836. 21970 if (code_point === null && pointer !== null && 21971 inRange(pointer, 8836, 10528)) 21972 return 0xE000 + pointer - 8836; 21973 21974 // 6. If code point is null and byte is an ASCII byte, prepend 21975 // byte to stream. 21976 if (code_point === null && isASCIIByte(bite)) 21977 stream.prepend(bite); 21978 21979 // 7. If code point is null, return error. 21980 if (code_point === null) 21981 return decoderError(fatal); 21982 21983 // 8. Return a code point whose value is code point. 21984 return code_point; 21985 } 21986 21987 // 4. If byte is an ASCII byte or 0x80, return a code point 21988 // whose value is byte. 21989 if (isASCIIByte(bite) || bite === 0x80) 21990 return bite; 21991 21992 // 5. If byte is in the range 0xA1 to 0xDF, return a code point 21993 // whose value is 0xFF61 + byte â 0xA1. 21994 if (inRange(bite, 0xA1, 0xDF)) 21995 return 0xFF61 + bite - 0xA1; 21996 21997 // 6. If byte is in the range 0x81 to 0x9F or 0xE0 to 0xFC, set 21998 // shift_jis lead to byte and return continue. 21999 if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) { 22000 shiftjis_lead = bite; 22001 return null; 22002 } 22003 22004 // 7. Return error. 22005 return decoderError(fatal); 22006 }; 22007 } 22008 22009 // 13.3.2 shift_jis encoder 22010 /** 22011 * @constructor 22012 * @implements {Encoder} 22013 * @param {{fatal: boolean}} options 22014 */ 22015 function ShiftJISEncoder(options) { 22016 var fatal = options.fatal; 22017 /** 22018 * @param {Stream} stream Input stream. 22019 * @param {number} code_point Next code point read from the stream. 22020 * @return {(number|!Array.<number>)} Byte(s) to emit. 22021 */ 22022 this.handler = function(stream, code_point) { 22023 // 1. If code point is end-of-stream, return finished. 22024 if (code_point === end_of_stream) 22025 return finished; 22026 22027 // 2. If code point is an ASCII code point or U+0080, return a 22028 // byte whose value is code point. 22029 if (isASCIICodePoint(code_point) || code_point === 0x0080) 22030 return code_point; 22031 22032 // 3. If code point is U+00A5, return byte 0x5C. 22033 if (code_point === 0x00A5) 22034 return 0x5C; 22035 22036 // 4. If code point is U+203E, return byte 0x7E. 22037 if (code_point === 0x203E) 22038 return 0x7E; 22039 22040 // 5. If code point is in the range U+FF61 to U+FF9F, return a 22041 // byte whose value is code point â 0xFF61 + 0xA1. 22042 if (inRange(code_point, 0xFF61, 0xFF9F)) 22043 return code_point - 0xFF61 + 0xA1; 22044 22045 // 6. If code point is U+2212, set it to U+FF0D. 22046 if (code_point === 0x2212) 22047 code_point = 0xFF0D; 22048 22049 // 7. Let pointer be the index shift_jis pointer for code point. 22050 var pointer = indexShiftJISPointerFor(code_point); 22051 22052 // 8. If pointer is null, return error with code point. 22053 if (pointer === null) 22054 return encoderError(code_point); 22055 22056 // 9. Let lead be floor(pointer / 188). 22057 var lead = floor(pointer / 188); 22058 22059 // 10. Let lead offset be 0x81, if lead is less than 0x1F, and 22060 // 0xC1 otherwise. 22061 var lead_offset = (lead < 0x1F) ? 0x81 : 0xC1; 22062 22063 // 11. Let trail be pointer % 188. 22064 var trail = pointer % 188; 22065 22066 // 12. Let offset be 0x40, if trail is less than 0x3F, and 0x41 22067 // otherwise. 22068 var offset = (trail < 0x3F) ? 0x40 : 0x41; 22069 22070 // 13. Return two bytes whose values are lead + lead offset and 22071 // trail + offset. 22072 return [lead + lead_offset, trail + offset]; 22073 }; 22074 } 22075 22076 /** @param {{fatal: boolean}} options */ 22077 encoders['Shift_JIS'] = function(options) { 22078 return new ShiftJISEncoder(options); 22079 }; 22080 /** @param {{fatal: boolean}} options */ 22081 decoders['Shift_JIS'] = function(options) { 22082 return new ShiftJISDecoder(options); 22083 }; 22084 22085 // 22086 // 14. Legacy multi-byte Korean encodings 22087 // 22088 22089 // 14.1 euc-kr 22090 22091 // 14.1.1 euc-kr decoder 22092 /** 22093 * @constructor 22094 * @implements {Decoder} 22095 * @param {{fatal: boolean}} options 22096 */ 22097 function EUCKRDecoder(options) { 22098 var fatal = options.fatal; 22099 22100 // euc-kr's decoder has an associated euc-kr lead (initially 0x00). 22101 var /** @type {number} */ euckr_lead = 0x00; 22102 /** 22103 * @param {Stream} stream The stream of bytes being decoded. 22104 * @param {number} bite The next byte read from the stream. 22105 * @return {?(number|!Array.<number>)} The next code point(s) 22106 * decoded, or null if not enough data exists in the input 22107 * stream to decode a complete code point. 22108 */ 22109 this.handler = function(stream, bite) { 22110 // 1. If byte is end-of-stream and euc-kr lead is not 0x00, set 22111 // euc-kr lead to 0x00 and return error. 22112 if (bite === end_of_stream && euckr_lead !== 0) { 22113 euckr_lead = 0x00; 22114 return decoderError(fatal); 22115 } 22116 22117 // 2. If byte is end-of-stream and euc-kr lead is 0x00, return 22118 // finished. 22119 if (bite === end_of_stream && euckr_lead === 0) 22120 return finished; 22121 22122 // 3. If euc-kr lead is not 0x00, let lead be euc-kr lead, let 22123 // pointer be null, set euc-kr lead to 0x00, and then run these 22124 // substeps: 22125 if (euckr_lead !== 0x00) { 22126 var lead = euckr_lead; 22127 var pointer = null; 22128 euckr_lead = 0x00; 22129 22130 // 1. If byte is in the range 0x41 to 0xFE, set pointer to 22131 // (lead â 0x81) Ã 190 + (byte â 0x41). 22132 if (inRange(bite, 0x41, 0xFE)) 22133 pointer = (lead - 0x81) * 190 + (bite - 0x41); 22134 22135 // 2. Let code point be null, if pointer is null, and the 22136 // index code point for pointer in index euc-kr otherwise. 22137 var code_point = (pointer === null) 22138 ? null : indexCodePointFor(pointer, index('euc-kr')); 22139 22140 // 3. If code point is null and byte is an ASCII byte, prepend 22141 // byte to stream. 22142 if (pointer === null && isASCIIByte(bite)) 22143 stream.prepend(bite); 22144 22145 // 4. If code point is null, return error. 22146 if (code_point === null) 22147 return decoderError(fatal); 22148 22149 // 5. Return a code point whose value is code point. 22150 return code_point; 22151 } 22152 22153 // 4. If byte is an ASCII byte, return a code point whose value 22154 // is byte. 22155 if (isASCIIByte(bite)) 22156 return bite; 22157 22158 // 5. If byte is in the range 0x81 to 0xFE, set euc-kr lead to 22159 // byte and return continue. 22160 if (inRange(bite, 0x81, 0xFE)) { 22161 euckr_lead = bite; 22162 return null; 22163 } 22164 22165 // 6. Return error. 22166 return decoderError(fatal); 22167 }; 22168 } 22169 22170 // 14.1.2 euc-kr encoder 22171 /** 22172 * @constructor 22173 * @implements {Encoder} 22174 * @param {{fatal: boolean}} options 22175 */ 22176 function EUCKREncoder(options) { 22177 var fatal = options.fatal; 22178 /** 22179 * @param {Stream} stream Input stream. 22180 * @param {number} code_point Next code point read from the stream. 22181 * @return {(number|!Array.<number>)} Byte(s) to emit. 22182 */ 22183 this.handler = function(stream, code_point) { 22184 // 1. If code point is end-of-stream, return finished. 22185 if (code_point === end_of_stream) 22186 return finished; 22187 22188 // 2. If code point is an ASCII code point, return a byte whose 22189 // value is code point. 22190 if (isASCIICodePoint(code_point)) 22191 return code_point; 22192 22193 // 3. Let pointer be the index pointer for code point in index 22194 // euc-kr. 22195 var pointer = indexPointerFor(code_point, index('euc-kr')); 22196 22197 // 4. If pointer is null, return error with code point. 22198 if (pointer === null) 22199 return encoderError(code_point); 22200 22201 // 5. Let lead be floor(pointer / 190) + 0x81. 22202 var lead = floor(pointer / 190) + 0x81; 22203 22204 // 6. Let trail be pointer % 190 + 0x41. 22205 var trail = (pointer % 190) + 0x41; 22206 22207 // 7. Return two bytes whose values are lead and trail. 22208 return [lead, trail]; 22209 }; 22210 } 22211 22212 /** @param {{fatal: boolean}} options */ 22213 encoders['EUC-KR'] = function(options) { 22214 return new EUCKREncoder(options); 22215 }; 22216 /** @param {{fatal: boolean}} options */ 22217 decoders['EUC-KR'] = function(options) { 22218 return new EUCKRDecoder(options); 22219 }; 22220 22221 22222 // 22223 // 15. Legacy miscellaneous encodings 22224 // 22225 22226 // 15.1 replacement 22227 22228 // Not needed - API throws RangeError 22229
22230 // 15.2 Common infrastructure for utf-16be and utf-16le 22231 22232 /** 22233 * @param {number} code_unit 22234 * @param {boolean} utf16be 22235 * @return {!Array.<number>} bytes 22236 */ 22237 function convertCodeUnitToBytes(code_unit, utf16be) { 22238 // 1. Let byte1 be code unit >> 8. 22239 var byte1 = code_unit >> 8; 22240 22241 // 2. Let byte2 be code unit & 0x00FF. 22242 var byte2 = code_unit & 0x00FF; 22243 22244 // 3. Then return the bytes in order: 22245 // utf-16be flag is set: byte1, then byte2. 22246 if (utf16be) 22247 return [byte1, byte2]; 22248 // utf-16be flag is unset: byte2, then byte1. 22249 return [byte2, byte1]; 22250 } 22251 22252 // 15.2.1 shared utf-16 decoder 22253 /** 22254 * @constructor 22255 * @implements {Decoder} 22256 * @param {boolean} utf16_be True if big-endian, false if little-endian. 22257 * @param {{fatal: boolean}} options 22258 */ 22259 function UTF16Decoder(utf16_be, options) { 22260 var fatal = options.fatal; 22261 var /** @type {?number} */ utf16_lead_byte = null, 22262 /** @type {?number} */ utf16_lead_surrogate = null; 22263 /** 22264 * @param {Stream} stream The stream of bytes being decoded. 22265 * @param {number} bite The next byte read from the stream. 22266 * @return {?(number|!Array.<number>)} The next code point(s) 22267 * decoded, or null if not enough data exists in the input 22268 * stream to decode a complete code point. 22269 */ 22270 this.handler = function(stream, bite) { 22271 // 1. If byte is end-of-stream and either utf-16 lead byte or 22272 // utf-16 lead surrogate is not null, set utf-16 lead byte and 22273 // utf-16 lead surrogate to null, and return error. 22274 if (bite === end_of_stream && (utf16_lead_byte !== null || 22275 utf16_lead_surrogate !== null)) { 22276 return decoderError(fatal); 22277 } 22278 22279 // 2. If byte is end-of-stream and utf-16 lead byte and utf-16 22280 // lead surrogate are null, return finished. 22281 if (bite === end_of_stream && utf16_lead_byte === null && 22282 utf16_lead_surrogate === null) { 22283 return finished; 22284 } 22285 22286 // 3. If utf-16 lead byte is null, set utf-16 lead byte to byte 22287 // and return continue. 22288 if (utf16_lead_byte === null) { 22289 utf16_lead_byte = bite; 22290 return null; 22291 } 22292 22293 // 4. Let code unit be the result of: 22294 var code_unit; 22295 if (utf16_be) { 22296 // utf-16be decoder flag is set 22297 // (utf-16 lead byte << 8) + byte. 22298 code_unit = (utf16_lead_byte << 8) + bite; 22299 } else { 22300 // utf-16be decoder flag is unset 22301 // (byte << 8) + utf-16 lead byte. 22302 code_unit = (bite << 8) + utf16_lead_byte; 22303 } 22304 // Then set utf-16 lead byte to null. 22305 utf16_lead_byte = null; 22306 22307 // 5. If utf-16 lead surrogate is not null, let lead surrogate 22308 // be utf-16 lead surrogate, set utf-16 lead surrogate to null, 22309 // and then run these substeps: 22310 if (utf16_lead_surrogate !== null) { 22311 var lead_surrogate = utf16_lead_surrogate; 22312 utf16_lead_surrogate = null; 22313 22314 // 1. If code unit is in the range U+DC00 to U+DFFF, return a 22315 // code point whose value is 0x10000 + ((lead surrogate â 22316 // 0xD800) << 10) + (code unit â 0xDC00). 22317 if (inRange(code_unit, 0xDC00, 0xDFFF)) { 22318 return 0x10000 + (lead_surrogate - 0xD800) * 0x400 + 22319 (code_unit - 0xDC00); 22320 } 22321 22322 // 2. Prepend the sequence resulting of converting code unit 22323 // to bytes using utf-16be decoder flag to stream and return 22324 // error. 22325 stream.prepend(convertCodeUnitToBytes(code_unit, utf16_be)); 22326 return decoderError(fatal); 22327 } 22328 22329 // 6. If code unit is in the range U+D800 to U+DBFF, set utf-16 22330 // lead surrogate to code unit and return continue. 22331 if (inRange(code_unit, 0xD800, 0xDBFF)) { 22332 utf16_lead_surrogate = code_unit; 22333 return null; 22334 } 22335 22336 // 7. If code unit is in the range U+DC00 to U+DFFF, return 22337 // error. 22338 if (inRange(code_unit, 0xDC00, 0xDFFF)) 22339 return decoderError(fatal); 22340 22341 // 8. Return code point code unit. 22342 return code_unit; 22343 }; 22344 } 22345 22346 // 15.2.2 shared utf-16 encoder 22347 /** 22348 * @constructor 22349 * @implements {Encoder} 22350 * @param {boolean} utf16_be True if big-endian, false if little-endian. 22351 * @param {{fatal: boolean}} options 22352 */ 22353 function UTF16Encoder(utf16_be, options) { 22354 var fatal = options.fatal; 22355 /** 22356 * @param {Stream} stream Input stream. 22357 * @param {number} code_point Next code point read from the stream. 22358 * @return {(number|!Array.<number>)} Byte(s) to emit. 22359 */ 22360 this.handler = function(stream, code_point) { 22361 // 1. If code point is end-of-stream, return finished. 22362 if (code_point === end_of_stream) 22363 return finished; 22364 22365 // 2. If code point is in the range U+0000 to U+FFFF, return the 22366 // sequence resulting of converting code point to bytes using 22367 // utf-16be encoder flag. 22368 if (inRange(code_point, 0x0000, 0xFFFF)) 22369 return convertCodeUnitToBytes(code_point, utf16_be); 22370 22371 // 3. Let lead be ((code point â 0x10000) >> 10) + 0xD800, 22372 // converted to bytes using utf-16be encoder flag. 22373 var lead = convertCodeUnitToBytes( 22374 ((code_point - 0x10000) >> 10) + 0xD800, utf16_be); 22375 22376 // 4. Let trail be ((code point â 0x10000) & 0x3FF) + 0xDC00, 22377 // converted to bytes using utf-16be encoder flag. 22378 var trail = convertCodeUnitToBytes( 22379 ((code_point - 0x10000) & 0x3FF) + 0xDC00, utf16_be); 22380 22381 // 5. Return a byte sequence of lead followed by trail. 22382 return lead.concat(trail); 22383 }; 22384 } 22385 22386 // 15.3 utf-16be 22387 // 15.3.1 utf-16be decoder 22388 /** @param {{fatal: boolean}} options */ 22389 encoders['UTF-16BE'] = function(options) { 22390 return new UTF16Encoder(true, options); 22391 }; 22392 // 15.3.2 utf-16be encoder 22393 /** @param {{fatal: boolean}} options */ 22394 decoders['UTF-16BE'] = function(options) { 22395 return new UTF16Decoder(true, options); 22396 }; 22397 22398 // 15.4 utf-16le 22399 // 15.4.1 utf-16le decoder 22400 /** @param {{fatal: boolean}} options */ 22401 encoders['UTF-16LE'] = function(options) { 22402 return new UTF16Encoder(false, options); 22403 }; 22404 // 15.4.2 utf-16le encoder 22405 /** @param {{fatal: boolean}} options */ 22406 decoders['UTF-16LE'] = function(options) { 22407 return new UTF16Decoder(false, options); 22408 }; 22409
22410 // 15.5 x-user-defined 22411 22412 // 15.5.1 x-user-defined decoder 22413 /** 22414 * @constructor 22415 * @implements {Decoder} 22416 * @param {{fatal: boolean}} options 22417 */ 22418 function XUserDefinedDecoder(options) { 22419 var fatal = options.fatal; 22420 /** 22421 * @param {Stream} stream The stream of bytes being decoded. 22422 * @param {number} bite The next byte read from the stream. 22423 * @return {?(number|!Array.<number>)} The next code point(s) 22424 * decoded, or null if not enough data exists in the input 22425 * stream to decode a complete code point. 22426 */ 22427 this.handler = function(stream, bite) { 22428 // 1. If byte is end-of-stream, return finished. 22429 if (bite === end_of_stream) 22430 return finished; 22431 22432 // 2. If byte is an ASCII byte, return a code point whose value 22433 // is byte. 22434 if (isASCIIByte(bite)) 22435 return bite; 22436 22437 // 3. Return a code point whose value is 0xF780 + byte â 0x80. 22438 return 0xF780 + bite - 0x80; 22439 }; 22440 } 22441 22442 // 15.5.2 x-user-defined encoder 22443 /** 22444 * @constructor 22445 * @implements {Encoder} 22446 * @param {{fatal: boolean}} options 22447 */ 22448 function XUserDefinedEncoder(options) { 22449 var fatal = options.fatal; 22450 /** 22451 * @param {Stream} stream Input stream. 22452 * @param {number} code_point Next code point read from the stream. 22453 * @return {(number|!Array.<number>)} Byte(s) to emit. 22454 */ 22455 this.handler = function(stream, code_point) { 22456 // 1.If code point is end-of-stream, return finished. 22457 if (code_point === end_of_stream) 22458 return finished; 22459 22460 // 2. If code point is an ASCII code point, return a byte whose 22461 // value is code point. 22462 if (isASCIICodePoint(code_point)) 22463 return code_point; 22464 22465 // 3. If code point is in the range U+F780 to U+F7FF, return a 22466 // byte whose value is code point â 0xF780 + 0x80. 22467 if (inRange(code_point, 0xF780, 0xF7FF)) 22468 return code_point - 0xF780 + 0x80; 22469 22470 // 4. Return error with code point. 22471 return encoderError(code_point); 22472 }; 22473 } 22474 22475 /** @param {{fatal: boolean}} options */ 22476 encoders['x-user-defined'] = function(options) { 22477 return new XUserDefinedEncoder(options); 22478 }; 22479 /** @param {{fatal: boolean}} options */ 22480 decoders['x-user-defined'] = function(options) { 22481 return new XUserDefinedDecoder(options); 22482 }; 22483 22484 if (!global['TextEncoder']) 22485 global['TextEncoder'] = TextEncoder; 22486 if (!global['TextDecoder']) 22487 global['TextDecoder'] = TextDecoder; 22488 22489 if (typeof module !== "undefined" && module.exports) { 22490 module.exports = { 22491 TextEncoder: global['TextEncoder'], 22492 TextDecoder: global['TextDecoder'], 22493 EncodingIndexes: global["encoding-indexes"] 22494 }; 22495 } 22496}(this)); 22497// 22498// this is a patch that provides fetch with keep alive support when making 22499// synchronous requests during unload that finish in a 0 status, a problem 22500// introduced by Chrome 80 22501// 22502(function () { 22503 var origInitByEnvironment; 22504 22505 // 22506 // only apply the patch if this browser has the capability to do that type 22507 // of request 22508 //
22509 // have to support fetch() and keepalive flag to continue 22510 if (typeof fetch !== "function" || ! ("keepalive" in new Request(""))) { 22511 return; 22512 } 22513 22514 // 22515 // because the type of request used for a particular LRS depends on the 22516 // URL (CORS, etc.) of the endpoint we can't determine the type of request 22517 // and whether we can do anything about it until construction of the LRS 22518 // itself which does an Environment initialization procedure, so this 22519 // patch is overloading that initialization so that we can wrap the request 22520 // method that is assigned into the LRS object with a function that can 22521 // detect and handle the specific case we're after, and passthrough any 22522 // case we're not attempting to fix 22523 // 22524 origInitByEnvironment = TinCan.LRS.prototype._initByEnvironment; 22525 22526 TinCan.LRS.prototype._initByEnvironment = function (cfg) { 22527 // _makeRequest is apparently not called with `this` bound correctly 22528 var lrs = this, 22529 origMakeRequest; 22530 22531 // call the original to set up the LRS object (`this`) as it would normally 22532 origInitByEnvironment.call(this, cfg); 22533 22534 // 22535 // after the original call the `_makeRequest` function will have been set up 22536 // to do what it needs to do, that function will return the XHR object when 22537 // it is called in a synchronous manner, which gives us a way to override 22538 // the behavior when we detect the condition we care about, specifically a 22539 // return status of 0 22540 // 22541 origMakeRequest = this._makeRequest; 22542 22543 this._makeRequest = function (_fullUrl, headers, cfg) { 22544 var origResult = origMakeRequest.apply(lrs, arguments), 22545 pairs = [], 22546 fullUrl = _fullUrl, 22547 fetchRequestCfg; 22548 22549 // not sure how, but we got an async request which this patch isn't 22550 // trying to solve for, so just return 22551 if (typeof cfg.callback !== "undefined") { 22552 return origResult; 22553 } 22554 22555 // this patch was originally introduced to handle the case of synchronous 22556 // requests being blocked during unload, and it was assumed that it was 22557 // okay to also attempt a fetch version of the request when the response 22558 // was a 0 status indicating offline because it would be better to have 22559 // it succeed if it were back online for instance, but by letting this 22560 // fall through to the fetch request means there is no way to capture 22561 // an original error in the non-unloading case (IOW, normal processing) 22562 // which means that requests that indicate offline during normal processing 22563 // don't result in an error being indicated and so the updates would 22564 // get lost, so only allow falling through to the fetch version when 22565 // we're in the process of unloading 22566 // 22567 if (! blnUnloading) { 22568 return origResult; 22569 } 22570 22571 // original request is unrecognized, succeeded, or doesn't indicate 22572 // a network error (zero status) so nothing else to do here 22573 // 22574 // the `err` property of the result indicates the HTTP status returned 22575 // from the request, a 0 status code is returned for the condition we're 22576 // attempting to fix with this patch, but could be returned in other cases 22577 // where trying a `fetch` is fine, but isn't likely to work either (like 22578 // network failures, etc.) 22579 // 22580 if (typeof origResult.err === "undefined" || origResult.err === null || origResult.err !== 0) { 22581 return origResult; 22582 } 22583 22584 /* 22585 The straightforward way to achieve this should be to just construct 22586 a request using effectively the same values as we sent for the native 22587 request, but formatted in such a way that we can pass it to fetch which 22588 is what the original attempt, still shown here, was which mostly worked, 22589 but owing to another bug in Chrome that prevents CORS requests to be 22590 sent in the manner below (specifically with `keepalive`)
22590we couldn't use 22591 this method. 22592 22593 // 22594 // unfortunately logic in the original `nativeRequest` needs to be 22595 // duplicated here to achieve the same request 22596 // 22597 for (prop in cfg.params) { 22598 if (cfg.params.hasOwnProperty(prop)) { 22599 pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop])); 22600 } 22601 } 22602 22603 if (pairs.length > 0) { 22604 fullUrl += "?" + pairs.join("&"); 22605 } 22606 22607 // 22608 // the rest of this is our patch to send the newer type of request 22609 // 22610 fetchRequestCfg = { 22611 mode: "cors", 22612 cache: "no-cache", 22613 credentials: "include", 22614 keepalive: true, 22615 22616 method: cfg.method, 22617 headers: headers 22618 } 22619 if (cfg.data) { 22620 fetchRequestCfg.body = cfg.data; 22621 } 22622 */ 22623 22624 /* 22625 This is the alternate method being used as described above, to avoid 22626 the CORS problem switch to an "IE Mode request" set up and use that 22627 but still within the confines of a keep alive fetch request 22628 */ 22629 22630 // 22631 // unfortunately logic in the original `nativeRequest` needs to be 22632 // duplicated here to achieve the same request 22633 // 22634 for (prop in cfg.params) { 22635 if (cfg.params.hasOwnProperty(prop)) { 22636 pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop])); 22637 } 22638 } 22639 22640 fullUrl = lrs._IEModeConversion(fullUrl, headers, pairs, cfg); 22641 22642 22643 // 22644 // the rest of this is our patch to send the newer type of request 22645 // 22646 fetchRequestCfg = { 22647 mode: "cors", 22648 cache: "no-cache", 22649 credentials: "include", 22650 keepalive: true, 22651 22652 method: cfg.method, 22653 headers: { 22654 // 22655 // TinCanJS' IE mode conversion routine alters the `headers` object 22656 // and copies the existing headers into the body content, but doesn't 22657 // remove the copied headers from the object so we can't wholesale 22658 // use what is left, so only pull out the Content-Type since it was 22659 // changed by that function, any other header would trigger a CORS 22660 // pre-flight request here which isn't currently (as of Chrome 79) 22661 // supported for `fetch` requests 22662 // 22663 "Content-Type": headers["Content-Type"] 22664 }, 22665 body: cfg.data 22666 } 22667 22668 try { 22669 // 22670 // we have promise handlers here but in the primary case that this code targets, 22671 // synchronous requests during exit, those handlers will never get hit so these 22672 // log messages are likely not to be seen 22673 // 22674 fetch(fullUrl, fetchRequestCfg).then( 22675 function (response) { 22676 lrs.log("Overridden request Fetch with KeepAlive finished with status " + response.status + ":" + response.statusText); 22677 } 22678 ) 22679 ["catch"]( // IE8 has .catch as reserved, so use bracket notation 22680 function (e) { 22681 lrs.log("Overridden request Fetch with KeepAlive returned error: " + e.message); 22682 } 22683 ); 22684 22685 // 22686 // We're making an assumption here that if we can fire this request, it 22687 // will complete successfully. Since we couldn't deal with a failure 22688 // anyway, there's no point in handling the Promise returned by the 22689 // fetch call. 22690 // 22691 // Not returning an object that is normally recognized does prevent 22692 // subsequent requests from not happening, so even though we can't detect 22693 // success/failure, this needs to at least act like success so that the 22694 // other requests are made 22695 // 22696 return { 22697 err: null 22698 }; 22699 } 22700 catch (e) { 22701 // This will likely never get hit, but we don't want any error to bubble up from this method 22702 lrs.log("Overridden request Fetch with KeepAlive threw error: " + e.message); 22703 22704 return { 22705 err: e.message, 22706 xhr: null 22707 }; 22708 } 22709 }; 22710 }; 22711}()); 22712/*! http://mths.be/punycode v1.2.3 by @mathias */ 22713;(function(root) { 22714 22715 /** Detect free variables */ 22716 var freeExports = typeof exports == 'object' && exports; 22717 var freeModule = typeof module == 'object' && module && 22718 module.exports == freeExports && module; 22719 var freeGlobal = typeof global == 'object' && global; 22720 if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { 22721 root = freeGlobal; 22722 } 22723 22724 /** 22725 * The `punycode` object. 22726 * @name punycode 22727 * @type Object 22728 */ 22729 var punycode, 22730 22731 /** Highest positive signed 32-bit float value */ 22732 maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 22733 22734 /** Bootstring parameters */ 22735 base = 36, 22736 tMin = 1, 22737 tMax = 26, 22738 skew = 38, 22739 damp = 700, 22740 initialBias = 72, 22741 initialN = 128, // 0x80 22742 delimiter = '-', // '\x2D' 22743 22744 /** Regular expressions */ 22745 regexPunycode = /^xn--/, 22746 regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars 22747 regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators 22748 22749 /** Error messages */ 22750 errors = { 22751 'overflow': 'Overflow: input needs wider integers to process', 22752 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', 22753 'invalid-input': 'Invalid input' 22754 }, 22755 22756 /** Convenience shortcuts */ 22757 baseMinusTMin = base - tMin, 22758 floor = Math.floor, 22759 stringFromCharCode = String.fromCharCode, 22760 22761 /** Temporary variable */ 22762 key; 22763 22764 /*--------------------------------------------------------------------------*/ 22765 22766 /** 22767 * A generic error utility function. 22768 * @private 22769 * @param {String} type The error type. 22770 * @returns {Error} Throws a `RangeError` with the applicable error message. 22771 */ 22772 function error(type) { 22773 throw RangeError(errors[type]); 22774 } 22775 22776 /** 22777 * A generic `Array#map` utility function. 22778 * @private 22779 * @param {Array} array The array to iterate over. 22780 * @param {Function} callback The function that gets called for every array 22781 * item. 22782 * @returns {Array} A new array of values returned by the callback function. 22783 */ 22784 function map(array, fn) { 22785 var length = array.length; 22786 while (length--) { 22787 array[length] = fn(array[length]); 22788 } 22789 return array; 22790 } 22791 22792 /** 22793 * A simple `Array#map`-like wrapper to work with domain name strings. 22794 * @private 22795 * @param {String} domain The domain name. 22796 * @param {Function} callback The function that gets called for every 22797 * character. 22798 * @returns {Array} A new string of characters returned by the callback 22799 * function. 22800 */ 22801 function mapDomain(string, fn) { 22802 return map(string.split(regexSeparators), fn).join('.'); 22803 } 22804 22805 /** 22806 * Creates an array containing the numeric code points of each Unicode 22807 * character in the string. While JavaScript uses UCS-2 internally, 22808 * this function will convert a pair of surrogate halves (each of which 22809 * UCS-2 exposes as separate characters) into a single code point, 22810 * matching UTF-16. 22811 * @see `punycode.ucs2.encode` 22812 * @see <http://mathiasbynens.be/notes/javascript-encoding> 22813 * @memberOf punycode.ucs2 22814 * @name decode 22815 * @param {String} string The Unicode input string (UCS-2). 22816 * @returns {Array} The new array of code points. 22817 */ 22818 function ucs2decode(string) { 22819 var output = [], 22820 counter = 0, 22821 length = string.length, 22822 value, 22823 extra; 22824 while (counter < length) { 22825 value = string.charCodeAt(counter++); 22826 if (value >= 0xD800 && value <= 0xDBFF && counter < length) { 22827 // high surrogate, and there is a next character 22828 extra = string.charCodeAt(counter++); 22829 if ((extra & 0xFC00) == 0xDC00) { // low surrogate
vendor: 9,410 bytes, lines 22830-23175
22830 output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); 22831 } else { 22832 // unmatched surrogate; only append this code unit, in case the next 22833 // code unit is the high surrogate of a surrogate pair 22834 output.push(value); 22835 counter--; 22836 } 22837 } else { 22838 output.push(value); 22839 } 22840 } 22841 return output; 22842 } 22843 22844 /** 22845 * Creates a string based on an array of numeric code points. 22846 * @see `punycode.ucs2.decode` 22847 * @memberOf punycode.ucs2 22848 * @name encode 22849 * @param {Array} codePoints The array of numeric code points. 22850 * @returns {String} The new Unicode string (UCS-2). 22851 */ 22852 function ucs2encode(array) { 22853 return map(array, function(value) { 22854 var output = ''; 22855 if (value > 0xFFFF) { 22856 value -= 0x10000; 22857 output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); 22858 value = 0xDC00 | value & 0x3FF; 22859 } 22860 output += stringFromCharCode(value); 22861 return output; 22862 }).join(''); 22863 } 22864 22865 /** 22866 * Converts a basic code point into a digit/integer. 22867 * @see `digitToBasic()` 22868 * @private 22869 * @param {Number} codePoint The basic numeric code point value. 22870 * @returns {Number} The numeric value of a basic code point (for use in 22871 * representing integers) in the range `0` to `base - 1`, or `base` if 22872 * the code point does not represent a value. 22873 */ 22874 function basicToDigit(codePoint) { 22875 if (codePoint - 48 < 10) { 22876 return codePoint - 22; 22877 } 22878 if (codePoint - 65 < 26) { 22879 return codePoint - 65; 22880 } 22881 if (codePoint - 97 < 26) { 22882 return codePoint - 97; 22883 } 22884 return base; 22885 } 22886 22887 /** 22888 * Converts a digit/integer into a basic code point. 22889 * @see `basicToDigit()` 22890 * @private 22891 * @param {Number} digit The numeric value of a basic code point. 22892 * @returns {Number} The basic code point whose value (when used for 22893 * representing integers) is `digit`, which needs to be in the range 22894 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is 22895 * used; else, the lowercase form is used. The behavior is undefined 22896 * if `flag` is non-zero and `digit` has no uppercase form. 22897 */ 22898 function digitToBasic(digit, flag) { 22899 // 0..25 map to ASCII a..z or A..Z 22900 // 26..35 map to ASCII 0..9 22901 return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); 22902 } 22903 22904 /** 22905 * Bias adaptation function as per section 3.4 of RFC 3492. 22906 * http://tools.ietf.org/html/rfc3492#section-3.4 22907 * @private 22908 */ 22909 function adapt(delta, numPoints, firstTime) { 22910 var k = 0; 22911 delta = firstTime ? floor(delta / damp) : delta >> 1; 22912 delta += floor(delta / numPoints); 22913 for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { 22914 delta = floor(delta / baseMinusTMin); 22915 } 22916 return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); 22917 } 22918 22919 /** 22920 * Converts a Punycode string of ASCII-only symbols to a string of Unicode 22921 * symbols. 22922 * @memberOf punycode 22923 * @param {String} input The Punycode string of ASCII-only symbols. 22924 * @returns {String} The resulting string of Unicode symbols. 22925 */ 22926 function decode(input) { 22927 // Don't use UCS-2 22928 var output = [], 22929 inputLength = input.length, 22930 out, 22931 i = 0, 22932 n = initialN, 22933 bias = initialBias, 22934 basic, 22935 j, 22936 index, 22937 oldi, 22938 w, 22939 k, 22940 digit, 22941 t, 22942 length, 22943 /** Cached calculation results */ 22944 baseMinusT; 22945 22946 // Handle the basic code points: let `basic` be the number of input code 22947 // points before the last delimiter, or `0` if there is none, then copy 22948 // the first basic code points to the output. 22949 22950 basic = input.lastIndexOf(delimiter); 22951 if (basic < 0) { 22952 basic = 0; 22953 } 22954 22955 for (j = 0; j < basic; ++j) { 22956 // if it's not a basic code point 22957 if (input.charCodeAt(j) >= 0x80) { 22958 error('not-basic'); 22959 } 22960 output.push(input.charCodeAt(j)); 22961 } 22962 22963 // Main decoding loop: start just after the last delimiter if any basic code 22964 // points were copied; start at the beginning otherwise. 22965 22966 for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { 22967 22968 // `index` is the index of the next character to be consumed. 22969 // Decode a generalized variable-length integer into `delta`, 22970 // which gets added to `i`. The overflow checking is easier 22971 // if we increase `i` as we go, then subtract off its starting 22972 // value at the end to obtain `delta`. 22973 for (oldi = i, w = 1, k = base; /* no condition */; k += base) { 22974 22975 if (index >= inputLength) { 22976 error('invalid-input'); 22977 } 22978 22979 digit = basicToDigit(input.charCodeAt(index++)); 22980 22981 if (digit >= base || digit > floor((maxInt - i) / w)) { 22982 error('overflow'); 22983 } 22984 22985 i += digit * w; 22986 t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 22987 22988 if (digit < t) { 22989 break; 22990 } 22991 22992 baseMinusT = base - t; 22993 if (w > floor(maxInt / baseMinusT)) { 22994 error('overflow'); 22995 } 22996 22997 w *= baseMinusT; 22998 22999 } 23000 23001 out = output.length + 1; 23002 bias = adapt(i - oldi, out, oldi == 0); 23003 23004 // `i` was supposed to wrap around from `out` to `0`, 23005 // incrementing `n` each time, so we'll fix that now: 23006 if (floor(i / out) > maxInt - n) { 23007 error('overflow'); 23008 } 23009 23010 n += floor(i / out); 23011 i %= out; 23012 23013 // Insert `n` at position `i` of the output 23014 output.splice(i++, 0, n); 23015 23016 } 23017 23018 return ucs2encode(output); 23019 } 23020 23021 /** 23022 * Converts a string of Unicode symbols to a Punycode string of ASCII-only 23023 * symbols. 23024 * @memberOf punycode 23025 * @param {String} input The string of Unicode symbols. 23026 * @returns {String} The resulting Punycode string of ASCII-only symbols. 23027 */ 23028 function encode(input) { 23029 var n, 23030 delta, 23031 handledCPCount, 23032 basicLength, 23033 bias, 23034 j, 23035 m, 23036 q, 23037 k, 23038 t, 23039 currentValue, 23040 output = [], 23041 /** `inputLength` will hold the number of code points in `input`. */ 23042 inputLength, 23043 /** Cached calculation results */ 23044 handledCPCountPlusOne, 23045 baseMinusT, 23046 qMinusT; 23047 23048 // Convert the input in UCS-2 to Unicode 23049 input = ucs2decode(input); 23050 23051 // Cache the length 23052 inputLength = input.length; 23053 23054 // Initialize the state 23055 n = initialN; 23056 delta = 0; 23057 bias = initialBias; 23058 23059 // Handle the basic code points 23060 for (j = 0; j < inputLength; ++j) { 23061 currentValue = input[j]; 23062 if (currentValue < 0x80) { 23063 output.push(stringFromCharCode(currentValue)); 23064 } 23065 } 23066 23067 handledCPCount = basicLength = output.length; 23068 23069 // `handledCPCount` is the number of code points that have been handled; 23070 // `basicLength` is the number of basic code points. 23071 23072 // Finish the basic string - if it is not empty - with a delimiter 23073 if (basicLength) { 23074 output.push(delimiter); 23075 } 23076 23077 // Main encoding loop: 23078 while (handledCPCount < inputLength) { 23079 23080 // All non-basic code points < n have been handled already. Find the next 23081 // larger one: 23082 for (m = maxInt, j = 0; j < inputLength; ++j) { 23083 currentValue = input[j]; 23084 if (currentValue >= n && currentValue < m) { 23085 m = currentValue; 23086 } 23087 } 23088 23089 // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, 23090 // but guard against overflow 23091 handledCPCountPlusOne = handledCPCount + 1; 23092 if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { 23093 error('overflow'); 23094 } 23095 23096 delta += (m - n) * handledCPCountPlusOne; 23097 n = m; 23098 23099 for (j = 0; j < inputLength; ++j) { 23100 currentValue = input[j]; 23101 23102 if (currentValue < n && ++delta > maxInt) { 23103 error('overflow'); 23104 } 23105 23106 if (currentValue == n) { 23107 // Represent delta as a generalized variable-length integer 23108 for (q = delta, k = base; /* no condition */; k += base) { 23109 t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); 23110 if (q < t) { 23111 break; 23112 } 23113 qMinusT = q - t; 23114 baseMinusT = base - t; 23115 output.push( 23116 stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) 23117 ); 23118 q = floor(qMinusT / baseMinusT); 23119 } 23120 23121 output.push(stringFromCharCode(digitToBasic(q, 0))); 23122 bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); 23123 delta = 0; 23124 ++handledCPCount; 23125 } 23126 } 23127 23128 ++delta; 23129 ++n; 23130 23131 } 23132 return output.join(''); 23133 } 23134 23135 /** 23136 * Converts a Punycode string representing a domain name to Unicode. Only the 23137 * Punycoded parts of the domain name will be converted, i.e. it doesn't 23138 * matter if you call it on a string that has already been converted to 23139 * Unicode. 23140 * @memberOf punycode 23141 * @param {String} domain The Punycode domain name to convert to Unicode. 23142 * @returns {String} The Unicode representation of the given Punycode 23143 * string. 23144 */ 23145 function toUnicode(domain) { 23146 return mapDomain(domain, function(string) { 23147 return regexPunycode.test(string) 23148 ? decode(string.slice(4).toLowerCase()) 23149 : string; 23150 }); 23151 } 23152 23153 /** 23154 * Converts a Unicode string representing a domain name to Punycode. Only the 23155 * non-ASCII parts of the domain name will be converted, i.e. it doesn't 23156 * matter if you call it with a domain that's already in ASCII. 23157 * @memberOf punycode 23158 * @param {String} domain The domain name to convert, as a Unicode string. 23159 * @returns {String} The Punycode representation of the given domain name. 23160 */ 23161 function toASCII(domain) { 23162 return mapDomain(domain, function(string) { 23163 return regexNonASCII.test(string) 23164 ? 'xn--' + encode(string) 23165 : string; 23166 }); 23167 } 23168 23169 /*--------------------------------------------------------------------------*/ 23170 23171 /** Define the public API */ 23172 punycode = { 23173 /** 23174 * A string representing the current Punycode.js version number. 23175 * @memberOf punycode
23176 * @type String 23177 */ 23178 'version': '1.2.3', 23179 /** 23180 * An object of methods to convert from JavaScript's internal character 23181 * representation (UCS-2) to Unicode code points, and back. 23182 * @see <http://mathiasbynens.be/notes/javascript-encoding> 23183 * @memberOf punycode 23184 * @type Object 23185 */ 23186 'ucs2': { 23187 'decode': ucs2decode, 23188 'encode': ucs2encode 23189 }, 23190 'decode': decode, 23191 'encode': encode, 23192 'toASCII': toASCII, 23193 'toUnicode': toUnicode 23194 }; 23195 23196 /** Expose `punycode` */ 23197 // Some AMD build optimizers, like r.js, check for specific condition patterns 23198 // like the following: 23199 if ( 23200 typeof define == 'function' && 23201 typeof define.amd == 'object' && 23202 define.amd 23203 ) { 23204 define(function() { 23205 return punycode; 23206 }); 23207 } else if (freeExports && !freeExports.nodeType) { 23208 if (freeModule) { // in Node.js or RingoJS v0.8.0+ 23209 freeModule.exports = punycode; 23210 } else { // in Narwhal or RingoJS v0.7.0- 23211 for (key in punycode) { 23212 punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); 23213 } 23214 } 23215 } else { // in Rhino or a web browser 23216 root.punycode = punycode; 23217 } 23218 23219}(this)); 23220/*! 23221 * URI.js - Mutating URLs 23222 * 23223 * Version: 1.14.2 23224 * 23225 * Author: Rodney Rehm 23226 * Web: http://medialize.github.io/URI.js/ 23227 * 23228 * Licensed under 23229 * MIT License http://www.opensource.org/licenses/mit-license 23230 * GPL v3 http://opensource.org/licenses/GPL-3.0 23231 * 23232 */ 23233(function (root, factory) { 23234 'use strict'; 23235 // https://github.com/umdjs/umd/blob/master/returnExports.js 23236 if (typeof exports === 'object') { 23237 // Node 23238 module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains')); 23239 } else if (typeof define === 'function' && define.amd) { 23240 // AMD. Register as an anonymous module. 23241 define(['./punycode', './IPv6', './SecondLevelDomains'], factory); 23242 } else { 23243 // Browser globals (root is window) 23244 root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root); 23245 } 23246}(this, function (punycode, IPv6, SLD, root) { 23247 'use strict'; 23248 /*global location, escape, unescape */ 23249 // FIXME: v2.0.0 renamce non-camelCase properties to uppercase 23250 /*jshint camelcase: false */ 23251 23252 // save current URI variable, if any 23253 var _URI = root && root.URI; 23254 23255 function URI(url, base) { 23256 // Allow instantiation without the 'new' keyword 23257 if (!(this instanceof URI)) { 23258 return new URI(url, base); 23259 } 23260 23261 if (url === undefined) { 23262 if (arguments.length) { 23263 throw new TypeError('undefined is not a valid argument for URI'); 23264 } 23265 23266 if (typeof location !== 'undefined') { 23267 url = location.href + ''; 23268 } else { 23269 url = ''; 23270 } 23271 } 23272 23273 this.href(url); 23274 23275 // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor 23276 if (base !== undefined) { 23277 return this.absoluteTo(base); 23278 } 23279 23280 return this; 23281 } 23282 23283 URI.version = '1.14.2'; 23284 23285 var p = URI.prototype; 23286 var hasOwn = Object.prototype.hasOwnProperty; 23287 23288 function escapeRegEx(string) { 23289 // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963 23290 return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 23291 } 23292 23293 function getType(value) { 23294 // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value 23295 if (value === undefined) { 23296 return 'Undefined'; 23297 } 23298 23299 return String(Object.prototype.toString.call(value)).slice(8, -1); 23300 } 23301 23302 function isArray(obj) { 23303 return getType(obj) === 'Array'; 23304 } 23305 23306 function filterArrayValues(data, value) { 23307 var lookup = {}; 23308 var i, length; 23309 23310 if (isArray(value)) { 23311 for (i = 0, length = value.length; i < length; i++) { 23312 lookup[value[i]] = true; 23313 } 23314 } else { 23315 lookup[value] = true; 23316 } 23317 23318 for (i = 0, length = data.length; i < length; i++) { 23319 if (lookup[data[i]] !== undefined) { 23320 data.splice(i, 1); 23321 length--; 23322 i--; 23323 } 23324 } 23325 23326 return data; 23327 } 23328 23329 function arrayContains(list, value) { 23330 var i, length; 23331 23332 // value may be string, number, array, regexp 23333 if (isArray(value)) { 23334 // Note: this can be optimized to O(n) (instead of current O(m * n)) 23335 for (i = 0, length = value.length; i < length; i++) { 23336 if (!arrayContains(list, value[i])) { 23337 return false;
23338 } 23339 } 23340 23341 return true; 23342 } 23343 23344 var _type = getType(value); 23345 for (i = 0, length = list.length; i < length; i++) { 23346 if (_type === 'RegExp') { 23347 if (typeof list[i] === 'string' && list[i].match(value)) { 23348 return true; 23349 } 23350 } else if (list[i] === value) { 23351 return true; 23352 } 23353 } 23354 23355 return false; 23356 } 23357 23358 function arraysEqual(one, two) { 23359 if (!isArray(one) || !isArray(two)) { 23360 return false; 23361 } 23362 23363 // arrays can't be equal if they have different amount of content 23364 if (one.length !== two.length) { 23365 return false; 23366 } 23367 23368 one.sort(); 23369 two.sort(); 23370 23371 for (var i = 0, l = one.length; i < l; i++) { 23372 if (one[i] !== two[i]) { 23373 return false; 23374 } 23375 } 23376 23377 return true; 23378 } 23379 23380 URI._parts = function() { 23381 return { 23382 protocol: null, 23383 username: null, 23384 password: null, 23385 hostname: null, 23386 urn: null, 23387 port: null, 23388 path: null, 23389 query: null, 23390 fragment: null, 23391 // state 23392 duplicateQueryParameters: URI.duplicateQueryParameters, 23393 escapeQuerySpace: URI.escapeQuerySpace 23394 }; 23395 }; 23396 // state: allow duplicate query parameters (a=1&a=1) 23397 URI.duplicateQueryParameters = false; 23398 // state: replaces + with %20 (space in query strings) 23399 URI.escapeQuerySpace = true; 23400 // static properties 23401 URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i; 23402 URI.idn_expression = /[^a-z0-9\.-]/i; 23403 URI.punycode_expression = /(xn--)/i; 23404 // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care? 23405 URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; 23406 // credits to Rich Brown 23407 // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 23408 // specification: http://www.ietf.org/rfc/rfc4291.txt 23409 URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; 23410 // expression used is "gruber revised" (@gruber v2) determined to be the 23411 // best solution in a regex-golf we did a couple of ages ago at 23412 // * http://mathiasbynens.be/demo/url-regex 23413 // * http://rodneyrehm.de/t/url-regex.html 23414 URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»]))/ig; 23415 URI.findUri = { 23416 // valid "scheme://" or "www." 23417 start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi, 23418 // everything up to the next whitespace 23419 end: /[\s\r\n]|$/, 23420 // trim trailing punctuation captured by end RegExp 23421 trim: /[`!()\[\]{};:'".,<>?«»]+$/ 23422 }; 23423 // http://www.iana.org/assignments/uri-schemes.html 23424 // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports 23425 URI.defaultPorts = { 23426 http: '80', 23427 https: '443', 23428 ftp: '21', 23429 gopher: '70', 23430 ws: '80', 23431 wss: '443' 23432 }; 23433 // allowed hostname characters according to RFC 3986 23434 // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded 23435 // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - 23436 URI.invalid_hostname_characters = /[^a-zA-Z0-9\.-]/; 23437 // map DOM Elements to their URI attribute 23438 URI.domAttributes = { 23439 'a': 'href', 23440 'blockquote': 'cite', 23441 'link': 'href', 23442 'base': 'href', 23443 'script': 'src', 23444 'form': 'action', 23445 'img': 'src', 23446 'area': 'href', 23447 'iframe': 'src', 23448 'embed': 'src', 23449 'source': 'src', 23450 'track': 'src', 23451 'input': 'src', // but only if type="image" 23452 'audio': 'src', 23453 'video': 'src' 23454 }; 23455 URI.getDomAttribute = function(node) { 23456 if (!node || !node.nodeName) { 23457 return undefined; 23458 } 23459 23460 var nodeName = node.nodeName.toLowerCase(); 23461 // <input> should only expose src for type="image"
23462 if (nodeName === 'input' && node.type !== 'image') { 23463 return undefined; 23464 } 23465 23466 return URI.domAttributes[nodeName]; 23467 }; 23468 23469 function escapeForDumbFirefox36(value) { 23470 // https://github.com/medialize/URI.js/issues/91 23471 return escape(value); 23472 } 23473 23474 // encoding / decoding according to RFC3986 23475 function strictEncodeURIComponent(string) { 23476 // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent 23477 return encodeURIComponent(string) 23478 .replace(/[!'()*]/g, escapeForDumbFirefox36) 23479 .replace(/\*/g, '%2A'); 23480 } 23481 23482 // Drawn from https://www.ietf.org/rfc/rfc3987.txt 23483 function _isIriCodePoint(point) { 23484 // Unless otherwise noted, all these hex ranges are defined in RFC 3987 under the 23485 // ucschar portion of the grammar. 23486 return ( 23487 point === 0x00002d || point === 0x0002E || // -, . 23488 point === 0x00005F || point === 0x0007E || // _, ~ 23489 (point >= 0x000030 && point < 0x000040) || // ASCII Digits 23490 (point >= 0x000041 && point < 0x00005B) || // ASCII Uppercase 23491 (point >= 0x000061 && point < 0x00007B) || // ASCII Lowercase 23492 (point >= 0x0000A0 && point < 0x00D800) || 23493 (point >= 0x00E000 && point < 0x00F8FF) || // iprivate 23494 (point >= 0x00F900 && point < 0x00FDD0) || 23495 (point >= 0x00FDF0 && point < 0x00FFF0) || 23496 (point >= 0x010000 && point < 0x01FFFE) || 23497 (point >= 0x020000 && point < 0x02FFFE) || 23498 (point >= 0x030000 && point < 0x03FFFE) || 23499 (point >= 0x040000 && point < 0x04FFFE) || 23500 (point >= 0x050000 && point < 0x05FFFE) || 23501 (point >= 0x060000 && point < 0x06FFFE) || 23502 (point >= 0x070000 && point < 0x07FFFE) || 23503 (point >= 0x080000 && point < 0x08FFFE) || 23504 (point >= 0x090000 && point < 0x09FFFE) || 23505 (point >= 0x0A0000 && point < 0x0AFFFE) || 23506 (point >= 0x0B0000 && point < 0x0BFFFE) || 23507 (point >= 0x0C0000 && point < 0x0CFFFE) || 23508 (point >= 0x0D0000 && point < 0x0DFFFE) || 23509 (point >= 0x0E0000 && point < 0x0EFFFE) || 23510 (point >= 0x0F0000 && point < 0x0FFFFE) || // iprivate 23511 (point >= 0x100000 && point < 0x10FFFE) // iprivate 23512 ); 23513 } 23514 23515 // IRIs are "unicode" (i.e., they use UTF-8 for %-encoding) URIs, but with a far greater range of 23516 // non-ASCII characters that are allowed. Note that ISO-8859 IRIs are an impossibility; ISO-8859 23517 // cannot represent the full range of unicode characters. 23518 function encodeIRIComponent(string) { 23519 var inputCodePoints = punycode.ucs2.decode(string); 23520 var output = ''; 23521 for (var i = 0; i < inputCodePoints.length; i++) { 23522 var codePoint = inputCodePoints[i]; 23523 if (_isIriCodePoint(codePoint)) { 23524 output += punycode.ucs2.encode([codePoint]); 23525 } else { 23526 var asString = punycode.ucs2.encode([codePoint]); 23527 output += strictEncodeURIComponent(asString); 23528 } 23529 } 23530 return output; 23531 } 23532 23533 function recodeIRIHostname(string) { 23534 if (URI.punycode_expression.test(string)) 23535 { 23536 string = punycode.toUnicode(string); 23537 } 23538 return encodeIRIComponent(string); 23539 } 23540 23541 URI._defaultRecodeHostname = punycode ? punycode.toASCII : function(string) { return string; }; 23542 URI.iso8859 = function() { 23543 URI.recodeHostname = URI._defaultRecodeHostname; 23544 URI.encode = escape; 23545 URI.decode = unescape; 23546 }; 23547 URI.unicode = function() { 23548 URI.recodeHostname = URI._defaultRecodeHostname; 23549 URI.encode = strictEncodeURIComponent; 23550 URI.decode = decodeURIComponent; 23551 }; 23552 URI.iri = function() { 23553 URI.recodeHostname = recodeIRIHostname; 23554 URI.encode = encodeIRIComponent; 23555 URI.decode = decodeURIComponent; 23556 }; 23557 // By default, start in unicode mode. 23558 URI.unicode(); 23559 URI.characters = { 23560 pathname: { 23561 encode: { 23562 // RFC3986 2.1: For consistency, URI producers and normalizers should 23563 // use uppercase hexadecimal digits for all percent-encodings. 23564 expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig, 23565 map: { 23566 // -._~!'()* 23567 '%24': '$', 23568 '%26': '&', 23569 '%2B': '+', 23570 '%2C': ',', 23571 '%3B': ';', 23572 '%3D': '=', 23573 '%3A': ':', 23574 '%40': '@' 23575 } 23576 }, 23577 decode: { 23578 expression: /[\/\?#]/g, 23579 map: { 23580 '/': '%2F', 23581 '?': '%3F', 23582 '#': '%23' 23583 } 23584 } 23585 }, 23586 reserved: { 23587 encode: { 23588 // RFC3986 2.1: For consistency, URI producers and normalizers should 23589 // use uppercase hexadecimal digits for all percent-encodings. 23590 expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig, 23591 map: { 23592 // gen-delims 23593 '%3A': ':', 23594 '%2F': '/', 23595 '%3F': '?', 23596 '%23': '#', 23597 '%5B': '[', 23598 '%5D': ']', 23599 '%40': '@', 23600 // sub-delims 23601 '%21': '!', 23602 '%24': '$', 23603 '%26': '&', 23604 '%27': '\'', 23605 '%28': '(', 23606 '%29': ')', 23607 '%2A': '*', 23608 '%2B': '+', 23609 '%2C': ',', 23610 '%3B': ';', 23611 '%3D': '=' 23612 } 23613 } 23614 }, 23615 urnpath: { 23616 // The characters under `encode` are the characters called out by RFC 2141 as being acceptable 23617 // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but 23618 // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also 23619 // note that the colon character is not featured in the encoding map; this is because URI.js 23620 // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it 23621 // should not appear unencoded in a segment itself. 23622 // See also the note above about RFC3986 and capitalalized hex digits. 23623 encode: { 23624 expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig, 23625 map: { 23626 '%21': '!', 23627 '%24': '$', 23628 '%27': '\'', 23629 '%28': '(', 23630 '%29': ')', 23631 '%2A': '*', 23632 '%2B': '+', 23633 '%2C': ',', 23634 '%3B': ';', 23635 '%3D': '=', 23636 '%40': '@' 23637 } 23638 }, 23639 // These characters are the characters called out by RFC2141 as "reserved" characters that 23640 // should never appear in a URN, plus the colon character (see note above). 23641 decode: { 23642 expression: /[\/\?#:]/g, 23643 map: { 23644 '/': '%2F', 23645 '?': '%3F', 23646 '#': '%23', 23647 ':': '%3A' 23648 } 23649 } 23650 } 23651 }; 23652 URI.encodeQuery = function(string, escapeQuerySpace) { 23653 var escaped = URI.encode(string + ''); 23654 if (escapeQuerySpace === undefined) { 23655 escapeQuerySpace = URI.escapeQuerySpace; 23656 } 23657 23658 return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped; 23659 }; 23660 URI.decodeQuery = function(string, escapeQuerySpace) { 23661 string += ''; 23662 if (escapeQuerySpace === undefined) { 23663 escapeQuerySpace = URI.escapeQuerySpace; 23664 } 23665 23666 try { 23667 return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string); 23668 } catch(e) { 23669 // we're not going to mess with weird encodings, 23670 // give up and return the undecoded original string 23671 // see https://github.com/medialize/URI.js/issues/87 23672 // see https://github.com/medialize/URI.js/issues/92 23673 return string; 23674 } 23675 }; 23676 // generate encode/decode path functions 23677 var _parts = {'encode':'encode', 'decode':'decode'}; 23678 var _part; 23679 var generateAccessor = function(_group, _part) { 23680 return function(string) {
23681 try { 23682 return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) { 23683 return URI.characters[_group][_part].map[c]; 23684 }); 23685 } catch (e) { 23686 // we're not going to mess with weird encodings, 23687 // give up and return the undecoded original string 23688 // see https://github.com/medialize/URI.js/issues/87 23689 // see https://github.com/medialize/URI.js/issues/92 23690 return string; 23691 } 23692 }; 23693 }; 23694 23695 for (_part in _parts) { 23696 URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]); 23697 URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]); 23698 } 23699 23700 var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) { 23701 return function(string) { 23702 // Why pass in names of functions, rather than the function objects themselves? The 23703 // definitions of some functions (but in particular, URI.decode) will occasionally change due 23704 // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure 23705 // that the functions we use here are "fresh". 23706 var actualCodingFunc; 23707 if (!_innerCodingFuncName) { 23708 actualCodingFunc = URI[_codingFuncName]; 23709 } else { 23710 actualCodingFunc = function(string) { 23711 return URI[_codingFuncName](URI[_innerCodingFuncName](string)); 23712 }; 23713 } 23714 23715 var segments = (string + '').split(_sep); 23716 23717 for (var i = 0, length = segments.length; i < length; i++) { 23718 segments[i] = actualCodingFunc(segments[i]); 23719 } 23720 23721 return segments.join(_sep); 23722 }; 23723 }; 23724 23725 // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions. 23726 URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment'); 23727 URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment'); 23728 URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode'); 23729 URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode'); 23730 23731 URI.encodeReserved = generateAccessor('reserved', 'encode'); 23732 23733 URI.parse = function(string, parts) { 23734 var pos; 23735 if (!parts) { 23736 parts = {}; 23737 } 23738 // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment] 23739 23740 // extract fragment 23741 pos = string.indexOf('#'); 23742 if (pos > -1) { 23743 // escaping? 23744 parts.fragment = string.substring(pos + 1) || null; 23745 string = string.substring(0, pos); 23746 } 23747 23748 // extract query 23749 pos = string.indexOf('?'); 23750 if (pos > -1) { 23751 // escaping? 23752 parts.query = string.substring(pos + 1) || null; 23753 string = string.substring(0, pos); 23754 } 23755 23756 // extract protocol 23757 if (string.substring(0, 2) === '//') { 23758 // relative-scheme 23759 parts.protocol = null; 23760 string = string.substring(2); 23761 // extract "user:pass@host:port" 23762 string = URI.parseAuthority(string, parts); 23763 } else { 23764 pos = string.indexOf(':'); 23765 if (pos > -1) { 23766 parts.protocol = string.substring(0, pos) || null;
23767 if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) { 23768 // : may be within the path 23769 parts.protocol = undefined; 23770 } else if (string.substring(pos + 1, pos + 3) === '//') { 23771 string = string.substring(pos + 3); 23772 23773 // extract "user:pass@host:port" 23774 string = URI.parseAuthority(string, parts); 23775 } else { 23776 string = string.substring(pos + 1); 23777 parts.urn = true; 23778 } 23779 } 23780 } 23781 23782 // what's left must be the path 23783 parts.path = string; 23784 23785 // and we're done 23786 return parts; 23787 }; 23788 URI.parseHost = function(string, parts) { 23789 // extract host:port 23790 var pos = string.indexOf('/'); 23791 var bracketPos; 23792 var t; 23793 23794 if (pos === -1) { 23795 pos = string.length; 23796 } 23797 23798 if (string.charAt(0) === '[') { 23799 // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6 23800 // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts 23801 // IPv6+port in the format [2001:db8::1]:80 (for the time being) 23802 bracketPos = string.indexOf(']'); 23803 parts.hostname = string.substring(1, bracketPos) || null; 23804 parts.port = string.substring(bracketPos + 2, pos) || null; 23805 if (parts.port === '/') { 23806 parts.port = null; 23807 } 23808 } else { 23809 var firstColon = string.indexOf(':'); 23810 var firstSlash = string.indexOf('/'); 23811 var nextColon = string.indexOf(':', firstColon + 1); 23812 if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) { 23813 // IPv6 host contains multiple colons - but no port 23814 // this notation is actually not allowed by RFC 3986, but we're a liberal parser 23815 parts.hostname = string.substring(0, pos) || null; 23816 parts.port = null; 23817 } else { 23818 t = string.substring(0, pos).split(':'); 23819 parts.hostname = t[0] || null; 23820 parts.port = t[1] || null; 23821 } 23822 } 23823 23824 if (parts.hostname && string.substring(pos).charAt(0) !== '/') { 23825 pos++; 23826 string = '/' + string; 23827 } 23828 23829 return string.substring(pos) || '/'; 23830 }; 23831 URI.parseAuthority = function(string, parts) { 23832 string = URI.parseUserinfo(string, parts); 23833 return URI.parseHost(string, parts); 23834 }; 23835 URI.parseUserinfo = function(string, parts) { 23836 // extract username:password 23837 var firstSlash = string.indexOf('/'); 23838 var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1); 23839 var t; 23840 23841 // authority@ must come before /path 23842 if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) { 23843 t = string.substring(0, pos).split(':'); 23844 parts.username = t[0] ? URI.decode(t[0]) : null; 23845 t.shift(); 23846 parts.password = t[0] ? URI.decode(t.join(':')) : null; 23847 string = string.substring(pos + 1); 23848 } else { 23849 parts.username = null; 23850 parts.password = null; 23851 } 23852 23853 return string; 23854 }; 23855 URI.parseQuery = function(string, escapeQuerySpace) { 23856 if (!string) { 23857 return {}; 23858 } 23859 23860 // throw out the funky business - "?"[name"="value"&"]+ 23861 string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, ''); 23862 23863 if (!string) { 23864 return {}; 23865 } 23866 23867 var items = {}; 23868 var splits = string.split('&'); 23869 var length = splits.length; 23870 var v, name, value; 23871 23872 for (var i = 0; i < length; i++) { 23873 v = splits[i].split('='); 23874 name = URI.decodeQuery(v.shift(), escapeQuerySpace); 23875 // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters 23876 value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null; 23877 23878 if (hasOwn.call(items, name)) { 23879 if (typeof items[name] === 'string') { 23880 items[name] = [items[name]]; 23881 } 23882 23883 items[name].push(value); 23884 } else { 23885 items[name] = value; 23886 } 23887 } 23888 23889 return items; 23890 }; 23891 23892 URI.build = function(parts) { 23893 var t = ''; 23894 23895 if (parts.protocol) { 23896 t += parts.protocol + ':'; 23897 } 23898 23899 if (!parts.urn && (t || parts.hostname)) { 23900 t += '//'; 23901 } 23902 23903 t += (URI.buildAuthority(parts) || ''); 23904 23905 if (typeof parts.path === 'string') {
23906 if (parts.path.charAt(0) !== '/' && typeof parts.hostname === 'string') { 23907 t += '/'; 23908 } 23909 23910 t += parts.path; 23911 } 23912 23913 if (typeof parts.query === 'string' && parts.query) { 23914 t += '?' + parts.query; 23915 } 23916 23917 if (typeof parts.fragment === 'string' && parts.fragment) { 23918 t += '#' + parts.fragment; 23919 } 23920 return t; 23921 }; 23922 URI.buildHost = function(parts) { 23923 var t = ''; 23924 23925 if (!parts.hostname) { 23926 return ''; 23927 } else if (URI.ip6_expression.test(parts.hostname)) { 23928 t += '[' + parts.hostname + ']'; 23929 } else { 23930 t += parts.hostname; 23931 } 23932 23933 if (parts.port) { 23934 t += ':' + parts.port; 23935 } 23936 23937 return t; 23938 }; 23939 URI.buildAuthority = function(parts) { 23940 return URI.buildUserinfo(parts) + URI.buildHost(parts); 23941 }; 23942 URI.buildUserinfo = function(parts) { 23943 var t = ''; 23944 23945 if (parts.username) { 23946 t += URI.encode(parts.username); 23947 23948 if (parts.password) { 23949 t += ':' + URI.encode(parts.password); 23950 } 23951 23952 t += '@'; 23953 } 23954 23955 return t; 23956 }; 23957 URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) { 23958 // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html 23959 // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed 23960 // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax! 23961 // URI.js treats the query string as being application/x-www-form-urlencoded 23962 // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type 23963 23964 var t = ''; 23965 var unique, key, i, length; 23966 for (key in data) { 23967 if (hasOwn.call(data, key) && key) { 23968 if (isArray(data[key])) { 23969 unique = {}; 23970 for (i = 0, length = data[key].length; i < length; i++) { 23971 if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) { 23972 t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace); 23973 if (duplicateQueryParameters !== true) { 23974 unique[data[key][i] + ''] = true; 23975 } 23976 } 23977 } 23978 } else if (data[key] !== undefined) { 23979 t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace); 23980 } 23981 } 23982 } 23983 23984 return t.substring(1); 23985 }; 23986 URI.buildQueryParameter = function(name, value, escapeQuerySpace) { 23987 // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded 23988 // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization 23989 return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : ''); 23990 }; 23991 23992 URI.addQuery = function(data, name, value) { 23993 if (typeof name === 'object') { 23994 for (var key in name) { 23995 if (hasOwn.call(name, key)) { 23996 URI.addQuery(data, key, name[key]); 23997 } 23998 } 23999 } else if (typeof name === 'string') { 24000 if (data[name] === undefined) { 24001 data[name] = value; 24002 return; 24003 } else if (typeof data[name] === 'string') { 24004 data[name] = [data[name]]; 24005 } 24006 24007 if (!isArray(value)) { 24008 value = [value]; 24009 } 24010 24011 data[name] = (data[name] || []).concat(value); 24012 } else { 24013 throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); 24014 } 24015 }; 24016 URI.removeQuery = function(data, name, value) { 24017 var i, length, key; 24018 24019 if (isArray(name)) { 24020 for (i = 0, length = name.length; i < length; i++) { 24021 data[name[i]] = undefined; 24022 } 24023 } else if (typeof name === 'object') { 24024 for (key in name) { 24025 if (hasOwn.call(name, key)) { 24026 URI.removeQuery(data, key, name[key]); 24027 } 24028 } 24029 } else if (typeof name === 'string') { 24030 if (value !== undefined) { 24031 if (data[name] === value) { 24032 data[name] = undefined; 24033 } else if (isArray(data[name])) { 24034 data[name] = filterArrayValues(data[name], value); 24035 } 24036 } else { 24037 data[name] = undefined; 24038 } 24039 } else { 24040 throw new TypeError('URI.removeQuery() accepts an object, string as the first parameter'); 24041 } 24042 }; 24043 URI.hasQuery = function(data, name, value, withinArray) { 24044 if (typeof name === 'object') { 24045 for (var key in name) { 24046 if (hasOwn.call(name, key)) { 24047 if (!URI.hasQuery(data, key, name[key])) { 24048 return false;
24049 } 24050 } 24051 } 24052 24053 return true; 24054 } else if (typeof name !== 'string') { 24055 throw new TypeError('URI.hasQuery() accepts an object, string as the name parameter'); 24056 } 24057 24058 switch (getType(value)) { 24059 case 'Undefined': 24060 // true if exists (but may be empty) 24061 return name in data; // data[name] !== undefined; 24062 24063 case 'Boolean': 24064 // true if exists and non-empty 24065 var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]); 24066 return value === _booly; 24067 24068 case 'Function': 24069 // allow complex comparison 24070 return !!value(data[name], name, data); 24071 24072 case 'Array': 24073 if (!isArray(data[name])) { 24074 return false; 24075 } 24076 24077 var op = withinArray ? arrayContains : arraysEqual; 24078 return op(data[name], value); 24079 24080 case 'RegExp': 24081 if (!isArray(data[name])) { 24082 return Boolean(data[name] && data[name].match(value)); 24083 } 24084 24085 if (!withinArray) { 24086 return false; 24087 } 24088 24089 return arrayContains(data[name], value); 24090 24091 case 'Number': 24092 value = String(value); 24093 /* falls through */ 24094 case 'String': 24095 if (!isArray(data[name])) { 24096 return data[name] === value; 24097 } 24098 24099 if (!withinArray) { 24100 return false; 24101 } 24102 24103 return arrayContains(data[name], value); 24104 24105 default: 24106 throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter'); 24107 } 24108 }; 24109 24110 24111 URI.commonPath = function(one, two) { 24112 var length = Math.min(one.length, two.length); 24113 var pos; 24114 24115 // find first non-matching character 24116 for (pos = 0; pos < length; pos++) { 24117 if (one.charAt(pos) !== two.charAt(pos)) { 24118 pos--; 24119 break; 24120 } 24121 } 24122 24123 if (pos < 1) { 24124 return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : ''; 24125 } 24126 24127 // revert to last / 24128 if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') { 24129 pos = one.substring(0, pos).lastIndexOf('/'); 24130 } 24131 24132 return one.substring(0, pos + 1); 24133 }; 24134 24135 URI.withinString = function(string, callback, options) { 24136 options || (options = {}); 24137 var _start = options.start || URI.findUri.start; 24138 var _end = options.end || URI.findUri.end; 24139 var _trim = options.trim || URI.findUri.trim; 24140 var _attributeOpen = /[a-z0-9-]=["']?$/i; 24141 24142 _start.lastIndex = 0; 24143 while (true) { 24144 var match = _start.exec(string); 24145 if (!match) { 24146 break; 24147 } 24148 24149 var start = match.index; 24150 if (options.ignoreHtml) { 24151 // attribut(e=["']?$) 24152 var attributeOpen = string.slice(Math.max(start - 3, 0), start); 24153 if (attributeOpen && _attributeOpen.test(attributeOpen)) { 24154 continue; 24155 } 24156 } 24157 24158 var end = start + string.slice(start).search(_end); 24159 var slice = string.slice(start, end).replace(_trim, ''); 24160 if (options.ignore && options.ignore.test(slice)) { 24161 continue; 24162 } 24163 24164 end = start + slice.length; 24165 var result = callback(slice, start, end, string); 24166 string = string.slice(0, start) + result + string.slice(end); 24167 _start.lastIndex = start + result.length; 24168 } 24169 24170 _start.lastIndex = 0; 24171 return string; 24172 }; 24173 24174 URI.ensureValidHostname = function(v) { 24175 // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986) 24176 // they are not part of DNS and therefore ignored by URI.js 24177 24178 if (v.match(URI.invalid_hostname_characters)) { 24179 // test punycode 24180 if (!punycode) { 24181 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-] and Punycode.js is not available'); 24182 } 24183 24184 if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) { 24185 throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]'); 24186 } 24187 } 24188 }; 24189 24190 // noConflict 24191 URI.noConflict = function(removeAll) { 24192 if (removeAll) { 24193 var unconflicted = { 24194 URI: this.noConflict() 24195 }; 24196 24197 if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') { 24198 unconflicted.URITemplate = root.URITemplate.noConflict(); 24199 } 24200 24201 if (root.IPv6 && typeof root.IPv6.noConflict === 'function') { 24202 unconflicted.IPv6 = root.IPv6.noConflict(); 24203 } 24204 24205 if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') { 24206 unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict(); 24207 } 24208 24209 return unconflicted; 24210 } else if (root.URI === this) { 24211 root.URI = _URI; 24212 } 24213 24214 return this; 24215 }; 24216 24217 p.build = function(deferBuild) { 24218 if (deferBuild === true) { 24219 this._deferred_build = true; 24220 } else if (deferBuild === undefined || this._deferred_build) { 24221 this._string = URI.build(this._parts); 24222 this._deferred_build = false;
24223 } 24224 24225 return this; 24226 }; 24227 24228 p.clone = function() { 24229 return new URI(this); 24230 }; 24231 24232 p.valueOf = p.toString = function() { 24233 return this.build(false)._string; 24234 }; 24235 24236 24237 function generateSimpleAccessor(_part){ 24238 return function(v, build) { 24239 if (v === undefined) { 24240 return this._parts[_part] || ''; 24241 } else { 24242 this._parts[_part] = v || null; 24243 this.build(!build); 24244 return this; 24245 } 24246 }; 24247 } 24248 24249 function generatePrefixAccessor(_part, _key){ 24250 return function(v, build) { 24251 if (v === undefined) { 24252 return this._parts[_part] || ''; 24253 } else { 24254 if (v !== null) { 24255 v = v + ''; 24256 if (v.charAt(0) === _key) { 24257 v = v.substring(1); 24258 } 24259 } 24260 24261 this._parts[_part] = v; 24262 this.build(!build); 24263 return this; 24264 } 24265 }; 24266 } 24267 24268 p.protocol = generateSimpleAccessor('protocol'); 24269 p.username = generateSimpleAccessor('username'); 24270 p.password = generateSimpleAccessor('password'); 24271 p.hostname = generateSimpleAccessor('hostname'); 24272 p.port = generateSimpleAccessor('port'); 24273 p.query = generatePrefixAccessor('query', '?'); 24274 p.fragment = generatePrefixAccessor('fragment', '#'); 24275 24276 p.search = function(v, build) { 24277 var t = this.query(v, build); 24278 return typeof t === 'string' && t.length ? ('?' + t) : t; 24279 }; 24280 p.hash = function(v, build) { 24281 var t = this.fragment(v, build); 24282 return typeof t === 'string' && t.length ? ('#' + t) : t; 24283 }; 24284 24285 p.pathname = function(v, build) { 24286 if (v === undefined || v === true) { 24287 var res = this._parts.path || (this._parts.hostname ? '/' : ''); 24288 return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res; 24289 } else { 24290 if (this._parts.urn) { 24291 this._parts.path = v ? URI.recodeUrnPath(v) : ''; 24292 } else { 24293 this._parts.path = v ? URI.recodePath(v) : '/'; 24294 } 24295 this.build(!build); 24296 return this; 24297 } 24298 }; 24299 p.path = p.pathname; 24300 p.href = function(href, build) { 24301 var key; 24302 24303 if (href === undefined) { 24304 return this.toString(); 24305 } 24306 24307 this._string = ''; 24308 this._parts = URI._parts(); 24309 24310 var _URI = href instanceof URI; 24311 var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname); 24312 if (href.nodeName) { 24313 var attribute = URI.getDomAttribute(href); 24314 href = href[attribute] || ''; 24315 _object = false; 24316 } 24317 24318 // window.location is reported to be an object, but it's not the sort 24319 // of object we're looking for: 24320 // * location.protocol ends with a colon 24321 // * location.query != object.search 24322 // * location.hash != object.fragment 24323 // simply serializing the unknown object should do the trick 24324 // (for location, not for everything...) 24325 if (!_URI && _object && href.pathname !== undefined) { 24326 href = href.toString(); 24327 } 24328 24329 if (typeof href === 'string' || href instanceof String) { 24330 this._parts = URI.parse(String(href), this._parts); 24331 } else if (_URI || _object) { 24332 var src = _URI ? href._parts : href; 24333 for (key in src) { 24334 if (hasOwn.call(this._parts, key)) { 24335 this._parts[key] = src[key]; 24336 } 24337 } 24338 } else { 24339 throw new TypeError('invalid input'); 24340 } 24341 24342 this.build(!build); 24343 return this; 24344 }; 24345 24346 // identification accessors 24347 p.is = function(what) { 24348 var ip = false; 24349 var ip4 = false; 24350 var ip6 = false; 24351 var name = false; 24352 var sld = false; 24353 var idn = false; 24354 var punycode = false; 24355 var relative = !this._parts.urn; 24356 24357 if (this._parts.hostname) { 24358 relative = false; 24359 ip4 = URI.ip4_expression.test(this._parts.hostname); 24360 ip6 = URI.ip6_expression.test(this._parts.hostname); 24361 ip = ip4 || ip6; 24362 name = !ip; 24363 sld = name && SLD && SLD.has(this._parts.hostname); 24364 idn = name && URI.idn_expression.test(this._parts.hostname); 24365 punycode = name && URI.punycode_expression.test(this._parts.hostname); 24366 } 24367 24368 switch (what.toLowerCase()) { 24369 case 'relative': 24370 return relative; 24371 24372 case 'absolute': 24373 return !relative; 24374 24375 // hostname identification 24376 case 'domain': 24377 case 'name': 24378 return name; 24379 24380 case 'sld': 24381 return sld; 24382 24383 case 'ip': 24384 return ip; 24385 24386 case 'ip4': 24387 case 'ipv4': 24388 case 'inet4': 24389 return ip4; 24390 24391 case 'ip6': 24392 case 'ipv6': 24393 case 'inet6': 24394 return ip6; 24395 24396 case 'idn': 24397 return idn; 24398 24399 case 'url': 24400 return !this._parts.urn; 24401 24402 case 'urn': 24403 return !!this._parts.urn; 24404 24405 case 'punycode': 24406 return punycode; 24407 } 24408 24409 return null; 24410 }; 24411 24412 // component specific input validation 24413 var _protocol = p.protocol; 24414 var _port = p.port; 24415 var _hostname = p.hostname; 24416 24417 p.protocol = function(v, build) { 24418 if (v !== undefined) { 24419 if (v) { 24420 // accept trailing :// 24421 v = v.replace(/:(\/\/)?$/, ''); 24422
24423 if (!v.match(URI.protocol_expression)) { 24424 throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]'); 24425 } 24426 } 24427 } 24428 return _protocol.call(this, v, build); 24429 }; 24430 p.scheme = p.protocol; 24431 p.port = function(v, build) { 24432 if (this._parts.urn) { 24433 return v === undefined ? '' : this; 24434 } 24435 24436 if (v !== undefined) { 24437 if (v === 0) { 24438 v = null; 24439 } 24440 24441 if (v) { 24442 v += ''; 24443 if (v.charAt(0) === ':') { 24444 v = v.substring(1); 24445 } 24446 24447 if (v.match(/[^0-9]/)) { 24448 throw new TypeError('Port "' + v + '" contains characters other than [0-9]'); 24449 } 24450 } 24451 } 24452 return _port.call(this, v, build); 24453 }; 24454 p.hostname = function(v, build) { 24455 if (this._parts.urn) { 24456 return v === undefined ? '' : this; 24457 } 24458 24459 if (v !== undefined) { 24460 var x = {}; 24461 URI.parseHost(v, x); 24462 v = x.hostname; 24463 } 24464 return _hostname.call(this, v, build); 24465 }; 24466 24467 // compound accessors 24468 p.host = function(v, build) { 24469 if (this._parts.urn) { 24470 return v === undefined ? '' : this; 24471 } 24472 24473 if (v === undefined) { 24474 return this._parts.hostname ? URI.buildHost(this._parts) : ''; 24475 } else { 24476 URI.parseHost(v, this._parts); 24477 this.build(!build); 24478 return this; 24479 } 24480 }; 24481 p.authority = function(v, build) { 24482 if (this._parts.urn) { 24483 return v === undefined ? '' : this; 24484 } 24485 24486 if (v === undefined) { 24487 return this._parts.hostname ? URI.buildAuthority(this._parts) : ''; 24488 } else { 24489 URI.parseAuthority(v, this._parts); 24490 this.build(!build); 24491 return this; 24492 } 24493 }; 24494 p.userinfo = function(v, build) { 24495 if (this._parts.urn) { 24496 return v === undefined ? '' : this; 24497 } 24498 24499 if (v === undefined) { 24500 if (!this._parts.username) { 24501 return ''; 24502 } 24503 24504 var t = URI.buildUserinfo(this._parts); 24505 return t.substring(0, t.length -1); 24506 } else { 24507 if (v[v.length-1] !== '@') { 24508 v += '@'; 24509 } 24510 24511 URI.parseUserinfo(v, this._parts); 24512 this.build(!build); 24513 return this; 24514 } 24515 }; 24516 p.resource = function(v, build) { 24517 var parts; 24518 24519 if (v === undefined) { 24520 return this.path() + this.search() + this.hash(); 24521 } 24522 24523 parts = URI.parse(v); 24524 this._parts.path = parts.path; 24525 this._parts.query = parts.query; 24526 this._parts.fragment = parts.fragment; 24527 this.build(!build); 24528 return this; 24529 }; 24530 24531 // fraction accessors 24532 p.subdomain = function(v, build) { 24533 if (this._parts.urn) { 24534 return v === undefined ? '' : this; 24535 } 24536 24537 // convenience, return "www" from "www.example.org" 24538 if (v === undefined) { 24539 if (!this._parts.hostname || this.is('IP')) { 24540 return ''; 24541 } 24542 24543 // grab domain and add another segment 24544 var end = this._parts.hostname.length - this.domain().length - 1; 24545 return this._parts.hostname.substring(0, end) || ''; 24546 } else { 24547 var e = this._parts.hostname.length - this.domain().length; 24548 var sub = this._parts.hostname.substring(0, e); 24549 var replace = new RegExp('^' + escapeRegEx(sub)); 24550 24551 if (v && v.charAt(v.length - 1) !== '.') { 24552 v += '.'; 24553 } 24554 24555 if (v) { 24556 URI.ensureValidHostname(v); 24557 } 24558 24559 this._parts.hostname = this._parts.hostname.replace(replace, v); 24560 this.build(!build); 24561 return this; 24562 } 24563 }; 24564 p.domain = function(v, build) { 24565 if (this._parts.urn) { 24566 return v === undefined ? '' : this; 24567 } 24568 24569 if (typeof v === 'boolean') { 24570 build = v; 24571 v = undefined; 24572 } 24573 24574 // convenience, return "example.org" from "www.example.org" 24575 if (v === undefined) { 24576 if (!this._parts.hostname || this.is('IP')) { 24577 return ''; 24578 } 24579 24580 // if hostname consists of 1 or 2 segments, it must be the domain 24581 var t = this._parts.hostname.match(/\./g); 24582 if (t && t.length < 2) { 24583 return this._parts.hostname; 24584 } 24585 24586 // grab tld and add another segment 24587 var end = this._parts.hostname.length - this.tld(build).length - 1; 24588 end = this._parts.hostname.lastIndexOf('.', end -1) + 1; 24589 return this._parts.hostname.substring(end) || ''; 24590 } else { 24591 if (!v) { 24592 throw new TypeError('cannot set domain empty'); 24593 } 24594 24595 URI.ensureValidHostname(v); 24596 24597 if (!this._parts.hostname || this.is('IP')) { 24598 this._parts.hostname = v; 24599 } else { 24600 var replace = new RegExp(escapeRegEx(this.domain()) + '$'); 24601 this._parts.hostname = this._parts.hostname.replace(replace, v); 24602 } 24603 24604 this.build(!build); 24605 return this; 24606 } 24607 }; 24608 p.tld = function(v, build) { 24609 if (this._parts.urn) { 24610 return v === undefined ? '' : this; 24611 } 24612 24613 if (typeof v === 'boolean') { 24614 build = v; 24615 v = undefined; 24616 } 24617 24618 // return "org" from "www.example.org" 24619 if (v === undefined) { 24620 if (!this._parts.hostname || this.is('IP')) { 24621 return ''; 24622 } 24623 24624 var pos = this._parts.hostname.lastIndexOf('.'); 24625 var tld = this._parts.hostname.substring(pos + 1); 24626 24627 if (build !== true && SLD && SLD.list[tld.toLowerCase()]) { 24628 return SLD.get(this._parts.hostname) || tld; 24629 } 24630 24631 return tld; 24632 } else { 24633 var replace; 24634 24635 if (!v) { 24636 throw new TypeError('cannot set TLD empty'); 24637 } else if (v.match(/[^a-zA-Z0-9-]/)) { 24638 if (SLD && SLD.is(v)) { 24639 replace = new RegExp(escapeRegEx(this.tld()) + '$'); 24640 this._parts.hostname = this._parts.hostname.replace(replace, v); 24641 } else { 24642 throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]'); 24643 } 24644 } else if (!this._parts.hostname || this.is('IP')) { 24645 throw new ReferenceError('cannot set TLD on non-domain host'); 24646 } else { 24647 replace = new RegExp(escapeRegEx(this.tld()) + '$'); 24648 this._parts.hostname = this._parts.hostname.replace(replace, v); 24649 } 24650 24651 this.build(!build); 24652 return this; 24653 } 24654 }; 24655 p.directory = function(v, build) { 24656 if (this._parts.urn) { 24657 return v === undefined ? '' : this; 24658 } 24659 24660 if (v === undefined || v === true) { 24661 if (!this._parts.path && !this._parts.hostname) { 24662 return ''; 24663 } 24664 24665 if (this._parts.path === '/') { 24666 return '/'; 24667 } 24668 24669 var end = this._parts.path.length - this.filename().length - 1; 24670 var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : ''); 24671 24672 return v ? URI.decodePath(res) : res; 24673 24674 } else { 24675 var e = this._parts.path.length - this.filename().length; 24676 var directory = this._parts.path.substring(0, e); 24677 var replace = new RegExp('^' + escapeRegEx(directory)); 24678 24679 // fully qualifier directories begin with a slash 24680 if (!this.is('relative')) { 24681 if (!v) { 24682 v = '/'; 24683 } 24684 24685 if (v.charAt(0) !== '/') { 24686 v = '/' + v; 24687 } 24688 } 24689 24690 // directories always end with a slash 24691 if (v && v.charAt(v.length - 1) !== '/') { 24692 v += '/'; 24693 } 24694 24695 v = URI.recodePath(v); 24696 this._parts.path = this._parts.path.replace(replace, v); 24697 this.build(!build); 24698 return this; 24699 } 24700 }; 24701 p.filename = function(v, build) { 24702 if (this._parts.urn) { 24703 return v === undefined ? '' : this; 24704 } 24705 24706 if (v === undefined || v === true) { 24707 if (!this._parts.path || this._parts.path === '/') { 24708 return ''; 24709 } 24710 24711 var pos = this._parts.path.lastIndexOf('/'); 24712 var res = this._parts.path.substring(pos+1); 24713 24714 return v ? URI.decodePathSegment(res) : res; 24715 } else { 24716 var mutatedDirectory = false;
24717 24718 if (v.charAt(0) === '/') { 24719 v = v.substring(1); 24720 } 24721 24722 if (v.match(/\.?\//)) { 24723 mutatedDirectory = true; 24724 } 24725 24726 var replace = new RegExp(escapeRegEx(this.filename()) + '$'); 24727 v = URI.recodePath(v); 24728 this._parts.path = this._parts.path.replace(replace, v); 24729 24730 if (mutatedDirectory) { 24731 this.normalizePath(build); 24732 } else { 24733 this.build(!build); 24734 } 24735 24736 return this; 24737 } 24738 }; 24739 p.suffix = function(v, build) { 24740 if (this._parts.urn) { 24741 return v === undefined ? '' : this; 24742 } 24743 24744 if (v === undefined || v === true) { 24745 if (!this._parts.path || this._parts.path === '/') { 24746 return ''; 24747 } 24748 24749 var filename = this.filename(); 24750 var pos = filename.lastIndexOf('.'); 24751 var s, res; 24752 24753 if (pos === -1) { 24754 return ''; 24755 } 24756 24757 // suffix may only contain alnum characters (yup, I made this up.) 24758 s = filename.substring(pos+1); 24759 res = (/^[a-z0-9%]+$/i).test(s) ? s : ''; 24760 return v ? URI.decodePathSegment(res) : res; 24761 } else { 24762 if (v.charAt(0) === '.') { 24763 v = v.substring(1); 24764 } 24765 24766 var suffix = this.suffix(); 24767 var replace; 24768 24769 if (!suffix) { 24770 if (!v) { 24771 return this; 24772 } 24773 24774 this._parts.path += '.' + URI.recodePath(v); 24775 } else if (!v) { 24776 replace = new RegExp(escapeRegEx('.' + suffix) + '$'); 24777 } else { 24778 replace = new RegExp(escapeRegEx(suffix) + '$'); 24779 } 24780 24781 if (replace) { 24782 v = URI.recodePath(v); 24783 this._parts.path = this._parts.path.replace(replace, v); 24784 } 24785 24786 this.build(!build); 24787 return this; 24788 } 24789 }; 24790 p.segment = function(segment, v, build) { 24791 var separator = this._parts.urn ? ':' : '/'; 24792 var path = this.path(); 24793 var absolute = path.substring(0, 1) === '/'; 24794 var segments = path.split(separator); 24795 24796 if (segment !== undefined && typeof segment !== 'number') { 24797 build = v; 24798 v = segment; 24799 segment = undefined; 24800 } 24801 24802 if (segment !== undefined && typeof segment !== 'number') { 24803 throw new Error('Bad segment "' + segment + '", must be 0-based integer'); 24804 } 24805 24806 if (absolute) { 24807 segments.shift(); 24808 } 24809 24810 if (segment < 0) { 24811 // allow negative indexes to address from the end 24812 segment = Math.max(segments.length + segment, 0); 24813 } 24814 24815 if (v === undefined) { 24816 /*jshint laxbreak: true */ 24817 return segment === undefined 24818 ? segments 24819 : segments[segment]; 24820 /*jshint laxbreak: false */ 24821 } else if (segment === null || segments[segment] === undefined) { 24822 if (isArray(v)) { 24823 segments = []; 24824 // collapse empty elements within array 24825 for (var i=0, l=v.length; i < l; i++) { 24826 if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) { 24827 continue; 24828 } 24829 24830 if (segments.length && !segments[segments.length -1].length) { 24831 segments.pop(); 24832 } 24833 24834 segments.push(v[i]); 24835 } 24836 } else if (v || typeof v === 'string') { 24837 if (segments[segments.length -1] === '') { 24838 // empty trailing elements have to be overwritten 24839 // to prevent results such as /foo//bar 24840 segments[segments.length -1] = v; 24841 } else { 24842 segments.push(v); 24843 } 24844 } 24845 } else { 24846 if (v) { 24847 segments[segment] = v; 24848 } else { 24849 segments.splice(segment, 1); 24850 } 24851 } 24852 24853 if (absolute) { 24854 segments.unshift(''); 24855 } 24856 24857 return this.path(segments.join(separator), build); 24858 }; 24859 p.segmentCoded = function(segment, v, build) { 24860 var segments, i, l; 24861 24862 if (typeof segment !== 'number') { 24863 build = v; 24864 v = segment; 24865 segment = undefined; 24866 } 24867 24868 if (v === undefined) { 24869 segments = this.segment(segment, v, build); 24870 if (!isArray(segments)) { 24871 segments = segments !== undefined ? URI.decode(segments) : undefined; 24872 } else { 24873 for (i = 0, l = segments.length; i < l; i++) { 24874 segments[i] = URI.decode(segments[i]); 24875 } 24876 } 24877 24878 return segments; 24879 } 24880 24881 if (!isArray(v)) { 24882 v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v; 24883 } else { 24884 for (i = 0, l = v.length; i < l; i++) { 24885 v[i] = URI.decode(v[i]); 24886 } 24887 } 24888 24889 return this.segment(segment, v, build); 24890 }; 24891 24892 // mutating query string 24893 var q = p.query; 24894 p.query = function(v, build) { 24895 if (v === true) { 24896 return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 24897 } else if (typeof v === 'function') { 24898 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 24899 var result = v.call(this, data); 24900 this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 24901 this.build(!build); 24902 return this; 24903 } else if (v !== undefined && typeof v !== 'string') { 24904 this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 24905 this.build(!build); 24906 return this; 24907 } else { 24908 return q.call(this, v, build); 24909 } 24910 }; 24911 p.setQuery = function(name, value, build) { 24912 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 24913 24914 if (typeof name === 'string' || name instanceof String) { 24915 data[name] = value !== undefined ? value : null; 24916 } else if (typeof name === 'object') { 24917 for (var key in name) { 24918 if (hasOwn.call(name, key)) { 24919 data[key] = name[key]; 24920 } 24921 } 24922 } else { 24923 throw new TypeError('URI.addQuery() accepts an object, string as the name parameter'); 24924 } 24925 24926 this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 24927 if (typeof name !== 'string') { 24928 build = value; 24929 } 24930 24931 this.build(!build); 24932 return this; 24933 }; 24934 p.addQuery = function(name, value, build) { 24935 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 24936 URI.addQuery(data, name, value === undefined ? null : value); 24937 this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 24938 if (typeof name !== 'string') { 24939 build = value; 24940 } 24941 24942 this.build(!build); 24943 return this; 24944 }; 24945 p.removeQuery = function(name, value, build) { 24946 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 24947 URI.removeQuery(data, name, value); 24948 this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace); 24949 if (typeof name !== 'string') { 24950 build = value; 24951 } 24952 24953 this.build(!build); 24954 return this; 24955 }; 24956 p.hasQuery = function(name, value, withinArray) { 24957 var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace); 24958 return URI.hasQuery(data, name, value, withinArray); 24959 }; 24960 p.setSearch = p.setQuery; 24961 p.addSearch = p.addQuery; 24962 p.removeSearch = p.removeQuery; 24963 p.hasSearch = p.hasQuery; 24964 24965 // sanitizing URLs 24966 p.normalize = function() { 24967 if (this._parts.urn) { 24968 return this 24969 .normalizeProtocol(false) 24970 .normalizePath(false) 24971 .normalizeQuery(false) 24972 .normalizeFragment(false) 24973 .build(); 24974 } 24975 24976 return this 24977 .normalizeProtocol(false) 24978 .normalizeHostname(false) 24979 .normalizePort(false) 24980 .normalizePath(false) 24981 .normalizeQuery(false) 24982 .normalizeFragment(false) 24983 .build(); 24984 }; 24985 p.normalizeProtocol = function(build) { 24986 if (typeof this._parts.protocol === 'string') { 24987 this._parts.protocol = this._parts.protocol.toLowerCase(); 24988 this.build(!build); 24989 } 24990 24991 return this; 24992 }; 24993 p.normalizeHostname = function(build) { 24994 if (this._parts.hostname) { 24995 // If this is an international domain name, or if it was an IDN before it was punycoded 24996 if (this.is('IDN') || this.is('punycode')) { 24997 this._parts.hostname = URI.recodeHostname(this._parts.hostname); 24998 } else if (this.is('IPv6') && IPv6) { 24999 this._parts.hostname = IPv6.best(this._parts.hostname); 25000 } 25001 25002 this._parts.hostname = this._parts.hostname.toLowerCase(); 25003 this.build(!build); 25004 } 25005 25006 return this; 25007 }; 25008 p.normalizePort = function(build) { 25009 // remove port of it's the protocol's default 25010 if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) { 25011 this._parts.port = null; 25012 this.build(!build); 25013 } 25014 25015 return this; 25016 };
25017 p.normalizePath = function(build) { 25018 var _path = this._parts.path; 25019 if (!_path) { 25020 return this; 25021 } 25022 25023 if (this._parts.urn) { 25024 this._parts.path = URI.recodeUrnPath(this._parts.path); 25025 this.build(!build); 25026 return this; 25027 } 25028 25029 if (this._parts.path === '/') { 25030 return this; 25031 } 25032 25033 var _was_relative; 25034 var _leadingParents = ''; 25035 var _parent, _pos; 25036 25037 // handle relative paths 25038 if (_path.charAt(0) !== '/') { 25039 _was_relative = true; 25040 _path = '/' + _path; 25041 } 25042 25043 // resolve simples 25044 _path = _path 25045 .replace(/(\/(\.\/)+)|(\/\.$)/g, '/') 25046 .replace(/\/{2,}/g, '/'); 25047 25048 // remember leading parents 25049 if (_was_relative) { 25050 _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || ''; 25051 if (_leadingParents) { 25052 _leadingParents = _leadingParents[0]; 25053 } 25054 } 25055 25056 // resolve parents 25057 while (true) { 25058 _parent = _path.indexOf('/..'); 25059 if (_parent === -1) { 25060 // no more ../ to resolve 25061 break; 25062 } else if (_parent === 0) { 25063 // top level cannot be relative, skip it 25064 _path = _path.substring(3); 25065 continue; 25066 } 25067 25068 _pos = _path.substring(0, _parent).lastIndexOf('/'); 25069 if (_pos === -1) { 25070 _pos = _parent; 25071 } 25072 _path = _path.substring(0, _pos) + _path.substring(_parent + 3); 25073 } 25074 25075 // revert to relative 25076 if (_was_relative && this.is('relative')) { 25077 _path = _leadingParents + _path.substring(1); 25078 } 25079 25080 _path = URI.recodePath(_path); 25081 this._parts.path = _path; 25082 this.build(!build); 25083 return this; 25084 }; 25085 p.normalizePathname = p.normalizePath; 25086 p.normalizeQuery = function(build) { 25087 if (typeof this._parts.query === 'string') { 25088 if (!this._parts.query.length) { 25089 this._parts.query = null; 25090 } else { 25091 this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace)); 25092 } 25093 25094 this.build(!build); 25095 } 25096 25097 return this; 25098 }; 25099 p.normalizeFragment = function(build) { 25100 if (!this._parts.fragment) { 25101 this._parts.fragment = null; 25102 this.build(!build); 25103 } 25104 25105 return this; 25106 }; 25107 p.normalizeSearch = p.normalizeQuery; 25108 p.normalizeHash = p.normalizeFragment; 25109 25110 function _generateNormalizer(hostnameRecoder, encoder, decoder) { 25111 return function() { 25112 var r = URI.recodeHostname 25113 var e = URI.encode; 25114 var d = URI.decode; 25115 25116 URI.encode = encoder; 25117 URI.decode = decoder; 25118 try { 25119 this.normalize(); 25120 } finally { 25121 URI.recodeHostname = r; 25122 URI.encode = e; 25123 URI.decode = d; 25124 } 25125 return this; 25126 }; 25127 } 25128 25129 // expect unicode input, iso8859 output 25130 p.iso8859 = _generateNormalizer(URI._defaultRecodeHostname, escape, decodeURIComponent); 25131 // expect iso8859 input, unicode output 25132 p.unicode = _generateNormalizer(URI._defaultRecodeHostname, strictEncodeURIComponent, unescape); 25133 // expect unicode input, IRI output 25134 p.iri = _generateNormalizer(recodeIRIHostname, encodeIRIComponent, decodeURIComponent); 25135 25136 p.readable = function() { 25137 var uri = this.clone(); 25138 // removing username, password, because they shouldn't be displayed according to RFC 3986 25139 uri.username('').password('').normalize(); 25140 var t = ''; 25141 if (uri._parts.protocol) { 25142 t += uri._parts.protocol + (uri._parts.urn ? ':' : '://'); 25143 } 25144 25145 if (uri._parts.hostname) { 25146 if (uri.is('punycode') && punycode) { 25147 t += punycode.toUnicode(uri._parts.hostname); 25148 if (uri._parts.port) { 25149 t += ':' + uri._parts.port; 25150 } 25151 } else { 25152 t += uri.host(); 25153 } 25154 } 25155 25156 if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') { 25157 t += '/'; 25158 } 25159 25160 t += uri.path(true); 25161 if (uri._parts.query) { 25162 var q = ''; 25163 for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) { 25164 var kv = (qp[i] || '').split('='); 25165 q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace) 25166 .replace(/&/g, '%26'); 25167 25168 if (kv[1] !== undefined) { 25169 q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace) 25170 .replace(/&/g, '%26'); 25171 } 25172 } 25173 t += '?' + q.substring(1); 25174 } 25175 25176 t += URI.decodeQuery(uri.hash(), true); 25177 return t; 25178 }; 25179 25180 // resolving relative and absolute URLs 25181 p.absoluteTo = function(base) { 25182 var resolved = this.clone(); 25183 var properties = ['protocol', 'username', 'password', 'hostname', 'port']; 25184 var basedir, i, p; 25185 25186 if (this._parts.urn) { 25187 throw new Error('URNs do not have any generally defined hierarchical components'); 25188 } 25189 25190 if (!(base instanceof URI)) { 25191 base = new URI(base); 25192 } 25193 25194 if (!resolved._parts.protocol) { 25195 resolved._parts.protocol = base._parts.protocol; 25196 } 25197 25198 if (this._parts.hostname) { 25199 return resolved; 25200 } 25201 25202 for (i = 0; (p = properties[i]); i++) { 25203 resolved._parts[p] = base._parts[p]; 25204 } 25205 25206 if (!resolved._parts.path) { 25207 resolved._parts.path = base._parts.path;
25208 if (!resolved._parts.query) { 25209 resolved._parts.query = base._parts.query; 25210 } 25211 } else if (resolved._parts.path.substring(-2) === '..') { 25212 resolved._parts.path += '/'; 25213 } 25214 25215 if (resolved.path().charAt(0) !== '/') { 25216 basedir = base.directory(); 25217 resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path; 25218 resolved.normalizePath(); 25219 } 25220 25221 resolved.build(); 25222 return resolved; 25223 }; 25224 p.relativeTo = function(base) { 25225 var relative = this.clone().normalize(); 25226 var relativeParts, baseParts, common, relativePath, basePath; 25227 25228 if (relative._parts.urn) { 25229 throw new Error('URNs do not have any generally defined hierarchical components'); 25230 } 25231 25232 base = new URI(base).normalize(); 25233 relativeParts = relative._parts; 25234 baseParts = base._parts; 25235 relativePath = relative.path(); 25236 basePath = base.path(); 25237 25238 if (relativePath.charAt(0) !== '/') { 25239 throw new Error('URI is already relative'); 25240 } 25241 25242 if (basePath.charAt(0) !== '/') { 25243 throw new Error('Cannot calculate a URI relative to another relative URI'); 25244 } 25245 25246 if (relativeParts.protocol === baseParts.protocol) { 25247 relativeParts.protocol = null; 25248 } 25249 25250 if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) { 25251 return relative.build(); 25252 } 25253 25254 if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) { 25255 return relative.build(); 25256 } 25257 25258 if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) { 25259 relativeParts.hostname = null; 25260 relativeParts.port = null; 25261 } else { 25262 return relative.build(); 25263 } 25264 25265 if (relativePath === basePath) { 25266 relativeParts.path = ''; 25267 return relative.build(); 25268 } 25269 25270 // determine common sub path 25271 common = URI.commonPath(relative.path(), base.path()); 25272 25273 // If the paths have nothing in common, return a relative URL with the absolute path. 25274 if (!common) { 25275 return relative.build(); 25276 } 25277 25278 var parents = baseParts.path 25279 .substring(common.length) 25280 .replace(/[^\/]*$/, '') 25281 .replace(/.*?\//g, '../'); 25282 25283 relativeParts.path = parents + relativeParts.path.substring(common.length); 25284 25285 return relative.build(); 25286 }; 25287 25288 // comparing URIs 25289 p.equals = function(uri) { 25290 var one = this.clone(); 25291 var two = new URI(uri); 25292 var one_map = {}; 25293 var two_map = {}; 25294 var checked = {}; 25295 var one_query, two_query, key; 25296 25297 one.normalize(); 25298 two.normalize(); 25299 25300 // exact match 25301 if (one.toString() === two.toString()) { 25302 return true; 25303 } 25304 25305 // extract query string 25306 one_query = one.query(); 25307 two_query = two.query(); 25308 one.query(''); 25309 two.query(''); 25310 25311 // definitely not equal if not even non-query parts match 25312 if (one.toString() !== two.toString()) { 25313 return false; 25314 } 25315 25316 // query parameters have the same length, even if they're permuted 25317 if (one_query.length !== two_query.length) { 25318 return false; 25319 } 25320 25321 one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace); 25322 two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace); 25323 25324 for (key in one_map) { 25325 if (hasOwn.call(one_map, key)) { 25326 if (!isArray(one_map[key])) { 25327 if (one_map[key] !== two_map[key]) { 25328 return false; 25329 } 25330 } else if (!arraysEqual(one_map[key], two_map[key])) { 25331 return false; 25332 } 25333 25334 checked[key] = true; 25335 } 25336 } 25337 25338 for (key in two_map) { 25339 if (hasOwn.call(two_map, key)) { 25340 if (!checked[key]) { 25341 // two contains a parameter not present in one 25342 return false; 25343 } 25344 } 25345 } 25346 25347 return true; 25348 }; 25349 25350 // state 25351 p.duplicateQueryParameters = function(v) { 25352 this._parts.duplicateQueryParameters = !!v; 25353 return this; 25354 }; 25355 25356 p.escapeQuerySpace = function(v) { 25357 this._parts.escapeQuerySpace = !!v; 25358 return this; 25359 }; 25360 25361 return URI; 25362})); 25363/* 25364 Copyright 2017 Rustici Software 25365 25366 See the LICENSE.md, you may not use this file except in compliance with the License. 25367 25368 Unless required by applicable law or agreed to in writing, softw
25368are 25369 distributed under the License is distributed on an "AS IS" BASIS, 25370 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25371 See the License for the specific language governing permissions and 25372 limitations under the License. 25373*/ 25374 25375/** 25376cmi5.js AU runtime library 25377 25378@module Cmi5 25379*/ 25380var Cmi5; 25381 25382(function () { 25383 "use strict"; 25384 var THIS_LIBRARY = { 25385 // set by the build step 25386 VERSION: "<%= pkg.version %>", 25387 NAME: "<%= pkg.name %>", 25388 DESCRIPTION: "<%= pkg.description %>" 25389 }, 25390 nativeRequest, 25391 xdrRequest, 25392 requestComplete, 25393 __delay, 25394 env = {}, 25395 STATE_LMS_LAUNCHDATA = "LMS.LaunchData", 25396 LAUNCH_MODE_NORMAL = "Normal", 25397 AGENT_PROFILE_LEARNER_PREFS = "cmi5LearnerPreferences", 25398 CATEGORY_ACTIVITY_CMI5 = new TinCan.Activity( 25399 { 25400 id: "https://w3id.org/xapi/cmi5/context/categories/cmi5" 25401 } 25402 ), 25403 CATEGORY_ACTIVITY_MOVEON = new TinCan.Activity( 25404 { 25405 id: "https://w3id.org/xapi/cmi5/context/categories/moveon" 25406 } 25407 ), 25408 OTHER_ACTIVITY_CMI5JS = new TinCan.Activity( 25409 { 25410 id: "http://id.tincanapi.com/activity/software/" + THIS_LIBRARY.NAME + "/" + THIS_LIBRARY.VERSION, 25411 definition: { 25412 name: { 25413 und: THIS_LIBRARY.NAME + " (" + THIS_LIBRARY.VERSION + ")" 25414 }, 25415 description: { 25416 "en": THIS_LIBRARY.DESCRIPTION 25417 }, 25418 type: "http://id.tincanapi.com/activitytype/source" 25419 } 25420 } 25421 ), 25422 EXTENSION_SESSION_ID = "https://w3id.org/xapi/cmi5/context/extensions/sessionid", 25423 EXTENSION_MASTERY_SCORE = "https://w3id.org/xapi/cmi5/context/extensions/masteryscore", 25424 VERB_INITIALIZED_ID = "http://adlnet.gov/expapi/verbs/initialized", 25425 VERB_TERMINATED_ID = "http://adlnet.gov/expapi/verbs/terminated", 25426 VERB_COMPLETED_ID = "http://adlnet.gov/expapi/verbs/completed", 25427 VERB_PASSED_ID = "http://adlnet.gov/expapi/verbs/passed", 25428 VERB_FAILED_ID = "http://adlnet.gov/expapi/verbs/failed", 25429 verbDisplay = {}, 25430 launchParameters = [ 25431 "endpoint", 25432 "fetch", 25433 "actor", 25434 "activityId", 25435 "registration" 25436 ], 25437 isInteger; 25438 25439 // polyfill for Number.isInteger from MDN 25440 isInteger = function (value) { 25441 return typeof value === "number" && isFinite(value) && Math.floor(value) === value; 25442 }; 25443 25444 verbDisplay[VERB_INITIALIZED_ID] = { 25445 "en": "initialized" 25446 }; 25447 verbDisplay[VERB_TERMINATED_ID] = { 25448 "en": "terminated" 25449 }; 25450 25451 // 25452 // Detect CORS and XDR support 25453 // 25454 env.hasCORS = false; 25455 env.useXDR = false; 25456 25457 if (typeof XMLHttpRequest !== "undefined" && typeof (new XMLHttpRequest()).withCredentials !== "undefined") { 25458 env.hasCORS = true; 25459 } 25460 else if (typeof XDomainRequest !== "undefined") { 25461 env.hasCORS = true; 25462 env.useXDR = true; 25463 } 25464 25465 /** 25466 Top level interface constructor. 25467 25468 It is highly recommended to use asynchronous calls to methods supporting a callback. 25469 25470 @class Cmi5 25471 @constructor 25472 @param {String} [launchString] AU Launch URL providing configuration options 25473 @throws {Error} Invalid launch string 25474 */ 25475 Cmi5 = function (launchString) { 25476 this.log("constructor", launchString); 25477 var url, 25478 cfg, 25479 i; 25480 25481 if (typeof launchString !== "undefined") { 25482 url = new URI(launchString); 25483 cfg = url.search(true); 25484 25485 for (i = 0; i < launchParameters.length; i += 1) { 25486 if (typeof cfg[launchParameters[i]] === "undefined" || cfg[launchParameters[i]] === "") { 25487 throw new Error("Invalid launch string missing or empty parameter: " + launchParameters[i]); 25488 } 25489 } 25490 25491 this.setFetch(cfg.fetch); 25492 this.setLRS(cfg.endpoint); 25493 this.setActor(cfg.actor); 25494 this.setActivity(cfg.activityId); 25495 this.setRegistration(cfg.registration); 25496 } 25497 }; 25498 25499 /** 25500 Version of this library 25501 25502 @property VERSION 25503 @static
25504 @type String 25505 */ 25506 Cmi5.VERSION = THIS_LIBRARY.VERSION; 25507 25508 /** 25509 Whether or not to enable debug logging 25510 25511 @property DEBUG 25512 @static 25513 @type Boolean 25514 @default false 25515 */ 25516 Cmi5.DEBUG = false; 25517 25518 Cmi5.prototype = { 25519 _fetch: null, 25520 _endpoint: null, 25521 _actor: null, 25522 _registration: null, 25523 _activity: null, 25524 25525 _lrs: null, 25526 _fetchRequest: null, 25527 _fetchContent: null, 25528 _lmsLaunchData: null, 25529 _contextTemplate: null, 25530 _learnerPrefs: null, 25531 _isActive: false, 25532 _initialized: null, 25533 _passed: null, 25534 _failed: null, 25535 _completed: null, 25536 _terminated: null, 25537 _durationStart: null, 25538 _progress: null, 25539 _includeSourceActivity: true, 25540 25541 /** 25542 Method to call to start the AU runtime 25543 25544 This is a simplified "boot" sequence for the AU that will call the individual parts of the start up sequence that would otherwise need to be called in order sequentially. 25545 25546 @method start 25547 @param {Function} callback Function to call on error or success 25548 @param {Object} [events] Functions to run at specific execution points 25549 @param {Function} [events.postFetch] Function to run after retrieving fetchUrl result 25550 @param {Function} [events.launchData] Function to run after retrieving launch data 25551 @param {Function} [events.learnerPrefs] Function to run after retrieving learner preferences 25552 @param {Function} [events.initializeStatement] Function to run after saving initialization statement 25553 */ 25554 start: function (callback, events) { 25555 this.log("start"); 25556 var self = this; 25557 25558 events = events || {}; 25559 25560 self.postFetch( 25561 function (err) { 25562 var prefix = "Failed to start AU - "; 25563 25564 if (typeof events.postFetch !== "undefined") { 25565 events.postFetch.apply(this, arguments); 25566 } 25567 if (err !== null) { 25568 callback(new Error(prefix + " POST to fetch: " + err)); 25569 return; 25570 } 25571 25572 self.loadLMSLaunchData( 25573 function (err) { 25574 if (typeof events.launchData !== "undefined") { 25575 events.launchData.apply(this, arguments); 25576 } 25577 if (err !== null) { 25578 callback(new Error(prefix + " load LMS LaunchData: " + err)); 25579 return; 25580 } 25581 25582 self.loadLearnerPrefs( 25583 function (err) { 25584 if (typeof events.learnerPrefs !== "undefined") { 25585 events.learnerPrefs.apply(this, arguments); 25586 } 25587 if (err !== null) { 25588 callback(new Error(prefix + " load learner preferences: " + err)); 25589 return; 25590 } 25591 25592 self.initialize( 25593 function (err) { 25594 if (typeof events.initializeStatement !== "undefined") { 25595 events.initializeStatement.apply(this, arguments); 25596 } 25597 if (err !== null) { 25598 callback(new Error(prefix + " send initialized statement: " + err)); 25599 return; 25600 } 25601 25602 callback(null); 25603 } 25604 ); 25605 } 25606 ); 25607 } 25608 ); 25609 } 25610 ); 25611 }, 25612 25613 /** 25614 Method to POST to the fetchUrl to retrieve the LRS credentials 25615 25616 `setFetch` has to be called first and is called by the constructor if the launch string was provided to it. 25617 25618 @method postFetch 25619 @param {Function} [callback] Function to call on error or success 25620 */ 25621 postFetch: function (callback) { 25622 this.log("postFetch"); 25623 var self = this, 25624 cbWrapper; 25625 25626 if (this._fetch === null) { 25627 callback(new Error("Can't POST to fetch URL without setFetch")); 25628 return; 25629 } 25630 25631 if (callback) { 25632 cbWrapper = function (err, xhr) { 25633 self.log("postFetch::cbWrapper"); 25634 self.log("postFetch::cbWrapper", err); 25635 self.log("postFetch::cbWrapper", xhr); 25636 var parsed, 25637 responseContent = xhr.responseText, 25638 responseContentType; 25639 25640 if (err !== null) { 25641 if (err === 0) { 25642 err = "Aborted, offline, or invalid CORS endpoint"; 25643 } 25644 else if (/^\d+$/.test(err)) { 25645 if (typeof xhr.getResponseHeader !== "undefined") { 25646 responseContentType = xhr.getResponseHeader("Content-Type"); 25647 } 25648 else if (typeof xhr.contentType !== "undefined") { 25649 responseContentType = xhr.contentType; 25650 } 25651 if (TinCan.Utils.isApplicationJSON(responseContentType)) { 25652 try { 25653 parsed = JSON.parse(responseContent); 25654 25655 if (typeof parsed["error-text"] !== "undefined") { 25656 err = parsed["error-text"] + " (" + parsed["error-code"] + ")"; 25657 } 25658 else { 25659 err = "Failed to detect 'error-text' property in JSON error response"; 25660 } 25661 } 25662 catch (ex) { 25663 err = "Failed to parse JSON error response: " + ex; 25664 } 25665 } 25666 else { 25667 err = xhr.responseText; 25668 } 25669 } 25670 else { 25671 err = xhr.responseText; 25672 } 25673 callback(new Error(err), xhr, parsed); 25674 return; 25675 } 25676 25677 try { 25678 parsed = JSON.parse(responseContent); 25679 } 25680 catch (ex) { 25681 self.log("postFetch::cbWrapper - failed to parse JSON response: " + ex); 25682 callback(new Error("Post fetch response malformed: failed to parse JSON response
25682(" + ex + ")"), xhr); 25683 return; 25684 } 25685 25686 if (parsed === null || typeof parsed !== "object" || typeof parsed["auth-token"] === "undefined") { 25687 self.log("postFetch::cbWrapper - failed to access 'auth-token' property"); 25688 callback(new Error("Post fetch response malformed: failed to access 'auth-token' in (" + responseContent + ")"), xhr, parsed); 25689 return; 25690 } 25691 25692 self._fetchContent = parsed; 25693 self._lrs.auth = "Basic " + parsed["auth-token"]; 25694 25695 callback(err, xhr, parsed); 25696 }; 25697 } 25698 25699 return this._fetchRequest( 25700 this._fetch, 25701 { 25702 method: "POST" 25703 }, 25704 cbWrapper 25705 ); 25706 }, 25707 25708 /** 25709 Method to load the LMS.LaunchData state document populated by the LMS 25710 25711 Fetch data has to have already been loaded, in order to have LRS credential. 25712 25713 @method loadLMSLaunchData 25714 @param {Function} callback Function to call on error or success 25715 */ 25716 loadLMSLaunchData: function (callback) { 25717 this.log("loadLMSLaunchData"); 25718 var self = this; 25719 25720 if (this._fetchContent === null) { 25721 callback(new Error("Can't retrieve LMS Launch Data without successful postFetch")); 25722 return; 25723 } 25724 25725 this._lrs.retrieveState( 25726 STATE_LMS_LAUNCHDATA, 25727 { 25728 activity: this._activity, 25729 agent: this._actor, 25730 registration: this._registration, 25731 callback: function (err, result) { 25732 if (err !== null) { 25733 callback(new Error("Failed to retrieve " + STATE_LMS_LAUNCHDATA + " State: " + err), result); 25734 return; 25735 } 25736 25737 // 25738 // a missing state isn't an error as far as TinCanJS is concerned, but 25739 // getting a 404 on the LMS LaunchData is a problem in cmi5 so fail here 25740 // in that case (which is when result is null) 25741 // 25742 if (result === null) { 25743 callback(new Error(STATE_LMS_LAUNCHDATA + " State not found"), result); 25744 return; 25745 } 25746 25747 self._lmsLaunchData = result.contents; 25748 25749 // 25750 // store a stringified version of the context template for cheap 25751 // cloning when we go to prepare it later for use in statements 25752 // 25753 self._contextTemplate = JSON.stringify(self._lmsLaunchData.contextTemplate); 25754 25755 callback(null, result); 25756 } 25757 } 25758 ); 25759 }, 25760 25761 /** 25762 Method to load learner prefs agent profile document possibly populated by the LMS 25763 25764 @method loadLearnerPrefs 25765 @param {Function} callback Function to call on error or success 25766 */ 25767 loadLearnerPrefs: function (callback) { 25768 this.log("loadLearnerPrefs"); 25769 var self = this; 25770 25771 if (this._lmsLaunchData === null) { 25772 callback(new Error("Can't retrieve Learner Preferences without successful loadLMSLaunchData")); 25773 return; 25774 } 25775 25776 this._lrs.retrieveAgentProfile( 25777 AGENT_PROFILE_LEARNER_PREFS, 25778 { 25779 agent: this._actor, 25780 callback: function (err, result) { 25781 if (err !== null) { 25782 callback(new Error("Failed to retrieve " + AGENT_PROFILE_LEARNER_PREFS + " Agent Profile" + err), result); 25783 return; 25784 } 25785 25786 // 25787 // result is null when the profile 404s which is not an error, 25788 // just means it hasn't been set to anything 25789 // 25790 if (result !== null) { 25791 self._learnerPrefs = result; 25792 } 25793 else { 25794 // 25795 // store an empty object locally to be able to distinguish a non-set 25796 // preference document vs a non-fetched preference document 25797 // 25798 self._learnerPrefs = new TinCan.AgentProfile( 25799 { 25800 id: AGENT_PROFILE_LEARNER_PREFS, 25801 contentType: "application/json", 25802 contents: {} 25803 } 25804 ); 25805 } 25806 25807 callback(null, result); 25808 } 25809 } 25810 ); 25811 }, 25812 25813 /** 25814 Method to save learner prefs to agent profile document in LRS 25815 25816 @method saveLearnerPrefs 25817 @param {Function} [callback] Function to call on error or success 25818 */ 25819 saveLearnerPrefs: function (callback) { 25820 this.log("saveLearnerPrefs"); 25821 var self = this, 25822 result, 25823 cbWrapper; 25824 25825 if (this._learnerPrefs === null) { 25826 result = new Error("Can't save Learner Preferences without first loading them"); 25827 if (callback) { 25828 callback(result); 25829 return; 25830 } 25831 return result; 25832 } 25833 25834 if (callback) { 25835 cbWrapper = function (err, result) { 25836 self.log("saveLearnerPrefs - saveAgentProfile callback", err, result); 25837 if (err !== null) { 25838 callback(new Error("Failed to save " + AGENT_PROFILE_LEARNER_PREFS + " Agent Profile: " + err), result); 25839 return; 25840 } 25841 25842 self._learnerPrefs.etag = TinCan.Utils.getSHA1String( 25843 (typeof self._learnerPrefs.contents === "object" && TinCan.Utils.isApplicationJSON(self._learnerPrefs.contentType)) 25844 ? JSON.stringify(self._learnerPrefs.contents) 25845 : self._learnerPrefs.contents 25846 ); 25847 25848 callback(null, result); 25849 }; 25850 } 25851 25852 result = this._lrs.saveAgentProfile( 25853 AGENT_PROFILE_LEARNER_PREFS, 25854 this._learnerPrefs.contents, 25855 { 25856 agent: this._actor, 25857 lastSHA1: this._learnerPrefs.etag, 25858 contentType: this._learnerPrefs.contentType, 25859 callback: cbWrapper 25860 } 25861 ); 25862 if (cbWrapper) { 25863 return; 25864 } 25865 25866 if (result.err !== null) { 25867 return new Error("Failed to save " + AGENT_PROFILE_LEARNER_PREFS + " Agent Profile: " + result.err); 25868 } 25869 25870 self._learnerPrefs.etag = TinCan.Utils.getSHA1String( 25871 (typeof self._learnerPrefs.contents === "object" && TinCan.Utils.isApplicationJSON(self._learnerPrefs.contentType)) 25872 ? JSON.stringify(self._learnerPrefs.contents) 25873 : self._learnerPrefs.contents 25874 ); 25875 25876 return; 25877 }, 25878 25879 /** 25880 Finalize initialization process by sending initialized statement, starting duration tracker, and marking AU active 25881 25882 @method initialize 25883 @param {Function} [callback] Function to call on error or success 25884 @throws {Error} <ul><li>Learner prefs not loaded</li><li>AU already initialized</li></ul> 25885 */ 25886 initialize: function (callback) { 25887 this.log("initialize"); 25888 var st, 25889 err, 25890 callbackWrapper, 25891 result; 25892 25893 if (this._learnerPrefs === null) { 25894 err = new Error("Can't send initialized statement without successful loadLearnerPrefs"); 25895 if (callback) { 25896 callback(err); 25897 return; 25898 } 25899 25900 throw err; 25901 } 25902 25903 if (this._initialized) { 25904 this.log("initialize - already initialized"); 25905 25906 err = new Error("AU already initialized"); 25907 if (callback) { 25908 callback(err); 25909 return; 25910 } 25911 25912 throw err; 25913 } 25914
25915 st = this.initializedStatement(); 25916 25917 if (callback) { 25918 callbackWrapper = function (err) { 25919 this.log("initialize - callbackWrapper: " + err); 25920 if (err === null) { 25921 this._initialized = true; 25922 this._isActive = true; 25923 this._durationStart = new Date().getTime(); 25924 } 25925 25926 callback.apply(this, arguments); 25927 }.bind(this); 25928 } 25929 25930 result = this.sendStatement(st, callbackWrapper); 25931 this.log("initialize - result: ", result); 25932 25933 if (! callback && result.response.err === null) { 25934 this._initialized = true; 25935 this._isActive = true; 25936 this._durationStart = new Date().getTime(); 25937 } 25938 25939 return result; 25940 }, 25941 25942 /** 25943 Method to indicate session termination should occur, sends terminated statement, marks AU inactive 25944 25945 @method terminate 25946 @param {Function} [callback] Function to call on error or success 25947 @throws {Error} <ul><li>AU not initialized</li><li>AU already terminated</li></ul> 25948 */ 25949 terminate: function (callback) { 25950 this.log("terminate"); 25951 var st, 25952 err, 25953 callbackWrapper, 25954 result; 25955 25956 if (! this._initialized) { 25957 this.log("terminate - not initialized"); 25958 25959 err = new Error("AU not initialized"); 25960 if (callback) { 25961 callback(err); 25962 return; 25963 } 25964 25965 throw err; 25966 } 25967 25968 if (this._terminated) { 25969 this.log("terminate - already terminated"); 25970 25971 err = new Error("AU already terminated"); 25972 if (callback) { 25973 callback(err); 25974 return; 25975 } 25976 25977 throw err; 25978 } 25979 25980 st = this.terminatedStatement(); 25981 25982 if (callback) { 25983 callbackWrapper = function (err) { 25984 this.log("terminate - callbackWrapper: " + err); 25985 if (err === null) { 25986 this._terminated = true; 25987 this._isActive = false; 25988 } 25989 25990 callback.apply(this, arguments); 25991 }.bind(this); 25992 } 25993
25994 result = this.sendStatement(st, callbackWrapper); 25995 this.log("terminate - result: ", result); 25996 25997 if (! callback && result.response.err === null) { 25998 this._terminated = true; 25999 this._isActive = false; 26000 } 26001 26002 return result; 26003 }, 26004 26005 /** 26006 Method to indicate learner has completed the AU, sends completed statement 26007 26008 @method completed 26009 @param {Function} [callback] Function to call on error or success 26010 @throws {Error} <ul><li>AU not active</li><li>AU not in normal launch mode</li><li>AU already completed</li></ul> 26011 */ 26012 completed: function (callback) { 26013 this.log("completed"); 26014 var st, 26015 err, 26016 callbackWrapper, 26017 result; 26018 26019 if (! this.isActive()) { 26020 this.log("completed - not active"); 26021 err = new Error("AU not active"); 26022 26023 if (callback) { 26024 callback(err); 26025 return; 26026 } 26027 26028 throw err; 26029 } 26030 26031 if (this.getLaunchMode() !== LAUNCH_MODE_NORMAL) { 26032 this.log("completed - non-Normal launch mode: ", this.getLaunchMode()); 26033 err = new Error("AU not in Normal launch mode"); 26034 26035 if (callback) { 26036 callback(err); 26037 return; 26038 } 26039 26040 throw err; 26041 } 26042 26043 if (this._completed) { 26044 this.log("completed - already completed"); 26045 err = new Error("AU already completed"); 26046 26047 if (callback) { 26048 callback(err); 26049 return; 26050 } 26051 26052 throw err; 26053 } 26054 26055 st = this.completedStatement(); 26056 26057 if (callback) { 26058 callbackWrapper = function (err) { 26059 this.log("completed - callbackWrapper: " + err); 26060 if (err === null) { 26061 this.setProgress(null); 26062 this._completed = true; 26063 } 26064 26065 callback.apply(this, arguments); 26066 }.bind(this); 26067 } 26068
26069 result = this.sendStatement(st, callbackWrapper); 26070 this.log("completed - result: ", result); 26071 26072 if (! callback && result.response.err === null) { 26073 this.setProgress(null); 26074 this._completed = true; 26075 } 26076 26077 return result; 26078 }, 26079 26080 /** 26081 Method to indicate learner has passed the AU, sends passed statement with optional score 26082 26083 @method passed 26084 @param {Object} [score] Score to be included in statement (see `passedStatement`) 26085 @param {Function} [callback] Function to call on error or success 26086 @throws {Error} <ul><li>AU not active,</li><li>AU not in normal launch mode,</li><li>AU already passed,</li><li>Failed to create passed statement (usually because of malformed score)</li></ul> 26087 */ 26088 passed: function (score, callback) { 26089 this.log("passed"); 26090 var st, 26091 err, 26092 callbackWrapper, 26093 result; 26094 26095 if (! this.isActive()) { 26096 this.log("passed - not active"); 26097 err = new Error("AU not active"); 26098 26099 if (callback) { 26100 callback(err); 26101 return; 26102 } 26103 26104 throw err; 26105 } 26106 26107 if (this.getLaunchMode() !== LAUNCH_MODE_NORMAL) { 26108 this.log("passed - non-Normal launch mode: ", this.getLaunchMode()); 26109 err = new Error("AU not in Normal launch mode"); 26110 26111 if (callback) { 26112 callback(err); 26113 return; 26114 } 26115 26116 throw err; 26117 } 26118 26119 if (this._passed !== null) { 26120 this.log("passed - already passed"); 26121 err = new Error("AU already passed"); 26122 26123 if (callback) { 26124 callback(err); 26125 return; 26126 } 26127 26128 throw err; 26129 } 26130 26131 try { 26132 st = this.passedStatement(score); 26133 } 26134 catch (ex) { 26135 this.log("passed - failed to create passed statement: " + ex); 26136 if (callback) { 26137 callback("Failed to create passed statement - " + ex); 26138 return; 26139 } 26140 26141 throw ex; 26142 } 26143 26144 if (callback) { 26145 callbackWrapper = function (err) { 26146 this.log("passed - callbackWrapper: " + err); 26147 if (err === null) { 26148 this._passed = true; 26149 } 26150 26151 callback.apply(this, arguments); 26152 }.bind(this); 26153 } 26154
26155 result = this.sendStatement(st, callbackWrapper); 26156 this.log("passed - result: ", result); 26157 26158 if (! callback && result.response.err === null) { 26159 this._passed = true; 26160 } 26161 26162 return result; 26163 }, 26164 26165 /** 26166 Method to indicate learner has failed the AU, sends failed statement with optional score 26167 26168 @method failed 26169 @param {Object} [score] Score to be included in statement (see `failedStatement`) 26170 @param {Function} [callback] Function to call on error or success 26171 @throws {Error} <ul><li>AU not active</li><li>AU not in normal launch mode</li><li>AU already passed/failed</li><li>Failed to create failed statement (usually because of malformed score)</li></ul> 26172 */ 26173 failed: function (score, callback) { 26174 this.log("failed"); 26175 var st, 26176 err, 26177 callbackWrapper, 26178 result; 26179 26180 if (! this.isActive()) { 26181 this.log("failed - not active"); 26182 err = new Error("AU not active"); 26183 26184 if (callback) { 26185 callback(err); 26186 return; 26187 } 26188 26189 throw err; 26190 } 26191 26192 if (this.getLaunchMode() !== LAUNCH_MODE_NORMAL) { 26193 this.log("failed - non-Normal launch mode: ", this.getLaunchMode()); 26194 err = new Error("AU not in Normal launch mode"); 26195 26196 if (callback) { 26197 callback(err); 26198 return; 26199 } 26200 26201 throw err; 26202 } 26203 26204 if (this._failed !== null || this._passed !== null) { 26205 this.log("failed - already passed/failed"); 26206 err = new Error("AU already passed/failed"); 26207 26208 if (callback) { 26209 callback(err); 26210 return; 26211 } 26212 26213 throw err; 26214 } 26215 26216 try { 26217 st = this.failedStatement(score); 26218 } 26219 catch (ex) { 26220 this.log("failed - failed to create failed statement: " + ex); 26221 if (callback) { 26222 callback("Failed to create failed statement - " + ex); 26223 return; 26224 } 26225 26226 throw ex; 26227 } 26228 26229 if (callback) { 26230 callbackWrapper = function (err) { 26231 this.log("failed - callbackWrapper: " + err); 26232 if (err === null) { 26233 this._failed = true; 26234 } 26235 26236 callback.apply(this, arguments); 26237 }.bind(this); 26238 } 26239
26240 result = this.sendStatement(st, callbackWrapper); 26241 this.log("failed - result: ", result); 26242 26243 if (! callback && result.response.err === null) { 26244 this._failed = true; 26245 } 26246 26247 return result; 26248 }, 26249 26250 /** 26251 Method indicating whether the AU is currently active, has been initialized and not terminated 26252 26253 @method isActive 26254 @return {Boolean} Active flag 26255 */ 26256 isActive: function () { 26257 this.log("isActive"); 26258 return this._isActive; 26259 }, 26260 26261 /** 26262 Safe version of logging, only displays when .DEBUG is true, and console.log 26263 is available 26264 26265 See `console.log` for parameters. 26266 26267 @method log 26268 */ 26269 log: function () { 26270 /* eslint-disable no-console */ 26271 if (Cmi5.DEBUG && typeof console !== "undefined" && console.log) { 26272 arguments[0] = "cmi5.js:" + arguments[0]; 26273 console.log.apply(console, arguments); 26274 } 26275 /* eslint-enable no-console */ 26276 }, 26277 26278 /** 26279 Switch on/off whether a source activity is included in statements by default 26280 26281 Default: on 26282 26283 @method includeSourceActivity 26284 @param {Boolean} val true is include, false is exclude 26285 */ 26286 includeSourceActivity: function (val) { 26287 this._includeSourceActivity = val ? true : false; 26288 }, 26289 26290 /** 26291 Retrieve the launch method as provided in the LMS launch data 26292 26293 @method getLaunchMethod 26294 @throws {Error} LMS launch data has not been loaded 26295 @return {String} launch method 26296 */ 26297 getLaunchMethod: function () { 26298 this.log("getLaunchMethod"); 26299 if (this._lmsLaunchData === null) { 26300 throw new Error("Can't determine launchMethod until LMS LaunchData has been loaded"); 26301 } 26302 26303 return this._lmsLaunchData.launchMethod; 26304 }, 26305 26306 /** 26307 Retrieve the launch mode as provided in the LMS launch data 26308 26309 @method getLaunchMode 26310 @throws {Error} LMS launch data has not been loaded 26311 @return {String} launch mode 26312 */ 26313 getLaunchMode: function () { 26314 this.log("getLaunchMode"); 26315 if (this._lmsLaunchData === null) { 26316 throw new Error("Can't determine launchMode until LMS LaunchData has been loaded"); 26317 } 26318 26319 return this._lmsLaunchData.launchMode; 26320 }, 26321 26322 /** 26323 Retrieve the launch parameters when provided by the AU and in the launch data 26324 26325 @method getLaunchParameters 26326 @throws {Error} LMS launch data has not been loaded 26327 @return {String|null} launch parameters when exist or null 26328 */ 26329 getLaunchParameters: function () { 26330 this.log("getLaunchParameters"); 26331 var result = null; 26332 26333 if (this._lmsLaunchData === null) { 26334 throw new Error("Can't determine LaunchParameters until LMS LaunchData has been loaded"); 26335 } 26336 26337 if (typeof this._lmsLaunchData.launchParameters !== "undefined") { 26338 result = this._lmsLaunchData.launchParameters; 26339 } 26340 26341 return result; 26342 }, 26343 26344 /** 26345 Retrieve the session id 26346 26347 @method getSessionId 26348 @throws {Error} LMS launch data has not been loaded 26349 @return {String} session id 26350 */ 26351 getSessionId: function () { 26352 this.log("getSessionId"); 26353 if (this._lmsLaunchData === null) { 26354 throw new Error("Can't determine session id until LMS LaunchData has been loaded"); 26355 } 26356 26357 return this._lmsLaunchData.contextTemplate.extensions[EXTENSION_SESSION_ID]; 26358 }, 26359 26360 /** 26361 Retrieve the moveOn value 26362 26363 @method getMoveOn 26364 @throws {Error} LMS launch data has not been loaded 26365 @return {String} moveOn value 26366 */ 26367 getMoveOn: function () { 26368 this.log("getMoveOn"); 26369 if (this._lmsLaunchData === null) { 26370 throw new Error("Can't determine moveOn until LMS LaunchData has been loaded"); 26371 } 26372 26373 return this._lmsLaunchData.moveOn; 26374 }, 26375 26376 /** 26377 Retrieve the mastery score as provided in LMS launch data 26378 26379 @method getMasteryScore 26380 @throws {Error} LMS launch data has not been loaded 26381 @return {String|null} mastery score or null 26382 */ 26383 getMasteryScore: function () { 26384 this.log("getMasteryScore"); 26385 var result = null; 26386 26387 if (this._lmsLaunchData === null) { 26388 throw new Error("Can't determine masteryScore until LMS LaunchData has been loaded"); 26389 } 26390 26391 if (typeof this._lmsLaunchData.masteryScore !== "undefined") { 26392 result = this._lmsLaunchData.masteryScore; 26393 } 26394 26395 return result; 26396 }, 26397 26398 /** 26399 Retrieve the return URL as provided in LMS launch data 26400 26401 @method getReturnURL 26402 @throws {Error} LMS launch data has not been loaded 26403 @return {String|null} mastery score or null 26404 */ 26405 getReturnURL: function () { 26406 this.log("getReturnURL"); 26407 var result = null; 26408 26409 if (this._lmsLaunchData === null) { 26410 throw new Error("Can't determine returnURL until LMS LaunchData has been loaded"); 26411 } 26412 26413 if (typeof this._lmsLaunchData.returnURL !== "undefined") { 26414 result = this._lmsLaunchData.returnURL; 26415 } 26416 26417 return result; 26418 }, 26419 26420 /** 26421 Retrieve the entitlement key as provided in LMS launch data 26422
26423 @method getEntitlementKey 26424 @throws {Error} LMS launch data has not been loaded 26425 @return {String|null} entitlement key 26426 */ 26427 getEntitlementKey: function () { 26428 this.log("getEntitlementKey"); 26429 var result = null; 26430 26431 if (this._lmsLaunchData === null) { 26432 throw new Error("Can't determine entitlementKey until LMS LaunchData has been loaded"); 26433 } 26434 26435 if (typeof this._lmsLaunchData.entitlementKey !== "undefined") { 26436 if (typeof this._lmsLaunchData.entitlementKey.alternate !== "undefined") { 26437 result = this._lmsLaunchData.entitlementKey.alternate; 26438 } 26439 else if (typeof this._lmsLaunchData.entitlementKey.courseStructure !== "undefined") { 26440 result = this._lmsLaunchData.entitlementKey.courseStructure; 26441 } 26442 } 26443 26444 return result; 26445 }, 26446 26447 /** 26448 Retrieve the language preference as provided in learner preferences 26449 26450 @method getLanguagePreference 26451 @throws {Error} Learner preference data has not been loaded 26452 @return {String|null} language preference 26453 */ 26454 getLanguagePreference: function () { 26455 this.log("getLanguagePreference"); 26456 var result = null; 26457 26458 if (this._learnerPrefs === null) { 26459 throw new Error("Can't determine language preference until learner preferences have been loaded"); 26460 } 26461 26462 if (typeof this._learnerPrefs.contents.languagePreference !== "undefined") { 26463 result = this._learnerPrefs.contents.languagePreference; 26464 } 26465 26466 return result; 26467 }, 26468 26469 /** 26470 Locally set the learner's language preference 26471 26472 @method setLanguagePreference 26473 @param {String} pref language preference code (use `""` to unset) 26474 @throws {Error} Learner preference data has not been loaded 26475 */ 26476 setLanguagePreference: function (pref) { 26477 this.log("setLanguagePreference"); 26478 26479 if (this._learnerPrefs === null) { 26480 throw new Error("Can't set language preference until learner preferences have been loaded"); 26481 } 26482 26483 if (pref === "") { 26484 pref = null; 26485 } 26486 26487 this._learnerPrefs.contents.languagePreference = pref; 26488 26489 return; 26490 }, 26491 26492 /** 26493 Retrieve the audio preference as provided in learner preferences 26494 26495 @method getAudioPreference 26496 @throws {Error} Learner preference data has not been loaded 26497 @return {String|null} audio preference 26498 */ 26499 getAudioPreference: function () { 26500 this.log("getAudioPreference"); 26501 var result = null; 26502 26503 if (this._learnerPrefs === null) { 26504 throw new Error("Can't determine audio preference until learner preferences have been loaded"); 26505 } 26506 26507 if (typeof this._learnerPrefs.contents.audioPreference !== "undefined") { 26508 result = this._learnerPrefs.contents.audioPreference; 26509 } 26510 26511 return result; 26512 }, 26513 26514 /** 26515 Locally set the learner's audio preference 26516 26517 @method setAudioPreference 26518 @param {String} pref "on", "off", or `null` 26519 @throws {Error} Learner preference data has not been loaded 26520 */ 26521 setAudioPreference: function (pref) { 26522 this.log("setAudioPreference"); 26523 26524 if (this._learnerPrefs === null) { 26525 throw new Error("Can't set audio preference until learner preferences have been loaded"); 26526 } 26527 26528 if (pref !== "on" && pref !== "off" && pref !== null) { 26529 throw new Error("Unrecognized value for audio preference: " + pref); 26530 } 26531 26532 this._learnerPrefs.contents.audioPreference = pref; 26533 26534 return; 26535 }, 26536 26537 /** 26538 Get the duration of this session so far 26539 26540 @method getDuration 26541 @return {Number} Number of milliseconds 26542 */ 26543 getDuration: function () { 26544 this.log("getDuration"); 26545 26546 return (new Date().getTime() - this._durationStart); 26547 }, 26548 26549 /** 26550 Locally set the progress towards completion 26551 26552 @method setProgress 26553 @param {Integer} progress progress as a percentage between 0 and 100 26554 @throws {Error} <ul><li>Not an integer</li><li>Less than zero or greater than 100</li></ul> 26555 */ 26556 setProgress: function (progress) { 26557 this.log("setProgress: ", progress); 26558 26559 if (progress !== null) { 26560 if (! isInteger(progress)) { 26561 throw new Error("Invalid progress measure (not an integer): " + progress); 26562 } 26563 if (progress < 0 || progress > 100) { 26564 throw new Error("Invalid progress measure must be greater than or equal to 0 and less than or equal to 100: " + progress); 26565 } 26566 } 26567 this._progress = progress; 26568 }, 26569 26570 /** 26571 Get progress 26572 26573 @method getProgress 26574 @return {Integer|null} Integer value of locally set progress measure or null when not set 26575 */ 26576 getProgress: function () { 26577 this.log("getProgress"); 26578 return this._progress; 26579 }, 26580 26581 /** 26582 Set the fetch URL, called by the `Cmi5` constructor when provided a launch URL 26583 26584 @method setFetch 26585 @param {String} fetchURL fetchURL as provided by the LMS in the launch string 26586 */ 26587 setFetch: function (fetchURL) { 26588 this.log("setFetch: ", fetchURL); 26589 var urlParts, 26590 schemeMatches, 26591 locationPort, 26592 isXD; 26593 26594 this._fetch = fetchURL; 26595 26596 // 26597 // default to native request mode 26598 // 26599 this._fetchRequest = nativeRequest; 26600 26601 // TODO: swap this for uri.js 26602 26603 urlParts = fetchURL.toLowerCase().match(/([A-Za-z]+:)\/\/([^:\/]+):?(\d+)?(\/.*)?$/); 26604 if (urlParts === null) {
26605 throw new Error("URL invalid: failed to divide URL parts"); 26606 } 26607 26608 // 26609 // determine whether this is a cross domain request, 26610 // whether our browser has CORS support at all, and then 26611 // if it does then if we are in IE with XDR only check that 26612 // the schemes match to see if we should be able to talk to 26613 // the other side 26614 // 26615 locationPort = location.port; 26616 schemeMatches = location.protocol.toLowerCase() === urlParts[1]; 26617 26618 // 26619 // normalize the location.port cause it appears to be "" when 80/443 26620 // but our endpoint may have provided it 26621 // 26622 if (locationPort === "") { 26623 locationPort = (location.protocol.toLowerCase() === "http:" ? "80" : (location.protocol.toLowerCase() === "https:" ? "443" : "")); 26624 } 26625 26626 isXD = ( 26627 26628 // is same scheme? 26629 ! schemeMatches 26630 26631 // is same host? 26632 || location.hostname.toLowerCase() !== urlParts[2] 26633 26634 // is same port? 26635 || locationPort !== ( 26636 (urlParts[3] !== null && typeof urlParts[3] !== "undefined" && urlParts[3] !== "") 26637 ? urlParts[3] 26638 : (urlParts[1] === "http:" ? "80" : (urlParts[1] === "https:" ? "443" : "") 26639 ) 26640 ) 26641 ); 26642 if (isXD) { 26643 if (env.hasCORS) { 26644 if (env.useXDR && schemeMatches) { 26645 this._fetchRequest = xdrRequest; 26646 } 26647 else if (env.useXDR && ! schemeMatches) { 26648 this.log("[error] URL invalid: cross domain request for differing scheme in IE with XDR"); 26649 throw new Error("URL invalid: cross domain request for differing scheme in IE with XDR"); 26650 } 26651 } 26652 else { 26653 this.log("[error] URL invalid: cross domain requests not supported in this browser"); 26654 throw new Error("URL invalid: cross domain requests not supported in this browser"); 26655 } 26656 } 26657 }, 26658 26659 /** 26660 Retrieve the fetch URL 26661 26662 @method getFetch 26663 @return {String} the previous set fetch URL 26664 */ 26665 getFetch: function () { 26666 return this._fetch; 26667 }, 26668 26669 /** 26670 Initialize the LRS to a `TinCan.LRS` object or update the existing object which will be used for all xAPI communications 26671 26672 Called by the `Cmi5` constructor when provided a launch URL. 26673 26674 @method setLRS 26675 @param {String} endpoint LRS location 26676 @param {String} auth Authentication token value 26677 */ 26678 setLRS: function (endpoint, auth) { 26679 this.log("setLRS: ", endpoint, auth); 26680 if (this._lrs !== null) { 26681 if ((typeof auth === "undefined" && endpoint === null) || endpoint !== null) { 26682 this._endpoint = this._lrs.endpoint = endpoint; 26683 } 26684 if (typeof auth !== "undefined" && auth !== null) { 26685 this._lrs.auth = auth; 26686 } 26687 } 26688 else { 26689 this._lrs = new TinCan.LRS( 26690 { 26691 endpoint: endpoint, 26692 auth: auth, 26693 allowFail: false 26694 } 26695 ); 26696 } 26697 }, 26698 26699 /** 26700 Retrieve the `TinCan.LRS` object 26701 26702 @method getLRS 26703 @return {TinCan.LRS} LRS object 26704 */ 26705 getLRS: function () { 26706 return this._lrs; 26707 }, 26708 26709 /** 26710 Initialize the actor using a `TinCan.Agent` that will represent the learner 26711 26712 Called by the `Cmi5` constructor when provided a launch URL. 26713 26714 @method setActor 26715 @param {String|TinCan.Agent} agent Pre-constructed Agent or string of JSON used to construct Agent 26716 @throws {Error} <ul><li>
26716Invalid actor, missing account IFI</li><li>Invalid account IFI</li></ul> 26717 */ 26718 setActor: function (agent) { 26719 if (! (agent instanceof TinCan.Agent)) { 26720 agent = TinCan.Agent.fromJSON(agent); 26721 } 26722 26723 // 26724 // don't generally want to do too much validation as the LMS 26725 // should be giving us valid information, *but* in this case 26726 // users need to be able to count on the type of object being 26727 // returned 26728 // 26729 if ((agent.account === null) || (! (agent.account instanceof TinCan.AgentAccount))) { 26730 throw new Error("Invalid actor: missing or invalid account"); 26731 } 26732 else if (agent.account.name === null) { 26733 throw new Error("Invalid actor: name is null"); 26734 } 26735 else if (agent.account.name === "") { 26736 throw new Error("Invalid actor: name is empty"); 26737 } 26738 else if (agent.account.homePage === null) { 26739 throw new Error("Invalid actor: homePage is null"); 26740 } 26741 else if (agent.account.homePage === "") { 26742 throw new Error("Invalid actor: homePage is empty"); 26743 } 26744 26745 this._actor = agent; 26746 }, 26747 26748 /** 26749 Retrieve the `TinCan.Agent` object representing the learner 26750 26751 @method getActor 26752 @return {TinCan.Agent} Learner's Agent 26753 */ 26754 getActor: function () { 26755 return this._actor; 26756 }, 26757 26758 /** 26759 Initialize the root object representing the AU 26760 26761 Called by the `Cmi5` constructor when provided a launch URL. 26762 26763 @method setActivity 26764 @param {String|TinCan.Activity} activity Pre-constructed Activity or string id used to construct Activity 26765 @throws {Error} <ul><li>Invalid activity, null id</li><li>Invalid activity, empty string id</li></ul> 26766 */ 26767 setActivity: function (activity) { 26768 if (! (activity instanceof TinCan.Activity)) { 26769 activity = new TinCan.Activity( 26770 { 26771 id: activity 26772 } 26773 ); 26774 } 26775 26776 if (activity.id === null) { 26777 throw new Error("Invalid activity: id is null"); 26778 } 26779 else if (activity.id === "") { 26780 throw new Error("Invalid activity: id is empty"); 26781 } 26782 26783 this._activity = activity; 26784 }, 26785 26786 /** 26787 Retrieve the `TinCan.Activity` that is the root object representing the AU 26788 26789 @method getActivity 26790 @return {TinCan.Activity} Root Activity 26791 */ 26792 getActivity: function () { 26793 return this._activity; 26794 }, 26795 26796 /** 26797 Set the registration value 26798 26799 Called by the `Cmi5` constructor when provided a launch URL. 26800 26801 @method setRegistration 26802 @param {String} registration UUID representing the registration 26803 @throws {Error} <ul><li>Invalid registration, null</li><li>Invalid registration, empty string</li></ul> 26804 */ 26805 setRegistration: function (registration) { 26806 if (registration === null) { 26807 throw new Error("Invalid registration: null"); 26808 } 26809 else if (registration === "") { 26810 throw new Error("Invalid registration: empty"); 26811 } 26812 26813 this._registration = registration; 26814 }, 26815 26816 /** 26817 Retrieve the registration associated with the session 26818 26819 @method getRegistration 26820 @return {String} Registration 26821 */ 26822 getRegistration: function () { 26823 return this._registration; 26824 }, 26825 26826 /** 26827 Validate a Score object's properties 26828 26829 @method validateScore 26830 @param {TinCan.Score|Object} score Score object to validate 26831 @throws {Error} <ul><li>Null or missing score argument</li><li>Non-integer min or max value (when provided)</li><li>Non-number, negative, or greater than 1 scaled value (when provided)</li><li>Non-integer, missing or invalid min/max, raw value (when provided)</li></ul> 26832 @return {Boolean} true for passing, otherwise exception is thrown 26833 */ 26834 validateScore: function (score) { 26835 if (typeof score === "undefined" || score === null) { 26836 throw new Error("cannot validate score (score not provided): " + score); 26837 } 26838 26839 if (typeof score.min !== "undefined") { 26840 if (! isInteger(score.min)) {
26841 throw new Error("score.min is not an integer"); 26842 } 26843 } 26844 if (typeof score.max !== "undefined") { 26845 if (! isInteger(score.max)) { 26846 throw new Error("score.max is not an integer"); 26847 } 26848 } 26849 26850 if (typeof score.scaled !== "undefined") { 26851 if (! /^(\-|\+)?[01]+(\.[0-9]+)?$/.test(score.scaled)) { 26852 throw new Error("scaled score not a recognized number: " + score.scaled); 26853 } 26854 26855 if (score.scaled < 0) { 26856 throw new Error("scaled score must be greater than or equal to 0"); 26857 } 26858 if (score.scaled > 1) { 26859 throw new Error("scaled score must be less than or equal to 1"); 26860 } 26861 } 26862 26863 if (typeof score.raw !== "undefined") { 26864 if (! isInteger(score.raw)) { 26865 throw new Error("score.raw is not an integer"); 26866 } 26867 if (typeof score.min === "undefined") { 26868 throw new Error("minimum score must be provided when including a raw score"); 26869 } 26870 if (typeof score.max === "undefined") { 26871 throw new Error("maximum score must be provided when including a raw score"); 26872 } 26873 if (score.raw < score.min) { 26874 throw new Error("raw score must be greater than or equal to minimum score"); 26875 } 26876 if (score.raw > score.max) { 26877 throw new Error("raw score must be less than or equal to maximum score"); 26878 } 26879 } 26880 26881 return true; 26882 }, 26883 26884 /** 26885 Prepare a cmi5 "allowed" statement including the actor, verb, object, and context 26886 26887 Used to construct a cmi5 "allowed" statement with all relevant information that can then be optionally added to prior to sending. 26888 26889 @method prepareStatement 26890 @param {String} verbId Verb identifier combined with other cmi5 pre-determined information (must be IRI) 26891 @return {TinCan.Statement} Statement 26892 */ 26893 prepareStatement: function (verbId) { 26894 // 26895 // the specification allows for statements that are 'cmi5 allowed' 26896 // as oppposed to 'cmi5 defined' to be sent by the AU, so give the 26897 // AU the ability to get a prepared statement with a populated context 26898 // based on the template but without the category having been added 26899 // 26900 var stCfg = { 26901 actor: this._actor, 26902 verb: { 26903 id: verbId 26904 }, 26905 target: this._activity, 26906 context: this._prepareContext() 26907 }, 26908 progress = this.getProgress(); 26909 26910 if (typeof verbDisplay[verbId] !== "undefined") { 26911 stCfg.verb.display = verbDisplay[verbId]; 26912 } 26913 26914 if (verbId !== VERB_COMPLETED_ID && progress !== null) { 26915 stCfg.result = { 26916 extensions: { 26917 "https://w3id.org/xapi/cmi5/result/extensions/progress": progress 26918 } 26919 }; 26920 } 26921 26922 return new TinCan.Statement(stCfg); 26923 }, 26924 26925 /** 26926 Store provided statement in the configured LRS 26927 26928 @method sendStatement 26929 @param {TinCan.Statement} st Statement to be stored 26930 @param {Function} [callback] Function to run on success/failure of statement save 26931 */ 26932 sendStatement: function (st, callback) { 26933 var cbWrapper, 26934 result; 26935 26936 if (callback) { 26937 cbWrapper = function (err, result) { 26938 if (err !== null) { 26939 callback(new Error(err), result); 26940 return; 26941 } 26942 26943 callback(err, result, st); 26944 }; 26945 } 26946
26947 result = this._lrs.saveStatement( 26948 st, 26949 { 26950 callback: cbWrapper 26951 } 26952 ); 26953 if (! callback) { 26954 return { 26955 response: result, 26956 statement: st 26957 }; 26958 } 26959 }, 26960 26961 /* 26962 * The ...Statement methods are provided for users that want to implement 26963 * a queueing like mechansim or something similar where they are expected 26964 * to abide by the AU restrictions on what statements can be sent, etc. on 26965 * their own. 26966 * 26967 * (Such as in SCORM Driver which was the impetus for adding them.) 26968 */ 26969 26970 /** 26971 Advanced Usage: retrieve prepared "initialized" statement 26972 26973 Statement methods are provided for users that want to implement 26974 a queueing like mechansim or something similar where they are expected 26975 to abide by the AU restrictions on what statements can be sent, etc. on 26976 their own. 26977 26978 @method initializedStatement 26979 @return {TinCan.Statement} Initialized statement 26980 */ 26981 initializedStatement: function () { 26982 this.log("initializedStatement"); 26983 return this._prepareStatement(VERB_INITIALIZED_ID); 26984 }, 26985 26986 /** 26987 Advanced Usage: retrieve prepared "terminated" statement 26988 26989 Statement methods are provided for users that want to implement 26990 a queueing like mechansim or something similar where they are expected 26991 to abide by the AU restrictions on what statements can be sent, etc. on 26992 their own. 26993 26994 @method terminatedStatement 26995 @return {TinCan.Statement} Terminated statement 26996 */ 26997 terminatedStatement: function () { 26998 this.log("terminatedStatement"); 26999 var st = this._prepareStatement(VERB_TERMINATED_ID); 27000 27001 st.result = st.result || new TinCan.Result(); 27002 st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration()); 27003 27004 return st; 27005 }, 27006 27007 /** 27008 Advanced Usage: retrieve prepared "passed" statement 27009 27010 Statement methods are provided for users that want to implement 27011 a queueing like mechansim or something similar where they are expected 27012 to abide by the AU restrictions on what statements can be sent, etc. on 27013 their own. 27014 27015 @method passedStatement 27016 @param {Object} [score] Object to be used as the score, must meet masteryScore requirements, etc. 27017 @return {TinCan.Statement} Passed statement 27018 */ 27019 passedStatement: function (score) { 27020 this.log("passedStatement"); 27021 var st = this._prepareStatement(VERB_PASSED_ID), 27022 masteryScore; 27023 27024 st.result = st.result || new TinCan.Result(); 27025 st.result.success = true; 27026 st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration()); 27027 27028 if (score) { 27029 try { 27030 this.validateScore(score); 27031 } 27032 catch (ex) { 27033 throw new Error("Invalid score - " + ex); 27034 } 27035 27036 masteryScore = this.getMasteryScore(); 27037 if (masteryScore !== null && typeof score.scaled !== "undefined") { 27038 if (score.scaled < masteryScore) { 27039 throw new Error("Invalid score - scaled score does not meet or exceed mastery score (" + score.scaled + " < " + masteryScore + ")"); 27040 } 27041 27042 st.context.extensions = st.context.extensions || {}; 27043 st.context.extensions[EXTENSION_MASTERY_SCORE] = masteryScore; 27044 } 27045 27046 st.result.score = new TinCan.Score(score); 27047 } 27048 27049 st.context.contextActivities.category.push(CATEGORY_ACTIVITY_MOVEON); 27050 27051 return st; 27052 }, 27053 27054 /** 27055 Advanced Usage: retrieve prepared "failed" statement 27056 27057 Statement methods are provided for users that want to implement 27058 a queueing like mechansim or something similar where they are expected 27059 to abide by the AU restrictions on what statements can be sent, etc. on 27060 their own. 27061 27062 @method failedStatement 27063 @param {Object} [score] Object to be used as the score, must meet masteryScore requirements, etc. 27064 @return {TinCan.Statement} Failed statement 27065 */ 27066 failedStatement: function (score) { 27067 this.log("failedStatement");
27068 var st = this._prepareStatement(VERB_FAILED_ID), 27069 masteryScore; 27070 27071 st.result = st.result || new TinCan.Result(); 27072 st.result.success = false; 27073 st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration()); 27074 27075 if (score) { 27076 try { 27077 this.validateScore(score); 27078 } 27079 catch (ex) { 27080 throw new Error("Invalid score - " + ex); 27081 } 27082 27083 masteryScore = this.getMasteryScore(); 27084 if (masteryScore !== null && typeof score.scaled !== "undefined") { 27085 if (score.scaled >= masteryScore) { 27086 throw new Error("Invalid score - scaled score exceeds mastery score (" + score.scaled + " >= " + masteryScore + ")"); 27087 } 27088 27089 st.context.extensions = st.context.extensions || {}; 27090 st.context.extensions[EXTENSION_MASTERY_SCORE] = masteryScore; 27091 } 27092 27093 st.result.score = new TinCan.Score(score); 27094 } 27095 27096 st.context.contextActivities.category.push(CATEGORY_ACTIVITY_MOVEON); 27097 27098 return st; 27099 }, 27100 27101 /** 27102 Advanced Usage: retrieve prepared "completed" statement 27103 27104 Statement methods are provided for users that want to implement 27105 a queueing like mechansim or something similar where they are expected 27106 to abide by the AU restrictions on what statements can be sent, etc. on 27107 their own. 27108 27109 @method completedStatement 27110 @return {TinCan.Statement} Completed statement 27111 */ 27112 completedStatement: function () { 27113 this.log("completedStatement"); 27114 var st = this._prepareStatement(VERB_COMPLETED_ID); 27115 27116 st.result = st.result || new TinCan.Result(); 27117 st.result.completion = true; 27118 st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(this.getDuration()); 27119 27120 st.context.contextActivities.category.push(CATEGORY_ACTIVITY_MOVEON); 27121 27122 return st; 27123 }, 27124 27125 /** 27126 @method _prepareContext 27127 @private 27128 */ 27129 _prepareContext: function () { 27130 // 27131 // deserializing a string version of the template is slower 27132 // but gives us cheap cloning capability so that we don't 27133 // alter the template itself 27134 // 27135 var context = JSON.parse(this._contextTemplate); 27136 27137 context.registration = this._registration; 27138 27139 if (this._includeSourceActivity) { 27140 context.contextActivities = context.contextActivities || new TinCan.ContextActivities(); 27141 context.contextActivities.other = context.contextActivities.other || []; 27142 context.contextActivities.other.push(OTHER_ACTIVITY_CMI5JS); 27143 } 27144 27145 return context; 27146 }, 27147 27148 /** 27149 @method _prepareStatement 27150 @private 27151 */ 27152 _prepareStatement: function (verbId) { 27153 // 27154 // statements sent by this lib are "cmi5 defined" statements meaning 27155 // they have the context category value added 27156 // 27157 var st = this.prepareStatement(verbId); 27158 27159 st.context.contextActivities = st.context.contextActivities || new TinCan.ContextActivities(); 27160 st.context.contextActivities.category = st.context.contextActivities.category || []; 27161 st.context.contextActivities.category.push(CATEGORY_ACTIVITY_CMI5); 27162 27163 return st; 27164 } 27165 }; 27166 27167 /** 27168 Turn on debug logging 27169 27170 @method enableDebug 27171 @static 27172 @param {Boolean} [includeTinCan] Whether to enable debug logging from TinCanJS 27173 */ 27174 Cmi5.enableDebug = function (includeTinCan) { 27175 Cmi5.DEBUG = true; 27176 27177 if (includeTinCan) { 27178 TinCan.enableDebug(); 27179 } 27180 }; 27181 27182 /** 27183 Turn off debug logging 27184 27185 @method disableDebug 27186 @static 27187 @param {Boolean} [includeTinCan] Whether to disable debug logging from TinCanJS 27188 */ 27189 Cmi5.disableDebug = function (includeTinCan) { 27190 Cmi5.DEBUG = false;
27191 27192 if (includeTinCan) { 27193 TinCan.disableDebug(); 27194 } 27195 }; 27196 27197 // 27198 // Setup request callback 27199 // 27200 requestComplete = function (xhr, cfg, control, callback) { 27201 this.log("requestComplete: " + control.finished + ", xhr.status: " + xhr.status); 27202 var requestCompleteResult, 27203 notFoundOk, 27204 httpStatus; 27205 27206 // 27207 // XDomainRequest doesn't give us a way to get the status, 27208 // so allow passing in a forged one 27209 // 27210 if (typeof xhr.status === "undefined") { 27211 httpStatus = control.fakeStatus; 27212 } 27213 else { 27214 // 27215 // older versions of IE don't properly handle 204 status codes 27216 // so correct when receiving a 1223 to be 204 locally 27217 // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request 27218 // 27219 httpStatus = (xhr.status === 1223) ? 204 : xhr.status; 27220 } 27221 27222 if (! control.finished) { 27223 // may be in sync or async mode, using XMLHttpRequest or IE XDomainRequest, onreadystatechange or 27224 // onload or both might fire depending upon browser, just covering all bases with event hooks and 27225 // using 'finished' flag to avoid triggering events multiple times 27226 control.finished = true; 27227 27228 notFoundOk = (cfg.ignore404 && httpStatus === 404); 27229 if ((httpStatus >= 200 && httpStatus < 400) || notFoundOk) { 27230 if (callback) { 27231 callback(null, xhr); 27232 } 27233 else { 27234 requestCompleteResult = { 27235 err: null, 27236 xhr: xhr 27237 }; 27238 return requestCompleteResult; 27239 } 27240 } 27241 else { 27242 requestCompleteResult = { 27243 err: httpStatus, 27244 xhr: xhr 27245 }; 27246 if (httpStatus === 0) { 27247 this.log("[warning] There was a problem communicating with the server. Aborted, offline, or invalid CORS endpoint (" + httpStatus + ")"); 27248 } 27249 else { 27250 this.log("[warning] There was a problem communicating with the server. (" + httpStatus + " | " + xhr.responseText + ")"); 27251 } 27252 if (callback) { 27253 callback(httpStatus, xhr); 27254 } 27255 return requestCompleteResult; 27256 } 27257 } 27258 else { 27259 return requestCompleteResult; 27260 } 27261 }; 27262 27263 // 27264 // one of the two of these is stuffed into the Cmi5 instance where a 27265 // request is needed which is fetch at the moment 27266 // 27267 nativeRequest = function (fullUrl, cfg, callback) { 27268 this.log("sendRequest using XMLHttpRequest"); 27269 var self = this, 27270 xhr, 27271 prop, 27272 pairs = [], 27273 data, 27274 control = { 27275 finished: false, 27276 fakeStatus: null 27277 }, 27278 async, 27279 fullRequest = fullUrl; 27280 27281 this.log("sendRequest using XMLHttpRequest - async: " + async); 27282 27283 cfg = cfg || {}; 27284 cfg.params = cfg.params || {}; 27285 cfg.headers = cfg.headers || {}; 27286 27287 async = typeof callback !== "undefined"; 27288 27289 for (prop in cfg.params) { 27290 if (cfg.params.hasOwnProperty(prop)) { 27291 pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop])); 27292 } 27293 } 27294 if (pairs.length > 0) { 27295 fullRequest += "?" + pairs.join("&"); 27296 } 27297 27298 xhr = new XMLHttpRequest(); 27299 27300 xhr.open(cfg.method, fullRequest, async); 27301 for (prop in cfg.headers) { 27302 if (cfg.headers.hasOwnProperty(prop)) { 27303 xhr.setRequestHeader(prop, cfg.headers[prop]); 27304 } 27305 } 27306 27307 if (typeof cfg.data !== "undefined") { 27308 cfg.data += ""; 27309 } 27310 data = cfg.data; 27311 27312 if (async) { 27313 xhr.onreadystatechange = function () { 27314 self.log("xhr.onreadystatechange - xhr.readyState: " + xhr.readyState); 27315 if (xhr.readyState === 4) { 27316 requestComplete.call(self, xhr, cfg, control, callback); 27317 } 27318 }; 27319 } 27320 27321 // 27322 // research indicates that IE is known to just throw exceptions 27323 // on .send and it seems everyone pretty much just ignores them 27324 // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549 27325 // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97) 27326 // 27327 try { 27328 xhr.send(data); 27329 } 27330 catch (ex) { 27331 this.log("sendRequest caught send exception: " + ex); 27332 } 27333 27334 if (async) { 27335 return; 27336 } 27337 27338 return requestComplete.call(this, xhr, cfg, control); 27339 }; 27340 xdrRequest = function (fullUrl, cfg, callback) { 27341 this.log("sendRequest using XDomainRequest"); 27342 var self = this, 27343 xhr, 27344 pairs = [], 27345 data, 27346 prop, 27347 until, 27348 control = { 27349 finished: false, 27350 fakeStatus: null 27351 }, 27352 err; 27353 27354 cfg = cfg || {}; 27355 cfg.params = cfg.params || {}; 27356 cfg.headers = cfg.headers || {}; 27357 27358 if (typeof cfg.headers["Content-Type"] !== "undefined" && cfg.headers["Content-Type"] !== "application/json") { 27359 err = new Error("Unsupported content type for IE Mode request"); 27360 if (callback) { 27361 callback(err, null); 27362 return null; 27363 } 27364 return { 27365 err: err, 27366 xhr: null 27367 }; 27368 } 27369 27370 for (prop in cfg.params) { 27371 if (cfg.params.hasOwnProperty(prop)) { 27372 pairs.push(prop + "=" + encodeURIComponent(cfg.params[prop])); 27373 } 27374 } 27375 27376 if (pairs.length > 0) { 27377 fullUrl += "?" + pairs.join("&"); 27378 } 27379 27380 xhr = new XDomainRequest(); 27381 xhr.open("POST", fullUrl); 27382 27383 if (! callback) { 27384 xhr.onload = function () { 27385 control.fakeStatus = 200; 27386 }; 27387 xhr.onerror = function () { 27388 control.fakeStatus = 400; 27389 }; 27390 xhr.ontimeout = function () { 27391 control.fakeStatus = 0; 27392 }; 27393 } 27394 else { 27395 xhr.onload = function () { 27396 control.fakeStatus = 200; 27397 requestComplete.call(self, xhr, cfg, control, callback); 27398 }; 27399 xhr.onerror = function () { 27400 control.fakeStatus = 400; 27401 requestComplete.call(self, xhr, cfg, control, callback); 27402 }; 27403 xhr.ontimeout = function () { 27404 control.fakeStatus = 0; 27405 requestComplete.call(self, xhr, cfg, control, callback); 27406 }; 27407 } 27408 27409 // 27410 // IE likes to randomly abort requests when some handlers 27411 // aren't defined, so define them with no-ops, see: 27412 // 27413 // http://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/ 27414 // http://social.msdn.microsoft.com/Forums/ie/en-US/30ef3add-767c-4436-b8a9-f1ca19b4812e/ie9-rtm-xdomainrequest-issued-requests-may-abort-if-all-event-handlers-not-specified 27415 // 27416 xhr.onprogress = function () {}; 27417 xhr.timeout = 0; 27418 27419 // 27420 // research indicates that IE is known to just throw exceptions 27421 // on .send and it seems everyone pretty much just ignores them 27422 // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549 27423 // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97) 27424 // 27425 try { 27426 xhr.send(data); 27427 } 27428 catch (ex) { 27429 this.log("sendRequest caught send exception: " + ex); 27430 } 27431 27432 if (! callback) { 27433 // synchronous call in IE, with no synchronous mode available 27434 until = 10000 + Date.now(); 27435 this.log("sendRequest - until: " + until + ", finished: " + control.finished); 27436 27437 while (Date.now() < until && control.fakeStatus === null) { 27438 __delay(); 27439 } 27440 return requestComplete.call(self, xhr, cfg, control); 27441 } 27442 27443 return; 27444 }; 27445 27446 /** 27447 Non-environment safe method used to create a delay to give impression
27448 of synchronous response (for IE, shocker) 27449 27450 @method __delay 27451 @private 27452 */ 27453 __delay = function () { 27454 // 27455 // use a synchronous request to the current location to allow the browser 27456 // to yield to the asynchronous request's events but still block in the 27457 // outer loop to make it seem synchronous to the end user 27458 // 27459 // removing this made the while loop too tight to allow the asynchronous 27460 // events through to get handled so that the response was correctly handled 27461 // 27462 var xhr = new XMLHttpRequest(), 27463 url = window.location + "?forcenocache=" + TinCan.Utils.getUUID(); 27464 27465 xhr.open("GET", url, false); 27466 xhr.send(null); 27467 }; 27468}()); 27469/* declare globals that are set via Desktop token replacement for configuration */ 27470var TC_COURSE_ID = "__TC_COURSE_ID__"; 27471var TC_COURSE_NAME = "__TC_COURSE_NAME__"; 27472var TC_COURSE_DESC = "__TC_COURSE_DESC__"; 27473var TC_RECORD_STORES = "__TC_RECORD_STORES__"; 27474 27475/* declare other globals we'll use */ 27476var TCAPI_STATUS = "", 27477 TCAPI_STATUS_CHANGED = false, 27478 TCAPI_SCORE = {}, 27479 TCAPI_COMPLETION_STATUS = "", 27480 TCAPI_SATISFACTION_STATUS = null, 27481 TCAPI_UPDATES_PENDING = false, 27482 TCAPI_IN_PROGRESS = false, 27483 27484 TCAPI_NO_ERROR = "", 27485 TCAPI_VERB_COMPLETED = "completed", 27486 TCAPI_VERB_EXPERIENCED = "experienced", 27487 TCAPI_VERB_ATTEMPTED = "attempted", 27488 TCAPI_VERB_ANSWERED = "answered", 27489 TCAPI_VERB_PASSED = "passed", 27490 TCAPI_VERB_FAILED = "failed", 27491 TCAPI_INIT_VERB = TCAPI_VERB_ATTEMPTED, 27492 27493 TCAPI_INTERACTION = "http://adlnet.gov/expapi/activities/cmi.interaction", 27494 TCAPI_INTERACTION_TYPE_TRUE_FALSE = "true-false", 27495 TCAPI_INTERACTION_TYPE_CHOICE = "choice", 27496 TCAPI_INTERACTION_TYPE_FILL_IN = "fill-in", 27497 TCAPI_INTERACTION_TYPE_MATCHING = "matching", 27498 TCAPI_INTERACTION_TYPE_PERFORMANCE = "performance", 27499 TCAPI_INTERACTION_TYPE_SEQUENCING = "sequencing", 27500 TCAPI_INTERACTION_TYPE_LIKERT = "likert", 27501 TCAPI_INTERACTION_TYPE_NUMERIC = "numeric", 27502 27503 TCAPI_STATE_BOOKMARK = "bookmark", 27504 TCAPI_STATE_TOTAL_TIME = "cumulative_time", 27505 TCAPI_STATE_SUSPEND_DATA = "suspend_data", 27506 27507 // these are non-HTTP status code errors from TinCanJS 27508 TCAPI_CONN_ERROR = 998, 27509 TCAPI_DEP_ERROR = 999, 27510 27511 TCAPI_ERROR_INVALID_PREFERENCE = 0, 27512 TCAPI_ERROR_INVALID_TIMESPAN = 1, 27513 27514 TCAPI_FUNC_NOOP = function () { /* no-op */ }, 27515 27516 intTCAPIError, 27517 strTCAPIErrorString, 27518 strTCAPIErrorDiagnostic; 27519 27520/* Declare global used for interacting with the LRS */ 27521var tincan; 27522 27523/* Declare global used for our own local cache of items */ 27524var tcapi_cache; 27525 27526function _TCAPI_HandleRequestErrorResult (result, mesg) { 27527 var errCode, 27528 errDiag, 27529 errMesg = mesg; 27530 27531 if (result.err !== null) { 27532 // 27533 // in the case of errors result.err either contains an Error object (theoretically) 27534 // or the HTTP status code if the request actually made it to the LRS, to distinguish 27535 // we can check it for all digits (which is safer than checking for an Error object, 27536 // at least in this version of TinCanJS), which then allows us to look at the response 27537 // text which is generally a more helpful message than the status code 27538 // 27539 if (/^\d+$/.test(result.err)) { 27540 if (result.err === 0) { 27541 // 0 status codes represent network errors in the request but 0 can't be used 27542 // as the error code because Driver treats 0 as the no error code 27543 errCode = TCAPI_CONN_ERROR; 27544 errDiag = "Aborted, offline, or invalid CORS endpoint"; 27545 } 27546 else { 27547 errCode = result.err; 27548 errDiag = result.xhr.responseText; 27549 } 27550 } 27551 else { 27552 errCode = TCAPI_DEP_ERROR; 27553 errDiag = result.err; 27554 } 27555 27556 TCAPI_SetErrorInfoManually(errCode, errMesg, errDiag); 27557 } 27558 else { 27559 WriteToDebug("Unexpected result object, `result.err` null?"); 27560 } 27561} 27562 27563// this is just here to make it possible to mock the launch URL 27564// for testing purposes 27565// 27566function TCAPI_GetLaunchUrl () { 27567 WriteToDebug("In TCAPI_GetLaunchUrl"); 27568 return location.href; 27569} 27570 27571function TCAPI_Initialize () { 27572 WriteToDebug("In TCAPI_Initialize"); 27573 27574 tcapi_cache = { 27575 totalPrevDuration: null, 27576 statementQueue: [] 27577 }; 27578 27579 TinCan.prototype.log = 27580 TinCan.LRS.prototype.log = function (msg, src) { 27581 src = src || this.LOG_SRC || "TinCan"; 27582 WriteToDebug("TinCan." + src + ": " + msg); 27583 }; 27584 27585 try { 27586 tincan = new TinCan ( 27587 { 27588 url: TCAPI_GetLaunchUrl(), 27589 recordStores: TC_RECORD_STORES, 27590 activity: { 27591 id: TC_COURSE_ID, 27592 definition: { 27593 name: TC_COURSE_NAME, 27594 description: TC_COURSE_DESC 27595 } 27596 } 27597 } 27598 ); 27599 } catch (ex) { 27600 WriteToDebug("TCAPI_Initialize - TinCan construction failed: " + JSON.stringify(ex)); 27601 return; 27602 } 27603 27604 if (tincan.recordStores.length === 0) { 27605 WriteToDebug("TCAPI_Initialize - resulted in no LRS: DATA CANNOT BE STORED"); 27606 return; 27607 } 27608 27609 //See if we've saved the cumulative time previously 27610 WriteToDebug("TCAPI_Initialize - fetching cumulative time from state: " + TCAPI_STATE_TOTAL_TIME); 27611 tincan.getState( 27612 TCAPI_STATE_TOTAL_TIME, 27613 { 27614 callback: function (err, state) { 27615 WriteToDebug("TCAPI_Initialize - getState callback"); 27616 var contents; 27617 if (err !== null) { 27618 WriteToDebug("TCAPI_Initialize - getState callback: " + err.responseText + " (" + err.status + ")"); 27619 return; 27620 } 27621 27622 WriteToDebug("TCAPI_Initialize - getState callback - state: " + state); 27623 if (state !== null && state.contents !== null && state.contents.match(/^\d+$/)) { 27624 tcapi_cache.totalPrevDuration = Number(state.contents); 27625 } 27626 else { 27627 tcapi_cache.totalPrevDuration = 0; 27628 } 27629 } 27630 } 27631 ); 27632 27633 TCAPI_STATUS = TCAPI_INIT_VERB; 27634 TCAPI_IN_PROGRESS = true; 27635 27636 // Record an initial launch statement 27637 WriteToDebug("TCAPI_Initialize - record initial launch statement");
27638 tincan.sendStatement( 27639 { 27640 verb: TCAPI_INIT_VERB, 27641 inProgress: TCAPI_IN_PROGRESS 27642 }, 27643 function (results, statement) { 27644 if (results[0].err !== null) { 27645 WriteToDebug("TCAPI_Initialize - record initial launch statement - err: " + results[0].err.responseText + " (" + results[0].err.status + ")"); 27646 return; 27647 } 27648 27649 WriteToDebug("TCAPI_Initialize - record initial launch statement success: " + statement.id); 27650 } 27651 ); 27652 27653 InitializeExecuted(true, ""); 27654 27655 return true; 27656} 27657 27658// 27659// setting state in IE7 was throwing an "Operation Aborted" message for no apparent 27660// reason after the state was already set, so just wrap it with a try/catch 27661// 27662function _TCAPI_SetStateSafe (key, value) { 27663 var result; 27664 27665 try { 27666 result = tincan.setState(key, value); 27667 } 27668 catch (ex) { 27669 // 27670 // this exception is assumed to be the one from IE7, errors should come back 27671 // from TinCanJS via the result object and in particular the `err` property 27672 // 27673 WriteToDebug("In _TCAPI_SetStateSafe - caught exception from setState: " + ex.message); 27674 } 27675 27676 if (result && result.err !== null) { 27677 _TCAPI_HandleRequestErrorResult(result, "Failed to save state (" + key + ")"); 27678 27679 return false; 27680 } 27681 27682 return true; 27683} 27684 27685function TCAPI_GetStudentID () { 27686 WriteToDebug("In TCAPI_GetStudentID"); 27687 27688 if (tincan.actor.mbox !== null) { 27689 return tincan.actor.mbox; 27690 } 27691 if (tincan.actor.mbox_sha1sum !== null) { 27692 return tincan.actor.mbox_sha1sum; 27693 } 27694 if (tincan.actor.openid !== null) { 27695 return tincan.actor.openid; 27696 } 27697 if (tincan.actor.account !== null) { 27698 // 27699 // it was discussed about whether returning just the account name 27700 // was a proper approach and decided because SD is really only 27701 // supported by LMS launch that the account would normally only 27702 // be used with the launching system's account and that in that 27703 // case the homePage isn't strictly necessary for ID determination 27704 // 27705 // more advanced use cases should avoid using this method if they 27706 // require the homePage as well 27707 // 27708 return tincan.actor.account.name; 27709 } 27710 return null; 27711} 27712 27713function TCAPI_GetStudentName () { 27714 WriteToDebug("In TCAPI_GetStudentName"); 27715 27716 return tincan.actor !== null ? tincan.actor.toString() : ""; 27717} 27718 27719function TCAPI_GetBookmark () { 27720 WriteToDebug("In TCAPI_GetBookmark"); 27721 var bookmark = "", 27722 getStateResult = tincan.getState(TCAPI_STATE_BOOKMARK); 27723 27724 if (getStateResult.state !== null) { 27725 bookmark = getStateResult.state.contents; 27726 } 27727 27728 return bookmark; 27729} 27730 27731function TCAPI_SetBookmark (value, name) { 27732 WriteToDebug("In TCAPI_SetBookmark - value: " + value + ", name: " + name); 27733 var stateResult; 27734 27735 TCAPI_ClearErrorInfo(); 27736 27737 stateResult = _TCAPI_SetStateSafe(TCAPI_STATE_BOOKMARK, value); 27738 if (! stateResult) { 27739 return false; 27740 } 27741 27742 WriteToDebug("In TCAPI_SetBookmark - sending statement: " + value);
27743 tincan.sendStatement( 27744 { 27745 verb: TCAPI_VERB_EXPERIENCED, 27746 object: { 27747 id: tincan.activity.id + "/" + value, 27748 definition: { 27749 name: { 27750 "en-US": ((name !== undefined && name !== "") ? name : value) 27751 } 27752 } 27753 }, 27754 context: { 27755 contextActivities: { 27756 parent: tincan.activity 27757 } 27758 } 27759 }, 27760 function (results, statement) { 27761 if (results[0].err !== null) { 27762 WriteToDebug("TCAPI_SetBookmark - sending statement: " + value + " - err: " + results[0].err.responseText + " (" + results[0].err.status + ")"); 27763 return; 27764 } 27765 27766 WriteToDebug("TCAPI_SetBookmark - sending statement success: " + value + " - id: " + statement.id); 27767 } 27768 ); 27769 27770 return true; 27771} 27772 27773function TCAPI_GetDataChunk () { 27774 WriteToDebug("In TCAPI_GetDataChunk"); 27775 var data = "", 27776 getStateResult = tincan.getState(TCAPI_STATE_SUSPEND_DATA); 27777 27778 if (getStateResult.state !== null) { 27779 data = getStateResult.state.contents; 27780 } 27781 27782 return data; 27783} 27784 27785function TCAPI_SetDataChunk (value) { 27786 WriteToDebug("In TCAPI_SetDataChunk"); 27787 27788 TCAPI_ClearErrorInfo(); 27789 27790 return _TCAPI_SetStateSafe(TCAPI_STATE_SUSPEND_DATA, value); 27791} 27792 27793function TCAPI_CommitData () { 27794 WriteToDebug("In TCAPI_CommitData - TCAPI_STATUS:" + TCAPI_STATUS); 27795 WriteToDebug("In TCAPI_CommitData - TCAPI_UPDATES_PENDING: " + TCAPI_UPDATES_PENDING); 27796 27797 var stmt, 27798 requestResults, 27799 result, 27800 statementAdded = false; 27801 27802 TCAPI_ClearErrorInfo(); 27803 27804 if (TCAPI_UPDATES_PENDING) { 27805 stmt = { 27806 verb: TCAPI_STATUS, 27807 inProgress: TCAPI_IN_PROGRESS, 27808 result: {} 27809 }; 27810 27811 if (TCAPI_COMPLETION_STATUS !== '' || ! TCAPI_IN_PROGRESS) { 27812 // report the duration in the ISO8601 format that SCORM provided 27813 stmt.result.duration = ConvertMilliSecondsIntoSCORM2004Time( 27814 GetSessionAccumulatedTime() + TCAPI_GetPreviouslyAccumulatedTime() 27815 ); 27816 } 27817 27818 if (TCAPI_COMPLETION_STATUS !== '') { 27819 stmt.result.completion = true; 27820 } 27821 27822 if (TCAPI_SATISFACTION_STATUS !== null) { 27823 stmt.result.success = TCAPI_SATISFACTION_STATUS; 27824 } 27825 27826 if (typeof TCAPI_SCORE.raw !== "undefined") { 27827 stmt.result.score = TCAPI_SCORE; 27828 } 27829 27830 tcapi_cache.statementQueue.push(stmt); 27831 27832 statementAdded = true; 27833 27834 // reset the pending updates flag 27835 TCAPI_UPDATES_PENDING = false; 27836 } 27837 27838 if (tcapi_cache.statementQueue.length > 0) { 27839 requestResults = tincan.sendStatements( 27840 tcapi_cache.statementQueue 27841 ); 27842 27843 if (typeof requestResults.results !== "undefined" && requestResults.results.length > 0) { 27844 // 27845 // we can only count on caring about the first result because 27846 // this supports multiple LRS send, but the first is the one 27847 // the package was configured with at launch 27848 // 27849 result = requestResults.results[0]; 27850 27851 if (result.err !== null) { 27852 _TCAPI_HandleRequestErrorResult(result, "Failed to commit data: statements"); 27853 27854 // 27855 // if there were updates above, then we added a statement to the queue, but since it 27856 // couldn't be sent we need to remove it from the queue and restore the fact that there 27857 // are updates pending, which also leaves the other statements that might have been queued
27858 // (via interactions for instance) in place for the next commit 27859 // 27860 if (statementAdded) { 27861 tcapi_cache.statementQueue.pop(); 27862 27863 TCAPI_UPDATES_PENDING = true; 27864 } 27865 27866 return false; 27867 } 27868 } 27869 27870 tcapi_cache.statementQueue = []; 27871 } 27872 27873 return true; 27874} 27875 27876function TCAPI_Finish (exitType, statusWasSet) { 27877 WriteToDebug("In TCAPI_Finish - exitType: " + exitType); 27878 27879 TCAPI_ClearErrorInfo(); 27880 27881 if (exitType === EXIT_TYPE_SUSPEND) { 27882 // 27883 // _TCAPI_SetStateSafe sets the error condition and can return `false` when it fails 27884 // but we'd rather have a chance to do CommitData than to return based on a failure 27885 // to capture the accumulated time. The error will be cleared immediately, but at least 27886 // the debug logs will have been captured and if the State request failed there is 27887 // a likely chance that so will the other(s) 27888 // 27889 _TCAPI_SetStateSafe( 27890 TCAPI_STATE_TOTAL_TIME, 27891 TCAPI_GetPreviouslyAccumulatedTime() + GetSessionAccumulatedTime() 27892 ); 27893 TCAPI_SetSuspended(); 27894 } 27895 27896 TCAPI_CommitData(); 27897 27898 return true; 27899} 27900 27901/***************************** 27902* Preferences 27903*****************************/ 27904 27905function TCAPI_GetAudioPlayPreference () { 27906 WriteToDebug("In TCAPI_GetAudioPlayPreference"); 27907 var intTempPreference = 0, 27908 getStateResult; 27909 27910 TCAPI_ClearErrorInfo(); 27911 27912 getStateResult = tincan.getState("cmi.student_preference.audio"); 27913 if (getStateResult.state !== null) { 27914 intTempPreference = getStateResult.state.contents; 27915 } 27916 27917 intTempPreference = parseInt(intTempPreference, 10); 27918 WriteToDebug("intTempPreference=" + intTempPreference); 27919 27920 if (intTempPreference > 0) { 27921 WriteToDebug("Returning On"); 27922 return PREFERENCE_ON; 27923 } 27924 else if (intTempPreference == 0) { 27925 WriteToDebug("Returning Default"); 27926 return PREFERENCE_DEFAULT; 27927 } 27928 else if (intTempPreference < 0) { 27929 WriteToDebug("returning Off"); 27930 return PREFERENCE_OFF; 27931 } 27932 27933 WriteToDebug("Error: Invalid preference"); 27934 TCAPI_SetErrorInfoManually( 27935 TCAPI_ERROR_INVALID_PREFERENCE, 27936 "Invalid audio preference received from LMS", 27937 "intTempPreference=" + intTempPreference 27938 ); 27939 return null; 27940} 27941 27942function TCAPI_GetAudioVolumePreference () { 27943 WriteToDebug("In TCAPI_GetAudioVollumePreference"); 27944 var intTempPreference = 100, 27945 getStateResult; 27946 27947 TCAPI_ClearErrorInfo(); 27948 27949 getStateResult = tincan.getState("cmi.student_preference.audio"); 27950 if (getStateResult.state !== null) { 27951 intTempPreference = getStateResult.state.contents; 27952 } 27953 27954 WriteToDebug("intTempPreference=" + intTempPreference); 27955 intTempPreference = parseInt(intTempPreference, 10); 27956 27957 if (intTempPreference <= 0) { 27958 WriteToDebug("Setting to 100"); 27959 intTempPreference = 100; 27960 } 27961 27962 if (intTempPreference > 100) { 27963 WriteToDebug("ERROR: invalid preference"); 27964 TCAPI_SetErrorInfoManually( 27965 TCAPI_ERROR_INVALID_PREFERENCE, 27966 "Invalid audio preference received from LMS", 27967 "intTempPreference=" + intTempPreference 27968 ); 27969 return null; 27970 } 27971 27972 WriteToDebug("Returning " + intTempPreference); 27973 return intTempPreference; 27974} 27975 27976function TCAPI_SetAudioPreference (PlayPreference, intPercentOfMaxVolume) { 27977 WriteToDebug("In TCAPI_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume); 27978 27979 TCAPI_ClearErrorInfo(); 27980 27981 if (PlayPreference == PREFERENCE_OFF) { 27982 WriteToDebug("Setting percent to -1 - OFF"); 27983 intPercentOfMaxVolume = -1; 27984 } 27985 27986 return _TCAPI_SetStateSafe("cmi.student_preference.audio", intPercentOfMaxVolume); 27987} 27988 27989function TCAPI_SetLanguagePreference (strLanguage) { 27990 WriteToDebug("In TCAPI_SetLanguagePreference strLanguage=" + strLanguage); 27991 TCAPI_ClearErrorInfo(); 27992 return _TCAPI_SetStateSafe("cmi.student_preference.language", strLanguage); 27993} 27994 27995function TCAPI_GetLanguagePreference () { 27996 WriteToDebug("In TCAPI_GetLanguagePreference"); 27997 var pref, 27998 getStateResult; 27999 28000 TCAPI_ClearErrorInfo(); 28001 getStateResult = tincan.getState("cmi.student_preference.language"); 28002 if (getStateResult.state !== null) { 28003 pref = getStateResult.state.contents; 28004 } 28005 28006 return pref; 28007} 28008 28009function TCAPI_SetSpeedPreference (intPercentOfMax) { 28010 WriteToDebug("In TCAPI_SetSpeedPreference intPercentOfMax=" + intPercentOfMax); 28011 var intTCAPISpeed; //TCAPI's scale is -100 to +100, our range is 1 to 100 28012 28013 TCAPI_ClearErrorInfo(); 28014 28015 intTCAPISpeed = (intPercentOfMax * 2) - 100; 28016 28017 WriteToDebug("intTCAPISpeed=" + intTCAPISpeed); 28018 return _TCAPI_SetStateSafe("cmi.student_preference.speed", intTCAPISpeed); 28019} 28020 28021function TCAPI_GetSpeedPreference () { 28022 WriteToDebug("In TCAPI_GetSpeedPreference"); 28023 var intTCAPISpeed = 100, 28024 intPercentOfMax, 28025 getStateResult 28026 ; 28027 28028 TCAPI_ClearErrorInfo(); 28029 28030 getStateResult = tincan.getState("cmi.student_preference.speed"); 28031 if (getStateResult.state !== null) { 28032 intTCAPISpeed = getStateResult.state.contents; 28033 } 28034 WriteToDebug("intTCAPISpeed=" + intTCAPISpeed); 28035 28036 if ( ! ValidInteger(intTCAPISpeed) ) { 28037 WriteToDebug("ERROR - invalid integer"); 28038 TCAPI_SetErrorInfoManually( 28039 TCAPI_ERROR_INVALID_SPEED, 28040 "Invalid speed preference received from LMS - not an integer", 28041 "intTCAPISpeed=" + intTCAPISpeed 28042 ); 28043 return null; 28044 } 28045 28046 intTCAPISpeed = parseInt(intTCAPISpeed, 10); 28047 28048 if (intTCAPISpeed < -100 || intTCAPISpeed > 100) { 28049 WriteToDebug("ERROR - out of range"); 28050 TCAPI_SetErrorInfoManually( 28051 TCAPI_ERROR_INVALID_SPEED, 28052 "Invalid speed preference received from LMS - out of range", 28053 "intTCAPISpeed=" + intTCAPISpeed 28054 ); 28055 return null; 28056 } 28057
28058 intPercentOfMax = (intTCAPISpeed + 100) / 2; 28059 intPercentOfMax = parseInt(intPercentOfMax, 10); 28060 28061 WriteToDebug("Returning " + intPercentOfMax); 28062 return intPercentOfMax; 28063} 28064 28065function TCAPI_SetTextPreference (intPreference) { 28066 WriteToDebug("In TCAPI_SetTextPreference intPreference=" + intPreference); 28067 TCAPI_ClearErrorInfo(); 28068 return _TCAPI_SetStateSafe("cmi.student_preference.text", intPreference); 28069} 28070 28071function TCAPI_GetTextPreference () { 28072 WriteToDebug("In TCAPI_GetTextPreference"); 28073 var intTempPreference = 0, 28074 getStateResult; 28075 28076 TCAPI_ClearErrorInfo(); 28077 28078 getStateResult = tincan.getState("cmi.student_preference.text"); 28079 if (getStateResult.state !== null) { 28080 intTempPreference = getStateResult.state.contents; 28081 } 28082 28083 intTempPreference = parseInt(intTempPreference, 10); 28084 WriteToDebug("intTempPreference=" + intTempPreference); 28085 28086 if (intTempPreference > 0) { 28087 WriteToDebug("Returning On"); 28088 return PREFERENCE_ON; 28089 } 28090 else if (intTempPreference == 0 || intTempPreference == "") { 28091 WriteToDebug("Returning Default"); 28092 return PREFERENCE_DEFAULT; 28093 } 28094 else if (intTempPreference < 0) { 28095 WriteToDebug("returning Off"); 28096 return PREFERENCE_OFF; 28097 } 28098 28099 WriteToDebug("Error: Invalid preference"); 28100 TCAPI_SetErrorInfoManually( 28101 TCAPI_ERROR_INVALID_PREFERENCE, 28102 "Invalid text preference received from LMS", 28103 "intTempPreference=" + intTempPreference 28104 ); 28105 return null; 28106} 28107 28108/***************************** 28109* Time management 28110*****************************/ 28111 28112// 28113// returns number of milliseconds duration of all launches previous 28114// to this session 28115// 28116function TCAPI_GetPreviouslyAccumulatedTime () { 28117 WriteToDebug("In TCAPI_GetPreviouslyAccumulatedTime"); 28118 var data = 0, 28119 getStateResult; 28120 28121 WriteToDebug("In TCAPI_GetPreviouslyAccumulatedTime - cached: " + tcapi_cache.totalPrevDuration); 28122 if (tcapi_cache.totalPrevDuration === null) { 28123 getStateResult = tincan.getState(TCAPI_STATE_TOTAL_TIME); 28124 if (getStateResult.state !== null) { 28125 data = Number(getStateResult.state.contents); 28126 } 28127 tcapi_cache.totalPrevDuration = (data === NaN ) ? 0 : data; 28128 } 28129 28130 return tcapi_cache.totalPrevDuration; 28131} 28132 28133// 28134// SaveTime is used to track session time which we don't need 28135// in TC because we are creating our own total durations 28136// 28137function TCAPI_SaveTime (intMilliSeconds) { 28138 WriteToDebug("In TCAPI_SaveTime"); 28139 return true; 28140} 28141 28142function TCAPI_GetMaxTimeAllowed () { 28143 WriteToDebug("In TCAPI_GetMaxTimeAllowed"); 28144 return null; 28145} 28146 28147/***************************** 28148* Scoring 28149*****************************/ 28150 28151function TCAPI_SetScore (intScore, intMaxScore, intMinScore) { 28152 WriteToDebug("In TCAPI_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 28153 28154 TCAPI_ClearErrorInfo(); 28155 28156 TCAPI_SCORE["raw"] = intScore; 28157 TCAPI_SCORE["max"] = intMaxScore; 28158 TCAPI_SCORE["min"] = intMinScore; 28159 28160 WriteToDebug("Returning " + TCAPI_SCORE); 28161 28162 TCAPI_UPDATES_PENDING = true; 28163 28164 return true; 28165} 28166 28167function TCAPI_GetScore () { 28168 WriteToDebug("In TCAPI_GetScore"); 28169 28170 TCAPI_ClearErrorInfo(); 28171 28172 WriteToDebug("Returning " + TCAPI_SCORE['raw']); 28173 return TCAPI_SCORE['raw']; 28174} 28175 28176function TCAPI_SetPointBasedScore (intScore, intMaxScore, intMinScore) { 28177 WriteToDebug("TCAPI_SetPointBasedScore - TCAPI does not support SetPointBasedScore, falling back to SetScore"); 28178 return TCAPI_SetScore(intScore, intMaxScore, intMinScore); 28179} 28180 28181function TCAPI_GetScaledScore (intScore, intMaxScore, intMinScore) { 28182 WriteToDebug("TCAPI_GetScaledScore - TCAPI does not support GetScaledScore, returning false"); 28183 return false; 28184} 28185 28186/*********************** 28187* Interactions 28188************************/ 28189 28190function TCAPI_RecordInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime, TCAPIInteractionType, strAlternateResponse, strAlternateCorrectResponse) { 28191 var blnTempResult, 28192 intInteractionIndex, 28193 strResult, 28194 actObj = {}, 28195 stmt, 28196 activityName, 28197 28198 // 28199 // All of the processing for this ID is handled in TCAPI_CreateValidIdentifier. 28200 // In that function, the tincan.activity.id is added to strID and then URI encoded. 28201 // To fix backwards compatibility, the TCAPI_DONT_USE_BROKEN_URN_IDS flag should be set to false, 28202 // which will cause "urn:scormdriver:" to be added between the tincan.activity.id 28203 // and the ID that is passed. 28204 // 28205 // We also set the activity definition name to the original ID that is passed (the interactionActivityId 28206 // without the tincan.activity.id on the beginning). 28207 // 28208 // All of these fixes make it so that future interactions use valid IDs without breaking any 28209 // backwards compatibility. 28210 // 28211 interactionActivityId = strID, 28212 interactionActivityType = TCAPI_INTERACTION 28213 ; 28214 28215 TCAPI_ClearErrorInfo(); 28216 28217 if (strID.indexOf(tincan.activity.id + "-") == 0) { 28218 activityName = strID.substring(tincan.activity.id.length + 1); 28219 } 28220 28221 if (! TCAPI_DONT_USE_BROKEN_URN_IDS) { 28222 interactionActivityId = tincan.activity.id + "-urn:scormdriver:" + activityName; 28223 } 28224 28225 switch (TCAPIInteractionType) { 28226 case "true-false": 28227 actObj = { 28228 id: interactionActivityId, 28229 definition: { 28230 description: {'en-US': strDescription}, 28231 type: interactionActivityType, 28232 interactionType: "true-false", 28233 name: {"und": activityName} 28234 } 28235 }; 28236 break; 28237 case "choice": 28238 actObj = { 28239 id: interactionActivityId, 28240 definition: { 28241 description: {'en-US': strDescription}, 28242 type: interactionActivityType, 28243 interactionType: "choice", 28244 name: {"und": activityName} 28245 } 28246 }; 28247 break; 28248 case "fill-in": 28249 actObj = { 28250 id: interactionActivityId, 28251 definition: { 28252 description: {'en-US': strDescription}, 28253 type: interactionActivityType, 28254 interactionType: "fill-in", 28255 name: {"und": activityName} 28256 } 28257 }; 28258 break; 28259 case "matching": 28260 actObj = { 28261 id: interactionActivityId, 28262 definition: { 28263 description: {'en-US': strDescription}, 28264 type: interactionActivityType, 28265 interactionType: "matching", 28266 name: {"und": activityName} 28267 //source: [], 28268 //target: [] 28269 } 28270 }; 28271 break; 28272 case "performance": 28273 actObj = { 28274 id: interactionActivityId, 28275 definition: { 28276 description: {'en-US': strDescription}, 28277 type: interactionActivityType, 28278 interactionType: "performance", 28279 name: {"und": activityName} 28280 //steps: [] 28281 } 28282 }; 28283 break; 28284 case "sequencing": 28285 actObj = { 28286 id: interactionActivityId, 28287 definition: { 28288 description: {'en-US': strDescription}, 28289 type: interactionActivityType, 28290 interactionType: "sequencing", 28291 name: {"und": activityName} 28292 //choices: [] 28293 } 28294 }; 28295 break; 28296 case "likert": 28297 actObj = { 28298 id: interactionActivityId, 28299 definition: { 28300 description: {'en-US': strDescription}, 28301 type: interactionActivityType, 28302 interactionType: "likert", 28303 name: {"und": activityName} 28304 //scale: [] 28305 } 28306 }; 28307 break; 28308 case "numeric": 28309 actObj = { 28310 id: interactionActivityId, 28311 definition: { 28312 description: {'en-US': strDescription}, 28313 type: interactionActivityType, 28314 interactionType: "numeric", 28315 name: {"und": activityName} 28316 } 28317 }; 28318 break; 28319 case "other": 28320 actObj = { 28321 id: interactionActivityId, 28322 definition: { 28323 description: {'en-US': strDescription}, 28324 type: interactionActivityType, 28325 interactionType: "other", 28326 name: {"und": activityName} 28327 } 28328 }; 28329 break; 28330 default: 28331 WriteToDebug("TCAPI_RecordInteraction received an invali
28331d TCPAIInteractionType of " + TCAPIInteractionType); 28332 return false; 28333 } 28334 28335 if (actObj.id !== null) { 28336 // 28337 // this makes it so that "" can never be a correct response, but that's the way 28338 // the other standards in Driver work, and is potentially a reasonable expectation 28339 // 28340 if (strCorrectResponse !== null && strCorrectResponse !== "") { 28341 actObj.definition.correctResponsesPattern = [strCorrectResponse]; 28342 } 28343 28344 stmt = { 28345 verb: TCAPI_VERB_ANSWERED, 28346 object: actObj, 28347 context: { 28348 contextActivities: { 28349 parent: tincan.activity, 28350 grouping: { 28351 id: tincan.activity.id + '-' + strLearningObjectiveID 28352 } 28353 } 28354 } 28355 }; 28356 28357 if ((strResponse !== null) || (intLatency !== null && intLatency !== "") || (intWeighting !== null && intWeighting !== "")) { 28358 stmt.result = {}; 28359 28360 if (strResponse !== null) { 28361 stmt.result.response = strResponse; 28362 if (blnCorrect === true || blnCorrect === INTERACTION_RESULT_CORRECT) { 28363 stmt.result.success = true; 28364 } 28365 else if (blnCorrect === false || blnCorrect === "" || blnCorrect === "false" || blnCorrect === INTERACTION_RESULT_WRONG) { 28366 stmt.result.success = false; 28367 } 28368 } 28369 if (intLatency !== null && intLatency !== "") { 28370 stmt.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(intLatency); 28371 } 28372 if (intWeighting !== null && intWeighting !== "") { 28373 stmt.result.extensions = stmt.result.extensions || {}; 28374 stmt.result.extensions["http://id.tincanapi.com/extension/cmi-interaction-weighting"] = intWeighting; 28375 } 28376 } 28377 28378 tcapi_cache.statementQueue.push(stmt); 28379 } 28380 28381 return true; 28382} 28383 28384function TCAPI_RecordTrueFalseInteraction (strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28385 WriteToDebug( 28386 "In TCAPI_RecordTrueFalseInteraction strID=" + strID + ", blnResponse=" + blnResponse + 28387 ", blnCorrect=" + blnCorrect + ", blnCorrectResponse=" + blnCorrectResponse + 28388 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28389 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 28390 ); 28391 28392 var strResponse = null, 28393 strCorrectResponse = null 28394 ; 28395 28396 if (blnResponse === true) { 28397 strResponse = "true"; 28398 } 28399 // test for false b/c it could be null in which case we want to leave it as `null` 28400 else if (blnResponse === false) { 28401 strResponse = "false"; 28402 } 28403 28404 if (blnCorrectResponse === true) { 28405 strCorrectResponse = "true"; 28406 } 28407 // test for false b/c it could be null in which case we want to leave it as `null` 28408 else if (blnCorrectResponse === false) { 28409 strCorrectResponse = "false"; 28410 } 28411 28412 return TCAPI_RecordInteraction( 28413 strID, 28414 strResponse, 28415 blnCorrect, 28416 strCorrectResponse, 28417 strDescription, 28418 intWeighting, 28419 intLatency, 28420 strLearningObjectiveID, 28421 dtmTime, 28422 TCAPI_INTERACTION_TYPE_TRUE_FALSE, 28423 strResponse, 28424 strCorrectResponse 28425 ); 28426} 28427 28428function TCAPI_RecordMultipleChoiceInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28429 WriteToDebug( 28430 "In TCAPI_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse + 28431 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 28432 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28433 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 28434 ); 28435 28436 var strResponse = null, 28437 strResponseLong = null, 28438 strCorrectResponse = "", 28439 strCorrectResponseLong = "" 28440 ; 28441 28442 if (aryResponse !== null) { 28443 strResponse = ""; 28444 strResponseLong = ""; 28445 28446 for (var i = 0; i < aryResponse.length; i++) {
28447 if (strResponse.length > 0) { strResponse += "[,]"; } 28448 if (strResponseLong.length > 0) { strResponseLong += "[,]"; } 28449 28450 strResponse += aryResponse[i].Short; 28451 strResponseLong += aryResponse[i].Long; 28452 } 28453 } 28454 28455 for (var i = 0; i < aryCorrectResponse.length; i++) { 28456 if (strCorrectResponse.length > 0) { strCorrectResponse += "[,]"; } 28457 if (strCorrectResponseLong.length > 0) { strCorrectResponseLong += "[,]"; } 28458 28459 strCorrectResponse += aryCorrectResponse[i].Short; 28460 strCorrectResponseLong += aryCorrectResponse[i].Long; 28461 } 28462 28463 return TCAPI_RecordInteraction( 28464 strID, 28465 strResponseLong, 28466 blnCorrect, 28467 strCorrectResponseLong, 28468 strDescription, 28469 intWeighting, 28470 intLatency, 28471 strLearningObjectiveID, 28472 dtmTime, 28473 TCAPI_INTERACTION_TYPE_CHOICE, 28474 strResponse, 28475 strCorrectResponse 28476 ); 28477} 28478 28479function TCAPI_RecordFillInInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28480 WriteToDebug( 28481 "In TCAPI_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse + 28482 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 28483 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28484 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 28485 ); 28486 28487 if (strResponse !== null) { 28488 strResponse = new String(strResponse); 28489 28490 if (strResponse.length > 255) { 28491 strResponse = strResponse.substr(0, 255); 28492 } 28493 } 28494 28495 if (strCorrectResponse !== null) { 28496 strCorrectResponse = new String(strCorrectResponse); 28497 28498 if (strCorrectResponse.length > 255) { 28499 strCorrectResponse = strCorrectResponse.substr(0, 255); 28500 } 28501 } 28502 28503 return TCAPI_RecordInteraction( 28504 strID, 28505 strResponse, 28506 blnCorrect, 28507 strCorrectResponse, 28508 strDescription, 28509 intWeighting, 28510 intLatency, 28511 strLearningObjectiveID, 28512 dtmTime, 28513 TCAPI_INTERACTION_TYPE_FILL_IN, 28514 strResponse, 28515 strCorrectResponse 28516 ); 28517} 28518 28519function TCAPI_RecordMatchingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28520 WriteToDebug( 28521 "In TCAPI_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 28522 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 28523 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28524 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 28525 ); 28526 28527 var strResponse = null, 28528 strResponseLong = null, 28529 strCorrectResponse = "", 28530 strCorrectResponseLong = "" 28531 ; 28532 28533 if (aryResponse !== null) { 28534 strResponse = ""; 28535 strResponseLong = ""; 28536 28537 for (var i = 0; i < aryResponse.length; i++) { 28538 if (strResponse.length > 0) { strResponse += "[,]"; } 28539 if (strResponseLong.length > 0) { strResponseLong += "[,]"; } 28540 28541 strResponse += aryResponse[i].Source.Short + "[.]" + aryResponse[i].Target.Short; 28542 strResponseLong += aryResponse[i].Source.Long + "[.]" + aryResponse[i].Target.Long; 28543 } 28544 } 28545 28546 for (var i = 0; i < aryCorrectResponse.length; i++) { 28547 if (strCorrectResponse.length > 0) { strCorrectResponse += "[,]"; } 28548 if (strCorrectResponseLong.length > 0) { strCorrectResponseLong += "[,]"; } 28549 28550 strCorrectResponse += aryCorrectResponse[i].Source.Short + "[.]" + aryCorrectResponse[i].Target.Short; 28551 strCorrectResponseLong += aryCorrectResponse[i].Source.Long + "[.]" + aryCorrectResponse[i].Target.Long; 28552 } 28553 28554 return TCAPI_RecordInteraction( 28555 strID, 28556 strResponseLong, 28557 blnCorrect, 28558 strCorrectResponseLong, 28559 strDescription, 28560 intWeighting, 28561 intLatency, 28562 strLearningObjectiveID, 28563 dtmTime, 28564 TCAPI_INTERACTION_TYPE_MATCHING, 28565 strResponse, 28566 strCorrectResponse 28567 ); 28568} 28569 28570function TCAPI_RecordPerformanceInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28571 WriteToDebug( 28572 "In TCAPI_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse + 28573 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 28574 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28575 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28576 ); 28577 28578 if (strResponse !== null) { 28579 strResponse = new String(strResponse); 28580 if (strResponse.length > 255) { 28581 strResponse = strResponse.substr(0, 255); 28582 } 28583 } 28584 28585 if (strCorrectResponse !== null) { 28586 strCorrectResponse = new String(strCorrectResponse); 28587 if (strCorrectResponse.length > 255) { 28588 strCorrectResponse = strCorrectResponse.substr(0, 255); 28589 } 28590 } 28591 28592 return TCAPI_RecordInteraction( 28593 strID, 28594 strResponse, 28595 blnCorrect, 28596 strCorrectResponse, 28597 strDescription, 28598 intWeighting, 28599 intLatency, 28600 strLearningObjectiveID, 28601 dtmTime, 28602 TCAPI_INTERACTION_TYPE_PERFORMANCE, 28603 strResponse, 28604 strCorrectResponse 28605 ); 28606} 28607 28608function TCAPI_RecordSequencingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28609 WriteToDebug( 28610 "In TCAPI_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 28611 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 28612 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28613 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 28614 ); 28615 28616 var strResponse = null, 28617 strResponseLong = null, 28618 strCorrectResponse = "", 28619 strCorrectResponseLong = "" 28620 ; 28621 28622 if (aryResponse !== null) { 28623 strResponse = ""; 28624 strResponseLong = ""; 28625 28626 for (var i = 0; i < aryResponse.length; i++) { 28627 if (strResponse.length > 0) { strResponse += "[,]"; } 28628 if (strResponseLong.length > 0) { strResponseLong += "[,]"; } 28629 28630 strResponse += aryResponse[i].Short; 28631 strResponseLong += aryResponse[i].Long; 28632 } 28633 } 28634 28635 for (var i = 0; i < aryCorrectResponse.length; i++) { 28636 if (strCorrectResponse.length > 0) { strCorrectResponse += "[,]"; } 28637 if (strCorrectResponseLong.length > 0) { strCorrectResponseLong += "[,]"; } 28638 28639 strCorrectResponse += aryCorrectResponse[i].Short; 28640 strCorrectResponseLong += aryCorrectResponse[i].Long; 28641 } 28642 28643 return TCAPI_RecordInteraction( 28644 strID, 28645 strResponseLong, 28646 blnCorrect, 28647 strCorrectResponseLong, 28648 strDescription, 28649 intWeighting, 28650 intLatency, 28651 strLearningObjectiveID, 28652 dtmTime, 28653 TCAPI_INTERACTION_TYPE_SEQUENCING, 28654 strResponse, 28655 strCorrectResponse 28656 ); 28657} 28658 28659function TCAPI_RecordLikertInteraction (strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28660 WriteToDebug( 28661 "In TCAPI_RecordLikertInteraction strID=" + strID + ", response=" + response + 28662 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 28663 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28664 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 28665 ); 28666 28667 var strResponse = null, 28668 strResponseLong = null, 28669 strCorrectResponse = null, 28670 strCorrectResponseLong = null 28671 ; 28672 28673 if (response !== null) { 28674 strResponse = response.Short; 28675 strResponseLong = response.Long; 28676 } 28677 if (correctResponse !== null) { 28678 strCorrectResponse = correctResponse.Short; 28679 strCorrectResponseLong = correctResponse.Long; 28680 } 28681 28682 return TCAPI_RecordInteraction( 28683 strID, 28684 strResponseLong, 28685 blnCorrect, 28686 strCorrectResponseLong, 28687 strDescription, 28688 intWeighting, 28689 intLatency, 28690 strLearningObjectiveID, 28691 dtmTime, 28692 TCAPI_INTERACTION_TYPE_LIKERT, 28693 strResponse, 28694 strCorrectResponse 28695 ); 28696} 28697 28698function TCAPI_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 28699 WriteToDebug( 28700 "In TCAPI_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse + 28701 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 28702 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 28703 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
28704 ); 28705 28706 if (typeof strCorrectResponse !== "undefined" && strCorrectResponse !== null) { 28707 // Tin Can can handle numeric ranges or decimals. As long as we received one of those we are ok to continue 28708 if (! IsValidDecimalRange(strCorrectResponse) && ! IsValidDecimal(strCorrectResponse)) { 28709 WriteToDebug("Returning False - TCAPI_RecordNumericInteraction received invalid correct response (not a decimal or range), strCorrectResponse=" + strCorrectResponse); 28710 return false; 28711 } 28712 } 28713 28714 return TCAPI_RecordInteraction( 28715 strID, 28716 strResponse, 28717 blnCorrect, 28718 strCorrectResponse, 28719 strDescription, 28720 intWeighting, 28721 intLatency, 28722 strLearningObjectiveID, 28723 dtmTime, 28724 TCAPI_INTERACTION_TYPE_NUMERIC, 28725 strResponse, 28726 strCorrectResponse 28727 ); 28728} 28729 28730function TCAPI_GetEntryMode () { 28731 WriteToDebug("In TCAPI_GetEntryMode"); 28732 /* 28733 var strEntry; 28734 28735 TCAPI_ClearErrorInfo(); 28736 28737 strEntry = TCAPI_CallLMSGetValue("cmi.core.entry"); 28738 28739 WriteToDebug("strEntry=" + strEntry); 28740 28741 if (strEntry == TCAPI_ENTRY_ABINITIO) { 28742 WriteToDebug("Returning first time"); 28743 return ENTRY_FIRST_TIME; 28744 } 28745 else if (strEntry == TCAPI_ENTRY_RESUME) { 28746 WriteToDebug("Returning resume"); 28747 return ENTRY_RESUME; 28748 } 28749 else if (strEntry == TCAPI_ENTRY_NORMAL) { 28750 WriteToDebug("returning normal"); 28751 return ENTRY_REVIEW; 28752 } 28753 else{ 28754 WriteToDebug("ERROR - invalide entry mode"); 28755 TCAPI_SetErrorInfoManually(TCAPI_ERROR_INVALID_ENTRY, 28756 "Invalid entry vocab received from LMS", 28757 "strEntry=" + strEntry); 28758 return null; 28759 } 28760 */ 28761 return null; 28762} 28763 28764function TCAPI_GetLessonMode () { 28765 WriteToDebug("In TCAPI_GetLessonMode"); 28766 /* 28767 var strLessonMode; 28768 28769 TCAPI_ClearErrorInfo(); 28770 28771 strLessonMode = TCAPI_CallLMSGetValue("cmi.core.lesson_mode"); 28772 28773 WriteToDebug("strLessonMode=" + strLessonMode); 28774 28775 if (strLessonMode == TCAPI_BROWSE) { 28776 WriteToDebug("Returning browse"); 28777 return MODE_BROWSE; 28778 } 28779 else if(strLessonMode == TCAPI_NORMAL) { 28780 WriteToDebug("returning normal"); 28781 return MODE_NORMAL; 28782 } 28783 else if(strLessonMode == TCAPI_REVIEW) { 28784 WriteToDebug("Returning Review"); 28785 return MODE_REVIEW; 28786 } 28787 else{ 28788 WriteToDebug("ERROR - invalid lesson mode"); 28789 TCAPI_SetErrorInfoManually(TCAPI_ERROR_INVALID_LESSON_MODE, 28790 "Invalid lesson_mode vocab received from LMS", 28791 "strLessonMode=" + strLessonMode); 28792 return null; 28793 } 28794 */ 28795 return null; 28796} 28797 28798function TCAPI_GetTakingForCredit () { 28799 WriteToDebug("In TCAPI_GetTakingForCredit"); 28800 /* 28801 var strCredit; 28802 28803 TCAPI_ClearErrorInfo(); 28804 28805 strCredit = TCAPI_CallLMSGetValue("cmi.core.credit"); 28806 28807 WriteToDebug("strCredit=" + strCredit); 28808 28809 if (strCredit == "credit") { 28810 WriteToDebug("Returning true"); 28811 return true; 28812 } 28813 else if (strCredit == "no-credit") { 28814 WriteToDebug("Returning false"); 28815 return false; 28816 } 28817 else{ 28818 WriteToDebug("ERROR - invalid credit"); 28819 TCAPI_SetErrorInfoManually(TCAPI_ERROR_INVALID_CREDIT, 28820 "Invalid credit vocab received from LMS", 28821 "strCredit=" + strCredit); 28822 return null; 28823 } 28824 */ 28825 return null; 28826} 28827 28828/***************************** 28829* Objectives 28830*****************************/ 28831 28832function TCAPI_SetObjectiveScore (strObjectiveID, intScore, intMaxScore, intMinScore) { 28833 WriteToDebug("In TCAPI_SetObjectiveScore, strObejctiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 28834 28835 return false; 28836} 28837 28838function TCAPI_SetObjectiveDescription (strObjectiveID, strObjectiveDescription) { 28839 WriteToDebug("In TCAPI_SetObjectiveDescription, strObjectiveDescription=" + strObjectiveDescription); 28840 28841 return false; 28842} 28843 28844function TCAPI_SetObjectiveStatus (strObjectiveID, Lesson_Status) { 28845 WriteToDebug("In TCAPI_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status); 28846 28847 return false;
28848} 28849 28850function TCAPI_GetObjectiveScore (strObjectiveID) { 28851 WriteToDebug("In TCAPI_GetObjectiveScore, strObejctiveID=" + strObjectiveID); 28852 28853 return false; 28854} 28855 28856function TCAPI_GetObjectiveDescription (strObjectiveID) { 28857 WriteToDebug("In TCAPI_GetObjectiveDescription, strObejctiveID=" + strObjectiveID); 28858 28859 return false; 28860} 28861 28862function TCAPI_GetObjectiveStatus (strObjectiveID) { 28863 WriteToDebug("In TCAPI_GetObjectiveStatus, strObejctiveID=" + strObjectiveID); 28864 28865 return false; 28866} 28867 28868/***************************** 28869* Status Management 28870*****************************/ 28871 28872function TCAPI_SetSuspended () { 28873 WriteToDebug("In TCAPI_SetSuspended"); 28874 28875 if (TCAPI_IN_PROGRESS) { 28876 TCAPI_IN_PROGRESS = false; 28877 TCAPI_UPDATES_PENDING = true; 28878 } 28879 28880 return true; 28881} 28882 28883function TCAPI_SetFailed () { 28884 WriteToDebug("In TCAPI_SetFailed"); 28885 28886 TCAPI_STATUS = TCAPI_VERB_FAILED; 28887 TCAPI_STATUS_CHANGED = true; 28888 TCAPI_SATISFACTION_STATUS = false; 28889 TCAPI_IN_PROGRESS = false; 28890 TCAPI_UPDATES_PENDING = true; 28891 28892 return true; 28893} 28894 28895function TCAPI_SetPassed () { 28896 WriteToDebug("In TCAPI_SetPassed"); 28897 28898 TCAPI_STATUS = TCAPI_VERB_PASSED; 28899 TCAPI_STATUS_CHANGED = true; 28900 TCAPI_SATISFACTION_STATUS = true; 28901 TCAPI_IN_PROGRESS = false; 28902 TCAPI_UPDATES_PENDING = true; 28903 28904 return true; 28905} 28906 28907function TCAPI_SetCompleted () { 28908 WriteToDebug("In TCAPI_SetCompleted"); 28909 28910 TCAPI_ClearErrorInfo(); 28911 28912 if (TCAPI_STATUS === TCAPI_INIT_VERB) { 28913 // only set verb to completed if not already passed or failed 28914 TCAPI_STATUS = TCAPI_VERB_COMPLETED; 28915 TCAPI_STATUS_CHANGED = true; 28916 } 28917 TCAPI_COMPLETION_STATUS = TCAPI_VERB_COMPLETED; 28918 TCAPI_IN_PROGRESS = false; 28919 TCAPI_UPDATES_PENDING = true; 28920 28921 return true; 28922} 28923 28924function TCAPI_ResetStatus () { 28925 WriteToDebug("In TCAPI_ResetStatus"); 28926 28927 TCAPI_ClearErrorInfo(); 28928 28929 TCAPI_STATUS = TCAPI_INIT_VERB; 28930 TCAPI_STATUS_CHANGED = true; 28931 TCAPI_COMPLETION_STATUS = ''; 28932 TCAPI_SATISFACTION_STATUS = null; 28933 TCAPI_IN_PROGRESS = true; 28934 TCAPI_UPDATES_PENDING = true; 28935 28936 return true; 28937} 28938 28939function TCAPI_GetStatus () { 28940 WriteToDebug("In TCAPI_GetStatus"); 28941 var strStatus = ""; 28942 28943 TCAPI_ClearErrorInfo(); 28944 28945 if (TCAPI_STATUS === TCAPI_VERB_COMPLETED) { 28946 strStatus = "completed"; 28947 } 28948 else if (TCAPI_STATUS === TCAPI_VERB_ATTEMPTED) { 28949 strStatus = "attempted"; 28950 } 28951 else if (TCAPI_STATUS === TCAPI_VERB_PASSED) { 28952 strStatus = "passed"; 28953 } 28954 else if (TCAPI_STATUS === TCAPI_VERB_FAILED) { 28955 strStatus = "failed"; 28956 } 28957 else { 28958 strStatus = TCAPI_STATUS; 28959 } 28960 28961 WriteToDebug("In TCAPI_GetStatus - strStatus=" + strStatus); 28962 return strStatus; 28963} 28964 28965function TCAPI_CreateValidIdentifier(str) { 28966 return tincan.activity.id + "-" + encodeURIComponent(str); 28967} 28968 28969/***************************** 28970* Unsupported Methods 28971* 28972* TODO: should these be supported through "state"? 28973*****************************/ 28974 28975function TCAPI_SetNavigationRequest (strNavRequest) { 28976 WriteToDebug("TCAPI_GetNavigationRequest - TCAPI does not support navigation requests, returning false"); 28977 return false; 28978} 28979 28980function TCAPI_GetNavigationRequest () { 28981 WriteToDebug("TCAPI_GetNavigationRequest - TCAPI does not support navigation requests, returning false"); 28982 return false; 28983} 28984 28985function TCAPI_CreateDataBucket (strBucketId, intMinSize, intMaxSize) { 28986 WriteToDebug("TCAPI_CreateDataBucket - TCAPI does not support SSP, returning false"); 28987 return false; 28988} 28989 28990function TCAPI_GetDataFromBucket (strBucketId) { 28991 WriteToDebug("TCAPI_GetDataFromBucket - TCAPI does not support SSP, returning empty string"); 28992 return ""; 28993} 28994 28995function TCAPI_PutDataInBucket (strBucketId, strData, blnAppendToEnd) { 28996 WriteToDebug("TCAPI_PutDataInBucket - TCAPI does not support SSP, returning false"); 28997 return false; 28998} 28999 29000function TCAPI_DetectSSPSupport () { 29001 WriteToDebug("TCAPI_DetectSSPSupport - TCAPI does not support SSP, returning false"); 29002 return false; 29003} 29004 29005function TCAPI_GetBucketInfo (strBucketId) { 29006 WriteToDebug("TCAPI_GetBucketInfo - TCAPI does not support SSP, returning empty SSPBucketSize"); 29007 return new SSPBucketSize(0, 0); 29008} 29009 29010function TCAPI_WriteComment (strComment) { 29011 WriteToDebug("In TCAPI_WriteComment - TCAPI does not support comments"); 29012 return false;
29013} 29014 29015function TCAPI_GetComments () { 29016 WriteToDebug("In TCAPI_GetComments - TCAPI does not support comments"); 29017 return ""; 29018} 29019 29020function TCAPI_GetLMSComments () { 29021 WriteToDebug("In TCAPI_GetLMSComments - TCAPI does not support LMS comments"); 29022 return ""; 29023} 29024 29025function TCAPI_GetLaunchData () { 29026 WriteToDebug("In TCAPI_GetLaunchData - TCAPI does not support launch data"); 29027 return false; 29028} 29029 29030function TCAPI_DisplayMessageOnTimeout () { 29031 TCAPI_ClearErrorInfo(); 29032 29033 WriteToDebug("In TCAPI_DisplayMessageOnTimeout - TCAPI does not support MessageOnTimeout"); 29034 return false; 29035} 29036 29037function TCAPI_ExitOnTimeout () { 29038 WriteToDebug("In TCAPI_ExitOnTimeout - TCAPI does not support ExitOnTimeout"); 29039 return false; 29040} 29041 29042function TCAPI_GetPassingScore () { 29043 WriteToDebug("In TCAPI_GetPassingScore - TCAPI does not support GetPassingScore"); 29044 return false; 29045} 29046 29047function TCAPI_GetProgressMeasure () { 29048 WriteToDebug("TCAPI_GetProgressMeasure - TCAPI does not support progress_measure, returning false"); 29049 return false; 29050} 29051 29052function TCAPI_SetProgressMeasure () { 29053 WriteToDebug("TCAPI_SetProgressMeasure - TCAPI does not support progress_measure, returning false"); 29054 return false; 29055} 29056 29057function TCAPI_GetObjectiveProgressMeasure () { 29058 WriteToDebug("TCAPI_GetObjectiveProgressMeasure - TCAPI does not support progress_measure, returning false"); 29059 return false; 29060} 29061 29062function TCAPI_SetObjectiveProgressMeasure () { 29063 WriteToDebug("TCAPI_SetObjectiveProgressMeasure - TCAPI does not support progress_measure, returning false"); 29064 return false; 29065} 29066 29067function TCAPI_GetInteractionType(strInteractionID) { 29068 WriteToDebug("TCAPI_GetInteractionType - TCAPI does not support interaction retrieval, returning empty string"); 29069 return ''; 29070} 29071 29072function TCAPI_GetInteractionTimestamp(strInteractionID) { 29073 WriteToDebug("TCAPI_GetInteractionTimestamp - TCAPI does not support interaction retrieval, returning empty string"); 29074 return ''; 29075} 29076 29077function TCAPI_GetInteractionCorrectResponses(strInteractionID) { 29078 WriteToDebug("TCAPI_GetInteractionCorrectResponses - TCAPI does not support interaction retrieval, returning empty array"); 29079 return []; 29080} 29081 29082function TCAPI_GetInteractionWeighting(strInteractionID) { 29083 WriteToDebug("TCAPI_GetInteractionWeighting - TCAPI does not support interaction retrieval, returning empty string"); 29084 return ''; 29085} 29086 29087function TCAPI_GetInteractionLearnerResponses(strInteractionID) { 29088 WriteToDebug("TCAPI_GetInteractionLearnerResponses - TCAPI does not support interaction retrieval, returning empty array"); 29089 return []; 29090} 29091 29092function TCAPI_GetInteractionResult(strInteractionID) { 29093 WriteToDebug("TCAPI_GetInteractionResult - TCAPI does not support interaction retrieval, returning empty string"); 29094 return ''; 29095} 29096 29097function TCAPI_GetInteractionLatency(strInteractionID) { 29098 WriteToDebug("TCAPI_GetInteractionDescription - TCAPI does not support interaction retrieval, returning empty string"); 29099 return ''; 29100} 29101 29102function TCAPI_GetInteractionDescription(strInteractionID) { 29103 WriteToDebug("TCAPI_GetInteractionDescription - TCAPI does not support interaction retrieval, returning empty string"); 29104 return ''; 29105} 29106 29107/***************************** 29108* Error Management 29109*****************************/ 29110 29111// TODO: global variables used that need to be declared 29112function TCAPI_ClearErrorInfo () { 29113 WriteToDebug("In TCAPI_ClearErrorInfo"); 29114 29115 intTCAPIError = TCAPI_NO_ERROR; 29116 strTCAPIErrorString = ""; 29117 strTCAPIErrorDiagnostic = ""; 29118} 29119 29120function TCAPI_SetErrorInfoManually (intNum, strString, strDiagnostic) { 29121 WriteToDebug("In TCAPI_SetErrorInfoManually"); 29122 WriteToDebug("ERROR-Num=" + intNum); 29123 WriteToDebug(" String=" + strString); 29124 WriteToDebug(" Diag=" + strDiagnostic); 29125 29126 intTCAPIError = intNum; 29127 strTCAPIErrorString = strString; 29128 strTCAPIErrorDiagnostic = strDiagnostic; 29129} 29130 29131function TCAPI_GetLastError () { 29132 WriteToDebug("In TCAPI_GetLastError"); 29133 29134 if (intTCAPIError === TCAPI_NO_ERROR) { 29135 WriteToDebug("Returning No Error"); 29136 return NO_ERROR; 29137 } 29138 else { 29139 WriteToDebug("Returning " + intTCAPIError); 29140 return intTCAPIError; 29141 } 29142} 29143 29144function TCAPI_GetLastErrorDesc () { 29145 WriteToDebug("In TCAPI_GetLastErrorDesc, " + strTCAPIErrorString + "\n" + strTCAPIErrorDiagnostic); 29146 return strTCAPIErrorString + "\n" + strTCAPIErrorDiagnostic; 29147} 29148/* declare other globals we'll use */ 29149 29150var CMI5_PENDING_STATUS = { 29151 completion: null, 29152 success: null, 29153 score: null 29154 }, 29155 CMI5_COMMITTED_STATUS = { 29156 completion: null, 29157 success: null, 29158 score: null, 29159 launchModes: [] 29160 },
29161 CMI5_STATEMENT_QUEUE = [], 29162 29163 // 29164 // cmi5.js will calculate session duration automatically, but SD has the ability 29165 // to let the user manually set the duration of learning so we use it to override 29166 // the automatically handled duration by cmi5.js 29167 // 29168 CMI5_SESSION_DURATION = null, 29169 29170 // 29171 // cmi5 doesn't have the concept of total duration across sessions so we manufacture 29172 // it using the State API 29173 // 29174 CMI5_TOTAL_PREV_DURATION = null, 29175 29176 CMI5_ENTRY_MODE = null, 29177 CMI5_INTERACTIONS = {}, 29178 CMI5_SSP_BUCKETS = {}, 29179 29180 CMI5_VERB_ID_FAILED = "http://adlnet.gov/expapi/verbs/failed", 29181 CMI5_VERB_ID_PASSED = "http://adlnet.gov/expapi/verbs/passed", 29182 CMI5_VERB_ID_COMPLETED = "http://adlnet.gov/expapi/verbs/completed", 29183 29184 // 29185 // local cache of these values so only need to round trip once, they are updated 29186 // when calling set so no need to re-fetch 29187 // 29188 CMI5_PREF_AUDIO_PLAY = null, 29189 CMI5_PREF_AUDIO_VOLUME = null, 29190 CMI5_PREF_LANGUAGE = null, 29191 CMI5_PREF_SPEED = null, 29192 CMI5_PREF_TEXT = null, 29193 29194 CMI5_INTERACTION_ACTIVITY_TYPE = "http://adlnet.gov/expapi/activities/cmi.interaction", 29195 CMI5_INTERACTION_TYPE_TRUE_FALSE = "true-false", 29196 CMI5_INTERACTION_TYPE_CHOICE = "choice", 29197 CMI5_INTERACTION_TYPE_FILL_IN = "fill-in", 29198 CMI5_INTERACTION_TYPE_LONG_FILL_IN = "long-fill-in", 29199 CMI5_INTERACTION_TYPE_MATCHING = "matching", 29200 CMI5_INTERACTION_TYPE_PERFORMANCE = "performance", 29201 CMI5_INTERACTION_TYPE_SEQUENCING = "sequencing", 29202 CMI5_INTERACTION_TYPE_LIKERT = "likert", 29203 CMI5_INTERACTION_TYPE_NUMERIC = "numeric", 29204 29205 // 29206 // these match their counterparts in the TCAPI_Functions where possible 29207 // 29208 CMI5_STATE_BOOKMARK = "bookmark", 29209 CMI5_STATE_GENERIC_DATA = "generic_data", 29210 CMI5_STATE_TOTAL_TIME = "cumulative_time", 29211 CMI5_STATE_COMMITTED_STATUS = "status", 29212 CMI5_STATE_COMMENTS = "learner_comments", 29213 CMI5_STATE_SSP_BUCKET_PREFIX = "sspBucket", 29214 CMI5_STATE_LANGUAGE_PREFERENCE = "cmi.student_preference.language", 29215 CMI5_STATE_VOLUME_PREFERENCE = "cmi.student_preference.audio", 29216 CMI5_STATE_SPEED_PREFERENCE = "cmi.student_preference.speed", 29217 CMI5_STATE_TEXT_PREFERENCE = "cmi.student_preference.text", 29218 29219 // reserve the 2xx-5xx space for HTTP return codes from cmi5.js 29220 CMI5_NO_ERROR = "", 29221 29222 // these are non-HTTP status code errors from cmi5.js 29223 CMI5_CONN_ERROR = 998, 29224 CMI5_DEP_ERROR = 999, 29225 29226 CMI5_ERROR_NOT_IMPLEMENTED = 998, 29227 CMI5_ERROR_INVALID_SCORE = 1, 29228 CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET = 2, 29229 CMI5_ERROR_UNRECOGNIZED_INTERACTION = 3, 29230 CMI5_ERROR_NON_NORMAL_MODE = 4, 29231 CMI5_ERROR_ALREADY_COMMITTED = 5, 29232 CMI5_ERROR_INVALID_STATUS_BASED_ON_SCORE = 6, 29233 CMI5_ERROR_SSP_BUCKET_LOAD_FAILED = 7, 29234 CMI5_ERROR_SSP_BUCKET_ALREADY_EXISTS = 8, 29235 CMI5_ERROR_SSP_BUCKET_INVALID_JSON = 9, 29236 CMI5_ERROR_INTERACTION_INVALID_JSON = 10, 29237 CMI5_ERROR_INTERACTION_LOAD_FAILED = 11, 29238 29239 intCMI5Error = CMI5_NO_ERROR, 29240 strCMI5ErrorString = "", 29241 strCMI5ErrorDiagnostic = "", 29242 29243 /* Declare global used for interacting via cmi5 */ 29244 cmi5; 29245 29246function CMI5_SaveState (key, value, contentType, includeRegistration) { 29247 WriteToDebug("In CMI5_SaveState - key: " + key + ", value: " + value); 29248 var saveStateCfg = { 29249 agent: cmi5.getActor(), 29250 activity: cmi5.getActivity(), 29251 contentType: contentType 29252 }, 29253 result, 29254 errCode, 29255 errMesg, 29256 errDiag; 29257 29258 if (typeof includeRegistration === "undefined" || includeRegistration === null || includeRegistration) { 29259 saveStateCfg.registration = cmi5.getRegistration(); 29260 } 29261 29262 result = cmi5.getLRS().saveState(key, value, saveStateCfg); 29263 if (result.err !== null) { 29264 WriteToDebug("Failed to save " + key + " in state: " + result.err); 29265 errMesg = "Failed to save state: " + key; 29266 29267 // 29268 // in the case of errors result.err either contains an Error object (theoretically) 29269 // or the HTTP status code if the request actually made it to the LRS, to distinguish 29270 // we can check it for all digits (which is safer than checking for an Error object, 29271 // at least in this version of TinCanJS), which then allows us to look at the response 29272 // text which is generally a more helpful message than the status code 29273 // 29274 if (/^\d+$/.test(result.err)) { 29275 errCode = result.err; 29276 29277 if (result.err === 0) { 29278 errDiag = "Aborted, offline, or invalid CORS endpoint"; 29279 } 29280 else { 29281 errDiag = result.xhr.responseText; 29282 } 29283 } 29284 else { 29285 errCode = CMI5_DEP_ERROR; 29286 errDiag = result.err; 29287 } 29288 29289 CMI5_SetErrorInfoManually(errCode, errMesg, errDiag); 29290 return false;
29291 } 29292 29293 return true; 29294} 29295 29296function CMI5_RetrieveState (key, includeRegistration) { 29297 WriteToDebug("In CMI5_RetrieveState - key: " + key); 29298 var retrieveStateCfg = { 29299 agent: cmi5.getActor(), 29300 activity: cmi5.getActivity() 29301 }, 29302 result, 29303 29304 // 29305 // all of the state values ultimately have to be strings because 29306 // the SCORM values are strings, so even on an error we are supposed 29307 // to return an empty string 29308 // 29309 value = "", 29310 errCode, 29311 errMesg, 29312 errDiag; 29313 29314 if (typeof includeRegistration === "undefined" || includeRegistration === null || includeRegistration) { 29315 retrieveStateCfg.registration = cmi5.getRegistration(); 29316 } 29317 29318 result = cmi5.getLRS().retrieveState(key, retrieveStateCfg); 29319 if (result.err !== null) { 29320 WriteToDebug("Failed to retrieve " + key + " from state: " + result.err); 29321 errMesg = "Failed to retrieve state: " + key; 29322 29323 // 29324 // in the case of errors result.err either contains an Error object (theoretically) 29325 // or the HTTP status code if the request actually made it to the LRS, to distinguish 29326 // we can check it for all digits (which is safer than checking for an Error object, 29327 // at least in this version of TinCanJS), which then allows us to look at the response 29328 // text which is generally a more helpful message than the status code 29329 // 29330 if (/^\d+$/.test(result.err)) { 29331 errCode = result.err; 29332 29333 if (result.err === 0) { 29334 errDiag = "Aborted, offline, or invalid CORS endpoint"; 29335 } 29336 else { 29337 errDiag = result.xhr.responseText; 29338 } 29339 } 29340 else { 29341 errCode = CMI5_DEP_ERROR; 29342 errDiag = result.err; 29343 } 29344 29345 CMI5_SetErrorInfoManually(errCode, errMesg, errDiag); 29346 return ""; 29347 } 29348 29349 if (result.state !== null) { 29350 value = result.state.contents; 29351 } 29352 29353 return value; 29354} 29355 29356// 29357// this is just here to make it possible to mock the launch URL 29358// for testing purposes 29359// 29360function CMI5_GetLaunchUrl () { 29361 WriteToDebug("In CMI5_GetLaunchUrl"); 29362 return document.location.href; 29363} 29364 29365function CMI5_Initialize () { 29366 WriteToDebug("In CMI5_Initialize"); 29367 var launchUrl; 29368 29369 Cmi5.prototype.log = function () { 29370 var mesg = "cmi5.js:", 29371 i; 29372 29373 for (i = 0; i < arguments.length; i += 1) { 29374 mesg += " "; 29375 mesg += typeof arguments[i] === "string" ? arguments[i] : JSON.stringify(arguments[i]); 29376 } 29377 29378 WriteToDebug(mesg); 29379 }; 29380 29381 TinCan.LRS.prototype.log = function (msg, src) { 29382 src = src || this.LOG_SRC || "LRS"; 29383 WriteToDebug("TinCan." + src + ": " + msg); 29384 }; 29385 29386 try { 29387 launchUrl = CMI5_GetLaunchUrl(); 29388 29389 WriteToDebug("CMI5_Initialize - instantiating cmi5 object from launch URL = " + launchUrl); 29390 cmi5 = new Cmi5(launchUrl); 29391 cmi5.includeSourceActivity(false); 29392 } 29393 catch (ex) { 29394 InitializeExecuted(false, "Failed to initialize cmi5 object (caught exception): " + ex); 29395 return; 29396 } 29397 29398 WriteToDebug("CMI5_Initialize - calling start on cmi5 instance"); 29399 cmi5.start( 29400 function (err) { 29401 var committedState, 29402 result; 29403 29404 if (err !== null) { 29405 WriteToDebug("CMI5_Initialize - cmi5 start failed: " + err); 29406 InitializeExecuted(false, "Failed to initialize: " + err.message); 29407 return; 29408 } 29409 WriteToDebug("CMI5_Initialize - cmi5 start succeeded"); 29410 29411 committedState = CMI5_RetrieveState(CMI5_STATE_COMMITTED_STATUS); 29412 29413 // 29414 // retrieve state doesn't provide a return value for detecting errors 29415 // so check to see if we have manually set errors (cause we shouldn't 29416 // unless the retrieve failed) 29417 // 29418 if (intCMI5Error) { 29419 WriteToDebug("CMI5_Initialize - failed to retrieve state for committed status: " + strCMI5ErrorString); 29420 InitializeExecuted(false, "Failed to initialize: " + strCMI5ErrorString); 29421 return; 29422 } 29423 29424 if (committedState !== "") { 29425 // 29426 // check committedState object for launchModes property because retrieveState 29427 // in TinCanJS tries to parse the JSON, and on failure eats the parse failure 29428 // and leaves the result contents as a string, but we always expect it to be
29429 // an object, and we know we shouldn't have saved bad JSON anyways 29430 // 29431 if (typeof committedState.launchModes === "undefined") { 29432 WriteToDebug("CMI5_Initialize - failed to retrieve state for committed status: likely invalid JSON parse failure"); 29433 InitializeExecuted(false, "Failed to initialize: likely invalid JSON parse failure"); 29434 return; 29435 } 29436 29437 CMI5_COMMITTED_STATUS = committedState; 29438 } 29439 29440 if (CMI5_COMMITTED_STATUS.completion === true) { 29441 CMI5_ENTRY_MODE = ENTRY_REVIEW; 29442 } 29443 else if (CMI5_COMMITTED_STATUS.launchModes.length > 0) { 29444 CMI5_ENTRY_MODE = ENTRY_RESUME; 29445 } 29446 else { 29447 CMI5_ENTRY_MODE = ENTRY_FIRST_TIME; 29448 } 29449 29450 CMI5_COMMITTED_STATUS.launchModes.push(cmi5.getLaunchMode()); 29451 29452 result = CMI5_SaveState(CMI5_STATE_COMMITTED_STATUS, CMI5_COMMITTED_STATUS, "application/json"); 29453 if (! result) { 29454 WriteToDebug("CMI5_Initialize - failed to store committed status"); 29455 InitializeExecuted(false, "Failed to initialize - failed to store committed status"); 29456 return; 29457 } 29458 29459 WriteToDebug("CMI5_Initialize - succeeded"); 29460 InitializeExecuted(true, ""); 29461 29462 // 29463 // go ahead and try to cache this now, but don't have initialization 29464 // pending while trying to retrieve the state since it is only used 29465 // when Finish'ing which shouldn't happen immediately 29466 // 29467 window.setTimeout( 29468 function () { 29469 CMI5_GetPreviouslyAccumulatedTime(); 29470 }, 29471 200 29472 ); 29473 }, 29474 { 29475 postFetch: function (err, response, parsed) { 29476 var rootActivity; 29477 29478 if (err !== null) { 29479 return; 29480 } 29481 29482 // 29483 // now that we have a credential into the LRS see if the LRS has more info 29484 // on our Activity for statement capture, leaving this async, which could 29485 // result in a return *after* the initialized statement is recorded which 29486 // isn't really a problem, and experience indicates it usually doesn't happen 29487 // 29488 rootActivity = cmi5.getActivity(); 29489 cmi5.getLRS().retrieveActivity( 29490 rootActivity.id, 29491 { 29492 callback: function (err, result) { 29493 if (err === null) { 29494 cmi5.setActivity(result); 29495 } 29496 } 29497 } 29498 ); 29499 } 29500 } 29501 ); 29502 29503 return true; 29504} 29505 29506function CMI5_CommitData () { 29507 WriteToDebug("In CMI5_CommitData"); 29508 var i, 29509 st, 29510 auActivityId, 29511 errMesg, 29512 errCode, 29513 errDiag, 29514 saveCommittedStatus = false, 29515 sourceActivity = new TinCan.Activity( 29516 { 29517 id: "http://id.tincanapi.com/activity/software/scormdriver/" + VERSION, 29518 definition: { 29519 name: { 29520 und: "ScormDriver (" + VERSION + ")" 29521 }, 29522 description: { 29523 en: "ScormDriver (" + VERSION + ")" 29524 }, 29525 type: "http://id.tincanapi.com/activitytype/source" 29526 } 29527 } 29528 ); 29529 29530 CMI5_ClearErrorInfo(); 29531 29532 // 29533 // commit the status updates first because the statement queue may have 29534 // a termination statement in it that ends the session preventing us from 29535 // making further calls 29536 // 29537 if (CMI5_PENDING_STATUS.completion !== null) { 29538 saveCommittedStatus = true; 29539 29540 CMI5_COMMITTED_STATUS.completion = CMI5_PENDING_STATUS.completion; 29541 } 29542 if (CMI5_PENDING_STATUS.success !== null) { 29543 saveCommittedStatus = true; 29544 29545 CMI5_COMMITTED_STATUS.success = CMI5_PENDING_STATUS.success; 29546 if (CMI5_PENDING_STATUS.score !== null) { 29547 CMI5_COMMITTED_STATUS.score = CMI5_PENDING_STATUS.score; 29548 } 29549 } 29550 29551 if (saveCommittedStatus) { 29552 result = CMI5_SaveState(CMI5_STATE_COMMITTED_STATUS, CMI5_COMMITTED_STATUS, "application/json"); 29553 if (! result) { 29554 WriteToDebug("CMI5_CommitData - failed to commit status"); 29555 return result; 29556 } 29557 } 29558
29559 if (CMI5_STATEMENT_QUEUE.length > 0) { 29560 if (CMI5_PENDING_STATUS.score !== null && CMI5_PENDING_STATUS.success !== null) { 29561 // 29562 // need to loop statements looking for passed/failed with this AU id 29563 // and set the score into the statement - this is what allows us to 29564 // allow the user to call SetScore multiple times 29565 // 29566 auActivityId = cmi5.getActivity().id; 29567 29568 for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) { 29569 st = CMI5_STATEMENT_QUEUE[i]; 29570 29571 if (st.target.id === auActivityId && (st.verb.id === CMI5_VERB_ID_FAILED || st.verb.id === CMI5_VERB_ID_PASSED)) { 29572 st.result = st.result || new TinCan.Result(); 29573 st.result.score = new TinCan.Score(CMI5_PENDING_STATUS.score); 29574 } 29575 } 29576 } 29577 29578 for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) { 29579 st = CMI5_STATEMENT_QUEUE[i]; 29580 29581 st.context.contextActivities.grouping = st.context.contextActivities.grouping || []; 29582 st.context.contextActivities.grouping.push(sourceActivity); 29583 } 29584 29585 result = cmi5.getLRS().saveStatements(CMI5_STATEMENT_QUEUE); 29586 if (result.err !== null) { 29587 errMesg = "Failed to commit data: statements"; 29588 29589 // 29590 // in the case of errors result.err either contains an Error object (theoretically) 29591 // or the HTTP status code if the request actually made it to the LRS, to distinguish 29592 // we can check it for all digits (which is safer than checking for an Error object, 29593 // at least in this version of TinCanJS), which then allows us to look at the response 29594 // text which is generally a more helpful message than the status code 29595 // 29596 if (/^\d+$/.test(result.err)) { 29597 if (result.err === 0) { 29598 // 0 status codes represent network errors in the request but 0 can't be used 29599 // as the error code because Driver treats 0 as the no error code 29600 errCode = CMI5_CONN_ERROR; 29601 errDiag = "Aborted, offline, or invalid CORS endpoint"; 29602 } 29603 else { 29604 errCode = result.err; 29605 errDiag = result.xhr.responseText; 29606 } 29607 } 29608 else { 29609 errCode = CMI5_DEP_ERROR; 29610 errDiag = result.err; 29611 } 29612 29613 CMI5_SetErrorInfoManually(errCode, errMesg, errDiag); 29614 return false; 29615 } 29616 29617 CMI5_STATEMENT_QUEUE = []; 29618 } 29619 29620 return true; 29621} 29622 29623function CMI5_Finish (exitType, statusWasSet) { 29624 WriteToDebug("In CMI5_Finish - exitType: " + exitType); 29625 var st = cmi5.terminatedStatement(), 29626 result; 29627 29628 CMI5_ClearErrorInfo(); 29629 29630 CMI5_SetDurationInStatement(st); 29631 29632 // FEATURE: record something related to the exit type, for instance on timeout set extension? 29633 29634 CMI5_STATEMENT_QUEUE.push(st); 29635 29636 result = CMI5_SaveState(CMI5_STATE_TOTAL_TIME, CMI5_GetPreviouslyAccumulatedTime() + GetSessionAccumulatedTime(), "text/plain"); 29637 if (! result) { 29638 WriteToDebug("CMI5_Finish - failed to record total time to State"); 29639 return false; 29640 } 29641 29642 result = CMI5_CommitData(); 29643 if (! result) { 29644 WriteToDebug("CMI5_Finish - failed to commit data"); 29645 return false; 29646 } 29647 29648 return true; 29649} 29650 29651function CMI5_ConcedeControl () { 29652 WriteToDebug("In CMI5_ConcedeControl"); 29653 var returnURL = cmi5.getReturnURL(), 29654 contentRoot = SearchParentsForContentRoot(); 29655 29656 // 29657 // if the LMS provided a return URL then the AU must honor it 29658 // 29659 if (returnURL !== null && contentRoot !== null) { 29660 contentRoot.location.assign(returnURL); 29661 return true; 29662 } 29663 29664 // 29665 // no return URL was provided so try to be a good citizen and 29666 // close ourselves, if this fails to close then we weren't 29667 // opened as part of a script call so there isn't much else 29668 // we can be expected to do 29669 // 29670 if (contentRoot !== null) { 29671 contentRoot.close(); 29672 return true; 29673 } 29674 29675 return true; 29676} 29677 29678function CMI5_GetStudentID () { 29679 WriteToDebug("In CMI5_GetStudentID"); 29680 var actor = cmi5.getActor(); 29681 29682 // 29683 // it was discussed about whether returning just the account name 29684 // was a proper approach and decided because SD is really only 29685 // supported by LMS launch that the account would normally only 29686 // be used with the launching system's account and in that 29687 // case the homePage isn't strictly necessary for ID determination 29688 // 29689 // more advanced use cases should avoid using this method if they 29690 // require the homePage as well 29691 // 29692 return actor.account.name; 29693} 29694 29695function CMI5_GetStudentName () { 29696 WriteToDebug("In CMI5_GetStudentName"); 29697 29698 return cmi5.getActor().toString(); 29699} 29700 29701function CMI5_GetBookmark () { 29702 WriteToDebug("In CMI5_GetBookmark"); 29703 29704 return CMI5_RetrieveState(CMI5_STATE_BOOKMARK); 29705} 29706 29707//
29708// description is optional and not part of the original interface, so GetBookmark need only 29709// support the 'location' argument, so no need to store description which was added to support 29710// capturing xAPI data for it 29711// 29712function CMI5_SetBookmark (location, description) { 29713 WriteToDebug("In CMI5_SetBookmark - location: " + location + ", description: " + description); 29714 var st, 29715 targetId = CreateValidIdentifier(location); 29716 29717 // 29718 // Driver's interface has always (well, long enough anyways) returned 29719 // empty string for "valid identifier" when passed `undefined`, `null` 29720 // or "" which is clearly not a valid identifier, so prevent writing 29721 // the statement in that case 29722 // 29723 if (targetId !== "") { 29724 // FEATURE: allow them to disable capture of these statements via launchParameters 29725 st = cmi5.prepareStatement("http://adlnet.gov/expapi/verbs/experienced"); 29726 29727 // 29728 // preparing a cmi5 statement sets the target as the AU's Activity 29729 // but we want to make that the parent and instead use the bookmarked Activity 29730 // as the target of the allowed statement 29731 // 29732 st.context.contextActivities = st.context.contextActivities || new TinCan.ContextActivities(); 29733 st.context.contextActivities.parent = st.context.contextActivities.parent || []; 29734 st.context.contextActivities.parent.push(st.target); 29735 29736 // 29737 // now with the parent set to the AU Activity set up our bookmarked Activity 29738 // as the target of the statement 29739 // 29740 st.target = new TinCan.Activity( 29741 { 29742 id: targetId 29743 } 29744 ); 29745 29746 CMI5_SetDurationInStatement(st); 29747 29748 CMI5_STATEMENT_QUEUE.push(st); 29749 } 29750 else { 29751 WriteToDebug("WARNING - No bookmarking statement written, empty location"); 29752 } 29753 29754 return CMI5_SaveState(CMI5_STATE_BOOKMARK, location, "text/plain"); 29755} 29756 29757function CMI5_GetDataChunk () { 29758 WriteToDebug("In CMI5_GetDataChunk"); 29759 29760 return CMI5_RetrieveState(CMI5_STATE_GENERIC_DATA); 29761} 29762 29763function CMI5_SetDataChunk (value) { 29764 WriteToDebug("In CMI5_SetDataChunk"); 29765 29766 return CMI5_SaveState(CMI5_STATE_GENERIC_DATA, value, "text/plain"); 29767} 29768 29769function CMI5_GetLaunchData () { 29770 WriteToDebug("In CMI5_GetLaunchData"); 29771 var result = cmi5.getLaunchParameters(); 29772 29773 if (result === null) { 29774 result = ""; 29775 } 29776 29777 return result; 29778} 29779 29780/***************************** 29781* Preferences 29782*****************************/ 29783 29784function CMI5_GetAudioPlayPreference () { 29785 WriteToDebug("In CMI5_GetAudioPlayPreference"); 29786 var audioPref; 29787 29788 CMI5_ClearErrorInfo(); 29789 29790 if (CMI5_PREF_AUDIO_PLAY === null) { 29791 audioPref = cmi5.getAudioPreference(); 29792 29793 if (audioPref === "on") { 29794 WriteToDebug(" Caching On"); 29795 CMI5_PREF_AUDIO_PLAY = PREFERENCE_ON; 29796 } 29797 else if (audioPref === "off") { 29798 WriteToDebug(" Caching Off"); 29799 CMI5_PREF_AUDIO_PLAY = PREFERENCE_OFF; 29800 } 29801 else { 29802 WriteToDebug(" Caching Default"); 29803 CMI5_PREF_AUDIO_PLAY = PREFERENCE_DEFAULT; 29804 } 29805 } 29806 29807 WriteToDebug("Returning preference: " + CMI5_PREF_AUDIO_PLAY); 29808 return CMI5_PREF_AUDIO_PLAY; 29809} 29810 29811function CMI5_GetAudioVolumePreference () { 29812 WriteToDebug("In CMI5_GetAudioVolumePreference"); 29813 var volumePref; 29814 29815 CMI5_ClearErrorInfo(); 29816 29817 if (CMI5_PREF_AUDIO_VOLUME === null) { 29818 volumePref = CMI5_RetrieveState(CMI5_STATE_VOLUME_PREFERENCE, false); 29819 if (volumePref === "") { 29820 volumePref = 100; 29821 } 29822 29823 CMI5_PREF_AUDIO_VOLUME = parseInt(volumePref, 10); 29824 } 29825 29826 WriteToDebug("Returning " + CMI5_PREF_AUDIO_VOLUME); 29827 return CMI5_PREF_AUDIO_VOLUME; 29828} 29829 29830function CMI5_SetAudioPreference (PlayPreference, intPercentOfMaxVolume) { 29831 WriteToDebug("In CMI5_SetAudioPreference PlayPreference=" + PlayPreference + ", intPercentOfMaxVolume=" + intPercentOfMaxVolume); 29832 var currentPlayPreference = cmi5.getAudioPreference(), 29833 overallResult = true, 29834 prefChanged = false, 29835 result; 29836 29837 CMI5_ClearErrorInfo(); 29838 29839 // update the local caches 29840 CMI5_PREF_AUDIO_PLAY = PlayPreference; 29841 CMI5_PREF_AUDIO_VOLUME = intPercentOfMaxVolume; 29842 29843 if ((PlayPreference == PREFERENCE_OFF) && (currentPlayPreference === null || currentPlayPreference === "on")) { 29844 // set preference to off 29845 cmi5.setAudioPreference("off"); 29846 prefChanged = true; 29847 } 29848 else if ((PlayPreference != PREFERENCE_OFF) && (currentPlayPreference === null || currentPlayPreference === "off")) { 29849 // set preference to on 29850 cmi5.setAudioPreference("on"); 29851 prefChanged = true; 29852 } 29853 29854 if (prefChanged) { 29855 result = cmi5.saveLearnerPrefs(); 29856 if (result) { 29857 WriteToDebug(" failed to save learner prefs, setting overall result to false"); 29858 overallResult = false;
29859 29860 CMI5_SetErrorInfoManually(CMI5_DEP_ERROR, "Failed to save learner preferences", result); 29861 } 29862 } 29863 29864 result = CMI5_SaveState(CMI5_STATE_VOLUME_PREFERENCE, intPercentOfMaxVolume, "text/plain", false); 29865 if (! result) { 29866 WriteToDebug(" failed to save volume preference in state, setting overall result to false"); 29867 overallResult = false; 29868 } 29869 29870 return overallResult; 29871} 29872 29873// 29874// implemented the language preference using state instead of cmi5's built in language 29875// preference because Driver's interface allows for any arbitrary string, but cmi5 doesn't; 29876// don't save with this registration because a user might want it across multiple registrations 29877// 29878function CMI5_SetLanguagePreference (strLanguage) { 29879 WriteToDebug("In CMI5_SetLanguagePreference strLanguage=" + strLanguage); 29880 29881 CMI5_ClearErrorInfo(); 29882 29883 CMI5_PREF_LANGUAGE = strLanguage; 29884 29885 return CMI5_SaveState(CMI5_STATE_LANGUAGE_PREFERENCE, strLanguage, "text/plain", false); 29886} 29887 29888function CMI5_GetLanguagePreference () { 29889 WriteToDebug("In CMI5_GetLanguagePreference"); 29890 29891 CMI5_ClearErrorInfo(); 29892 29893 if (CMI5_PREF_LANGUAGE === null) { 29894 CMI5_PREF_LANGUAGE = CMI5_RetrieveState(CMI5_STATE_LANGUAGE_PREFERENCE, false); 29895 } 29896 29897 return CMI5_PREF_LANGUAGE; 29898} 29899 29900function CMI5_SetSpeedPreference (intPercentOfMax) { 29901 WriteToDebug("In CMI5_SetSpeedPreference intPercentOfMax=" + intPercentOfMax); 29902 29903 CMI5_ClearErrorInfo(); 29904 29905 CMI5_PREF_SPEED = intPercentOfMax; 29906 29907 return CMI5_SaveState(CMI5_STATE_SPEED_PREFERENCE, intPercentOfMax, "text/plain", false); 29908} 29909 29910function CMI5_GetSpeedPreference () { 29911 WriteToDebug("In CMI5_GetSpeedPreference"); 29912 var pref; 29913 29914 CMI5_ClearErrorInfo(); 29915 29916 if (CMI5_PREF_SPEED === null) { 29917 pref = CMI5_RetrieveState(CMI5_STATE_SPEED_PREFERENCE, false); 29918 if (pref === "") { 29919 pref = 100; 29920 } 29921 29922 CMI5_PREF_SPEED = pref; 29923 } 29924 29925 return CMI5_PREF_SPEED; 29926} 29927 29928function CMI5_SetTextPreference (intPreference) { 29929 WriteToDebug("In CMI5_SetTextPreference intPreference=" + intPreference); 29930 29931 CMI5_ClearErrorInfo(); 29932 29933 CMI5_PREF_TEXT = intPreference; 29934 29935 return CMI5_SaveState(CMI5_STATE_TEXT_PREFERENCE, intPreference, "text/plain", false); 29936} 29937 29938function CMI5_GetTextPreference () { 29939 WriteToDebug("In CMI5_GetTextPreference"); 29940 29941 CMI5_ClearErrorInfo(); 29942 29943 if (CMI5_PREF_TEXT === null) { 29944 CMI5_PREF_TEXT = CMI5_RetrieveState(CMI5_STATE_TEXT_PREFERENCE, false); 29945 if (CMI5_PREF_TEXT === "") { 29946 CMI5_PREF_TEXT = PREFERENCE_DEFAULT; 29947 } 29948 } 29949 29950 return CMI5_PREF_TEXT; 29951} 29952 29953function CMI5_GetEntryMode () { 29954 WriteToDebug("In CMI5_GetEntryMode"); 29955 29956 CMI5_ClearErrorInfo(); 29957 29958 // this should be set during initialize 29959 return CMI5_ENTRY_MODE; 29960} 29961 29962function CMI5_GetLessonMode () { 29963 WriteToDebug("In CMI5_GetLessonMode - launchMode: " + cmi5.getLaunchMode()); 29964 var launchMode = cmi5.getLaunchMode(); 29965 29966 CMI5_ClearErrorInfo(); 29967 29968 if (launchMode === "Normal") { 29969 return MODE_NORMAL; 29970 } 29971 else if (launchMode === "Browse") { 29972 return MODE_BROWSE; 29973 } 29974 else if (launchMode === "Review") { 29975 return MODE_REVIEW; 29976 } 29977 29978 CMI5_SetErrorInfoManually(CMI5_DEP_ERROR, "Invalid launch mode from LMS", "launchMode = " + launchMode); 29979 29980 return null; 29981} 29982 29983function CMI5_GetTakingForCredit () { 29984 WriteToDebug("In CMI5_GetTakingForCredit - launchMode: " + cmi5.getLaunchMode()); 29985 29986 if (cmi5.getLaunchMode() === "Normal") { 29987 return true; 29988 } 29989 return false; 29990} 29991 29992/***************************** 29993* Time management 29994*****************************/ 29995 29996// 29997// returns number of milliseconds duration of all launches previous 29998// to this session 29999// 30000function CMI5_GetPreviouslyAccumulatedTime () { 30001 var retrievedValue; 30002 30003 WriteToDebug("In CMI5_GetPreviouslyAccumulatedTime - cached: " + CMI5_TOTAL_PREV_DURATION); 30004 if (CMI5_TOTAL_PREV_DURATION === null) { 30005 retrievedValue = CMI5_RetrieveState(CMI5_STATE_TOTAL_TIME); 30006 WriteToDebug(" fetched previous duration for caching: " + retrievedValue); 30007 30008 if (retrievedValue === "") { 30009 WriteToDebug(" empty value converting to zero"); 30010 CMI5_TOTAL_PREV_DURATION = 0; 30011 } 30012 else { 30013 CMI5_TOTAL_PREV_DURATION = parseInt(retrievedValue, 10); 30014 } 30015 } 30016 30017 return CMI5_TOTAL_PREV_DURATION; 30018} 30019 30020function CMI5_SaveTime (intMilliSeconds) { 30021 WriteToDebug("In CMI5_SaveTime"); 30022 30023 CMI5_SESSION_DURATION = intMilliSeconds; 30024 30025 return true; 30026} 30027 30028function CMI5_GetMaxTimeAllowed () { 30029 WriteToDebug("In CMI5_GetMaxTimeAllowed"); 30030 return MAX_CMI_TIME; 30031} 30032 30033/***************************** 30034* Scoring 30035*****************************/ 30036 30037function CMI5_SetScore (intScore, intMaxScore, intMinScore) { 30038 WriteToDebug("In CMI5_SetScore intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 30039 var score, 30040 masteryScore; 30041 30042 CMI5_ClearErrorInfo(); 30043 30044 if (cmi5.getLaunchMode() !== "Normal") { 30045 CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetScore: " + intScore, "launch mode (" + cmi5.getLaunchMode() + ")
30045not a recording mode"); 30046 return false; 30047 } 30048 30049 // 30050 // check if passed has already been committed, if so can't set a score to get recorded 30051 // 30052 if (CMI5_COMMITTED_STATUS.success === true) { 30053 CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetScore: " + intScore, "success status already committed"); 30054 return false; 30055 } 30056 30057 score = { 30058 raw: Math.round(intScore), 30059 max: intMaxScore, 30060 min: intMinScore, 30061 30062 // 30063 // SCORM 1.2 used integer values between 0 and 100 for score, 2004 introduced scaled value 30064 // so Driver's API is based on the raw value that gets normalized for the scaled value, so 30065 // we divide by 100 and then round to 2 decimal places because theoretically an integer 30066 // couldn't have more precision 30067 // 30068 // of course this is very silly in the case when min isn't 0, and/or max isn't 100 because 30069 // then the scaled score is completely out of whack, for example: 30070 // 30071 // min 0, max 4, score 2 === 0.02 30072 // min 0, max 4, masteryScore = 0.5, so passing score === 50 30073 // 30074 // *but* we can't really change Driver's implementation at this point as those ships have 30075 // sailed, so just perpetuate the awfulness 30076 // 30077 scaled: RoundToPrecision(intScore / 100, 2) 30078 }; 30079 30080 try { 30081 cmi5.validateScore(score); 30082 } 30083 catch (ex) { 30084 CMI5_SetErrorInfoManually(CMI5_ERROR_INVALID_SCORE, "Failed to SetScore: " + intScore, "invalid score: " + ex.message); 30085 return false; 30086 } 30087 30088 CMI5_PENDING_STATUS.score = score; 30089 30090 // 30091 // to mimic the SCORM behavior, check to see if there is a masteryScore and if so 30092 // determine pass/fail and set that into the pending state 30093 // 30094 masteryScore = cmi5.getMasteryScore(); 30095 if (masteryScore !== null) { 30096 if (score.scaled >= masteryScore) { 30097 WriteToDebug(" automatically calling SetPassed based on masteryScore"); 30098 return CMI5_SetPassed(); 30099 } 30100 else if (score.scaled < masteryScore) { 30101 WriteToDebug(" automatically calling SetFailed based on masteryScore"); 30102 return CMI5_SetFailed(); 30103 } 30104 } 30105 30106 return true; 30107} 30108 30109function CMI5_GetScore () { 30110 WriteToDebug("In CMI5_GetScore"); 30111 var result = ""; 30112 30113 CMI5_ClearErrorInfo(); 30114 30115 if (CMI5_COMMITTED_STATUS !== null && typeof CMI5_COMMITTED_STATUS.score !== "undefined" && CMI5_COMMITTED_STATUS.score !== null) { 30116 result = CMI5_COMMITTED_STATUS.score.raw; 30117 } 30118 else if (CMI5_PENDING_STATUS.score !== null) { 30119 result = CMI5_PENDING_STATUS.score.raw; 30120 } 30121 30122 return result; 30123} 30124 30125function CMI5_GetScaledScore () { 30126 WriteToDebug("CMI5_GetScaledScore"); 30127 var result = ""; 30128 30129 CMI5_ClearErrorInfo(); 30130 30131 if (CMI5_COMMITTED_STATUS !== null && typeof CMI5_COMMITTED_STATUS.score !== "undefined" && CMI5_COMMITTED_STATUS.score !== null) { 30132 result = CMI5_COMMITTED_STATUS.score.scaled; 30133 } 30134 else if (CMI5_PENDING_STATUS.score !== null) { 30135 result = CMI5_PENDING_STATUS.score.scaled; 30136 } 30137 30138 return result; 30139} 30140 30141function CMI5_SetPointBasedScore (intScore, intMaxScore, intMinScore) { 30142 WriteToDebug("CMI5_SetPointBasedScore - cmi5 does not support SetPointBasedScore, falling back to SetScore"); 30143 return CMI5_SetScore(intScore, intMaxScore, intMinScore); 30144} 30145 30146/*********************** 30147* Interactions 30148************************/ 30149 30150// 30151// strID and strLearningObjectiveID come in from CreateValidIdentifier so already 30152// have the AU id attached when needed 30153// 30154// strAlternateResponse and strAlternateCorrectResponse are for LMSs that can't 30155// handle longer values, we never need them 30156// 30157// dtmTime is just the current date/time which we capture as the 'timestamp' in 30158// the statements anyways so no need to use it (cause the API user can't pass it) 30159// 30160function CMI5_RecordInteraction ( 30161 strID, 30162 strResponse, 30163 blnCorrect, 30164 strCorrectResponse, 30165 strDescription, 30166 intWeighting, 30167 intLatency, 30168 strLearningObjectiveID, 30169 dtmTime, 30170 interactionType, 30171 strAlternateResponse, 30172 strAlternateCorrectResponse 30173) { 30174 var st; 30175 30176 CMI5_ClearErrorInfo(); 30177 30178 if (cmi5.getLaunchMode() !== "Normal") { 30179 CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to RecordInteraction: launch mode (" + cmi5.getLaunchMode() + ")
30179not a recording mode"); 30180 return false; 30181 } 30182 30183 st = cmi5.prepareStatement("http://adlnet.gov/expapi/verbs/answered"); 30184 30185 st.result = st.result || new TinCan.Result(); 30186 if (strResponse !== null) { 30187 st.result.response = strResponse; 30188 } 30189 30190 // 30191 // for INTERACTION_RESULT_UNANTICIPATED or INTERACTION_RESULT_NEUTRAL just 30192 // leave .success out 30193 // 30194 if (blnCorrect === true || blnCorrect === INTERACTION_RESULT_CORRECT) { 30195 st.result.success = true; 30196 } 30197 else if (blnCorrect === false || blnCorrect === "" || blnCorrect === "false" || blnCorrect === INTERACTION_RESULT_WRONG) { 30198 st.result.success = false; 30199 } 30200 30201 if (intLatency !== null && intLatency !== "") { 30202 st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(intLatency); 30203 } 30204 if (intWeighting !== null && intWeighting !== "") { 30205 st.result.extensions = st.result.extensions || {}; 30206 st.result.extensions["http://id.tincanapi.com/extension/cmi-interaction-weighting"] = intWeighting; 30207 } 30208 30209 // 30210 // preparing a cmi5 statement sets the target as the AU's Activity 30211 // but we want to make that the parent and instead use the interaction Activity 30212 // as the target of the allowed statement 30213 // 30214 st.context.contextActivities = st.context.contextActivities || new TinCan.ContextActivities(); 30215 st.context.contextActivities.parent = st.context.contextActivities.parent || []; 30216 st.context.contextActivities.parent.push(st.target); 30217 30218 if (strLearningObjectiveID !== "") { 30219 st.context.contextActivities.grouping = st.context.contextActivities.grouping || []; 30220 st.context.contextActivities.grouping.push( 30221 new TinCan.Activity( 30222 { 30223 id: strLearningObjectiveID 30224 } 30225 ) 30226 ); 30227 } 30228 30229 // 30230 // now with the parent set to the AU Activity set up our interaction Activity 30231 // as the target of the statement 30232 // 30233 st.target = new TinCan.Activity( 30234 { 30235 id: strID, 30236 definition: { 30237 type: CMI5_INTERACTION_ACTIVITY_TYPE, 30238 interactionType: interactionType 30239 } 30240 } 30241 ); 30242 30243 // FEATURE: provide a way to configure what language is in use? 30244 if (strDescription !== null && strDescription !== "") { 30245 st.target.definition.description = { 30246 und: strDescription 30247 }; 30248 } 30249 30250 // 30251 // this makes it so that "" can never be a correct response, but that's the way 30252 // the other standards in Driver work, and is potentially a reasonable expectation 30253 // 30254 if (strCorrectResponse !== null && strCorrectResponse !== "") { 30255 st.target.definition.correctResponsesPattern = [strCorrectResponse]; 30256 } 30257 30258 CMI5_STATEMENT_QUEUE.push(st); 30259 30260 CMI5_CreateInteraction.apply(this, arguments); 30261 CMI5_SaveInteraction(strID); 30262 30263 return true; 30264} 30265 30266function CMI5_RecordTrueFalseInteraction (strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30267 WriteToDebug( 30268 "In CMI5_RecordTrueFalseInteraction strID=" + strID + ", strResponse=" + strResponse + 30269 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 30270 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30271 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 30272 ); 30273 30274 var strResponse = null, 30275 strCorrectResponse = null; 30276 30277 if (blnResponse === true) { 30278 strResponse = "true"; 30279 } 30280 // test for false b/c it could be null in which case we want to leave it as `null` 30281 else if (blnResponse === false) { 30282 strResponse = "false"; 30283 } 30284 30285 if (blnCorrectResponse === true) { 30286 strCorrectResponse = "true"; 30287 } 30288 // test for false b/c it could be null in which case we want to leave it as `null` 30289 else if (blnCorrectResponse === false) { 30290 strCorrectResponse = "false"; 30291 } 30292 30293 return CMI5_RecordInteraction( 30294 strID, 30295 strResponse, 30296 blnCorrect, 30297 strCorrectResponse, 30298 strDescription, 30299 intWeighting, 30300 intLatency, 30301 strLearningObjectiveID, 30302 dtmTime, 30303 CMI5_INTERACTION_TYPE_TRUE_FALSE 30304 ); 30305} 30306 30307function CMI5_RecordMultipleChoiceInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30308 WriteToDebug( 30309 "In CMI5_RecordMultipleChoiceInteraction strID=" + strID + ", aryResponse=" + aryResponse + 30310 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 30311 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30312 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30313 ); 30314 30315 var strResponse = null, 30316 strCorrectResponse = "", 30317 i; 30318 30319 if (aryResponse !== null) { 30320 strResponse = ""; 30321 30322 for (i = 0; i < aryResponse.length; i += 1) { 30323 if (strResponse.length > 0) { 30324 strResponse += "[,]"; 30325 } 30326 30327 strResponse += (aryResponse[i].Long !== "" ? aryResponse[i].Long : aryResponse[i].Short); 30328 } 30329 } 30330 30331 for (i = 0; i < aryCorrectResponse.length; i += 1) { 30332 if (strCorrectResponse.length > 0) { 30333 strCorrectResponse += "[,]"; 30334 } 30335 30336 strCorrectResponse += (aryCorrectResponse[i].Long !== "" ? aryCorrectResponse[i].Long : aryCorrectResponse[i].Short); 30337 } 30338 30339 return CMI5_RecordInteraction( 30340 strID, 30341 strResponse, 30342 blnCorrect, 30343 strCorrectResponse, 30344 strDescription, 30345 intWeighting, 30346 intLatency, 30347 strLearningObjectiveID, 30348 dtmTime, 30349 CMI5_INTERACTION_TYPE_CHOICE 30350 ); 30351} 30352 30353function CMI5_RecordFillInInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30354 WriteToDebug( 30355 "In CMI5_RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse + 30356 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 30357 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30358 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 30359 ); 30360 30361 var interactionType = CMI5_INTERACTION_TYPE_FILL_IN; 30362 30363 if (strResponse !== null) { 30364 strResponse = new String(strResponse); 30365 } 30366 30367 if (strCorrectResponse !== null) { 30368 strCorrectResponse = new String(strCorrectResponse); 30369 30370 if (strCorrectResponse.length > 250) { 30371 interactionType = CMI5_INTERACTION_TYPE_LONG_FILL_IN; 30372 } 30373 } 30374 30375 return CMI5_RecordInteraction( 30376 strID, 30377 strResponse, 30378 blnCorrect, 30379 strCorrectResponse, 30380 strDescription, 30381 intWeighting, 30382 intLatency, 30383 strLearningObjectiveID, 30384 dtmTime, 30385 interactionType 30386 ); 30387} 30388 30389function CMI5_RecordMatchingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30390 WriteToDebug( 30391 "In CMI5_RecordMatchingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 30392 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 30393 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30394 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 30395 ); 30396 30397 var strResponse = null, 30398 strCorrectResponse = "", 30399 i; 30400 30401 if (aryResponse !== null) { 30402 strResponse = ""; 30403 30404 for (i = 0; i < aryResponse.length; i += 1) { 30405 if (strResponse.length > 0) { 30406 strResponse += "[,]"; 30407 } 30408 30409 strResponse += (aryResponse[i].Source.Long !== "" ? aryResponse[i].Source.Long : aryResponse[i].Source.Short) + "[.]" + (aryResponse[i].Target.Long !== "" ? aryResponse[i].Target.Long : aryResponse[i].Target.Short); 30410 } 30411 } 30412 30413 for (i = 0; i < aryCorrectResponse.length; i += 1) { 30414 if (strCorrectResponse.length > 0) { 30415 strCorrectResponse += "[,]"; 30416 } 30417 30418 strCorrectResponse += (aryCorrectResponse[i].Source.Long !== "" ? aryCorrectResponse[i].Source.Long : aryCorrectResponse[i].Source.Short) + "[.]" + (aryCorrectResponse[i].Target.Long !== "" ? aryCorrectResponse[i].Target.Long : aryCorrectResponse[i].Target.Short); 30419 } 30420 30421 return CMI5_RecordInteraction( 30422 strID, 30423 strResponse, 30424 blnCorrect, 30425 strCorrectResponse, 30426 strDescription, 30427 intWeighting, 30428 intLatency, 30429 strLearningObjectiveID, 30430 dtmTime, 30431 CMI5_INTERACTION_TYPE_MATCHING 30432 ); 30433} 30434 30435function CMI5_RecordPerformanceInteraction (strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30436 WriteToDebug( 30437 "In CMI5_RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse + 30438 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 30439 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30440 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime
30441 ); 30442 30443 if (strResponse !== null) { 30444 strResponse = new String(strResponse); 30445 } 30446 30447 if (strCorrectResponse !== null) { 30448 strCorrectResponse = new String(strCorrectResponse); 30449 } 30450 30451 return CMI5_RecordInteraction( 30452 strID, 30453 strResponse, 30454 blnCorrect, 30455 strCorrectResponse, 30456 strDescription, 30457 intWeighting, 30458 intLatency, 30459 strLearningObjectiveID, 30460 dtmTime, 30461 CMI5_INTERACTION_TYPE_PERFORMANCE 30462 ); 30463} 30464 30465function CMI5_RecordSequencingInteraction (strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30466 WriteToDebug( 30467 "In CMI5_RecordSequencingInteraction strID=" + strID + ", aryResponse=" + aryResponse + 30468 ", blnCorrect=" + blnCorrect + ", aryCorrectResponse=" + aryCorrectResponse + 30469 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30470 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 30471 ); 30472 30473 var strResponse = null, 30474 strCorrectResponse = "", 30475 i; 30476 30477 if (aryResponse !== null) { 30478 strResponse = ""; 30479 30480 for (i = 0; i < aryResponse.length; i += 1) { 30481 if (strResponse.length > 0) { 30482 strResponse += "[,]"; 30483 } 30484 30485 strResponse += (aryResponse[i].Long !== "" ? aryResponse[i].Long : aryResponse[i].Short); 30486 } 30487 } 30488 30489 for (i = 0; i < aryCorrectResponse.length; i += 1) { 30490 if (strCorrectResponse.length > 0) { 30491 strCorrectResponse += "[,]"; 30492 } 30493 30494 strCorrectResponse += (aryCorrectResponse[i].Long !== "" ? aryCorrectResponse[i].Long : aryCorrectResponse[i].Short); 30495 } 30496 30497 return CMI5_RecordInteraction( 30498 strID, 30499 strResponse, 30500 blnCorrect, 30501 strCorrectResponse, 30502 strDescription, 30503 intWeighting, 30504 intLatency, 30505 strLearningObjectiveID, 30506 dtmTime, 30507 CMI5_INTERACTION_TYPE_SEQUENCING 30508 ); 30509} 30510 30511function CMI5_RecordLikertInteraction (strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30512 WriteToDebug( 30513 "In CMI5_RecordLikertInteraction strID=" + strID + ", response=" + response + 30514 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 30515 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30516 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 30517 ); 30518 30519 var strResponse = null, 30520 strCorrectResponse = ""; 30521 30522 if (response !== null) { 30523 strResponse = (response.Long !== "" ? response.Long : response.Short); 30524 } 30525 if (correctResponse !== null) { 30526 strCorrectResponse = (correctResponse.Long !== "" ? correctResponse.Long : correctResponse.Short); 30527 } 30528 30529 return CMI5_RecordInteraction( 30530 strID, 30531 strResponse, 30532 blnCorrect, 30533 strCorrectResponse, 30534 strDescription, 30535 intWeighting, 30536 intLatency, 30537 strLearningObjectiveID, 30538 dtmTime, 30539 CMI5_INTERACTION_TYPE_LIKERT 30540 ); 30541} 30542 30543function CMI5_RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime) { 30544 WriteToDebug( 30545 "In CMI5_RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse + 30546 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 30547 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + 30548 ", strLearningObjectiveID=" + strLearningObjectiveID + ", dtmTime=" + dtmTime 30549 ); 30550 30551 if (typeof strCorrectResponse !== "undefined" && strCorrectResponse !== null) { 30552 // cmi5 can handle numeric ranges or decimals. As long as we received one of those we are ok to continue 30553 if (! IsValidDecimalRange(strCorrectResponse) && ! IsValidDecimal(strCorrectResponse)) { 30554 WriteToDebug("Returning False - CMI5_RecordNumericInteraction received invali
30554d correct response (not a decimal or range), strCorrectResponse=" + strCorrectResponse); 30555 return false; 30556 } 30557 } 30558 30559 return CMI5_RecordInteraction( 30560 strID, 30561 strResponse, 30562 blnCorrect, 30563 strCorrectResponse, 30564 strDescription, 30565 intWeighting, 30566 intLatency, 30567 strLearningObjectiveID, 30568 dtmTime, 30569 CMI5_INTERACTION_TYPE_NUMERIC 30570 ); 30571} 30572 30573// 30574// Previously recorded interactions have metadata that can be accessed 30575// by the following functions for some standards, implemented this via 30576// the state mechanism and a local cache, if the interation hasn't been 30577// previously recorded then the content should not expect to use these 30578// 30579function CMI5_CreateInteraction ( 30580 strID, 30581 strResponse, 30582 blnCorrect, 30583 strCorrectResponse, 30584 strDescription, 30585 intWeighting, 30586 intLatency, 30587 strLearningObjectiveID, 30588 dtmTime, 30589 interactionType, 30590 strAlternateResponse, 30591 strAlternateCorrectResponse 30592) { 30593 CMI5_INTERACTIONS[strID] = { 30594 type: interactionType, 30595 timestamp: dtmTime.toJSON(), 30596 correctResponses: strCorrectResponse, 30597 learnerResponses: strResponse, 30598 weighting: intWeighting, 30599 result: blnCorrect, 30600 latency: intLatency, 30601 description: strDescription 30602 }; 30603 30604 return; 30605} 30606 30607function CMI5_LoadInteraction (strInteractionId) { 30608 WriteToDebug("CMI5_LoadInteraction - strInteractionId = " + strInteractionId); 30609 var result; 30610 30611 if (typeof CMI5_INTERACTIONS[strInteractionId] !== "undefined") { 30612 WriteToDebug(" already locally cached, returning"); 30613 return true; 30614 } 30615 30616 result = CMI5_RetrieveState(strInteractionId); 30617 30618 // 30619 // retrieve state doesn't provide a return value for detecting errors 30620 // so check to see if we have manually set errors (cause we shouldn't 30621 // unless the retrieve failed) 30622 // 30623 if (intCMI5Error) { 30624 WriteToDebug(" failed to retrieve state for interaction: " + strCMI5ErrorString); 30625 return false; 30626 } 30627 30628 if (result !== "" && typeof result.timestamp === "undefined") { 30629 // interaction was found but most likely failed to parse as JSON 30630 // which retrieveState in TinCanJS eats as an error, but make it 30631 // one here 30632 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_INVALID_JSON, "Invalid JSON for interaction in State", "result= " + result); 30633 return false; 30634 } 30635 30636 if (result === "") { 30637 // interaction doesn't exist which is okay here, 30638 // just don't store anything in our local cache 30639 return true; 30640 } 30641 30642 CMI5_INTERACTIONS[strInteractionId] = result; 30643 30644 WriteToDebug(" cached interaction locally"); 30645 return true; 30646} 30647 30648function CMI5_SaveInteraction (strInteractionId) { 30649 WriteToDebug("CMI5_SaveInteraction - strInteractionId = " + strInteractionId); 30650 30651 if (typeof CMI5_INTERACTIONS[strInteractionId] === "undefined") { 30652 CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_INTERACTION, "Cannot save unknown interaction: " + strInteractionId); 30653 return false; 30654 } 30655 30656 return CMI5_SaveState(strInteractionId, CMI5_INTERACTIONS[strInteractionId], "application/json"); 30657} 30658 30659function CMI5_GetInteractionType(strInteractionID) { 30660 WriteToDebug("CMI5_GetInteractionType - strInteractionID = " + strInteractionID); 30661 var result; 30662 30663 CMI5_ClearErrorInfo(); 30664 30665 result = CMI5_LoadInteraction(strInteractionID); 30666 if (! result) { 30667 WriteToDebug("Failed to load interaction: " + strInteractionID); 30668 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction type: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30669 return false; 30670 } 30671 30672 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30673 return CMI5_INTERACTIONS[strInteractionID].type; 30674 } 30675 30676 return ""; 30677} 30678 30679function CMI5_GetInteractionTimestamp(strInteractionID) { 30680 WriteToDebug("CMI5_GetInteractionTimestamp - strInteractionID = " + strInteractionID); 30681 var result; 30682 30683 CMI5_ClearErrorInfo(); 30684 30685 result = CMI5_LoadInteraction(strInteractionID); 30686 if (! result) { 30687 WriteToDebug("Failed to load interaction: " + strInteractionID); 30688 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction timestamp: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30689 return false;
30690 } 30691 30692 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30693 return new Date(CMI5_INTERACTIONS[strInteractionID].timestamp); 30694 } 30695 30696 return ""; 30697} 30698 30699function CMI5_GetInteractionCorrectResponses(strInteractionID) { 30700 WriteToDebug("CMI5_GetInteractionCorrectResponses - strInteractionID = " + strInteractionID); 30701 var result, 30702 responses = []; 30703 30704 CMI5_ClearErrorInfo(); 30705 30706 result = CMI5_LoadInteraction(strInteractionID); 30707 if (! result) { 30708 WriteToDebug("Failed to load interaction: " + strInteractionID); 30709 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction correct responses: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30710 return false; 30711 } 30712 30713 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30714 responses = CMI5_INTERACTIONS[strInteractionID].correctResponses.split("[,]"); 30715 30716 if (CMI5_INTERACTIONS[strInteractionID].type === CMI5_INTERACTION_TYPE_MATCHING) { 30717 responses = CMI5_ProcessResponseArray(responses); 30718 } 30719 } 30720 30721 return responses; 30722} 30723 30724function CMI5_GetInteractionLearnerResponses(strInteractionID) { 30725 WriteToDebug("CMI5_GetInteractionLearnerResponses - strInteractionID = " + strInteractionID); 30726 var result, 30727 responses = []; 30728 30729 CMI5_ClearErrorInfo(); 30730 30731 result = CMI5_LoadInteraction(strInteractionID); 30732 if (! result) { 30733 WriteToDebug("Failed to load interaction: " + strInteractionID); 30734 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction learner responses: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30735 return false; 30736 } 30737 30738 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30739 responses = CMI5_INTERACTIONS[strInteractionID].learnerResponses.split("[,]"); 30740 30741 if (CMI5_INTERACTIONS[strInteractionID].type === CMI5_INTERACTION_TYPE_MATCHING) { 30742 responses = CMI5_ProcessResponseArray(responses); 30743 } 30744 } 30745 30746 return responses; 30747} 30748 30749function CMI5_ProcessResponseArray(responses) { 30750 var i; 30751 30752 for (i = 0; i < responses.length; i += 1) { 30753 responses[i] = CreateMatchingResponse(responses[i]); 30754 } 30755 30756 return responses; 30757} 30758 30759function CMI5_GetInteractionWeighting(strInteractionID) { 30760 WriteToDebug("CMI5_GetInteractionWeighting - strInteractionID = " + strInteractionID); 30761 var result; 30762 30763 CMI5_ClearErrorInfo(); 30764 30765 result = CMI5_LoadInteraction(strInteractionID); 30766 if (! result) { 30767 WriteToDebug("Failed to load interaction: " + strInteractionID); 30768 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction weighting: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30769 return false; 30770 } 30771 30772 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30773 return CMI5_INTERACTIONS[strInteractionID].weighting; 30774 } 30775 30776 return ""; 30777} 30778 30779function CMI5_GetInteractionResult(strInteractionID) { 30780 WriteToDebug("CMI5_GetInteractionResult - strInteractionID = " + strInteractionID); 30781 var result; 30782 30783 CMI5_ClearErrorInfo(); 30784 30785 result = CMI5_LoadInteraction(strInteractionID); 30786 if (! result) { 30787 WriteToDebug("Failed to load interaction: " + strInteractionID); 30788 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction result: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30789 return false; 30790 } 30791 30792 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30793 return CMI5_INTERACTIONS[strInteractionID].result; 30794 } 30795 30796 return ""; 30797} 30798 30799function CMI5_GetInteractionLatency(strInteractionID) { 30800 WriteToDebug("CMI5_GetInteractionLatency - strInteractionID = " + strInteractionID); 30801 var result; 30802 30803 CMI5_ClearErrorInfo(); 30804 30805 result = CMI5_LoadInteraction(strInteractionID); 30806 if (! result) { 30807 WriteToDebug("Failed to load interaction: " + strInteractionID); 30808 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction latency: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30809 return false;
30810 } 30811 30812 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30813 return CMI5_INTERACTIONS[strInteractionID].latency; 30814 } 30815 30816 return ""; 30817} 30818 30819function CMI5_GetInteractionDescription(strInteractionID) { 30820 WriteToDebug("CMI5_GetInteractionDescription - strInteractionID = " + strInteractionID); 30821 var result; 30822 30823 CMI5_ClearErrorInfo(); 30824 30825 result = CMI5_LoadInteraction(strInteractionID); 30826 if (! result) { 30827 WriteToDebug("Failed to load interaction: " + strInteractionID); 30828 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_LOAD_FAILED, "Cannot get interaction description: " + strInteractionID, "Failed to load interaction: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 30829 return false; 30830 } 30831 30832 if (typeof CMI5_INTERACTIONS[strInteractionID] !== "undefined") { 30833 return CMI5_INTERACTIONS[strInteractionID].description; 30834 } 30835 30836 return ""; 30837} 30838 30839/***************************** 30840* Objectives 30841*****************************/ 30842 30843function CMI5_SetObjectiveScore (strObjectiveID, intScore, intMaxScore, intMinScore) { 30844 WriteToDebug("In CMI5_SetObjectiveScore, strObjectiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 30845 30846 ClearErrorInfo(); 30847 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveScore not implemented in cmi5", ""); 30848 30849 return false; 30850} 30851 30852function CMI5_SetObjectiveDescription (strObjectiveID, strObjectiveDescription) { 30853 WriteToDebug("In CMI5_SetObjectiveDescription, strObjectiveDescription=" + strObjectiveDescription); 30854 30855 ClearErrorInfo(); 30856 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveDescription not implemented in cmi5", ""); 30857 30858 return false; 30859} 30860 30861function CMI5_SetObjectiveStatus (strObjectiveID, Lesson_Status) { 30862 WriteToDebug("In CMI5_SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status); 30863 30864 ClearErrorInfo(); 30865 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveStatus not implemented in cmi5", ""); 30866 30867 return false; 30868} 30869 30870function CMI5_SetObjectiveProgressMeasure () { 30871 WriteToDebug("In CMI5_SetObjectiveProgressMeasure"); 30872 30873 ClearErrorInfo(); 30874 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetObjectiveProgressMeasure not implemented in cmi5", ""); 30875 30876 return false; 30877} 30878 30879function CMI5_GetObjectiveScore (strObjectiveID) { 30880 WriteToDebug("In CMI5_GetObjectiveScore, strObjectiveID=" + strObjectiveID); 30881 30882 ClearErrorInfo(); 30883 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveScore not implemented in cmi5", ""); 30884 30885 return false; 30886} 30887 30888function CMI5_GetObjectiveDescription (strObjectiveID) { 30889 WriteToDebug("In CMI5_GetObjectiveDescription, strObjectiveID=" + strObjectiveID); 30890 30891 ClearErrorInfo(); 30892 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveDescription not implemented in cmi5", ""); 30893 30894 return false; 30895} 30896 30897function CMI5_GetObjectiveStatus (strObjectiveID) { 30898 WriteToDebug("In CMI5_GetObjectiveStatus, strObjectiveID=" + strObjectiveID); 30899 30900 ClearErrorInfo(); 30901 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveStatus not implemented in cmi5", ""); 30902 30903 return false; 30904} 30905 30906function CMI5_GetObjectiveProgressMeasure () { 30907 WriteToDebug("CMI5_GetObjectiveProgressMeasure"); 30908 30909 ClearErrorInfo(); 30910 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetObjectiveProgressMeasure not implemented in cmi5", ""); 30911 30912 return false; 30913} 30914 30915/***************************** 30916* Status Management 30917*****************************/ 30918 30919function CMI5_SetFailed () { 30920 WriteToDebug("In CMI5_SetFailed"); 30921 var masteryScore, 30922 auActivityId, 30923 tempSt, 30924 i, 30925 st; 30926 30927 CMI5_ClearErrorInfo(); 30928 30929 CMI5_ResetBlnStatusWasSetIfAppropriate(); 30930 30931 if (cmi5.getLaunchMode() !== "Normal") { 30932 CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetFailed", "Launch mode (" + cmi5.getLaunchMode() + ") not a recording mode"); 30933 return false; 30934 } 30935 30936 // 30937 // check to see if success has already been permanently set 30938 // 30939 if (CMI5_COMMITTED_STATUS.success === true) { 30940 CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetFailed", "Success status already committed"); 30941 return false;
30942 } 30943 30944 // 30945 // if pending status already set nothing to do 30946 // 30947 if (CMI5_PENDING_STATUS.success === false) { 30948 // FEATURE: should we update the duration/timestamp of the pending statement? 30949 return true; 30950 } 30951 30952 // 30953 // check to see if a pending score has been set and if so make sure it works with masteryScore 30954 // 30955 masteryScore = cmi5.getMasteryScore(); 30956 if (CMI5_PENDING_STATUS.score !== null && masteryScore !== null && CMI5_PENDING_STATUS.score.scaled >= masteryScore) { 30957 CMI5_SetErrorInfoManually(CMI5_ERROR_INVALID_STATUS_BASED_ON_SCORE, "Failed to SetFailed", "Pending score conflicts with failure"); 30958 return false; 30959 } 30960 30961 // 30962 // if not permanently set but is pending set then need to remove passed statement 30963 // 30964 if (CMI5_PENDING_STATUS.success === true) { 30965 auActivityId = cmi5.getActivity().id; 30966 30967 for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) { 30968 tempSt = CMI5_STATEMENT_QUEUE[i]; 30969 30970 if (tempSt.target.id === auActivityId && tempSt.verb.id === CMI5_VERB_ID_PASSED) { 30971 CMI5_STATEMENT_QUEUE.splice(i, i + 1); 30972 } 30973 } 30974 } 30975 CMI5_PENDING_STATUS.success = false; 30976 30977 st = cmi5.failedStatement(); 30978 30979 CMI5_SetDurationInStatement(st); 30980 30981 CMI5_STATEMENT_QUEUE.push(st); 30982 30983 return true; 30984} 30985 30986function CMI5_SetPassed () { 30987 WriteToDebug("In CMI5_SetPassed"); 30988 var masteryScore, 30989 auActivityId, 30990 tempSt, 30991 i, 30992 st; 30993 30994 CMI5_ClearErrorInfo(); 30995 30996 CMI5_ResetBlnStatusWasSetIfAppropriate(); 30997 30998 if (cmi5.getLaunchMode() !== "Normal") { 30999 CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetPassed", "Launch mode (" + cmi5.getLaunchMode() + ") not a recording mode"); 31000 return false; 31001 } 31002 31003 // 31004 // check to see if success has already been permanently set 31005 // 31006 if (CMI5_COMMITTED_STATUS.success === true) { 31007 CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetPassed", "Success status already committed"); 31008 return false; 31009 } 31010 31011 // 31012 // if pending status already set nothing to do 31013 // 31014 if (CMI5_PENDING_STATUS.success === true) { 31015 // FEATURE: should we update the duration/timestamp of the pending statement? 31016 return true; 31017 } 31018 31019 // 31020 // check to see if a pending score has been set and if so make sure it works with masteryScore 31021 // 31022 masteryScore = cmi5.getMasteryScore(); 31023 if (CMI5_PENDING_STATUS.score !== null && masteryScore !== null && CMI5_PENDING_STATUS.score.scaled < masteryScore) { 31024 CMI5_SetErrorInfoManually(CMI5_ERROR_INVALID_STATUS_BASED_ON_SCORE, "Failed to SetPassed", "Pending score conflicts with failure"); 31025 return false; 31026 } 31027 31028 CMI5_PENDING_STATUS.success = true; 31029 31030 st = cmi5.passedStatement(); 31031 31032 CMI5_SetDurationInStatement(st); 31033 31034 CMI5_STATEMENT_QUEUE.push(st); 31035 31036 return true; 31037} 31038 31039function CMI5_SetCompleted () { 31040 WriteToDebug("In CMI5_SetCompleted"); 31041 var st; 31042 31043 CMI5_ClearErrorInfo(); 31044 31045 if (cmi5.getLaunchMode() !== "Normal") { 31046 CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to SetCompleted", "Launch mode (" + cmi5.getLaunchMode() + ") not a recording mode"); 31047 return false; 31048 } 31049 31050 // 31051 // check to see if completion has already been permanently set 31052 // 31053 if (CMI5_COMMITTED_STATUS.completion !== null) { 31054 CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to SetCompleted", "Completion status already committed"); 31055 return false; 31056 } 31057 31058 // 31059 // if pending status already set nothing to do 31060 // 31061 if (CMI5_PENDING_STATUS.completion !== null) { 31062 // FEATURE: should we update the duration/timestamp of the pending statement? 31063 return true; 31064 } 31065 CMI5_PENDING_STATUS.completion = true; 31066 31067 st = cmi5.completedStatement(); 31068 31069 CMI5_STATEMENT_QUEUE.push(st); 31070 31071 CMI5_SetDurationInStatement(st); 31072 31073 return true; 31074} 31075 31076function CMI5_ResetStatus () { 31077 WriteToDebug("In CMI5_ResetStatus"); 31078 var auActivityId, 31079 i, 31080 tempSt; 31081 31082 CMI5_ClearErrorInfo(); 31083 31084 if (cmi5.getLaunchMode() !== "Normal") { 31085 CMI5_SetErrorInfoManually(CMI5_ERROR_NON_NORMAL_MODE, "Failed to ResetStatus", "Launc
31085h mode (" + cmi5.getLaunchMode() + ") not a recording mode"); 31086 return false; 31087 } 31088 31089 // 31090 // check to see if any previous status information has already 31091 // been committed, if it has then don't allow resetting the status 31092 // 31093 if (CMI5_COMMITTED_STATUS.completion !== null || CMI5_COMMITTED_STATUS.success === true) { 31094 CMI5_SetErrorInfoManually(CMI5_ERROR_ALREADY_COMMITTED, "Failed to ResetStatus", "Success and/or completion status has already been committed"); 31095 return false; 31096 } 31097 31098 // FEATURE: make it configurable to do partial resets? 31099 31100 CMI5_PENDING_STATUS.completion = null; 31101 CMI5_PENDING_STATUS.success = null; 31102 31103 auActivityId = cmi5.getActivity().id; 31104 for (i = 0; i < CMI5_STATEMENT_QUEUE.length; i += 1) { 31105 tempSt = CMI5_STATEMENT_QUEUE[i]; 31106 31107 if ( 31108 tempSt.target.id = auActivityId 31109 && 31110 (tempSt.verb.id === CMI5_VERB_ID_COMPLETED || tempSt.verb.id === CMI5_VERB_ID_PASSED || tempSt.verb.id === CMI5_VERB_ID_FAILED) 31111 ) { 31112 CMI5_STATEMENT_QUEUE.splice(i, i + 1); 31113 } 31114 } 31115 31116 return true; 31117} 31118 31119function CMI5_GetStatus () { 31120 WriteToDebug("In CMI5_GetStatus"); 31121 var result, 31122 i, 31123 numAttempts = 0; 31124 31125 CMI5_ClearErrorInfo(); 31126 31127 if (CMI5_COMMITTED_STATUS.success === true || CMI5_PENDING_STATUS.success === true) { 31128 result = LESSON_STATUS_PASSED; 31129 } 31130 else if (CMI5_COMMITTED_STATUS.success === false || CMI5_PENDING_STATUS.success === false) { 31131 result = LESSON_STATUS_FAILED; 31132 } 31133 else if (CMI5_COMMITTED_STATUS.completion === true || CMI5_PENDING_STATUS.completion === true) { 31134 result = LESSON_STATUS_COMPLETED; 31135 } 31136 else { 31137 for (i = 0; i < CMI5_COMMITTED_STATUS.launchModes.length; i += 1) { 31138 if (CMI5_COMMITTED_STATUS.launchModes[i] === "Normal") { 31139 numAttempts += 1; 31140 } 31141 } 31142 31143 if (numAttempts === 0) { 31144 result = LESSON_STATUS_BROWSED; 31145 } 31146 else { 31147 if (CMI5_ENTRY_MODE === ENTRY_FIRST_TIME) { 31148 result = LESSON_STATUS_NOT_ATTEMPTED; 31149 } 31150 else { 31151 result = LESSON_STATUS_INCOMPLETE; 31152 } 31153 } 31154 } 31155 31156 WriteToDebug("In CMI5_GetStatus - result = " + result); 31157 return result; 31158} 31159 31160function CMI5_GetPassingScore () { 31161 WriteToDebug("In CMI5_GetPassingScore"); 31162 var result; 31163 31164 CMI5_ClearErrorInfo(); 31165 31166 result = cmi5.getMasteryScore(); 31167 if (result === null) { 31168 return 0; 31169 } 31170 31171 // 31172 // Driver's interface assumes an integer in the range of 0 to 100 for SCORM 1.2 31173 // conformance, so multiply by 100 (see comment in CMI5_SetScore why this is silly) 31174 // 31175 result = result * 100; 31176 31177 return result; 31178} 31179 31180/***************************** 31181* Shared State Persistence 31182*****************************/ 31183 31184function CMI5_DetectSSPSupport () { 31185 WriteToDebug("CMI5_DetectSSPSupport - implemented via state"); 31186 31187 ClearErrorInfo(); 31188 31189 return true; 31190} 31191 31192function CMI5_CreateDataBucket (strBucketId, intMinSize, intMaxSize) { 31193 WriteToDebug("CMI5_CreateDataBucket - strBucketId = " + strBucketId); 31194 var result; 31195 31196 ClearErrorInfo(); 31197 31198 // 31199 // theoretically buckets are unique so make sure it doesn't already exist, 31200 // but it is possible this session hasn't laoded it yet, so make sure to 31201 // have fetched it first 31202 // 31203 result = CMI5_LoadBucket(strBucketId); 31204 if (! result) { 31205 WriteToDebug("Failed pre-existing bucket check, bucket can't be allocated"); 31206 CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Failed to create bucket: " + strBucketId, "pre-existence check load failure: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 31207 return false; 31208 } 31209 31210 if (typeof CMI5_SSP_BUCKETS[strBucketId] !== "undefined") { 31211 WriteToDebug("Bucket already exists and can't be re-allocated."); 31212 CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_ALREADY_EXISTS, "Failed to create bucket: " + strBucketId, ""); 31213 return false; 31214 } 31215 31216 // 31217 // the bucket size is relatively limited and allowed to be set by the LMS, 31218 // an LRS is unlikely to have that small of a limit on state so we assume 31219 // that the max requested size is the amount that is always available 31220 // 31221 // the value stored in a bucket is always a string, so go ahead and initialize 31222 // it in an empty state 31223 // 31224 CMI5_SSP_BUCKETS[strBucketId] = { 31225 allocatedSpace: intMaxSize, 31226 contents: "" 31227 }; 31228 return CMI5_SaveBucket(strBucketId); 31229} 31230 31231function CMI5_GetDataFromBucket (strBucketId) { 31232 WriteToDebug("CMI5_GetDataFromBucket - strBucketId = " + strBucketId); 31233 var result; 31234 31235 ClearErrorInfo(); 31236 31237 result = CMI5_LoadBucket(strBucketId); 31238 if (! result) { 31239 WriteToDebug("Failed to load bucket: " + strBucketId); 31240 CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Cannot get data from bucket: " + strBucketId, "Failed to load bucket: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 31241 return false;
31242 } 31243 31244 if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") { 31245 CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot get data from bucket: " + strBucketId, "unknown after load"); 31246 return false; 31247 } 31248 31249 return CMI5_SSP_BUCKETS[strBucketId].contents; 31250} 31251 31252function CMI5_PutDataInBucket (strBucketId, strData, blnAppendToEnd) { 31253 WriteToDebug("CMI5_PutDataInBucket - strBucketId = " + strBucketId); 31254 var result; 31255 31256 ClearErrorInfo(); 31257 31258 result = CMI5_LoadBucket(strBucketId); 31259 if (! result) { 31260 WriteToDebug("Failed to load bucket: " + strBucketId); 31261 CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Cannot put data into bucket: " + strBucketId, "Failed to load bucket: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 31262 return false; 31263 } 31264 31265 if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") { 31266 CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot put data into bucket: " + strBucketId, "unknown after load"); 31267 return false; 31268 } 31269 31270 if (blnAppendToEnd) { 31271 CMI5_SSP_BUCKETS[strBucketId].contents = CMI5_SSP_BUCKETS[strBucketId].contents + strData; 31272 } 31273 else { 31274 CMI5_SSP_BUCKETS[strBucketId].contents = strData; 31275 } 31276 31277 return CMI5_SaveBucket(strBucketId); 31278} 31279 31280function CMI5_GetBucketInfo (strBucketId) { 31281 WriteToDebug("CMI5_GetBucketInfo - strBucketId = " + strBucketId); 31282 var result; 31283 31284 ClearErrorInfo(); 31285 31286 result = CMI5_LoadBucket(strBucketId); 31287 if (! result) { 31288 WriteToDebug("Failed to load bucket: " + strBucketId); 31289 CMI5_SetErrorInfoManually(CMI5_ERROR_SSP_BUCKET_LOAD_FAILED, "Cannot get info for bucket: " + strBucketId, "Failed to load bucket: " + strCMI5ErrorString + " (" + strCMI5ErrorDiagnostic + ")"); 31290 return false; 31291 } 31292 31293 if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") { 31294 CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot get info for bucket: " + strBucketId, "unknown after load"); 31295 return false; 31296 } 31297 31298 return new SSPBucketSize(CMI5_SSP_BUCKETS[strBucketId].allocatedSpace, CMI5_SSP_BUCKETS[strBucketId].contents.length); 31299} 31300 31301function CMI5_LoadBucket (strBucketId) { 31302 WriteToDebug("CMI5_LoadBucket - strBucketId = " + strBucketId); 31303 var result; 31304 31305 if (typeof CMI5_SSP_BUCKETS[strBucketId] !== "undefined") { 31306 WriteToDebug(" already locally cached, returning"); 31307 return true; 31308 } 31309 31310 result = CMI5_RetrieveState(CMI5_STATE_SSP_BUCKET_PREFIX + "-" + strBucketId); 31311 31312 // 31313 // retrieve state doesn't provide a return value for detecting errors 31314 // so check to see if we have manually set errors (cause we shouldn't 31315 // unless the retrieve failed) 31316 // 31317 if (intCMI5Error) { 31318 WriteToDebug(" failed to retrieve state for possible existing bucket: " + strCMI5ErrorString); 31319 return false; 31320 } 31321 31322 if (result !== "" && typeof result.allocatedSpace === "undefined") { 31323 // bucket was found but most likely failed to parse as JSON 31324 // which retrieveState in TinCanJS eats as an error, but make it 31325 // one here 31326 CMI5_SetErrorInfoManually(CMI5_ERROR_INTERACTION_INVALID_JSON, "Invalid JSON for bucket in State", "result= " + result); 31327 return false; 31328 } 31329 31330 if (result === "") { 31331 // bucket doesn't exist which is okay here, 31332 // just don't store anything in our local cache 31333 return true; 31334 } 31335 31336 CMI5_SSP_BUCKETS[strBucketId] = result; 31337 31338 WriteToDebug(" cached bucket locally"); 31339 return true; 31340} 31341 31342function CMI5_SaveBucket (strBucketId) { 31343 WriteToDebug("CMI5_SaveBucket - strBucketId = " + strBucketId); 31344 31345 if (typeof CMI5_SSP_BUCKETS[strBucketId] === "undefined") { 31346 CMI5_SetErrorInfoManually(CMI5_ERROR_UNRECOGNIZED_SSP_BUCKET, "Cannot save unknown bucket: " + strBucketId); 31347 return false; 31348 } 31349 31350 return CMI5_SaveState(CMI5_STATE_SSP_BUCKET_PREFIX + "-" + strBucketId, CMI5_SSP_BUCKETS[strBucketId], "application/json"); 31351} 31352 31353/***************************** 31354* Miscellaneous 31355*****************************/ 31356
31357function CMI5_SetDurationInStatement(st) { 31358 var duration; 31359 31360 if (CMI5_SESSION_DURATION !== null) { 31361 duration = CMI5_SESSION_DURATION; 31362 } 31363 else { 31364 duration = GetSessionAccumulatedTime(); 31365 } 31366 31367 st.result = st.result || new TinCan.Result(); 31368 st.result.duration = TinCan.Utils.convertMillisecondsToISO8601Duration(duration); 31369 31370 return; 31371} 31372 31373function CMI5_CreateValidIdentifier (str) { 31374 if (str === undefined || str === "" || str === null) { 31375 WriteToDebug("WARNING - returning invalid identifier, invalid argument provided: " + str); 31376 return ""; 31377 } 31378 31379 // 31380 // switch URI to IRI mode temporarily, then capture the `encodeIRIComponent` function 31381 // off of the object for our local use, build and use our IRI and then return it 31382 // to its normal default state 31383 // 31384 // we need `encodeIRIComponent` to be able to encode the fragment which is otherwise 31385 // left alone by the URI normalization 31386 // 31387 URI.iri(); 31388 31389 var uri = new URI(str), 31390 encodeIRIComponent = URI.encode, 31391 id; 31392 31393 if (! uri.is("absolute")) { 31394 // 31395 // getting here means it is not an absolute URI so we need to 31396 // manufacture one, to make it unique we'll add it to the AU's 31397 // Activity id 31398 // 31399 uri = new URI(cmi5.getActivity().id); 31400 if (uri.fragment() === "") { 31401 // no existing fragment, so make our identifier the fragment 31402 uri.hash(encodeIRIComponent(str)); 31403 } 31404 else { 31405 // existing fragment, make our identifier part of it 31406 uri.hash(uri.fragment() + "-" + encodeIRIComponent(str)); 31407 } 31408 } 31409 31410 // .iri is a "normalizer" (calls .normalize internally) 31411 uri.iri(); 31412 31413 id = uri.toString(); 31414 31415 URI.unicode(); 31416 31417 return id; 31418} 31419 31420function CMI5_GetComments () { 31421 WriteToDebug("In CMI5_GetComments"); 31422 31423 return CMI5_RetrieveState(CMI5_STATE_COMMENTS); 31424} 31425 31426function CMI5_WriteComment (strComment) { 31427 WriteToDebug("In CMI5_WriteComment - comment: " + strComment); 31428 31429 return CMI5_SaveState(CMI5_STATE_COMMENTS, strComment, "text/plain"); 31430} 31431 31432function CMI5_GetLMSComments () { 31433 WriteToDebug("In CMI5_GetLMSComments - cmi5 does not support LMS comments"); 31434 31435 ClearErrorInfo(); 31436 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetLMSComments not implemented in cmi5", ""); 31437 31438 return ""; 31439} 31440 31441function CMI5_SetNavigationRequest (strNavRequest) { 31442 WriteToDebug("CMI5_GetNavigationRequest - cmi5 does not support navigation requests, returning false"); 31443 31444 ClearErrorInfo(); 31445 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetNavigationRequest not implemented in cmi5", ""); 31446 31447 return false; 31448} 31449 31450function CMI5_GetNavigationRequest () { 31451 WriteToDebug("CMI5_GetNavigationRequest - cmi5 does not support navigation requests, returning false"); 31452 31453 ClearErrorInfo(); 31454 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetNavigationRequest not implemented in cmi5", ""); 31455 31456 return false; 31457} 31458 31459function CMI5_GetProgressMeasure () { 31460 WriteToDebug("CMI5_GetProgressMeasure - cmi5 does not support progress_measure, returning false"); 31461 31462 ClearErrorInfo(); 31463 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "GetProgressMeasure not implemented in cmi5", ""); 31464 31465 return false; 31466} 31467 31468function CMI5_SetProgressMeasure () { 31469 WriteToDebug("CMI5_SetProgressMeasure - cmi5 does not support progress_measure, returning false"); 31470 31471 ClearErrorInfo(); 31472 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "SetProgressMeasure not implemented in cmi5", ""); 31473 31474 return false; 31475} 31476 31477function CMI5_DisplayMessageOnTimeout () { 31478 WriteToDebug("In CMI5_DisplayMessageOnTimeout - cmi5 does not support MessageOnTimeout"); 31479 31480 ClearErrorInfo(); 31481 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "DisplayMessageOnTimeout not implemented in cmi5", ""); 31482 31483 return false; 31484} 31485 31486function CMI5_ExitOnTimeout () { 31487 WriteToDebug("In CMI5_ExitOnTimeout - cmi5 does not support ExitOnTimeout"); 31488 31489 ClearErrorInfo(); 31490 CMI5_SetErrorInfoManually(CMI5_ERROR_NOT_IMPLEMENTED, "ExitOnTimeout not implemented in cmi5", ""); 31491 31492 return false;
31493} 31494 31495function CMI5_ResetBlnStatusWasSetIfAppropriate () { 31496 var isAlreadyComplete = CMI5_PENDING_STATUS.completion || CMI5_COMMITTED_STATUS.completion; 31497 31498 if (! isAlreadyComplete) { 31499 // Overriding the default behavior so cmi5 courses can treat completion and success completely independently 31500 WriteToDebug("Overriding blnStatusWasSet to false."); 31501 blnStatusWasSet = false; 31502 } 31503} 31504 31505/***************************** 31506* Error Management 31507*****************************/ 31508 31509function CMI5_ClearErrorInfo () { 31510 WriteToDebug("In CMI5_ClearErrorInfo"); 31511 31512 intCMI5Error = CMI5_NO_ERROR; 31513 strCMI5ErrorString = ""; 31514 strCMI5ErrorDiagnostic = ""; 31515} 31516 31517function CMI5_SetErrorInfoManually (intNum, strString, strDiagnostic) { 31518 WriteToDebug("In CMI5_SetErrorInfoManually"); 31519 WriteToDebug("ERROR-Num=" + intNum); 31520 WriteToDebug(" String=" + strString); 31521 WriteToDebug(" Diag=" + strDiagnostic); 31522 31523 intCMI5Error = intNum; 31524 strCMI5ErrorString = strString; 31525 strCMI5ErrorDiagnostic = strDiagnostic; 31526} 31527 31528function CMI5_GetLastError () { 31529 WriteToDebug("In CMI5_GetLastError"); 31530 31531 if (intCMI5Error === CMI5_NO_ERROR) { 31532 WriteToDebug("Returning No Error"); 31533 return NO_ERROR; 31534 } 31535 31536 WriteToDebug("Returning " + intCMI5Error); 31537 return intCMI5Error; 31538} 31539 31540function CMI5_GetLastErrorDesc () { 31541 WriteToDebug("In CMI5_GetLastErrorDesc, " + strCMI5ErrorString + "\n" + strCMI5ErrorDiagnostic); 31542 return strCMI5ErrorString + "\n" + strCMI5ErrorDiagnostic; 31543} 31544 31545function LMSStandardAPI (strStandard) { 31546 var interfaceMethods = [ 31547 "Initialize", 31548 "Finish", 31549 "CommitData", 31550 "GetStudentID", 31551 "GetStudentName", 31552 "GetBookmark", 31553 "SetBookmark", 31554 "GetDataChunk", 31555 "SetDataChunk", 31556 "GetLaunchData", 31557 "GetComments", 31558 "WriteComment", 31559 "GetLMSComments", 31560 "GetAudioPlayPreference", 31561 "GetAudioVolumePreference", 31562 "SetAudioPreference", 31563 "SetLanguagePreference", 31564 "GetLanguagePreference", 31565 "SetSpeedPreference", 31566 "GetSpeedPreference", 31567 "SetTextPreference", 31568 "GetTextPreference", 31569 "GetPreviouslyAccumulatedTime", 31570 "SaveTime", 31571 "GetMaxTimeAllowed", 31572 "DisplayMessageOnTimeout", 31573 "ExitOnTimeout", 31574 "GetPassingScore", 31575 "SetScore", 31576 "GetScore", 31577 "GetScaledScore", 31578 "RecordTrueFalseInteraction", 31579 "RecordMultipleChoiceInteraction", 31580 "RecordFillInInteraction", 31581 "RecordMatchingInteraction", 31582 "RecordPerformanceInteraction", 31583 "RecordSequencingInteraction", 31584 "RecordLikertInteraction", 31585 "RecordNumericInteraction", 31586 "GetEntryMode", 31587 "GetLessonMode", 31588 "GetTakingForCredit", 31589 "SetObjectiveScore", 31590 "SetObjectiveStatus", 31591 "GetObjectiveScore", 31592 "GetObjectiveStatus", 31593 "SetObjectiveDescription", 31594 "GetObjectiveDescription", 31595 "SetFailed", 31596 "SetPassed", 31597 "SetCompleted", 31598 "ResetStatus", 31599 "GetStatus", 31600 "GetLastError", 31601 "GetLastErrorDesc", 31602 "GetInteractionType", 31603 "GetInteractionTimestamp", 31604 "GetInteractionCorrectResponses", 31605 "GetInteractionWeighting", 31606 "GetInteractionLearnerResponses", 31607 "GetInteractionResult", 31608 "GetInteractionLatency", 31609 "GetInteractionDescription", 31610 "CreateDataBucket", 31611 "GetDataFromBucket", 31612 "PutDataInBucket", 31613 "DetectSSPSupport", 31614 "GetBucketInfo", 31615 "GetProgressMeasure", 31616 "SetProgressMeasure", 31617 "SetPointBasedScore", 31618 "SetNavigationRequest", 31619 "GetNavigationRequest", 31620 "SetObjectiveProgressMeasure", 31621 "GetObjectiveProgressMeasure", 31622 "CreateValidIdentifier", 31623 "ConcedeControl" 31624 ], 31625 i; 31626 31627 WriteToDebug("In LMSStandardAPI strStandard=" + strStandard); 31628 31629 if (strStandard == "") { 31630 WriteToDebug("No standard specified, using NONE"); 31631 strStandard = "NONE"; 31632 } 31633 31634 this.Standard = strStandard; 31635 31636 for (i = 0; i < interfaceMethods.length; i += 1) { 31637 if (typeof window[strStandard + "_" + interfaceMethods[i]] !== "undefined") { 31638 this[interfaceMethods[i]] = window[strStandard + "_" + interfaceMethods[i]]; 31639 } 31640 else { 31641 WriteToDebug("In LMSStandardAPI missing interface method=" + interfaceMethods[i]); 31642 } 31643 } 31644} 31645//API functions - each function validates inputs, then passes call to Standard function 31646 31647//global status variables 31648var blnCalledFinish = false;
31649var blnStandAlone = false; 31650var blnLoaded = false; 31651var blnReachedEnd = false; 31652var blnStatusWasSet = false; 31653var blnLmsPresent = false; 31654var blnUnloading = false; 31655 31656//time tracking variables 31657var dtmStart = null; 31658var dtmEnd = null; 31659var intAccumulatedMS = 0; 31660var blnOverrodeTime = false; 31661var intTimeOverrideMS = null; 31662 31663//debug info 31664var aryDebug = new Array(); 31665var strDebug = ""; 31666var winDebug; 31667 31668var intError = NO_ERROR; 31669var strErrorDesc = ""; 31670 31671var objLMS = null; 31672 31673//public 31674function Start(){ 31675 31676 var strStandAlone; 31677 var strShowInteractiveDebug; 31678 var objTempAPI = null; 31679 var aiccUrl = ""; 31680 var endpoint = ""; 31681 var cmi5FetchUrl = ""; 31682 31683 WriteToDebug("<h1>SCORM Driver starting up</h1>"); 31684 WriteToDebug("----------------------------------------"); 31685 WriteToDebug("----------------------------------------"); 31686 WriteToDebug("In Start - Version: " + VERSION + " Last Modified=" + window.document.lastModified); 31687 WriteToDebug("Browser Info (" + navigator.appName + " " + navigator.appVersion + ")"); 31688 WriteToDebug("URL: " + window.document.location.href); 31689 WriteToDebug("----------------------------------------"); 31690 WriteToDebug("----------------------------------------"); 31691 31692 ClearErrorInfo(); 31693 31694 strStandAlone = GetQueryStringValue("StandAlone", window.location.search); 31695 strShowInteractiveDebug = GetQueryStringValue("ShowDebug", window.location.search); 31696 31697 WriteToDebug("strStandAlone=" + strStandAlone + " strShowInteractiveDebug=" + strShowInteractiveDebug); 31698 31699 if (ConvertStringToBoolean(strStandAlone)){ 31700 WriteToDebug("Entering Stand Alone Mode"); 31701 blnStandAlone = true; 31702 } 31703 31704 if (blnStandAlone){ 31705 //allow NONE standard here to override config setting 31706 WriteToDebug("Using NONE Standard"); 31707 objLMS = new LMSStandardAPI("NONE"); 31708 } 31709 else{ 31710 WriteToDebug("Standard From Configuration File - " + strLMSStandard); 31711 31712 if (strLMSStandard.toUpperCase() == "AUTO") { 31713 //to try to overcome some unsafe javascript warnings in AUTO mode, 31714 //let's check for AICC first: 31715 WriteToDebug("Searching for recognized querystring parameters"); 31716 aiccUrl = GetQueryStringValue("AICC_URL", document.location.search); 31717 endpoint = GetQueryStringValue("endpoint", document.location.search); 31718 cmi5FetchUrl = GetQueryStringValue("fetch", document.location.search); 31719 31720 if (aiccUrl != null && aiccUrl != "") { 31721 WriteToDebug("Found AICC querystring parameters, using AICC"); 31722 objLMS = new LMSStandardAPI("AICC"); 31723 blnLmsPresent = true; 31724 } 31725 else if (endpoint != null && endpoint != "") { 31726 WriteToDebug("Found endpoint querystring parameter - checking cmi5 or Tin Can"); 31727 if (cmi5FetchUrl != null && cmi5FetchUrl != "") { 31728 WriteToDebug("Found fetch querystring parameter, using cmi5"); 31729 objLMS = new LMSStandardAPI("CMI5"); 31730 blnLmsPresent = true; 31731 } 31732 else { 31733 WriteToDebug("Did not find fetch querystring parameter, using Tin Can"); 31734 objLMS = new LMSStandardAPI("TCAPI"); 31735 blnLmsPresent = true; 31736 31737 // overwrite this so that tc-config.js is loaded for initialization below 31738 strLMSStandard = "TCAPI"; 31739 } 31740 } 31741 else { 31742 WriteToDebug("Auto-detecting standard - Searching for SCORM 2004 API"); 31743 /* 31744 We wrap the API grabbers in try blocks because we don't know what kind of envrionment we were 31745 launched in (especially in AUTO mode). The LMS may have taken away the opener/parent window (like 31746 the Saba preview mode does in AICC) or there could be pages from different domains to deal with. 31747 */ 31748 try{ 31749 objTempAPI = SCORM2004_GrabAPI(); 31750 } 31751 catch (e){ 31752 WriteToDebug("Error grabbing 2004 API-" + e.name + ":" + e.message); 31753 } 31754 if (! (typeof(objTempAPI) == "undefined" || objTempAPI == null)){ 31755 WriteToDebug("Found SCORM 2004 API, using SCORM 2004"); 31756 objLMS = new LMSStandardAPI("SCORM2004"); 31757 blnLmsPresent = true; 31758 } 31759 else { 31760 WriteToDebug("Searching for SCORM 1.2 API"); 31761 try{ 31762 objTempAPI = SCORM_GrabAPI(); 31763 } 31764 catch (e){ 31765 WriteToDebug("Error grabbing 1.2 API-" + e.name + ":" + e.message); 31766 } 31767 31768 if (! (typeof(objTempAPI) == "undefined" || objTempAPI == null)) { 31769 WriteToDebug("Found SCORM API, using SCORM"); 31770 objLMS = new LMSStandardAPI("SCORM"); 31771 blnLmsPresent = true; 31772 } 31773 else { 31774 if (ALLOW_NONE_STANDARD === true) { 31775 WriteToDebug("Could not determine standard, defaulting to Stand Alone"); 31776 objLMS = new LMSStandardAPI("NONE"); 31777 } 31778 else { 31779 WriteToDebug("Could not determine standard, Stand Alone is disabled in configuration"); 31780 DisplayError("Could not determine standard. SCORM, AICC, Tin Can, and C
31780MI5 APIs could not be found"); 31781 return; 31782 } 31783 } 31784 } 31785 } 31786 } 31787 else { 31788 WriteToDebug("Using Standard From Configuration File - " + strLMSStandard); 31789 objLMS = new LMSStandardAPI(strLMSStandard); 31790 31791 blnLmsPresent = true; //set to true here and revert back to false if Initialize fails (in InitiaizeExecuted) 31792 } 31793 } 31794 31795 if (ConvertStringToBoolean(strShowInteractiveDebug) || 31796 ( !(typeof(SHOW_DEBUG_ON_LAUNCH) == "undefined") && SHOW_DEBUG_ON_LAUNCH === true ) 31797 ){ 31798 WriteToDebug("Showing Interactive Debug Windows"); 31799 ShowDebugWindow(); 31800 } 31801 31802 WriteToDebug("Calling Standard Initialize"); 31803 31804 if (strLMSStandard.toUpperCase() == "TCAPI"){ 31805 // dynamically load the tc_config.js file before calling init; 31806 // load from up one directory so that the content developer 31807 // does not need to edit anything inside of scormdriver/ and 31808 // this puts tc-config.js at the same level as the tincan.xml 31809 loadScript("../tc-config.js", objLMS.Initialize); 31810 }else{ 31811 objLMS.Initialize(); 31812 } 31813 31814 TouchCloud(); 31815 31816 return; 31817} 31818 31819 31820 31821function InitializeExecuted(blnSuccess, strErrorMessage){ 31822 31823 WriteToDebug("In InitializeExecuted, blnSuccess=" + blnSuccess + ", strErrorMessage=" + strErrorMessage); 31824 31825 if (!blnSuccess){ 31826 WriteToDebug("ERROR - LMS Initialize Failed"); 31827 if (strErrorMessage == ""){ 31828 strErrorMessage = "An Error Has Occurred"; 31829 } 31830 blnLmsPresent = false; 31831 DisplayError(strErrorMessage); 31832 return; 31833 } 31834
31835 // AICC needs to check for completion here instead of on Initialize so the GetParam has time to finish processing 31836 if(objLMS.Standard=='AICC'){ 31837 AICC_InitializeExecuted(); 31838 } 31839 31840 // continue on for everyone else 31841 31842 blnLoaded = true; 31843 dtmStart = new Date(); 31844 31845 LoadContent(); 31846 31847 return; 31848} 31849 31850 31851//private - no need to call this directly, use "Finish", "Suspend" or "Timeout" 31852function ExecFinish(ExitType){ 31853 31854 WriteToDebug("In ExecFinish, ExiType=" + ExitType); 31855 31856 ClearErrorInfo(); 31857 31858 if (blnLoaded && ! blnCalledFinish ){ 31859 31860 WriteToDebug("Haven't called finish before, finishing"); 31861 31862 blnCalledFinish = true; 31863 31864 if (blnReachedEnd && (!EXIT_SUSPEND_IF_COMPLETED)){ 31865 WriteToDebug("Reached End, overiding exit type to FINISH"); 31866 ExitType = EXIT_TYPE_FINISH; 31867 } 31868 31869 if(EXIT_NORMAL_IF_PASSED == true && objLMS.GetStatus()==LESSON_STATUS_PASSED){ 31870 WriteToDebug("Passed status and config value set, overiding exit type to FINISH"); 31871 ExitType = EXIT_TYPE_FINISH; 31872 } 31873 31874 //MR - 5/31/05 - changed this call to happen in the SetSessionTime function so we can be sure it is always called 31875 //if (blnOverrodeTime){ 31876 // WriteToDebug("Overrode Time"); 31877 // objLMS.SaveTime(intTimeOverrideMS); 31878 //} 31879 31880 //DE - 5/6/2010 - we should only accumulate and save the time 31881 // on exit if it wasn't set already using SetSessionTime 31882 if(!blnOverrodeTime){ 31883 WriteToDebug("Did not override time"); 31884 dtmEnd = new Date(); 31885 AccumulateTime(); 31886 objLMS.SaveTime(intAccumulatedMS); 31887 } 31888 31889 blnLoaded = false; 31890 31891 WriteToDebug("Calling LMS Finish"); 31892 return objLMS.Finish(ExitType, blnStatusWasSet); 31893 31894 } 31895 31896 return true; 31897} 31898 31899 31900//Utilities 31901 31902//public 31903//tells child frames when the API is ready 31904function IsLoaded(){ 31905 WriteToDebug("In IsLoaded, returning -" + blnLoaded); 31906 return blnLoaded; 31907} 31908 31909 31910//public 31911function WriteToDebug(strInfo){ 31912 31913 if (blnDebug){ 31914 31915 var dtm = new Date(); 31916 var strLine; 31917 31918 strLine = aryDebug.length + ":" + dtm.toString() + " - " + strInfo; 31919 31920 aryDebug[aryDebug.length] = strLine; 31921 31922 if (winDebug && !winDebug.closed){ 31923 winDebug.document.body.appendChild( winDebug.document.createTextNode(strLine) ); 31924 winDebug.document.body.appendChild( winDebug.document.createElement("br") ); 31925 } 31926 31927 } 31928 return; 31929} 31930 31931//public 31932function ShowDebugWindow(){ 31933 var renderLog = function () { 31934 var i, 31935 len = aryDebug.length; 31936 31937 winDebug.document.body.innerHTML = ""; 31938 for (i = 0; i < len; i += 1) { 31939 winDebug.document.body.appendChild( winDebug.document.createTextNode(aryDebug[i]) ); 31940 winDebug.document.body.appendChild( winDebug.document.createElement("br") ); 31941 } 31942 }; 31943 31944 if (winDebug && !winDebug.closed){ 31945 winDebug.close(); 31946 } 31947 31948 winDebug = window.open("lms/blank.html", "Debug", "width=600,height=300,resizable,scrollbars"); 31949 31950 if (winDebug === null) { 31951 alert("Debug window could not be opened, popup blocker in place?"); 31952 } 31953 else { 31954 if (winDebug.addEventListener || winDebug.attachEvent) { 31955 winDebug[winDebug.addEventListener ? 'addEventListener' : 'attachEvent']( 31956 (winDebug.attachEvent ? 'on' : '') + 'load', 31957 renderLog, 31958 false 31959 ); 31960 } 31961 31962 renderLog(); 31963 31964 winDebug.document.close(); 31965 winDebug.focus(); 31966 } 31967 31968 return; 31969} 31970 31971//public 31972function DisplayError(strMessage){ 31973 31974 var blnShowDebug; 31975 31976 WriteToDebug("In DisplayError, strMessage=" + strMessage); 31977 31978 blnShowDebug = confirm("An error has occurred:\n\n" + strMessage + "\n\nPress 'OK' to view debug information to send to technical support."); 31979 31980 if (blnShowDebug){ 31981 ShowDebugWindow(); 31982 } 31983 31984} 31985 31986 31987//public 31988//combines API error information with Standard's error information 31989function GetLastError(){ 31990 31991 WriteToDebug("In GetLastError, intError=" + intError); 31992 31993 if (intError != NO_ERROR){ 31994 WriteToDebug("Returning API Error"); 31995 return intError; 31996 } 31997 else if (IsLoaded() && objLMS.GetLastError() != NO_ERROR){ 31998 WriteToDebug("Returning LMS Error"); 31999 return ERROR_LMS; 32000 } 32001 32002 WriteToDebug("Returning No Error"); 32003 return NO_ERROR; 32004} 32005 32006function GetLastLMSErrorCode(){ 32007 32008 WriteToDebug("In GetLastLMSErrorCode, intError=" + intError); 32009 32010 var LMSError = objLMS.GetLastError(); 32011 32012 if (IsLoaded() && LMSError != NO_ERROR){ 32013 32014 32015 WriteToDebug("Returning LMS Error: " + LMSError); 32016 return LMSError; 32017 } 32018 32019 WriteToDebug("Returning No Error"); 32020 return NO_ERROR; 32021} 32022 32023//public 32024function GetLastErrorDesc(){ 32025 WriteToDebug("In GetLastErrorDesc"); 32026 32027 if (intError != NO_ERROR){ 32028 WriteToDebug("Returning API Error - " + strErrorDesc); 32029 return strErrorDesc; 32030 } 32031 else if (IsLoaded() && objLMS.GetLastError() != NO_ERROR){ 32032 WriteToDebug("Returning LMS Error"); 32033 return objLMS.GetLastErrorDesc(); 32034 } 32035 32036 WriteToDebug("Returning No Error"); 32037 return ""; 32038} 32039 32040//private 32041function SetErrorInfo(intErrorNumToSet, strErrorDescToSet){ 32042 32043 32044 WriteToDebug("In SetErrorInfo - Num=" + intErrorNumToSet + " Desc=" + strErrorDescToSet); 32045 32046 intError = intErrorNumToSet; 32047 strErrorDesc = strErrorDescToSet; 32048} 32049 32050//private 32051function ClearErrorInfo(){ 32052 WriteToDebug("In ClearErrorInfo"); 32053 32054 var intError = NO_ERROR; 32055 var strErrorDesc = ""; 32056} 32057 32058 32059//public 32060function CommitData(){ 32061 WriteToDebug("In CommitData"); 32062 32063 ClearErrorInfo(); 32064 32065 if (! IsLoaded()){ 32066 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32067 return false;
32068 } 32069 32070 //MR - 5/31/05 - We want to make sure this call happens someplace other than just in the onunload event, so periodically save the data as it accumulates 32071 if (! blnOverrodeTime){ 32072 WriteToDebug("Did not override time, saving incremental time"); 32073 dtmEnd = new Date(); 32074 AccumulateTime(); 32075 dtmStart = new Date(); 32076 objLMS.SaveTime(intAccumulatedMS); 32077 } 32078 32079 return objLMS.CommitData(); 32080} 32081 32082 32083//Finish functions 32084 32085//public 32086function Suspend(){ 32087 WriteToDebug("In Suspend"); 32088 ClearErrorInfo(); 32089 32090 return ExecFinish(EXIT_TYPE_SUSPEND); 32091} 32092 32093//public 32094function Finish(){ 32095 WriteToDebug("In Finish"); 32096 ClearErrorInfo(); 32097 32098 return ExecFinish(EXIT_TYPE_FINISH); 32099} 32100 32101//public 32102function TimeOut(){ 32103 WriteToDebug("In TimeOut"); 32104 ClearErrorInfo(); 32105 32106 return ExecFinish(EXIT_TYPE_TIMEOUT); 32107} 32108 32109//public 32110function Unload(){ 32111 WriteToDebug("In Unload"); 32112 ClearErrorInfo(); 32113 32114 blnUnloading = true; 32115 32116 return ExecFinish(DEFAULT_EXIT_TYPE); 32117} 32118 32119//public 32120function SetReachedEnd(){ 32121 WriteToDebug("In SetReachedEnd"); 32122 ClearErrorInfo(); 32123 32124 if (! IsLoaded()){ 32125 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32126 return false; 32127 } 32128 32129 if (blnStatusWasSet == false){ 32130 objLMS.SetCompleted(); 32131 } 32132 32133 blnReachedEnd = true; 32134 32135 return true; 32136} 32137 32138//public 32139function ConcedeControl(userAction) 32140{ 32141 WriteToDebug("Conceding control with type: " + EXIT_BEHAVIOR); 32142 ClearErrorInfo(); 32143 32144 //It's ok to always call Suspend in here and not Finish. The proper way to get an exit type 32145 //of Finish is to call SetReachedEnd before calling ConcedeControl. This will ensure that 32146 //the ExecFinish function always uses the ExitTypeFinish behavior (which is to set the status to completed 32147 //if no status was previously set) 32148 32149 if (typeof objLMS.ConcedeControl !== "undefined") { 32150 Suspend(); 32151 return objLMS.ConcedeControl(); 32152 } 32153 32154 if (userAction && objLMS.Standard === "AICC") { 32155 objLMS.actionConceded = true; 32156 Suspend(); 32157 return; 32158 } 32159 32160 Suspend(); 32161 PerformConcedeActions(); 32162} 32163 32164function PerformConcedeActions() { 32165 var contentRoot = null; 32166 var urlBase = null; 32167 32168 switch (EXIT_BEHAVIOR) 32169 { 32170 case "SCORM_RECOMMENDED": 32171 contentRoot = SearchParentsForContentRoot(); 32172 32173 if (contentRoot==window.top) 32174 { 32175 contentRoot.window.close(); 32176 } 32177 else 32178 { 32179 if (contentRoot != null){
32180 //DE - 10/26/2010 - Add ability to specify absolute URL for EXIT_TARGET 32181 if(IsAbsoluteUrl(EXIT_TARGET)){ 32182 contentRoot.scormdriver_content.location.href = EXIT_TARGET; 32183 } else { 32184 urlBase = GetContentRootUrlBase(contentRoot); 32185 contentRoot.scormdriver_content.location.href= urlBase + EXIT_TARGET; 32186 } 32187 } 32188 } 32189 break; 32190 case "ALWAYS_CLOSE": 32191 window.close(); 32192 break; 32193 case "ALWAYS_CLOSE_TOP": 32194 window.top.close(); 32195 break; 32196 case "ALWAYS_CLOSE_PARENT": 32197 window.parent.close(); 32198 break; 32199 case "NOTHING": 32200 break; 32201 case "REDIR_CONTENT_FRAME": 32202 contentRoot = SearchParentsForContentRoot(); 32203 if (contentRoot != null){ 32204 //DE - 10/26/2010 - Add ability to specify absolute URL for EXIT_TARGET 32205 if(IsAbsoluteUrl(EXIT_TARGET)){ 32206 contentRoot.scormdriver_content.location.href = EXIT_TARGET; 32207 } else { 32208 urlBase = GetContentRootUrlBase(contentRoot); 32209 contentRoot.scormdriver_content.location.href= urlBase + EXIT_TARGET; 32210 } 32211 } 32212 break; 32213 } 32214 32215 return true; 32216 32217} 32218 32219function GetContentRootUrlBase(contentRoot){ 32220 32221 //Must remove the querystring before working with the url, or parameters that contain 32222 //a slash in them will break this (as with AICC_URL) 32223 var urlParts = contentRoot.location.href.split("?")[0].split("/"); 32224 delete urlParts[urlParts.length - 1]; 32225 contentRoot = urlParts.join("/"); 32226 return contentRoot; 32227} 32228 32229function SearchParentsForContentRoot(){ 32230 32231 var contentRoot = null; 32232 var wnd = window; 32233 var i=0; //safety guard to prevent infinite loop 32234 32235 if (wnd.scormdriver_content){ 32236 contentRoot = wnd; 32237 return contentRoot; 32238 } 32239 32240 while (contentRoot == null && wnd != window.top && (i++ < 100)){ 32241 if (wnd.scormdriver_content){ 32242 contentRoot = wnd; 32243 return contentRoot; 32244 } 32245 else{ 32246 wnd = wnd.parent; 32247 } 32248 } 32249 WriteToDebug("Unable to locate content root"); 32250 return null; 32251} 32252 32253 32254//Storing and retrieving data 32255 32256//public 32257function GetStudentID(){ 32258 WriteToDebug("In GetStudentID"); 32259 ClearErrorInfo(); 32260 32261 if (! IsLoaded()){ 32262 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32263 return ""; 32264 } 32265 32266 return objLMS.GetStudentID(); 32267 32268} 32269 32270//public 32271function GetStudentName(){ 32272 WriteToDebug("In GetStudentName"); 32273 ClearErrorInfo(); 32274 32275 if (! IsLoaded()){ 32276 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32277 return ""; 32278 } 32279 32280 return objLMS.GetStudentName(); 32281} 32282 32283//public 32284function GetBookmark(){ 32285 WriteToDebug("In GetBookmark"); 32286 ClearErrorInfo(); 32287 32288 if (! IsLoaded()){ 32289 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32290 return ""; 32291 } 32292 32293 return objLMS.GetBookmark(); 32294 32295} 32296 32297//public 32298function SetBookmark(strBookmark, strDesc){ 32299 WriteToDebug("In SetBookmark - strBookmark=" + strBookmark + ", strDesc=" + strDesc); 32300 ClearErrorInfo(); 32301 32302 if (! IsLoaded()){ 32303 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32304 return false; 32305 } 32306 32307 return objLMS.SetBookmark(strBookmark, strDesc); 32308} 32309 32310//public 32311function GetDataChunk(){ 32312 WriteToDebug("In GetDataChunk"); 32313 ClearErrorInfo(); 32314 32315 if (! IsLoaded()){ 32316 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32317 return ""; 32318 } 32319 32320 return objLMS.GetDataChunk(); 32321} 32322 32323//public 32324function SetDataChunk(strData){ 32325 WriteToDebug("In SetDataChunk strData=" + strData); 32326 ClearErrorInfo(); 32327 32328 if (! IsLoaded()){ 32329 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32330 return false; 32331 } 32332 32333 return objLMS.SetDataChunk(strData); 32334} 32335 32336//public 32337function GetLaunchData(){ 32338 WriteToDebug("In GetLaunchData"); 32339 ClearErrorInfo(); 32340 32341 if (! IsLoaded()){ 32342 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32343 return ""; 32344 } 32345 32346 return objLMS.GetLaunchData(); 32347} 32348 32349//public 32350function GetComments(){ 32351 32352 var strCommentString; 32353 var aryComments; 32354 var i; 32355 32356 WriteToDebug("In GetComments"); 32357 32358 ClearErrorInfo(); 32359 32360 if (! IsLoaded()){ 32361 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32362 return null; 32363 } 32364 32365 strCommentString = objLMS.GetComments(); 32366 32367 WriteToDebug("strCommentString=" + strCommentString); 32368 32369 strCommentString = new String(strCommentString); 32370 32371 if (strCommentString != ""){ 32372 32373 aryComments = strCommentString.split(" | "); 32374 32375 for (i=0; i < aryComments.length; i++){ 32376 WriteToDebug("Returning Comment #" + i); 32377 aryComments[i] = new String(aryComments[i]); 32378 aryComments[i] = aryComments[i].replace(/\|\|/g, "|"); 32379 WriteToDebug("Comment #" + i + "=" + aryComments[i]); 32380 } 32381 } 32382 else{ 32383 aryComments = new Array(0); 32384 } 32385 32386 return aryComments; 32387} 32388 32389//public 32390function WriteComment(strComment){ 32391 32392 var strExistingCommentString; 32393 32394 WriteToDebug("In WriteComment strComment=" + strComment); 32395 32396 ClearErrorInfo(); 32397 32398 strComment = new String(strComment); 32399 32400 if (! IsLoaded()){ 32401 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32402 return false;
32403 } 32404 32405 strComment = strComment.replace(/\|/g, "||"); 32406 32407 strExistingCommentString = objLMS.GetComments(); 32408 32409 if (strExistingCommentString != "" && strExistingCommentString != 'undefined'){ 32410 strComment = strExistingCommentString + " | " + strComment; 32411 } 32412 32413 return objLMS.WriteComment(strComment); 32414} 32415 32416 32417//public 32418function GetLMSComments(){ 32419 WriteToDebug("In GetLMSComments"); 32420 ClearErrorInfo(); 32421 32422 if (! IsLoaded()){ 32423 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32424 return ""; 32425 } 32426 32427 return objLMS.GetLMSComments(); 32428} 32429 32430 32431//Preferences 32432 32433//public 32434function GetAudioPlayPreference(){ 32435 WriteToDebug("In GetAudioPlayPreference"); 32436 ClearErrorInfo(); 32437 32438 if (! IsLoaded()){ 32439 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32440 return PREFERENCE_DEFAULT; 32441 } 32442 32443 return objLMS.GetAudioPlayPreference(); 32444} 32445 32446//public 32447//returns int 1-100 32448function GetAudioVolumePreference(){ 32449 WriteToDebug("GetAudioVolumePreference"); 32450 ClearErrorInfo(); 32451 32452 if (! IsLoaded()){ 32453 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32454 return 100; 32455 } 32456 32457 return objLMS.GetAudioVolumePreference(); 32458} 32459 32460//public 32461//percent is int 1-100 32462//PlayPreference is On/Off 32463function SetAudioPreference(PlayPreference, intPercentOfMaxVolume){ 32464 32465 WriteToDebug("In SetAudioPreference PlayPreference=" + PlayPreference + " intPercentOfMaxVolume=" + intPercentOfMaxVolume); 32466 32467 ClearErrorInfo(); 32468 32469 if (! IsLoaded()){ 32470 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32471 return false; 32472 } 32473 32474 if (PlayPreference != PREFERENCE_OFF && 32475 PlayPreference != PREFERENCE_ON){ 32476 32477 WriteToDebug("Error Invalid PlayPreference"); 32478 32479 SetErrorInfo(ERROR_INVALID_PREFERENCE, "Invalid PlayPreference passed to SetAudioPreference, PlayPreference=" + PlayPreference); 32480 32481 return false; 32482 } 32483 32484 if ( ! ValidInteger(intPercentOfMaxVolume) ){ 32485 WriteToDebug("Error Invalid PercentOfMaxVolume - not an integer"); 32486 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxVolume passed to SetAudioPreference (not an integer), intPercentOfMaxVolume=" + intPercentOfMaxVolume); 32487 return false; 32488 } 32489 32490 intPercentOfMaxVolume = parseInt(intPercentOfMaxVolume, 10); 32491 32492 if (intPercentOfMaxVolume < 1 || intPercentOfMaxVolume > 100){ 32493 WriteToDebug("Error Invalid PercentOfMaxVolume - out of range"); 32494 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxVolume passed to SetAudioPreference (must be between 1 and 100), intPercentOfMaxVolume=" + intPercentOfMaxVolume); 32495 return false; 32496 } 32497 32498 WriteToDebug("Calling to LMS"); 32499 return objLMS.SetAudioPreference(PlayPreference, intPercentOfMaxVolume); 32500 32501} 32502 32503//public 32504function GetLanguagePreference(){ 32505 WriteToDebug("In GetLanguagePreference"); 32506 ClearErrorInfo(); 32507 32508 if (! IsLoaded()){ 32509 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32510 return ""; 32511 } 32512 32513 return objLMS.GetLanguagePreference(); 32514} 32515 32516//public 32517function SetLanguagePreference(strLanguage){ 32518 WriteToDebug("In SetLanguagePreference strLanguage=" + strLanguage); 32519 ClearErrorInfo(); 32520 32521 if (! IsLoaded()){ 32522 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32523 return false; 32524 } 32525 32526 return objLMS.SetLanguagePreference(strLanguage); 32527 32528} 32529 32530//public 32531function GetSpeedPreference(){ 32532 WriteToDebug("In GetSpeedPreference"); 32533 ClearErrorInfo(); 32534 32535 if (! IsLoaded()){ 32536 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32537 return 100; 32538 } 32539 32540 return objLMS.GetSpeedPreference(); 32541} 32542 32543 32544//public 32545function SetSpeedPreference(intPercentOfMax){ 32546 WriteToDebug("In SetSpeedPreference intPercentOfMax=" + intPercentOfMax); 32547 ClearErrorInfo(); 32548 32549 if (! IsLoaded()){ 32550 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32551 return false; 32552 } 32553 32554 if ( ! ValidInteger(intPercentOfMax) ){ 32555 WriteToDebug("ERROR Invalid Percent of MaxSpeed, not an integer");
32556 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxSpeed passed to SetSpeedPreference (not an integer), intPercentOfMax=" + intPercentOfMax); 32557 return false; 32558 } 32559 32560 intPercentOfMax = parseInt(intPercentOfMax, 10); 32561 32562 if (intPercentOfMax < 0 || intPercentOfMax > 100){ 32563 WriteToDebug("ERROR Invalid Percent of MaxSpeed, out of range"); 32564 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid PercentOfMaxSpeed passed to SetSpeedPreference (must be between 1 and 100), intPercentOfMax=" + intPercentOfMax); 32565 return false; 32566 } 32567 32568 WriteToDebug("Calling to LMS"); 32569 return objLMS.SetSpeedPreference(intPercentOfMax); 32570 32571} 32572 32573//public 32574function GetTextPreference(){ 32575 WriteToDebug("In GetTextPreference"); 32576 ClearErrorInfo(); 32577 32578 if (! IsLoaded()){ 32579 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32580 return false; 32581 } 32582 32583 return objLMS.GetTextPreference(); 32584} 32585 32586//public 32587function SetTextPreference(intPreference){ 32588 WriteToDebug("In SetTextPreference intPreference=" + intPreference); 32589 ClearErrorInfo(); 32590 32591 if (! IsLoaded()){ 32592 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32593 return false; 32594 } 32595 32596 if (intPreference != PREFERENCE_DEFAULT && 32597 intPreference != PREFERENCE_OFF && 32598 intPreference != PREFERENCE_ON){ 32599 WriteToDebug("Error - Invalid Preference"); 32600 SetErrorInfo(ERROR_INVALID_PREFERENCE, "Invalid Preference passed to SetTextPreference, intPreference=" + intPreference); 32601 32602 return false; 32603 } 32604 32605 return objLMS.SetTextPreference(intPreference); 32606 32607} 32608 32609 32610 32611 32612//Timing 32613 32614//public 32615function GetPreviouslyAccumulatedTime(){ 32616 WriteToDebug("In GetPreviouslyAccumulatedTime"); 32617 ClearErrorInfo(); 32618 32619 if (! IsLoaded()){ 32620 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32621 return 0; 32622 } 32623 32624 return objLMS.GetPreviouslyAccumulatedTime(); 32625} 32626 32627 32628 32629//private 32630function AccumulateTime(){ 32631 WriteToDebug("In AccumulateTime dtmStart=" + dtmStart + " dtmEnd=" + dtmEnd + " intAccumulatedMS=" + intAccumulatedMS); 32632 if (dtmEnd != null && dtmStart != null){ 32633 WriteToDebug("Accumulating Time"); 32634 intAccumulatedMS += (dtmEnd.getTime() - dtmStart.getTime()); 32635 WriteToDebug("intAccumulatedMS=" + intAccumulatedMS); 32636 } 32637 32638} 32639 32640 32641 32642//public 32643function GetSessionAccumulatedTime(){ 32644 32645 WriteToDebug("In GetSessionAccumulatedTime"); 32646 32647 ClearErrorInfo(); 32648 32649 WriteToDebug("Setting dtmEnd to now"); 32650 32651 dtmEnd = new Date(); 32652 32653 WriteToDebug("Accumulating Time"); 32654 32655 AccumulateTime(); 32656 32657 if (dtmStart != null){ 32658 WriteToDebug("Resetting dtmStart"); 32659 dtmStart = new Date(); 32660 } 32661 32662 WriteToDebug("Setting dtmEnd to null"); 32663 dtmEnd = null; 32664 32665 WriteToDebug("Returning " + intAccumulatedMS); 32666 32667 return intAccumulatedMS; 32668} 32669 32670 32671//public 32672function SetSessionTime(intMilliseconds){ 32673 32674 WriteToDebug("In SetSessionTime"); 32675 32676 ClearErrorInfo(); 32677 32678 if ( ! ValidInteger(intMilliseconds)){ 32679 WriteToDebug("ERROR parameter is not an integer"); 32680 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMilliseconds passed to SetSessionTime (not an integer), intMilliseconds=" + intMilliseconds); 32681 return false; 32682 } 32683 32684 intMilliseconds = parseInt(intMilliseconds, 10); 32685 32686 if (intMilliseconds < 0){ 32687 WriteToDebug("Error, parameter is less than 0"); 32688 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMilliseconds passed to SetSessionTime (must be greater than 0), intMilliseconds=" + intMilliseconds); 32689 return false; 32690 } 32691 32692 blnOverrodeTime = true; 32693 intTimeOverrideMS = intMilliseconds; 32694 32695 //MR - 5/31/05 - added this immediate call because in AICC the onunload call isn't always reliable 32696 objLMS.SaveTime(intTimeOverrideMS); 32697 32698 return true; 32699} 32700 32701//public 32702function PauseTimeTracking(){ 32703 32704 WriteToDebug("In PauseTimeTracking"); 32705 32706 ClearErrorInfo(); 32707 32708 WriteToDebug("Setting dtmEnd to now"); 32709 dtmEnd = new Date(); 32710 32711 WriteToDebug("Accumulating Time"); 32712 AccumulateTime(); 32713 32714 WriteToDebug("Setting Start and End times to null"); 32715 dtmStart = null; 32716 dtmEnd = null; 32717 32718 return true; 32719} 32720 32721//public 32722//note in docs - can be used to start tracking at a point other than beginning 32723function ResumeTimeTracking(){ 32724
32725 WriteToDebug("In ResumeTimeTracking"); 32726 32727 ClearErrorInfo(); 32728 32729 WriteToDebug("Setting dtmStart to now"); 32730 32731 dtmStart = new Date(); 32732 32733 return true; 32734 32735} 32736 32737 32738//public 32739function GetMaxTimeAllowed(){ 32740 32741 WriteToDebug("In GetMaxTimeAllowed"); 32742 32743 ClearErrorInfo(); 32744 32745 if (! IsLoaded()){ 32746 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32747 return MAX_CMI_TIME; 32748 } 32749 32750 return objLMS.GetMaxTimeAllowed(); 32751 32752} 32753 32754 32755//public 32756function DisplayMessageOnTimeout(){ 32757 32758 WriteToDebug("In DisplayMessageOnTimeOut"); 32759 32760 ClearErrorInfo(); 32761 32762 if (! IsLoaded()){ 32763 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32764 return false; 32765 } 32766 32767 return objLMS.DisplayMessageOnTimeout(); 32768 32769} 32770 32771//public 32772function ExitOnTimeout(){ 32773 32774 WriteToDebug("In ExitOnTimeOut"); 32775 32776 ClearErrorInfo(); 32777 32778 if (! IsLoaded()){ 32779 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32780 return false; 32781 } 32782 32783 return objLMS.ExitOnTimeout(); 32784 32785} 32786 32787 32788//Testing 32789 32790//public 32791function GetPassingScore(){ 32792 WriteToDebug("In GetPassingScore"); 32793 ClearErrorInfo(); 32794 32795 if (! IsLoaded()){ 32796 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32797 return 0; 32798 } 32799 32800 return objLMS.GetPassingScore(); 32801 32802} 32803 32804//public 32805function GetScore(){ 32806 32807 WriteToDebug("In GetScore"); 32808 ClearErrorInfo(); 32809 32810 if (! IsLoaded()){ 32811 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32812 return 0; 32813 } 32814 32815 return objLMS.GetScore(); 32816 32817} 32818 32819//public 32820function GetScaledScore(){ 32821 32822 WriteToDebug("In GetScaledScore"); 32823 ClearErrorInfo(); 32824 32825 if (! IsLoaded()){ 32826 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32827 return 0; 32828 } 32829 32830 return objLMS.GetScaledScore(); 32831 32832} 32833 32834//public 32835function SetScore(intScore, intMaxScore, intMinScore){ 32836 32837 WriteToDebug("In SetScore, intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 32838 32839 ClearErrorInfo(); 32840 32841 if (! IsLoaded()){ 32842 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32843 return false; 32844 } 32845 32846 if (! IsValidDecimal(intScore)){ 32847 WriteToDebug("ERROR - intScore not a valid decimal"); 32848 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (not a valid decimal), intScore=" + intScore); 32849 return false; 32850 } 32851 32852 if (! IsValidDecimal(intMaxScore)){ 32853 WriteToDebug("ERROR - intMaxScore not a valid decimal"); 32854 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (not a valid decimal), intMaxScore=" + intMaxScore); 32855 return false; 32856 } 32857 32858 if (! IsValidDecimal(intMinScore)){ 32859 WriteToDebug("ERROR - intMinScore not a valid decimal"); 32860 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (not a valid decimal), intMinScore=" + intMinScore); 32861 return false; 32862 } 32863 32864 WriteToDebug("Converting SCORES to floats"); 32865 intScore = parseFloat(intScore); 32866 intMaxScore = parseFloat(intMaxScore); 32867 intMinScore = parseFloat(intMinScore); 32868 32869 //check the ranges for SCORM 1.2 32870 if(strLMSStandard == 'SCORM') 32871 { 32872 WriteToDebug("DEBUG - SCORM 1.2 so checking max score length"); 32873 if (intScore < 0 || intScore > 100){ 32874 WriteToDebug("ERROR - intScore out of range"); 32875 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (must be between 0-100), intScore=" + intScore); 32876 return false; 32877 } 32878 32879 if (intMaxScore < 0 || intMaxScore > 100){ 32880 WriteToDebug("ERROR - intMaxScore out of range"); 32881 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (must be between 0-100), intMaxScore=" + intMaxScore); 32882 return false; 32883 } 32884 32885 if (intMinScore < 0 || intMinScore > 100){ 32886 WriteToDebug("ERROR - intMinScore out of range"); 32887 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (must be between 0-100), intMinScore=" + intMinScore); 32888 return false;
32889 } 32890 } 32891 32892 if (SCORE_CAN_ONLY_IMPROVE === true){ 32893 32894 var previousScore = GetScore(); 32895 32896 if (previousScore != null && previousScore != "" && previousScore > intScore){ 32897 WriteToDebug("Previous score was greater than new score, configuration only allows scores to improve, returning."); 32898 return true; 32899 } 32900 } 32901 32902 WriteToDebug("Calling to LMS"); 32903 return objLMS.SetScore(intScore, intMaxScore, intMinScore); 32904 32905} 32906 32907//public 32908function SetPointBasedScore(intScore, intMaxScore, intMinScore){ 32909 32910 WriteToDebug("In SetPointBasedScore, intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 32911 32912 ClearErrorInfo(); 32913 32914 if (! IsLoaded()){ 32915 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 32916 return false; 32917 } 32918 32919 if (! IsValidDecimal(intScore)){ 32920 WriteToDebug("ERROR - intScore not a valid decimal"); 32921 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (not a valid decimal), intScore=" + intScore); 32922 return false; 32923 } 32924 32925 if (! IsValidDecimal(intMaxScore)){ 32926 WriteToDebug("ERROR - intMaxScore not a valid decimal"); 32927 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (not a valid decimal), intMaxScore=" + intMaxScore); 32928 return false; 32929 } 32930 32931 if (! IsValidDecimal(intMinScore)){ 32932 WriteToDebug("ERROR - intMinScore not a valid decimal"); 32933 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (not a valid decimal), intMinScore=" + intMinScore); 32934 return false; 32935 } 32936 32937 WriteToDebug("Converting SCORES to floats"); 32938 intScore = parseFloat(intScore); 32939 intMaxScore = parseFloat(intMaxScore); 32940 intMinScore = parseFloat(intMinScore); 32941 32942 //check the ranges for SCORM 1.2 32943 if(strLMSStandard == 'SCORM') 32944 { 32945 if (intScore < 0 || intScore > 100){ 32946 WriteToDebug("ERROR - intScore out of range"); 32947 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetScore (must be between 0-100), intScore=" + intScore); 32948 return false; 32949 } 32950 32951 if (intMaxScore < 0 || intMaxScore > 100){ 32952 WriteToDebug("ERROR - intMaxScore out of range"); 32953 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetScore (must be between 0-100), intMaxScore=" + intMaxScore); 32954 return false; 32955 } 32956 32957 if (intMinScore < 0 || intMinScore > 100){ 32958 WriteToDebug("ERROR - intMinScore out of range"); 32959 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetScore (must be between 0-100), intMinScore=" + intMinScore); 32960 return false; 32961 } 32962 } 32963 32964 if (SCORE_CAN_ONLY_IMPROVE === true){ 32965 32966 var previousScore = GetScore(); 32967 32968 if (previousScore != null && previousScore != "" && previousScore > intScore){ 32969 WriteToDebug("Previous score was greater than new score, configuration only allows scores to improve, returning."); 32970 return true; 32971 } 32972 } 32973 32974 WriteToDebug("Calling to LMS"); 32975 return objLMS.SetPointBasedScore(intScore, intMaxScore, intMinScore); 32976 32977} 32978 32979 32980//class to hold a short and long version of an identifier for an interaction response 32981function CreateResponseIdentifier(strShort, strLong){ 32982 // manipulate the short identifier into something that will work 32983 strShort = strShort.replace(/\W|_/g, ''); 32984 strShort = strShort.length > 0 ? strShort.substr(0, 1) : 'z'; 32985 32986 //validate that the short id is a single alphanumeric character 32987 32988 if (strShort.replace(" ", "") == ""){ 32989 WriteToDebug("Short Identifier is empty"); 32990 SetErrorInfo(ERROR_INVALID_ID, "Invalid short identifier, strShort=" + strShort); 32991 return false; 32992 } 32993 32994 if (strShort.length != 1){ 32995 WriteToDebug("ERROR - Short Identifier not 1 character"); 32996 SetErrorInfo(ERROR_INVALID_ID, "Invalid short identifier, strShort=" + strShort); 32997 return false; 32998 } 32999 33000 if ( ! IsAlphaNumeric(strShort) ){ 33001 WriteToDebug("ERROR - Short Identifier not alpha numeric"); 33002 SetErrorInfo(ERROR_INVALID_ID, "Invalid short identifier, strShort=" + strShort); 33003 return false;
33004 } 33005 33006 //convert the short identifier to lower case because it is unclear in the SCORM 1.2 spec whether or not 33007 //the response is allowed to be upper case and some LMS's may only allow lower case 33008 strShort = strShort.toLowerCase(); 33009 33010 strLong = CreateValidIdentifier(strLong); 33011 33012 33013 return new ResponseIdentifier(strShort, strLong); 33014} 33015 33016 33017 33018 33019function ResponseIdentifier(strShort, strLong){ 33020 this.Short = new String(strShort); 33021 this.Long = new String(strLong); 33022 33023 this.toString = function (){ 33024 return "[Response Identifier " + this.Short + ", " + this.Long + "]"; 33025 }; 33026} 33027 33028//Represents a response to a matching interaction 33029//Contains two values, a source and a target, each of which can be a string or a ResponseIdentifier 33030function MatchingResponse(source, target){ 33031 33032 if (source.constructor == String){ 33033 source = CreateResponseIdentifier(source, source); 33034 } 33035 33036 if (target.constructor == String){ 33037 target = CreateResponseIdentifier(target, target); 33038 } 33039 33040 this.Source = source; 33041 this.Target = target; 33042 33043 this.toString = function (){ 33044 return "[Matching Response " + this.Source + ", " + this.Target + "]"; 33045 }; 33046} 33047 33048function CreateMatchingResponse(pattern) 33049{ 33050 var aryPairs = new Array(); 33051 var aryEachPair = new Array(); 33052 pattern = new String(pattern); 33053 33054 33055 aryPairs = pattern.split("[,]"); 33056 33057 for(var i=0; i<aryPairs.length; i++) 33058 { 33059 var thisPair = new String(aryPairs[i]); 33060 aryEachPair = thisPair.split("[.]"); 33061 WriteToDebug("Matching Response [" + i + "] source: " + aryEachPair[0] + " target: " + aryEachPair[1]); 33062 aryPairs[i] = new MatchingResponse(aryEachPair[0], aryEachPair[1]); 33063 } 33064 33065 WriteToDebug("pattern: " + pattern + " becomes " + aryPairs[0]); 33066 33067 if (aryPairs.length==0) return aryPairs[0]; 33068 else return aryPairs; 33069} 33070 33071/** 33072 * Convert this string into something that would be a valid identifier in this 33073 * learning standard. 33074 */ 33075function CreateValidIdentifier(str) { 33076 return objLMS.CreateValidIdentifier(str); 33077} 33078 33079function CreateUriIdentifier(str, iri) { 33080 // I'm not sure this is valid, in any learning standard, but this is what 33081 // we had in earlier versions of driver 33082 if (str === undefined || str === null || str === "") 33083 { 33084 return ""; 33085 } 33086 33087 str = Trim(str); 33088 33089 var uri = new URI(str); 33090 33091 // note that URNs return true here 33092 if (!uri.is('absolute')) 33093 { 33094 // If we're here, then the string we received probably wasn't a URN. So 33095 // let's attempt to create one in the 'scormdriver' namespace. 33096 str = 'urn:scormdriver:' + encodeURIComponent(str); 33097 uri = new URI(str); 33098 } 33099 33100 uri.normalize(); 33101 33102 if (iri) { 33103 uri.iri(); 33104 } 33105 33106 return uri.toString(); 33107} 33108 33109//make sure all the criteria are met to allow this value to be an identifier 33110function CreateValidIdentifierLegacy(str){ 33111 if (str != null || str != "") { 33112 str = new String(str); 33113 str = Trim(str); 33114 33115 //if the string starts with "urn:" then it has special requirements 33116 if (str.toLowerCase().indexOf("urn:") == 0) { 33117 str = str.substr(4); 33118 } 33119 33120 //urns may only contain the following characters: letters, numbers - ( ) + . : = @ ; $ _ ! * ' % 33121 //if anything else is found, replace it with _ 33122 str = str.replace(/[^\w\-\(\)\+\.\:\=\@\;\$\_\!\*\'\%]/g, "_"); 33123 33124 return str; 33125 } 33126 33127 return ""; 33128} 33129 33130function Trim(str){ 33131 str = str + ''; 33132 str = str.replace(/^\s*/, ""); 33133 str = str.replace(/\s*$/, ""); 33134 return str; 33135} 33136 33137 33138 33139function RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33140 33141 //clean the ID if needed 33142 strID = CreateValidIdentifier(strID); 33143 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33144 33145 WriteToDebug("In RecordTrueFalseInteraction strID=" + strID + ", blnResponse=" + blnResponse + 33146 ", blnCorrect=" + blnCorrect + ", blnCorrectResponse=" + blnCorrectResponse + 33147 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33148 33149 33150 if ( !(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33151 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33152 return true; 33153 } 33154 33155 ClearErrorInfo(); 33156 33157 if (! IsLoaded()){ 33158 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33159 return false;
33160 } 33161 33162 //blnResponse and blnCorrectResponse must be valid boolean values 33163 if ((blnResponse != true && blnResponse != false && blnResponse !== null) || (blnResponse === null && ! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE)){ 33164 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The Response parameter must be a valid boolean value."); 33165 return false; 33166 } 33167 if (blnCorrectResponse != null && blnCorrectResponse != true && blnCorrectResponse != false){ 33168 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The Correct Response parameter must be a valid boolean value or null."); 33169 return false; 33170 } 33171 33172 var dtmTime = new Date(); 33173 33174 WriteToDebug("Calling to LMS"); 33175 return objLMS.RecordTrueFalseInteraction(strID, blnResponse, blnCorrect, blnCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33176} 33177 33178//public 33179//strResponse and strCorrectResponse can be either: 33180// -a single character alphanumeric string representing the response 33181// -a single ResponseIdentifier object 33182// -an array of ResponseIdentifier objects representing multiple selections that must be/were made 33183 33184function RecordMultipleChoiceInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33185 33186 //clean the ID if needed 33187 strID = CreateValidIdentifier(strID); 33188 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33189 33190 WriteToDebug("In RecordMultipleChoiceInteraction strID=" + strID + ", response=" + response + 33191 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 33192 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33193 33194 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33195 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33196 return true; 33197 } 33198 33199 ClearErrorInfo(); 33200 33201 if (! IsLoaded()){ 33202 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33203 return false; 33204 } 33205 33206 strID = new String(strID); 33207 33208 var aryResponse; 33209 var aryCorrectResponse; 33210 33211 //translate the 3 possible argument types into to an array of ResponseIdentifier objects to be passed to the standard functions 33212 if (response === null) { 33213 if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) { 33214 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)"); 33215 return false; 33216 } 33217 33218 aryResponse = null; 33219 } 33220 else if (response.constructor == String){ 33221 33222 aryResponse = new Array(); 33223 var responseIdentifier = CreateResponseIdentifier(response, response); 33224 33225 if (responseIdentifier == false){ 33226 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33227 return false; 33228 } 33229 33230 aryResponse[0] = responseIdentifier; 33231 } 33232 else if (response.constructor == ResponseIdentifier){ 33233 aryResponse = new Array(); 33234 aryResponse[0] = response; 33235 } 33236 else if (response.constructor == Array || response.constructor.toString().search("Array") > 0){ //sometimes the array constructor isn't handled correctly so we check its string 33237 aryResponse = response; 33238 } 33239 else if(window.console && response.constructor.toString() == '(Internal Function)' && response.length > 0){ 33240 // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string 33241 aryResponse = response; 33242 } 33243 else{ 33244 if(window.console){ 33245 window.console.log("ERROR_INVALID_INTERACTION_RESPONSE :: The response is not in the correct format."); 33246 } 33247 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33248 return false; 33249 } 33250 33251 33252 if (correctResponse != null && correctResponse != undefined && correctResponse != ""){ 33253 if (correctResponse.constructor == String){ 33254 33255 aryCorrectResponse = new Array(); 33256 responseIdentifier = CreateResponseIdentifier(correctResponse, correctResponse); 33257 33258 if (responseIdentifier == false){ 33259 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format"); 33260 return false;
33261 } 33262 33263 aryCorrectResponse[0] = responseIdentifier; 33264 33265 } 33266 else if (correctResponse.constructor == ResponseIdentifier){ 33267 aryCorrectResponse = new Array(); 33268 aryCorrectResponse[0] = correctResponse; 33269 } 33270 else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0){ //sometimes the array constructor isn't handled correctly so we check its string 33271 aryCorrectResponse = correctResponse; 33272 } 33273 else if(window.console && correctResponse.constructor.toString() == '(Internal Function)' && correctResponse.length > 0){ 33274 // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string 33275 aryCorrectResponse = correctResponse; 33276 } 33277 else{ 33278 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format"); 33279 return false; 33280 } 33281 } 33282 else{ 33283 aryCorrectResponse = new Array(); 33284 } 33285 33286 var dtmTime = new Date(); 33287 33288 WriteToDebug("Calling to LMS"); 33289 return objLMS.RecordMultipleChoiceInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33290 33291 33292} 33293 33294 33295function RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33296 33297 //clean the ID if needed 33298 strID = CreateValidIdentifier(strID); 33299 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33300 33301 WriteToDebug("In RecordFillInInteraction strID=" + strID + ", strResponse=" + strResponse + 33302 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 33303 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33304 33305 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33306 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33307 return true; 33308 } 33309 33310 ClearErrorInfo(); 33311 33312 if (! IsLoaded()){ 33313 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33314 return false; 33315 } 33316 33317 var dtmTime = new Date(); 33318 33319 WriteToDebug("Calling to LMS"); 33320 return objLMS.RecordFillInInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33321 33322} 33323 33324 33325//public 33326//strResponse and strCorrectResponse can be either: 33327// -a single MatchingResponse object 33328// -an array of MatchingResponse objects representing multiple selections that must be/were made 33329 33330function RecordMatchingInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33331 33332 //clean the ID if needed 33333 strID = CreateValidIdentifier(strID); 33334 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33335 33336 WriteToDebug("In RecordMatchingInteraction strID=" + strID + ", response=" + response + 33337 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 33338 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33339 33340 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33341 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33342 return true; 33343 } 33344 33345 ClearErrorInfo(); 33346 33347 if (! IsLoaded()){ 33348 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33349 return false; 33350 } 33351 33352 var aryResponse; 33353 var aryCorrectResponse; 33354 33355 if (response === null) { 33356 if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) { 33357 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)"); 33358 return false;
33359 } 33360 33361 aryResponse = null; 33362 } 33363 else if (response.constructor == MatchingResponse){ 33364 aryResponse = new Array(); 33365 aryResponse[0] = response; 33366 } 33367 else if (response.constructor == Array || response.constructor.toString().search("Array") > 0){ //sometimes the array constructor isn't handled correctly so we check its string 33368 aryResponse = response; 33369 } 33370 else if(window.console && response.constructor.toString() == '(Internal Function)' && response.length > 0){ 33371 // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string 33372 aryResponse = response; 33373 } 33374 else{ 33375 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33376 return false; 33377 } 33378 33379 if (correctResponse != null && correctResponse != undefined){ 33380 if (correctResponse.constructor == MatchingResponse){ 33381 aryCorrectResponse = new Array(); 33382 aryCorrectResponse[0] = correctResponse; 33383 } 33384 else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0){ //sometimes the array constructor isn't handled correctly so we check its string 33385 aryCorrectResponse = correctResponse; 33386 } 33387 else if(window.console && correctResponse.constructor.toString() == '(Internal Function)' && correctResponse.length > 0){ 33388 // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string 33389 aryCorrectResponse = correctResponse; 33390 } 33391 else{ 33392 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33393 return false; 33394 } 33395 } 33396 else{ 33397 aryCorrectResponse = new Array(); 33398 } 33399 33400 var dtmTime = new Date(); 33401 33402 WriteToDebug("Calling to LMS"); 33403 return objLMS.RecordMatchingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33404 33405} 33406 33407function RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33408 33409 //clean the ID if needed 33410 strID = CreateValidIdentifier(strID); 33411 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33412 33413 WriteToDebug("In RecordPerformanceInteraction strID=" + strID + ", strResponse=" + strResponse + 33414 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 33415 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33416 33417 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33418 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33419 return true; 33420 } 33421 33422 ClearErrorInfo(); 33423 33424 if (! IsLoaded()){ 33425 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33426 return false; 33427 } 33428 33429 var dtmTime = new Date(); 33430 33431 WriteToDebug("Calling to LMS"); 33432 return objLMS.RecordPerformanceInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33433} 33434 33435//public 33436//response and correctResponse can be either: 33437// -a string representing the response 33438// -a single ResponseIdentifier object 33439// -an array of ResponseIdentifier objects representing multiple steps that must be/were made 33440 33441function RecordSequencingInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33442 33443 //clean the ID if needed 33444 strID = CreateValidIdentifier(strID); 33445 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33446 33447 WriteToDebug("In RecordSequencingInteraction strID=" + strID + ", response=" + response + 33448 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 33449 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33450 33451 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33452 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33453 return true; 33454 } 33455 33456 ClearErrorInfo(); 33457 33458 if (! IsLoaded()){ 33459 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33460 return false;
33461 } 33462 33463 33464 var aryResponse; 33465 var aryCorrectResponse; 33466 33467 //translate the 3 possible argument types into to an array of ResponseIdentifier objects to be passed to the standard functions 33468 if (response === null) { 33469 if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) { 33470 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)"); 33471 return false; 33472 } 33473 33474 aryResponse = null; 33475 } 33476 else if (response.constructor == String){ 33477 33478 aryResponse = new Array(); 33479 var responseIdentifier = CreateResponseIdentifier(response, response); 33480 33481 if (responseIdentifier == false){ 33482 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33483 return false; 33484 } 33485 33486 aryResponse[0] = responseIdentifier; 33487 } 33488 else if (response.constructor == ResponseIdentifier){ 33489 aryResponse = new Array(); 33490 aryResponse[0] = response; 33491 } 33492 else if (response.constructor == Array || response.constructor.toString().search("Array") > 0){ //sometimes the array constructor isn't handled correctly so we check its string 33493 aryResponse = response; 33494 } 33495 else if(window.console && response.constructor.toString() == '(Internal Function)' && response.length > 0){ 33496 // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string 33497 aryResponse = response; 33498 } 33499 else{ 33500 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33501 return false; 33502 } 33503 33504 33505 if (correctResponse != null && correctResponse != undefined && correctResponse != ""){ 33506 if (correctResponse.constructor == String){ 33507 33508 aryCorrectResponse = new Array(); 33509 responseIdentifier = CreateResponseIdentifier(correctResponse, correctResponse); 33510 33511 if (responseIdentifier == false){ 33512 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format"); 33513 return false; 33514 } 33515 33516 aryCorrectResponse[0] = responseIdentifier; 33517 33518 } 33519 else if (correctResponse.constructor == ResponseIdentifier){ 33520 aryCorrectResponse = new Array(); 33521 aryCorrectResponse[0] = correctResponse; 33522 } 33523 else if (correctResponse.constructor == Array || correctResponse.constructor.toString().search("Array") > 0){ //sometimes the array constructor isn't handled correctly so we check its string 33524 aryCorrectResponse = correctResponse; 33525 } 33526 else if(window.console && correctResponse.constructor.toString() == '(Internal Function)' && correctResponse.length > 0){ 33527 // we're in Safari. Safari doesn't return the Array function as a string like IE and FireFox. It does however return the ResponseIdentifier function as a string 33528 aryCorrectResponse = correctResponse; 33529 } 33530 else{ 33531 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The correct response is not in the correct format"); 33532 return false; 33533 } 33534 } 33535 else{ 33536 aryCorrectResponse = new Array(); 33537 } 33538 33539 33540 var dtmTime = new Date(); 33541 33542 WriteToDebug("Calling to LMS"); 33543 return objLMS.RecordSequencingInteraction(strID, aryResponse, blnCorrect, aryCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33544 33545} 33546 33547//public 33548//response and correctResponse can be either: 33549// -a string representing the response 33550// -a single ResponseIdentifier object 33551 33552function RecordLikertInteraction(strID, response, blnCorrect, correctResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33553 33554 //clean the ID if needed 33555 strID = CreateValidIdentifier(strID); 33556 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33557 33558 WriteToDebug("In RecordLikertInteraction strID=" + strID + ", response=" + response + 33559 ", blnCorrect=" + blnCorrect + ", correctResponse=" + correctResponse + 33560 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33561 33562 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33563 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33564 return true; 33565 } 33566 33567 ClearErrorInfo(); 33568 33569 if (! IsLoaded()){ 33570 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33571 return false;
33572 } 33573 33574 var riResponse; 33575 var riCorrectResponse; 33576 33577 //translate the 3 possible argument types into to an array of ResponseIdentifier objects to be passed to the standard functions 33578 if (response === null) { 33579 if (! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) { 33580 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format (null response not allowed)"); 33581 return false; 33582 } 33583 33584 riResponse = null; 33585 } 33586 else if (response.constructor == String){ 33587 riResponse = CreateResponseIdentifier(response, response); 33588 } 33589 else if (response.constructor == ResponseIdentifier){ 33590 riResponse = response; 33591 } 33592 else{ 33593 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33594 return false; 33595 } 33596 33597 33598 if (correctResponse == null || correctResponse == undefined){ 33599 riCorrectResponse = null; 33600 } 33601 else if (correctResponse.constructor == ResponseIdentifier){ 33602 riCorrectResponse = correctResponse; 33603 } 33604 else if (correctResponse.constructor == String){ 33605 riCorrectResponse = CreateResponseIdentifier(correctResponse, correctResponse); 33606 } 33607 else{ 33608 SetErrorInfo(ERROR_INVALID_INTERACTION_RESPONSE, "The response is not in the correct format"); 33609 return false; 33610 } 33611 33612 var dtmTime = new Date(); 33613 33614 WriteToDebug("Calling to LMS"); 33615 return objLMS.RecordLikertInteraction(strID, riResponse, blnCorrect, riCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33616 33617} 33618 33619function RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID){ 33620 33621 //clean the IDs if needed 33622 strID = CreateValidIdentifier(strID); 33623 strLearningObjectiveID = CreateValidIdentifier(strLearningObjectiveID); 33624 33625 WriteToDebug("In RecordNumericInteraction strID=" + strID + ", strResponse=" + strResponse + 33626 ", blnCorrect=" + blnCorrect + ", strCorrectResponse=" + strCorrectResponse + 33627 ", strDescription=" + strDescription + ", intWeighting=" + intWeighting + ", intLatency=" + intLatency + ", strLearningObjectiveID=" + strLearningObjectiveID); 33628 33629 if (!(typeof(DO_NOT_REPORT_INTERACTIONS) == "undefined") && DO_NOT_REPORT_INTERACTIONS === true){ 33630 WriteToDebug("Configuration specifies interactions should not be reported, exiting."); 33631 return true; 33632 } 33633 33634 ClearErrorInfo(); 33635 33636 if (! IsLoaded()){ 33637 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33638 return false; 33639 } 33640 33641 if ((strResponse === null && ! ALLOW_INTERACTION_NULL_LEARNER_RESPONSE) || (strResponse !== null && ! IsValidDecimal(strResponse))){ 33642 WriteToDebug("ERROR - Invalid Response, not a valid decmial"); 33643 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Response passed to RecordNumericInteraction (not a valid decimal), strResponse=" + strResponse); 33644 return false; 33645 } 33646 33647 var dtmTime = new Date(); 33648 33649 WriteToDebug("Calling to LMS"); 33650 return objLMS.RecordNumericInteraction(strID, strResponse, blnCorrect, strCorrectResponse, strDescription, intWeighting, intLatency, strLearningObjectiveID, dtmTime); 33651} 33652 33653 33654//State Functions 33655 33656//public 33657function GetStatus(){ 33658 33659 WriteToDebug("In GetStatus"); 33660 33661 ClearErrorInfo(); 33662 33663 if (! IsLoaded()){ 33664 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33665 return LESSON_STATUS_INCOMPLETE; 33666 } 33667 33668 return objLMS.GetStatus(); 33669 33670} 33671 33672//public 33673function ResetStatus(){ 33674 WriteToDebug("In ResetStatus"); 33675 33676 ClearErrorInfo(); 33677 33678 if (! IsLoaded()){ 33679 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33680 return false; 33681 } 33682 33683 WriteToDebug("Setting blnStatusWasSet to false"); 33684 33685 blnStatusWasSet = false; 33686 33687 return objLMS.ResetStatus(); 33688} 33689 33690//public 33691function GetProgressMeasure(){ 33692 WriteToDebug("In GetProgressMeasure"); 33693 33694 ClearErrorInfo(); 33695 33696 if (! IsLoaded()){ 33697 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33698 return LESSON_STATUS_INCOMPLETE; 33699 } 33700 33701 return objLMS.GetProgressMeasure(); 33702} 33703//public 33704function SetProgressMeasure(numMeasure){ 33705 WriteToDebug("In SetProgressMeasure, passing in: "+ numMeasure); 33706 33707 ClearErrorInfo(); 33708 33709 if (! IsLoaded()){ 33710 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33711 return LESSON_STATUS_INCOMPLETE; 33712 } 33713 33714 return objLMS.SetProgressMeasure(numMeasure); 33715} 33716 33717//public 33718//optional to call 33719function SetPassed(){ 33720 WriteToDebug("In SetPassed"); 33721 33722 ClearErrorInfo(); 33723 33724 if (! IsLoaded()){ 33725 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33726 return false;
33727 } 33728 33729 WriteToDebug("Setting blnStatusWasSet to true"); 33730 33731 blnStatusWasSet = true; 33732 33733 return objLMS.SetPassed(); 33734 33735} 33736 33737//public 33738function SetFailed(){ 33739 WriteToDebug("In SetFailed"); 33740 33741 ClearErrorInfo(); 33742 33743 if (! IsLoaded()){ 33744 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33745 return false; 33746 } 33747 33748 WriteToDebug("Setting blnStatusWasSet to true"); 33749 33750 blnStatusWasSet = true; 33751 33752 return objLMS.SetFailed(); 33753 33754} 33755 33756 33757//public 33758function GetEntryMode(){ 33759 WriteToDebug("In GetEntryMode"); 33760 ClearErrorInfo(); 33761 33762 if (! IsLoaded()){ 33763 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33764 return ENTRY_FIRST_TIME; 33765 } 33766 33767 return objLMS.GetEntryMode(); 33768 33769} 33770 33771//public 33772function GetLessonMode(){ 33773 WriteToDebug("In GetLessonMode"); 33774 ClearErrorInfo(); 33775 33776 if (! IsLoaded()){ 33777 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33778 return MODE_NORMAL; 33779 } 33780 33781 return objLMS.GetLessonMode(); 33782 33783} 33784 33785//public 33786function GetTakingForCredit(){ 33787 WriteToDebug("In GetTakingForCredit"); 33788 ClearErrorInfo(); 33789 33790 if (! IsLoaded()){ 33791 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33792 return false; 33793 } 33794 33795 return objLMS.GetTakingForCredit(); 33796 33797} 33798 33799 33800//Objectives 33801 33802//public 33803function SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore){ 33804 33805 WriteToDebug("In SetObjectiveScore, intObjectiveID=" + strObjectiveID + ", intScore=" + intScore + ", intMaxScore=" + intMaxScore + ", intMinScore=" + intMinScore); 33806 33807 ClearErrorInfo(); 33808 33809 if (! IsLoaded()){ 33810 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33811 return false; 33812 } 33813 33814 strObjectiveID = new String(strObjectiveID); 33815 if (strObjectiveID.replace(" ", "") == ""){ 33816 WriteToDebug("ERROR - Invalid ObjectiveID, empty string"); 33817 SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveScore (must have a value), strObjectiveID=" + strObjectiveID); 33818 return false; 33819 } 33820 33821 if (! IsValidDecimal(intScore)){ 33822 WriteToDebug("ERROR - Invalid Score, not a valid decmial"); 33823 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetObjectiveScore (not a valid decimal), intScore=" + intScore); 33824 return false; 33825 } 33826 33827 if (! IsValidDecimal(intMaxScore)){ 33828 WriteToDebug("ERROR - Invalid Max Score, not a valid decmial"); 33829 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetObjectiveScore (not a valid decimal), intMaxScore=" + intMaxScore); 33830 return false; 33831 } 33832 33833 if (! IsValidDecimal(intMinScore)){ 33834 WriteToDebug("ERROR - Invalid Min Score, not a valid decmial"); 33835 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetObjectiveScore (not a valid decimal), intMinScore=" + intMinScore); 33836 return false; 33837 } 33838 33839 WriteToDebug("Converting Scores to floats"); 33840 intScore = parseFloat(intScore); 33841 intMaxScore = parseFloat(intMaxScore); 33842 intMinScore = parseFloat(intMinScore); 33843 33844 if (intScore < 0 || intScore > 100){ 33845 WriteToDebug("ERROR - Invalid Score, out of range"); 33846 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Score passed to SetObjectiveScore (must be between 0-100), intScore=" + intScore); 33847 return false; 33848 } 33849 33850 if (intMaxScore < 0 || intMaxScore > 100){ 33851 WriteToDebug("ERROR - Invalid Max Score, out of range"); 33852 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Score passed to SetObjectiveScore (must be between 0-100), intMaxScore=" + intMaxScore); 33853 return false; 33854 } 33855 33856 if (intMinScore < 0 || intMinScore > 100){ 33857 WriteToDebug("ERROR - Invalid Min Score, out of range"); 33858 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Score passed to SetObjectiveScore (must be between 0-100), intMinScore=" + intMinScore); 33859 return false; 33860 } 33861 33862 WriteToDebug("Calling To LMS"); 33863 return objLMS.SetObjectiveScore(strObjectiveID, intScore, intMaxScore, intMinScore); 33864 33865} 33866 33867//public 33868function SetObjectiveStatus(strObjectiveID, Lesson_Status){ 33869 33870 WriteToDebug("In SetObjectiveStatus strObjectiveID=" + strObjectiveID + ", Lesson_Status=" + Lesson_Status); 33871 33872 ClearErrorInfo(); 33873 33874 33875 if (! IsLoaded()){ 33876 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33877 return false;
33878 } 33879 33880 33881 strObjectiveID = new String(strObjectiveID); 33882 if (strObjectiveID.replace(" ", "") == ""){ 33883 WriteToDebug("ERROR - Invalid ObjectiveID, empty string"); 33884 SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveStatus (must have a value), strObjectiveID=" + strObjectiveID); 33885 return false; 33886 } 33887 33888 if ( 33889 (Lesson_Status != LESSON_STATUS_PASSED) && 33890 (Lesson_Status != LESSON_STATUS_COMPLETED) && 33891 (Lesson_Status != LESSON_STATUS_FAILED) && 33892 (Lesson_Status != LESSON_STATUS_INCOMPLETE) && 33893 (Lesson_Status != LESSON_STATUS_BROWSED) && 33894 (Lesson_Status != LESSON_STATUS_NOT_ATTEMPTED) 33895 ){ 33896 WriteToDebug("ERROR - Invalid Status"); 33897 SetErrorInfo(ERROR_INVALID_STATUS, "Invalid status passed to SetObjectiveStatus, Lesson_Status=" + Lesson_Status); 33898 return false; 33899 33900 } 33901 33902 WriteToDebug("Calling To LMS"); 33903 return objLMS.SetObjectiveStatus(strObjectiveID, Lesson_Status); 33904 33905 33906} 33907 33908 33909//public 33910function GetObjectiveStatus(strObjectiveID){ 33911 33912 WriteToDebug("In GetObjectiveStatus, strObjectiveID=" + strObjectiveID); 33913 33914 ClearErrorInfo(); 33915 33916 if (! IsLoaded()){ 33917 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33918 return false; 33919 } 33920 33921 return objLMS.GetObjectiveStatus(strObjectiveID); 33922} 33923 33924 33925 33926 33927//public 33928function SetObjectiveDescription(strObjectiveID, strObjectiveDescription){ 33929 33930 WriteToDebug("In SetObjectiveDescription strObjectiveID=" + strObjectiveID + ", strObjectiveDescription=" + strObjectiveDescription); 33931 33932 ClearErrorInfo(); 33933 33934 33935 if (! IsLoaded()){ 33936 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33937 return false; 33938 } 33939 33940 33941 strObjectiveID = new String(strObjectiveID); 33942 if (strObjectiveID.replace(" ", "") == ""){ 33943 WriteToDebug("ERROR - Invalid ObjectiveID, empty string"); 33944 SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveStatus (must have a value), strObjectiveID=" + strObjectiveID); 33945 return false; 33946 } 33947 33948 33949 //include validation for string length? 33950 33951 WriteToDebug("Calling To LMS"); 33952 return objLMS.SetObjectiveDescription(strObjectiveID, strObjectiveDescription); 33953 33954 33955} 33956 33957 33958//public 33959function GetObjectiveDescription(strObjectiveID){ 33960 33961 WriteToDebug("In GetObjectiveDescription, strObjectiveID=" + strObjectiveID); 33962 33963 ClearErrorInfo(); 33964 33965 if (! IsLoaded()){ 33966 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33967 return false; 33968 } 33969 33970 return objLMS.GetObjectiveDescription(strObjectiveID); 33971} 33972 33973 33974 33975 33976 33977//public 33978function GetObjectiveScore(strObjectiveID){ 33979 33980 WriteToDebug("In GetObjectiveScore, strObjectiveID=" + strObjectiveID); 33981 33982 ClearErrorInfo(); 33983 33984 if (! IsLoaded()){ 33985 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 33986 return false; 33987 } 33988 33989 return objLMS.GetObjectiveScore(strObjectiveID); 33990} 33991 33992 33993//public 33994function IsLmsPresent(){ 33995 return blnLmsPresent; 33996} 33997 33998//public 33999function SetObjectiveProgressMeasure(strObjectiveID, strObjectiveProgressMeasure){ 34000 34001 WriteToDebug("In SetObjectiveProgressMeasure strObjectiveID=" + strObjectiveID + ", strObjectiveProgressMeasure=" + strObjectiveProgressMeasure); 34002 34003 ClearErrorInfo(); 34004 34005 34006 if (! IsLoaded()){ 34007 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34008 return false; 34009 } 34010 34011 34012 strObjectiveID = new String(strObjectiveID); 34013 if (strObjectiveID.replace(" ", "") == ""){ 34014 WriteToDebug("ERROR - Invalid ObjectiveID, empty string"); 34015 SetErrorInfo(ERROR_INVALID_ID, "Invalid ObjectiveID passed to SetObjectiveProgressMeasure (must have a value), strObjectiveID=" + strObjectiveID); 34016 return false; 34017 } 34018 34019 34020 //include validation for string length? 34021 34022 WriteToDebug("Calling To LMS"); 34023 return objLMS.SetObjectiveProgressMeasure(strObjectiveID, strObjectiveProgressMeasure); 34024 34025 34026} 34027 34028 34029//public 34030function GetObjectiveProgressMeasure(strObjectiveID){ 34031 34032 WriteToDebug("In GetObjectiveProgressMeasure, strObjectiveID=" + strObjectiveID); 34033 34034 ClearErrorInfo(); 34035 34036 if (! IsLoaded()){ 34037 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34038 return false;
34039 } 34040 34041 return objLMS.GetObjectiveProgressMeasure(strObjectiveID); 34042} 34043 34044 34045//public 34046function SetNavigationRequest(strNavRequest){ 34047 WriteToDebug("In SetNavigationRequest"); 34048 34049 ClearErrorInfo(); 34050 34051 if (! IsLoaded()){ 34052 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34053 return false; 34054 } 34055 34056 return objLMS.SetNavigationRequest(strNavRequest); 34057} 34058//public 34059function GetNavigationRequest(){ 34060 WriteToDebug("In GetNavigationRequest"); 34061 34062 ClearErrorInfo(); 34063 34064 if (! IsLoaded()){ 34065 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34066 return false; 34067 } 34068 34069 return objLMS.GetNavigationRequest(); 34070} 34071 34072//NOTE ON INTERACTION RETRIEVAL 34073//A. It is only available in certain standards, standards where it is unavailable will return nothing 34074//B. The interaction records are currently reported using "journaling", whereby each entry is appended 34075// Retrieval methods will retrieve only the most recent value 34076//public 34077function GetInteractionType(strInteractionID) 34078{ 34079 // Clean the ID if needed 34080 strInteractionID = CreateValidIdentifier(strInteractionID); 34081 34082 WriteToDebug("In GetInteractionType, strInteractionID=" + strInteractionID); 34083 34084 ClearErrorInfo(); 34085 34086 if (! IsLoaded()){ 34087 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34088 return false; 34089 } 34090 34091 return objLMS.GetInteractionType(strInteractionID); 34092} 34093 34094//public 34095function GetInteractionTimestamp(strInteractionID) 34096{ 34097 // Clean the ID if needed 34098 strInteractionID = CreateValidIdentifier(strInteractionID); 34099 34100 WriteToDebug("In GetInteractionTimestamp, strInteractionID=" + strInteractionID); 34101 34102 ClearErrorInfo(); 34103 34104 if (! IsLoaded()){ 34105 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34106 return false; 34107 } 34108 34109 return objLMS.GetInteractionTimestamp(strInteractionID); 34110} 34111 34112 34113 34114//public 34115function GetInteractionCorrectResponses(strInteractionID) 34116{ 34117 // Clean the ID if needed 34118 strInteractionID = CreateValidIdentifier(strInteractionID); 34119 34120 WriteToDebug("In GetInteractionCorrectResponses, strInteractionID=" + strInteractionID); 34121 34122 ClearErrorInfo(); 34123 34124 if (! IsLoaded()){ 34125 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34126 return false; 34127 } 34128 34129 return objLMS.GetInteractionCorrectResponses(strInteractionID); 34130} 34131 34132 34133 34134//public 34135function GetInteractionWeighting(strInteractionID) 34136{ 34137 // Clean the ID if needed 34138 strInteractionID = CreateValidIdentifier(strInteractionID); 34139 34140 WriteToDebug("In GetInteractionWeighting, strInteractionID=" + strInteractionID); 34141 34142 ClearErrorInfo(); 34143 34144 if (! IsLoaded()){ 34145 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34146 return false; 34147 } 34148 34149 return objLMS.GetInteractionWeighting(strInteractionID); 34150} 34151 34152 34153 34154//public 34155function GetInteractionLearnerResponses(strInteractionID) 34156{ 34157 // Clean the ID if needed 34158 strInteractionID = CreateValidIdentifier(strInteractionID); 34159 34160 WriteToDebug("In GetInteractionLearnerResponses, strInteractionID=" + strInteractionID); 34161 34162 ClearErrorInfo(); 34163 34164 if (! IsLoaded()){ 34165 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34166 return false; 34167 } 34168 34169 return objLMS.GetInteractionLearnerResponses(strInteractionID); 34170} 34171 34172 34173 34174//public 34175function GetInteractionResult(strInteractionID) 34176{ 34177 // Clean the ID if needed 34178 strInteractionID = CreateValidIdentifier(strInteractionID); 34179 34180 WriteToDebug("In GetInteractionResult, strInteractionID=" + strInteractionID); 34181 34182 ClearErrorInfo(); 34183 34184 if (! IsLoaded()){ 34185 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34186 return false; 34187 } 34188 34189 return objLMS.GetInteractionResult(strInteractionID); 34190} 34191 34192 34193 34194//public 34195function GetInteractionLatency(strInteractionID) 34196{ 34197 // Clean the ID if needed 34198 strInteractionID = CreateValidIdentifier(strInteractionID); 34199 34200 WriteToDebug("In GetInteractionLatency, strInteractionID=" + strInteractionID); 34201 34202 ClearErrorInfo(); 34203 34204 if (! IsLoaded()){ 34205 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34206 return false;
34207 } 34208 34209 return objLMS.GetInteractionLatency(strInteractionID); 34210} 34211 34212 34213 34214//public 34215function GetInteractionDescription(strInteractionID) 34216{ 34217 // Clean the ID if needed 34218 strInteractionID = CreateValidIdentifier(strInteractionID); 34219 34220 WriteToDebug("In GetInteractionDescription, strInteractionID=" + strInteractionID); 34221 34222 ClearErrorInfo(); 34223 34224 if (! IsLoaded()){ 34225 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34226 return false; 34227 } 34228 34229 return objLMS.GetInteractionDescription(strInteractionID); 34230} 34231 34232 34233//public 34234/* 34235Bucket sizes are listed in characters. In SCORM, all transmitted data is represented as JavaScript strings. 34236JavaScript strings use two bytes for each character so the actual size (in bytes) of what is allocated will be 34237double the value that is passed to this function. 34238*/ 34239function CreateDataBucket(strBucketId, intMinSize, intMaxSize){ 34240 34241 WriteToDebug("In CreateDataBucket, strBucketId=" + strBucketId + ", intMinSize=" + intMinSize + ", intMaxSize=" + intMaxSize); 34242 34243 ClearErrorInfo(); 34244 34245 if (! IsLoaded()){ 34246 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34247 return false; 34248 } 34249 34250 strBucketId = new String(strBucketId); 34251 if (strBucketId.replace(" ", "") == ""){ 34252 WriteToDebug("ERROR - Invalid BucketId, empty string"); 34253 SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to CreateDataBucket (must have a value), strBucketId=" + strBucketId); 34254 return false; 34255 } 34256 34257 if ( ! ValidInteger(intMinSize) ){ 34258 WriteToDebug("ERROR Invalid Min Size, not an integer"); 34259 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMinSize passed to CreateDataBucket (not an integer), intMinSize=" + intMinSize); 34260 return false; 34261 } 34262 34263 if ( ! ValidInteger(intMaxSize) ){ 34264 WriteToDebug("ERROR Invalid Max Size, not an integer"); 34265 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid intMaxSize passed to CreateDataBucket (not an integer), intMaxSize=" + intMaxSize); 34266 return false; 34267 } 34268 34269 intMinSize = parseInt(intMinSize, 10); 34270 intMaxSize = parseInt(intMaxSize, 10); 34271 34272 if (intMinSize < 0){ 34273 WriteToDebug("ERROR Invalid Min Size, must be greater than or equal to 0"); 34274 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Min Size passed to CreateDataBucket (must be greater than or equal to 0), intMinSize=" + intMinSize); 34275 return false; 34276 } 34277 34278 if (intMaxSize <= 0){ 34279 WriteToDebug("ERROR Invalid Max Size, must be greater than 0"); 34280 SetErrorInfo(ERROR_INVALID_NUMBER, "Invalid Max Size passed to CreateDataBucket (must be greater than 0), intMaxSize=" + intMaxSize); 34281 return false; 34282 } 34283 34284 //need to double the values to get from "characters" (the representation exposed by this API) to "octets" the value used by SSP 34285 intMinSize = (intMinSize * 2); 34286 intMaxSize = (intMaxSize * 2); 34287 34288 return objLMS.CreateDataBucket(strBucketId, intMinSize, intMaxSize); 34289} 34290 34291//public 34292function GetDataFromBucket(strBucketId){ 34293 34294 WriteToDebug("In GetDataFromBucket, strBucketId=" + strBucketId); 34295 34296 ClearErrorInfo(); 34297 34298 if (! IsLoaded()){ 34299 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34300 return false; 34301 } 34302 34303 strBucketId = new String(strBucketId); 34304 if (strBucketId.replace(" ", "") == ""){ 34305 WriteToDebug("ERROR - Invalid BucketId, empty string"); 34306 SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to GetDataFromBucket (must have a value), strBucketId=" + strBucketId); 34307 return false; 34308 } 34309 34310 return objLMS.GetDataFromBucket(strBucketId); 34311} 34312 34313//public 34314function PutDataInBucket(strBucketId, strData, blnAppendToEnd){ 34315 34316 WriteToDebug("In PutDataInBucket, strBucketId=" + strBucketId + ", blnAppendToEnd=" + blnAppendToEnd + ", strData=" + strData); 34317 34318 ClearErrorInfo(); 34319 34320 if (! IsLoaded()){ 34321 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34322 return false; 34323 } 34324 34325 strBucketId = new String(strBucketId); 34326 if (strBucketId.replace(" ", "") == ""){ 34327 WriteToDebug("ERROR - Invalid BucketId, empty string"); 34328 SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to PutDataInBucket (must have a value), strBucketId=" + strBucketId); 34329 return false;
34330 } 34331 34332 if (blnAppendToEnd != true){ 34333 WriteToDebug("blnAppendToEnd was not explicitly true so setting it to false, blnAppendToEnd=" + blnAppendToEnd); 34334 blnAppendToEnd = false; 34335 } 34336 34337 return objLMS.PutDataInBucket(strBucketId, strData, blnAppendToEnd); 34338} 34339 34340//public 34341function DetectSSPSupport(){ 34342 return objLMS.DetectSSPSupport(); 34343} 34344 34345//public returns a SSPBucketSize object containing information about the allocated size of the specified bucket 34346function GetBucketInfo(strBucketId){ 34347 34348 WriteToDebug("In GetBucketInfo, strBucketId=" + strBucketId ); 34349 34350 ClearErrorInfo(); 34351 34352 if (! IsLoaded()){ 34353 SetErrorInfo(ERROR_NOT_LOADED, "Cannot make calls to the LMS before calling Start"); 34354 return false; 34355 } 34356 34357 strBucketId = new String(strBucketId); 34358 if (strBucketId.replace(" ", "") == ""){ 34359 WriteToDebug("ERROR - Invalid BucketId, empty string"); 34360 SetErrorInfo(ERROR_INVALID_ID, "Invalid strBucketId passed to GetBucketInfo (must have a value), strBucketId=" + strBucketId); 34361 return false; 34362 } 34363 34364 var bucketInfo = objLMS.GetBucketInfo(strBucketId); 34365 34366 //bucket size needs to be halfed to translate from octets to characters 34367 bucketInfo.TotalSpace = (bucketInfo.TotalSpace / 2); 34368 bucketInfo.UsedSpace = (bucketInfo.UsedSpace / 2); 34369 34370 WriteToDebug("GetBucketInfo returning " + bucketInfo ); 34371 34372 return bucketInfo; 34373} 34374 34375 34376//Represents the size of an SSP Bucket 34377//Contains two values, a total space and used space, each of which is represented in characters 34378function SSPBucketSize(totalSpace, usedSpace){ 34379 34380 this.TotalSpace = totalSpace; 34381 this.UsedSpace = usedSpace; 34382 34383 this.toString = function (){ 34384 return "[SSPBucketSize " + this.TotalSpace + ", " + this.UsedSpace + "]"; 34385 }; 34386}
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.