From efe005df8ed515426c29fadaa5f8f54a72cb7fc6 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Thu, 12 Apr 2018 15:58:39 -0400 Subject: [PATCH 1/8] Add a Scorm 2004 API, with a list of things to do to achieve full compliance --- Gruntfile.js | 2 +- src/scormAPI2004.compliance.md | 215 +++++++++ src/scormAPI2004.js | 842 +++++++++++++++++++++++++++++++++ 3 files changed, 1058 insertions(+), 1 deletion(-) create mode 100644 src/scormAPI2004.compliance.md create mode 100644 src/scormAPI2004.js diff --git a/Gruntfile.js b/Gruntfile.js index d595b2d..bb10591 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ module.exports = function(grunt) { var banner = "/*! <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today('yyyy-mm-dd') %> */"; - var src = ["src/init.js", "src/constants.js", "src/jsonFormatter.js", "src/baseAPI.js", "src/scormAPI.js"]; + var src = ["src/init.js", "src/constants.js", "src/jsonFormatter.js", "src/baseAPI.js", "src/scormAPI.js", "src/scormAPI2004.js"]; grunt.initConfig({ pkg: grunt.file.readJSON("package.json"), diff --git a/src/scormAPI2004.compliance.md b/src/scormAPI2004.compliance.md new file mode 100644 index 0000000..722c6e7 --- /dev/null +++ b/src/scormAPI2004.compliance.md @@ -0,0 +1,215 @@ +# Scorm Compliance + + + +## Documentation and Resources + +[SCORM® 2004 4th Edition](https://adlnet.gov/research/scorm/scorm-2004-4th-edition/) + + + +## Scorm 2004 Compliance ([PDF](https://adlnet.gov/wp-content/uploads/2011/07/SCORM_2004_4ED_v1_1_TR_20090814.pdf)) + + +### API Implementation Compliance Requirements (Page 19) + +#### TODO +| REQ ID | Requirement | +| ------ | ----------- | +| REQ_22 | The LMS shall launch the learning resources defined in the content package manifest (imsmanifest.xml) based on the **<resource>** referenced by a leaf **<item>** found in the content organization (**<organization>**). | +| REQ_22.1 | The LMS shall be able to launch a SCORM 2004 4th Ed. compliant SCO. SCOs are identified in an imsmanifest.xml as a **<resource>** with an attribute of **adlcp:scormType="sco"**. | +| REQ_22.2 | The LMS shall be able to launch a SCORM 2004 4th Ed. compliant SCO. SCOs are identified in an imsmanifest.xml as a **<resource>** with an attribute of **adlcp:scormType="asset"**. | +| REQ_3.2 | If a non-empty characterstring parameter is passed to a method that requires an empty characterstring parameter (e.g. Initialize, Terminate, Commit), error code 201 shall be returned by the LMS. | +| REQ_5.2.1 | If the communication session has been initialized and the **Terminate()** API method is successful, the LMS shall persist any data set by the SCO (equivalent to an implicit Commit method call). | +| REQ_5.2.1.1 | If the persistence of data fails, the LMS shall set the error code to General Commit Failure (391) and return false. | +| REQ_6.10 | If the communication session has been initialized and the **GetValue()** API method is invoked where parameter is not specified (i.e., specified by an empty characterstring – “”), then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (“”).

**ADL Note:** See the SCORM Run-Time Environment for more specific situations for using this error condition. | +| REQ_6.11 | If the communication state has been initialized and the **GetValue()** API method is invoked requesting the children of a data model element that does not have children, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring ("").

**ADL Note:** See the SCORM Run-Time Environment for more specific situations for using this error condition. | +| REQ_6.12 | If the communication state has been initialized and the **GetValue()** API method is invoked requesting the number of entries (i.e., _count) currently stored in a data model element that is not an array, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring ("").

**ADL Note:** See the SCORM Run-Time Environment for more specific situations for using this error condition. | +| REQ_7.13 | If the communication session has been initialized and the **SetValue()** API method is invoked where parameter_1 is not specified (i.e., specified by an empty characterstring – “”), then the LMS shall set the error code to General Set Failure (351) and return false.

**ADL Note:** Refer to the SCORM Run-Time Environment for more specific situations for using this error condition. | +| REQ_7.14 | If the communication session has been initialized and the **SetValue()** API method is invoked where a SCO attempted to set a new value (i.e., not replace an existing value) in an array where the index number used (n) is not the next available position in the array, then the LMS shall set the error code to General Set Failure (351) and return false.

**ADL Note:** Refer to the SCORM Run-Time Environment for more specific situations for this error condition. | +| REQ_7.15 | If the communication session has been initialized and the **SetValue()** API method is invoked where a SCO attempted to set a data model element and the defined SPM for that data model element is exceeded, then the LMS shall set at least the defined SPM for that data model element, set the error code to No Error (0) and return true.

**ADL Note:** Refer to the SCORM Run-Time Environment for more details on the SPMs defined for the data model elements. | +| REQ_8.2.1 | During a **Commit()** API method call, the LMS shall persist any data that was set, and has not been persisted since the last successful call to **Commit("")** or **Initialize("")**, whichever occurred most recently. | +| REQ_8.3 | If the communication state has been initialized and the **Commit()** API method fails, the LMS shall set the error code to General Commit Failure (391) and return false. | + +#### To Verify +| REQ ID | Requirement | +| ------ | ----------- | +| REQ_6.5 | If the communication state has been initialized and the **GetValue()** API method is invoked where the request is for a data model element that has not been initialized with a value, the LMS shall set the error code to Data Model Element Value Not Initialized (403) and return an empty characterstring (""). | + +#### Maybe TODO? +| REQ ID | Requirement | +| ------ | ----------- | +| REQ_4.2 | If the communication session has not been initialized and the **Initialize()** API method is invoked and fails, the LMS shall set the error code to General Initialization Failure (102) and return false. | +| REQ_5.3 | If the communication session has been initialized and the **Terminate()** API method fails, the LMS shall set the error code to General Termination Failure (111) and return false. | +| REQ_6.4 | If the communication state has been initialized and the **GetValue()** API method is invoked where the parameter is recognized but not implemented by the LMS, the LMS shall set the error code to Unimplemented Data Model Element (402) and return an empty characterstring (""). | +| REQ_6.7 | If the communication state has been initialized and the **GetValue()** API method is invoked unsuccessfully and no specific error condition exists, the LMS shall set the error code to General Get Failure (301) and return an empty characterstring ("").

**ADL Note:** See the SCORM Run-Time Environment for more specific situations for using this error condition. | +| REQ_7.4 | If the communication session has been initialized and the **SetValue()** API method is invoked where parameter_1 is recognized but not implemented by the LMS, the LMS shall set the error code to Unimplemented Data Model Element (402) and return false. | +| REQ_7.6 | If the communication session has been initialized and the **SetValue()** API method is invoked where parameter_2 does not match the datatype required by parameter_1, the LMS shall set the error code to Data Model Element Type Mismatch (406) and return false. | +| REQ_7.7 | If the communication session has been initialized and the **SetValue()** API method is invoked where parameter_2 matches the datatype of parameter_1 but the value was not in the specified range of values required for parameter_1, the LMS shall set the error code to Data Model Element Value Out Of Range (407) and return false. | +| REQ_7.8 | If the communication session has been initialized and the **SetValue()** API method is invoked where relevant dependencies are not in place (See the Run-Time Environment Data Model compliance requirements for additional details), the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. | +| REQ_7.11 | If the communication state has been initialized and the **SetValue()** API method is invoked unsuccessfully and no specific error condition exists, the LMS shall set the error code to General Set Failure (351) and return false.

**ADL Note:** Refer to the SCORM Run-Time Environment for more specific situations for this error condition. | + +#### To Continue Doing +| REQ ID | Requirement | +| ------ | ----------- | +| REQ_2.3 | The LMS' API instance shall be instantiated before the SCO is launched. | +| REQ_10.3.1 | **GetErrorString()**: The characterstring returned shall have a maximum length of 255 characters. | +| REQ_11.2.1 | **GetDiagnostic()**: The textual characterstring returned shall have a maximum length of 255 characters. | + + +### Run-Time Environment Data Model Compliance Requirements (Page 26) +| REQ ID | Requirement | +| ------ | ----------- | +| REQ_57.3.3 | If the SCO invokes a GetValue() request on the **cmi.comments_from_learner.n.comment** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_57.3.4 | If the SCO invokes a SetValue() request on the **cmi.comments_from_learner.n.comment** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_57.4.3 | If the SCO invokes a GetValue() request on the **cmi.comments_from_learner.n.location** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_57.4.4 | If the SCO invokes a SetValue() request on the **cmi.comments_from_learner.n.location** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_57.5.3 | If the SCO invokes a GetValue() request on the **cmi.comments_from_learner.n.timestamp** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_57.5.4 | If the SCO invokes a SetValue() request on the **cmi.comments_from_learner.n.timestamp** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_58.3.3 | If the SCO invokes a GetValue() request on the **cmi.comments_from_lms.n.comment** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_58.4.3 | If the SCO invokes a GetValue() request on the **cmi.comments_from_lms.n.location** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_58.5.3 | If the SCO invokes a GetValue() request on the **cmi.comments_from_lms.n.timestamp** data model element where the index (n) provided is a number that is greater than the current number of **cmi.comments_from_learner** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_59.4 | The LMS shall implement the **cmi.completion_status** such that it has the following effect on sequencing behaviors: | +| REQ_59.4.1 | If the SCO or LMS sets the **cmi.completion_status** of the SCO to unknown, then the LMS’ sequencing implementation shall behave as if the Attempt Progress Status for the learning activity associated with the SCO is false. | +| REQ_59.4.2 | If the SCO or LMS sets the **cmi.completion_status** of the SCO to completed, then the LMS’ sequencing implementation shall behave as if the Attempt Progress Status for the learning activity associated with the SCO is true, and the Attempt Completion Status for the learning activity associated with the SCO is true. | +| REQ_59.4.3 | If the SCO or LMS sets the **cmi.completion_status** of the SCO to incomplete, then the LMS’ sequencing implementation shall behave as if the Attempt Progress Status for the learning activity associated with the SCO is true, and the Attempt Completion Status for the learning activity associated with the SCO is false. | +| REQ_59.4.4 | If the SCO or LMS sets **cmi.completion_status** of the SCO to not attempted, then the LMS’ sequencing implementation shall behave as if the Attempt Progress Status for the learning activity associated with the SCO shall be true and the Attempt Completion Status for the learning activity associated with the SCO shall be false. | +| REQ_59.5 | The LMS shall evaluate the value of the **cmi.completion_status** data model element and return the result in the response to a GetValue() request according to the following requirements: | +| REQ_59.5.1 | If a **cmi.completion_threshold** is defined for the SCO and the **cmi.progress_measure** data model element's value is set by the SCO and the value is less than the **cmi.completion_threshold** data model element's value, then the LMS shall evaluate and return the value of incomplete. | +| REQ_59.5.2 | If a **cmi.completion_threshold** is defined for the SCO and the **cmi.progress_measure** data model element's value is set by the SCO and the value is greater than or equal to the **cmi.completion_threshold** data model element's value, then the LMS shall evaluate and return the value of completed. | +| REQ_59.5.3 | If a **cmi.completion_threshold** is defined for the SCO and the **cmi.progress_measure** data model element is never set by the SCO, the LMS shall evaluate and return the value of unknown. | +| REQ_59.5.4 | If no **cmi.completion_threshold** is defined for the SCO the LMS shall rely on the value set for the **cmi.completion_status** data model element by the SCO and return that value. If no value was set by the SCO for the **cmi.completion_status** data model element then the LMS shall return unknown. | +| REQ_60.2 | The LMS shall implement the **cmi.completion_threshold** data model element as real (10,7). The data model element shall support a range between (0.0 .. 1.0). | +| REQ_60.3 | The LMS shall initialize this data model element using the element value of the SCORM 2004 4th Ed. Content Packaging Extensions Version 2.0 namespace element *<adlcp:completionThreshold>**. If the element value for the **<adlcp:completionThreshold>** element does not exist as a child element of the **<imscp:item>** element (associated with the SCO resource) and the SCORM 2004 4th Ed. use of **adlcp:minProgressMeasure** is not being use (see REQ_60.4), then the element shall remain uninitialized.

**Note:** In this case uninitialized is defined to indicate that no value should be assigned to **cmi.completion_threshold**. In this case, if a SCO invokes a GetValue() request then the LMS shall return an empty characterstring and set the error code to 403.- Data Model Element Value Not Initialized

**Note:** This use of **<adlcp:completionThreshold>** is depreciated with SCORM 2004 4th Ed.. Legacy packages may continue to use the element value to initialize **cmi.completion_threshold**, however these packages should be updated to use the new **adlcp:minProgressMeasure** attribute.

**Note:** The legacy use of **<adlcp:completionThreshold>**, via its element value, cannot be used in conjunction with **adlcp:minProgressMeasure** attribute. | +| REQ_60.4 | The LMS shall initialize this data model element using the attribute value of the SCORM 2004 4th Ed. Content Packaging Extensions Version 2.0 namespace attribute **adlcp:minProgressMeasure**, contained within the element **<adlcp:completionThreshold>**. If the attribute value for the **adlcp:minProgressMeasure** does not exist and **adlcp:completedByMeasure** attribute is true then the default value, 1.0, shall be used to initialize **cmi.completion_threshold**. | +| REQ_60.5 | If a SCO attempts to retrieve the **cmi.completion_threshold** and no completion threshold was defined in the content package manifest (via the **<adlcp:completionThreshold>** element or its **adlcp:minProgressMeasure** attribute), then the LMS shall adhere to the GetValue API method requirements (Data Model Element Value Not Initialized). | +| REQ_62.3 | The LMS shall initialize the **cmi.entry** data model element based on the following rules: | +| REQ_62.3.1 | If this is the first learner session on a learner attempt, then the LMS shall set the **cmi.entry** to ab-initio prior to initial launch of the SCO.

**ADL Note:** Upon a subsequent call to GetValue(cmi.entry), the LMS shall return ab-initio. | +| REQ_62.3.2 | If the learner attempt on the SCO is being resumed from a suspended learner session (**cmi.exit** is set to suspend), then the LMS shall initialize the **cmi.entry** value to resume.

**ADL Note:** Upon a subsequent call to GetValue(cmi.entry), the LMS shall return resume. | +| REQ_62.3.3 | For all other conditions, the LMS shall set the **cmi.entry** to an empty characterstring (""). | +| REQ_63.4 | The LMS shall implement **cmi.exit** such that it has the following effect on sequencing behaviors: | +| REQ_63.4.1 | If the SCO sets **cmi.exit** to time-out, the LMS shall process an "Exit All" navigation request when the SCO is taken away, instead of any pending (from the learner or LMS) navigation request. | +| REQ_63.4.2 | If the SCO sets **cmi.exit** to suspend, the LMS sequencing implementation shall behave as if the Activity is Suspended value of the learning activity associated with the SCO is true. | +| REQ_63.4.3 | If the SCO sets **cmi.exit** to logout, the LMS sequencing implementation shall process a "Exit All" navigation request when the SCO is taken away, instead of any pending (from the learner or LMS) navigation request.

**ADL Note:** The value of logout is being deprecated and should not be used. If the value is used by a SCO, the LMS should adhere to this requirement. | +| REQ_63.5 | If there are additional learner sessions within a learner attempt, the **cmi.exit** data model element’s value shall be reset back to the default value of an empty characterstring (“”) at the beginning of each additional learner session within the learner attempt. | +| REQ_64.3.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.id** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). +| REQ_64.3.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.id** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. +| REQ_64.4.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.type** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). +| REQ_64.4.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.type** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. +| REQ_64.5.2.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.objectives.m.id** data model element where the index (m) provided is a number that is greater than the current number of **cmi.interactions.n.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.5.2.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.objectives.m.id** data model element where the index (m) provided is a number that is greater than the current number of **cmi.interactions.n.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.5.2.5 | If the **cmi.interactions.n.id** has not been set prior to the request to set the **cmi.interactions.n.objectives.m.id**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.5.2.6 | If a SCO invokes a SetValue() request on the **cmi.interactions.n.objectives.m.id** where the value of the id is identical to an id that is already being persisted by the LMS for the given interaction, then the LMS shall set the error code to General Set Failure (351), return false and not persist the value being set. | +| REQ_64.6.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.timestamp** data model element where the index (m) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.6.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.timestamp** data model element where the index (m) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.6.5 | If the **cmi.interactions.n.id** has not been set prior to the request to set the **cmi.interactions.n.timestamp**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.7 | The LMS shall implement the **cmi.interactions.n.correct_responses** data model elements.

**ADL Note:** The SPM value for the number of correct_responses that the LMS shall support changes based on the **cmi.interactions.n.type** element. | +| REQ_64.7.2.2 | The LMS shall implement the **cmi.interactions.n.correct_responses.m.pattern** data model element to adhere to the appropriate interaction type (**cmi.interactions.n.type**). This type changes based on the value of the **cmi.interactions.n.type** data model element. Refer to *Section 2.1.4 Run-Time Environment Data Model Data Type Compliance Requirements* for details on the datatype of correct_responses. | +| REQ_64.7.2.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.correct_responses.m.pattern** data model element where the index (m) provided is a number that is greater than the current number of **cmi.interactions.n.correct_responses** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.7.2.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.correct_responses.m.pattern** data model element where the index (m) provided is a number that is greater than the current number of **cmi.interactions.n.correct_responses** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.7.2.5 | If the **cmi.interactions.n.id** and **cmi.interactions.n.type** have not been set prior to the request to set the **cmi.interactions.n.correct_responses.m.pattern**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.7.2.6 | If the interaction type (**cmi.interactions.n.type**) is `true-false` then the LMS shall manage 1 and only 1 correct response pattern. | +| REQ_64.7.2.6.1 | If a SCO attempts to set more than one correct response pattern then the LMS shall set the error code to 351 and return false. | +| REQ_64.7.2.7 | If the interaction type (**cmi.interactions.n.type**) is `choice` then the LMS shall manage at least 10 correct response patterns (SPM requirement). | +| REQ_64.7.2.7.1 | If the interaction type (**cmi.interactions.n.type**) is `choice` then correct response patterns shall be unique for a SCO. | +| REQ_64.7.2.7.1.1 | If a correct response pattern for a choice interaction type, within the scope of a SCO, is found not to be unique with respect to the other correct response patterns managed by the LMS for the SCO, then the LMS shall set the error code to 351 and return false. | +| REQ_64.7.2.8 | If the interaction type (**cmi.interactions.n.type**) is `fill-in` then the LMS shall manage at least 5 correct response patterns (SPM requirement). | +| REQ_64.7.2.9 | If the interaction type (**cmi.interactions.n.type**) is `long-fill-in` then the LMS shall manage at least 5 correct response patterns (SPM requirement). | +| REQ_64.7.2.10 | If the interaction type (**cmi.interactions.n.type**) is `likert` then the LMS shall manage 1 and only 1 correct response pattern. | +| REQ_64.7.2.10.1 | If a SCO attempts to set more than one correct response pattern then the LMS shall set the error code to 351 and return false. | +| REQ_64.7.2.11 | If the interaction type (**cmi.interactions.n.type**) is `matching` then the LMS shall manage at least 5 correct response patterns (SPM requirement). | +| REQ_64.7.2.12 | If the interaction type (**cmi.interactions.n.type**) is `performance` then the LMS shall manage at least 5 correct response patterns (SPM requirement). | +| REQ_64.7.2.13 | If the interaction type (**cmi.interactions.n.type**) is `sequencing` then the LMS shall manage at least 5 correct response patterns (SPM requirement). | +| REQ_64.7.2.13.1 | If the interaction type (**cmi.interactions.n.type**) is `sequencing` then correct response patterns shall be unique for a SCO. | +| REQ_64.7.2.13.1.1 | If a correct response pattern for a sequencing interaction type, within the scope of a SCO, is found not to be unique with respect to the other correct response patterns managed by the LMS for the SCO, then the LMS shall set the error code to 351 and return false. | +| REQ_64.7.2.14 | If the interaction type (**cmi.interactions.n.type**) is `numeric` then the LMS shall manage 1 and only 1 correct response pattern. | +| REQ_64.7.2.14.1 | If a SCO attempts to set more than one correct response pattern then the LMS shall set the error code to 351 and return false. | +| REQ_64.7.2.15 | If the interaction type (**cmi.interactions.n.type**) is `other` then the LMS shall manage 1 and only 1 correct response pattern. | +| REQ_64.7.2.15.1 | If a SCO attempts to set more than one correct response pattern then the LMS shall set the error code to 351 and return false. | +| REQ_64.8.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.weighting** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.8.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.weighting** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.8.5 | If the **cmi.interactions.n.id** has not been set prior to the request to set the **cmi.interactions.n.weighting**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.9.2 | The LMS shall implement the **cmi.interactions.n.learner_response** data model element to adhere to the appropriate interaction type (**cmi.interactions.n.type**). This type changes based on the value of the **cmi.interactions.n.type** data model element. See the Data Model Datatype compliance requirements for details on the datatype of **cmi.interactions.n.learner_response**. | +| REQ_64.9.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.learner_response** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.9.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.learner_response** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.9.5 | If the **cmi.interactions.n.id** and **cmi.interactions.n.type** have not been set prior to the request to set the **cmi.interactions.n.learner_response**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.10.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.result** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.10.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.result** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.10.5 | If the cmi.interactions.n.id has not been set prior to the request to set the **cmi.interactions.n.result**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.11.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.latency** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.11.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.latency** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.11.5 | If the cmi.interactions.n.id has not been set prior to the request to set the **cmi.interactions.n.latency**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_64.12.3 | If the SCO invokes a GetValue() request on the **cmi.interactions.n.description** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_64.12.4 | If the SCO invokes a SetValue() request on the **cmi.interactions.n.description** data model element where the index (n) provided is a number that is greater than the current number of **cmi.interactions** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_64.12.5 | If the cmi.interactions.n.id has not been set prior to the request to set the **cmi.interactions.n.description**, then the LMS shall set the error code to Data Model Dependency Not Established (408), return false and not change the current state of the data model element. | +| REQ_65.3 | The LMS shall initialize the **cmi.launch_data** data model element using the SCORM 2004 4th Ed. Content Packaging Extensions Version 2.0 namespace element **<adlcp:dataFromLMS>**. If an **<adlcp:dataFromLMS>** element does not exist as a child element of the **<imscp:item>** element (associated with the SCO resource), then the element shall remain uninitialized. | +| REQ_70.3 | The LMS shall initialize the **cmi.max_time_allowed** data model element using the IMS Simple Sequencing namespace element **<imsss:attemptAbsoluteDurationLimit>**. If an **<imsss:attemptAbsoluteDurationLimit>** element does not exist as a child element of the **<imscp:item>** element (associated with the SCO resource), then the element shall remain uninitialized.

**Note:** In this case uninitialized is defined to indicate that no value should be assigned to **cmi.max_time_allowed**. In this case, if a SCO invokes a GetValue() request then the LMS shall return an empty characterstring and set the error code to 403.- Data Model Element Value Not Initialized | +| REQ_72.3.3 | For each objective defined (**<imsss:primaryObjective>** or **<imsss:objective>**) that includes an objectiveID attribute, the LMS is responsible for adhering to the following initialization requirements: | +| REQ_72.3.3.1 | The **objectiveID** attribute shall be used to initialize the **cmi.objectives.n.id** value. | +| REQ_72.3.3.2 | If a Read Objective Normalized Measure map is defined (in a **<imsss:mapInfo>** element) and the sequencing implementation is maintaining a valid Objective Normalize Measure (Objective Measure Status is true), then the Objective Normalized Measure shall be used to initialize the **cmi.objectives.n.score.scaled**. | +| REQ_72.3.3.3 | If a Read Objective Satisfied Status map is defined (in a **<imsss:mapInfo>** element) and the sequencing implementation is maintaining a valid Objective Satisfied Status (Objective Progress Status is true), then the Objective Satisfied Status shall be used to initialize the **cmi.objectives.n.success_status** as defined by the following requirements. | +| REQ_72.3.3.3.1 | If the Objective Satisfied Status is true, then the **cmi.objectives.n.success_status** shall be initialized to passed. | +| REQ_72.3.3.3.2 | If the Objective Satisfied Status is false, then the **cmi.objectives.n.success_status** shall be initialized to failed. | +| REQ_72.3.4 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.id** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.3.5 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.id** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.3.6 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.id** data model element and the value of the identifier is not unique (i.e., another identifier exists in the collection of objectives at a different array position (n)), then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.4.2.3 | The LMS shall implement **cmi.objectives.n.score.scaled** such that it has the following effect on sequencing behaviors: | +| REQ_72.4.2.3.1 | If the SCO does not set **cmi.objectives.n.score.scaled** for an objective of the SCO, then the LMS’ sequencing implementation shall behave as if the Objective Measure Status for the associated objective (based on objective IDs) of the learning activity associated with the SCO is false. | +| REQ_72.4.2.3.2 | If the SCO sets **cmi.objectives.n.score.scaled** for an objective of the SCO, then the LMS’ sequencing implementation shall behave as if the Objective Measure Status for the objective (based on objective IDs) of the learning activity associated with the SCO is true, and the Objective Normalized Measure for the objective (based on objective IDs) of the learning activity associated with the SCO is equal to the value of **cmi.objectives.n.score.scaled**. | +| REQ_72.4.2.4 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.score.scaled** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.4.2.5 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.score.scaled** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.4.2.6 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.score.scaled** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_72.4.3.3 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.score.raw** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.4.3.4 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.score.raw** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.4.3.5 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.score.raw** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_72.4.4.3 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.score.min** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.4.4.4 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.score.min** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.4.4.5 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.score.min** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_72.4.5.3 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.score.max** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.4.5.4 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.score.max** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.4.5.5 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.score.max** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_72.5.4 | The LMS shall implement **cmi.objectives.n.success_status** such that it has the following effect on sequencing behaviors: | +| REQ_72.5.4.1 | If the SCO sets **cmi.objectives.n.success_status** for an objective of the SCO to `unknown`, the LMS’ sequencing implementation shall behave as if the Objective Progress Status for the objective (based on objective IDs) of the learning activity associated with the SCO is false. | +| REQ_72.5.4.2 | If the SCO sets **cmi.objectives.n.success_status** for an objective of the SCO to `passed`, the LMS’ sequencing implementation shall behave as if the Objective Progress Status for the objective (based on objective IDs) of the learning activity associated with the SCO is true, and the Objective Satisfied Status for the objective (based on objective IDs) of the learning activity associated with the SCO is true. | +| REQ_72.5.4.3 | If the SCO sets **cmi.objectives.n.success_status** for an objective of the SCO to `failed`, the LMS’ sequencing implementation shall behave as if the Objective Progress Status for the objective (based on objective IDs) of the learning activity associated with the SCO is true, and the Objective Satisfied Status for the objective (based on objective IDs) of the learning activity associated with the SCO is false. | +| REQ_72.5.5 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.success_status** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.5.6 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.success_status** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.5.7 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.success_status** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_72.6.3 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.completion_status** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.6.4 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.completion_status** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.6.5 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.completion_status** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_72.7.3 | If the SCO invokes a GetValue() request on the **cmi.objectives.n.description** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Get Failure (301) and return an empty characterstring (""). | +| REQ_72.7.4 | If the SCO invokes a SetValue() request on the **cmi.objectives.n.description** data model element where the index (n) provided is a number that is greater than the current number of **cmi.objectives** being stored, then the LMS shall set the error code to General Set Failure (351) and return false. | +| REQ_72.7.5 | Since the **cmi.objectives.n.id** is required to be set first prior to any other objective information, if the SCO attempts to set **cmi.objectives.n.description** (prior to setting the identifier) then the LMS shall set the error code to Data Model Dependency Not Established (408) and return false. The LMS shall not alter the state of the data model element based on the request. | +| REQ_74.3 | The LMS is responsible for initializing the **cmi.scaled_passing_score** data model element using the IMS Simple Sequencing namespace element **<imsss:minNormalizedMeasure>** associated with an **<imsss:primaryObjective>** element for the **<imscp:item>** element that references a SCO resource as defined by the following requirements. | +| REQ_74.3.1 | If the IMS Simple Sequencing namespace attribute **imsss:satisfiedByMeasure** associated with the **<imsss:primaryObjective>** element for the **<imscp:item>** element that references the SCO is equal to true, then the value provided by the **<imsss:minNormalizedMeasure>** element associated with the **<imsss:primaryObjective>** element for the **<imscp:item>** element that references the SCO resource shall be used to initialize the **cmi.scaled_passing_score** data model element. | +| REQ_74.3.2 | If the IMS Simple Sequencing namespace attribute **imsss:satisfiedByMeasure** associated with the **<imsss:primaryObjective>** element for the **<imscp:item>** element that references the SCO is equal to true and no value is provided for the **<imsss:minNormalizedMeasure>** element associated with the **<imsss:primaryObjective>** element for the **<imscp:item>** element that references the SCO resource, then the LMS shall initialize the **cmi.scaled_passing_score** to 1.0. | +| REQ_74.3.3 | If the IMS Simple Sequencing namespace attribute **imsss:satisfiedByMeasure** associated with the **<imsss:primaryObjective>** element for the **<imscp:item>** element that references the SCO is equal to false, then the LMS shall not make any assumptions of a scaled passing score (i.e., **cmi.scaled_passing_score** data model element). | +| REQ_75.2.3 | The LMS shall implement **cmi.score.scaled** such that it has the following effect on sequencing behaviors: | +| REQ_75.2.3.1 | If the SCO does not set **cmi.score.scaled**, then the LMS sequencing implementation shall behave as if the Objective Measure Status for the primary objective of the learning activity associated with the SCO is false. | +| REQ_75.2.3.2 | If the SCO sets **cmi.score.scaled**, the LMS sequencing implementation shall behave as if the Objective Measure Status for the primary objective of the learning activity associated with the SCO is true, and the Objective Normalized Measure for the primary objective of the learning activity associated with the SCO is equal the value of **cmi.score.scaled**. | +| REQ_76.3 | Since a SCO is not required to set a value for this data model element (not required to keep track of the session time), an LMS shall keep track of session time from the time the LMS launches the SCO. If the SCO reports a different session time, then the LMS shall use the session time as reported by the SCO instead of the session time as measured by the LMS. | +| REQ_76.4 | When the SCO issues the **Terminate("")** or the user navigates away, the LMS shall take the last **cmi.session_time** that the SCO set (if there was a set) and accumulate this time to the **cmi.total_time**. | +| REQ_76.5 | If there are additional learner sessions within a learner attempt, the **cmi.session_time** shall be reset at the beginning of each additional learner session within the learner attempt. | +| REQ_77.4 | The LMS shall implement **cmi.success_status** such that it has the following effect on sequencing behaviors: | +| REQ_77.4.1 | If the SCO or LMS sets **cmi.success_status**, of the SCO to unknown, then the LMS’ sequencing implementation shall behave as if the Objective Progress Status for the primary objective of the learning activity associated with the SCO is false. | +| REQ_77.4.2 | If the SCO or LMS sets **cmi.success_status**, of the SCO to passed, then the LMS’ sequencing implementation shall behave as if the Objective Progress Status for the primary objective of the learning activity associated with the SCO is true, and the Objective Satisfied Status for the primary objective of the learning activity associated with the SCO is true. | +| REQ_77.4.3 | If the SCO or LMS sets **cmi.success_status**, of the SCO to failed, then the LMS’ sequencing implementation shall behave as if the Objective Progress Status for the primary objective of the learning activity associated with the SCO is true, and the Objective Satisfied Status for the primary objective of the learning activity associated with the SCO is false. | +| REQ_77.5 | The LMS shall evaluate the value of the **cmi.success_status** data model element and return the result in the response to a GetValue() request according to the following requirements: | +| REQ_77.5.1 | If a **cmi.scaled_passing_score** is defined for the SCO and the **cmi.score.scaled** data model element’s value is set by the SCO and the value is greater than or equal to the **cmi.scaled_passing_score**, then the LMS shall evaluate and return the value of passed. | +| REQ_77.5.2 | If a **cmi.scaled_passing_score** is defined for the SCO and the **cmi.score.scaled** data model element’s value is set by the SCO and the value is less than the **cmi.scaled_passing_score**, then the LMS shall evaluate and return the value of failed. | +| REQ_77.5.3 | If the **cmi.scaled_passing_score** has been defined and the SCO does not set a **cmi.score.scaled**, the LMS shall evaluate and return the value of unknown. | +| REQ_77.5.4 | If no **cmi.scaled_passing_score** has been defined for the SCO, then the LMS shall rely on the value set for the **cmi.success_status** data model element by the SCO and return that value. If no value was set by the SCO for the **cmi.success_status** data model element then the LMS shall return unknown. | +| REQ_79.3 | The LMS shall initialize this data model element using the SCORM 2004 4th Ed. Content Packaging Extensions Version 2.0 namespace element **<adlcp:timeLimitAction>**. If an **<adlcp:timeLimitAction>** element does not exist as a child element of the **<imscp:item>** element (associated with the SCO resource), then the element shall be initialized to the default value of continue,no message. | + + +### Run-Time Environment Data Model Data Type Compliance Requirements (Page 51) +| REQ ID | Requirement | +| ------ | ----------- | +| TODO | TODO | + + +### Run-Time Navigation Data Model Compliance Requirements (Page 59) +| REQ ID | Requirement | +| ------ | ----------- | +| TODO | TODO | diff --git a/src/scormAPI2004.js b/src/scormAPI2004.js new file mode 100644 index 0000000..fe95b6e --- /dev/null +++ b/src/scormAPI2004.js @@ -0,0 +1,842 @@ +(function() { + /** + * Based on the Scorm 2004 definitions from https://scorm.com + * + * Scorm 2004 Overview for Developers: https://scorm.com/scorm-explained/technical-scorm/scorm-2004-overview-for-developers/ + * Run-Time Reference: http://scorm.com/scorm-explained/technical-scorm/run-time/run-time-reference/ + * Scorm specification: https://adlnet.gov/research/scorm/scorm-2004-4th-edition/ + * Testing requirements: https://adlnet.gov/wp-content/uploads/2011/07/SCORM_2004_4ED_v1_1_TR_20090814.pdf + * + * SPM = Smallest Permitted Maximum + */ + var BaseAPI = window.simplifyScorm.BaseAPI; + var constants = window.simplifyScorm.constants; + var jsonFormatter = window.simplifyScorm.jsonFormatter; + + window.API_1484_11 = new API_1484_11(); + + function API_1484_11() { + var _self = this; + + BaseAPI.call(_self); + + // API Signature + _self.version = "1.0"; + _self.Initialize = LMSInitialize; + _self.Terminate = LMSTerminate; + _self.GetValue = LMSGetValue; + _self.SetValue = LMSSetValue; + _self.Commit = LMSCommit; + _self.GetLastError = LMSGetLastError; + _self.GetErrorString = LMSGetErrorString; + _self.GetDiagnostic = LMSGetDiagnostic; + + // Data Model + _self.cmi = new CMI(_self); + _self.adl = new ADL(_self); + + // Utility Functions + _self.getLmsErrorMessageDetails = getLmsErrorMessageDetails; + + /** + * @param Empty String + * @returns {string} bool + */ + function LMSInitialize() { + var returnValue = constants.SCORM_FALSE; + + if (_self.currentState === constants.STATE_INITIALIZED) { + _self.throwSCORMError(_self, 103); + } else if (_self.currentState === constants.STATE_TERMINATED) { + _self.throwSCORMError(_self, 104); + } else { + _self.currentState = constants.STATE_INITIALIZED; + _self.lastErrorCode = 0; + returnValue = constants.SCORM_TRUE; + _self.processListeners("Initialize"); + } + + _self.apiLog("Initialize", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * @param Empty String + * @returns {string} bool + */ + function LMSTerminate() { + var returnValue = constants.SCORM_FALSE; + + if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + _self.throwSCORMError(_self, 112); + } else if (_self.currentState === constants.STATE_TERMINATED) { + _self.throwSCORMError(_self, 113); + } else { + _self.currentState = constants.STATE_TERMINATED; + _self.lastErrorCode = 0; + returnValue = constants.SCORM_TRUE; + _self.processListeners("Terminate"); + } + + _self.apiLog("Terminate", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * @param CMIElement + * @returns {string} + */ + function LMSGetValue(CMIElement) { + var returnValue = ""; + + if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + _self.throwSCORMError(_self, 122); + } else if (_self.currentState === constants.STATE_TERMINATED) { + _self.throwSCORMError(_self, 123); + } else { + _self.lastErrorCode = 0; + returnValue = getCMIValue(CMIElement); + _self.processListeners("GetValue", CMIElement); + } + + _self.apiLog("GetValue", CMIElement, ": returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * @param CMIElement + * @param value + * @returns {string} + */ + function LMSSetValue(CMIElement, value) { + var returnValue = ""; + + if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + _self.throwSCORMError(_self, 132); + } else if (_self.currentState === constants.STATE_TERMINATED) { + _self.throwSCORMError(_self, 133); + } else { + _self.lastErrorCode = 0; + returnValue = setCMIValue(CMIElement, value); + _self.processListeners("SetValue", CMIElement, value); + } + + _self.apiLog("SetValue", CMIElement, ": " + value + ": result: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Orders LMS to store all content parameters + * + * @returns {string} bool + */ + function LMSCommit() { + var returnValue = constants.SCORM_FALSE; + + if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + _self.throwSCORMError(_self, 142); + } else if (_self.currentState === constants.STATE_TERMINATED) { + _self.throwSCORMError(_self, 143); + } else { + _self.lastErrorCode = 0; + returnValue = constants.SCORM_TRUE; + _self.processListeners("Commit"); + } + + _self.apiLog("Commit", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Returns last error code + * + * @returns {string} + */ + function LMSGetLastError() { + var returnValue = String(_self.lastErrorCode); + + _self.processListeners("GetLastError"); + + _self.apiLog("GetLastError", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Returns the errorNumber error description + * + * @param CMIErrorCode + * @returns {string} + */ + function LMSGetErrorString(CMIErrorCode) { + var returnValue = ""; + + if (CMIErrorCode !== null && CMIErrorCode !== "") { + returnValue = _self.getLmsErrorMessageDetails(CMIErrorCode); + _self.processListeners("GetErrorString"); + } + + _self.apiLog("GetErrorString", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Returns a comprehensive description of the errorNumber error. + * + * @param CMIErrorCode + * @returns {string} + */ + function LMSGetDiagnostic(CMIErrorCode) { + var returnValue = ""; + + if (CMIErrorCode !== null && CMIErrorCode !== "") { + returnValue = _self.getLmsErrorMessageDetails(CMIErrorCode, true); + _self.processListeners("GetDiagnostic"); + } + + _self.apiLog("GetDiagnostic", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + + return returnValue; + } + + /** + * Sets a value on the CMI Object + * + * @param CMIElement + * @param value + * @returns {string} + */ + function setCMIValue(CMIElement, value) { + if (!CMIElement || CMIElement === "") { + return constants.SCORM_FALSE; + } + + var structure = CMIElement.split("."); + var refObject = _self; + var returnValue = constants.SCORM_FALSE; + + for (var i = 0; i < structure.length; i++) { + if (i === structure.length - 1) { + if (!refObject.hasOwnProperty(structure[i])) { + _self.throwSCORMError(_self, 401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); + } else { + refObject[structure[i]] = value; + if (_self.lastErrorCode == 0) { + returnValue = constants.SCORM_TRUE; + } + } + } else { + refObject = refObject[structure[i]]; + + if (refObject.hasOwnProperty("childArray")) { + var index = parseInt(structure[i + 1], 10); + + // SCO is trying to set an item on an array + if (!isNaN(index)) { + var item = refObject.childArray[index]; + + if (item) { + refObject = item; + } else { + var newChild; + + if (CMIElement.indexOf("cmi.comments_from_learner") > -1) { + newChild = new CommentsFromLearnerObject(_self); + } else if (CMIElement.indexOf("cmi.comments_from_lms") > -1) { + newChild = new CommentsFromLMSObject(_self); + } else if (CMIElement.indexOf("cmi.objectives") > -1) { + newChild = new ObjectivesObject(_self); + } else if (CMIElement.indexOf(".correct_responses") > -1) { + newChild = new InteractionsCorrectResponsesObject(_self); + } else if (CMIElement.indexOf(".objectives") > -1) { + newChild = new InteractionsObjectivesObject(_self); + } else if (CMIElement.indexOf("cmi.interactions") > -1) { + newChild = new InteractionsObject(_self); + } + + if (!newChild) { + _self.throwSCORMError(_self, 401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); + } else { + refObject.childArray.push(newChild); + + refObject = newChild; + } + } + + // Have to update i value to skip the array position + i++; + } + } + } + } + + if (returnValue === constants.SCORM_FALSE) { + _self.apiLog("There was an error setting the value for: " + CMIElement + ", value of: " + value, constants.LOG_LEVEL_WARNING); + } + + return returnValue; + } + + /** + * Gets a value from the CMI Object + * + * @param CMIElement + * @returns {*} + */ + function getCMIValue(CMIElement) { + if (!CMIElement || CMIElement === "") { + return ""; + } + + var structure = CMIElement.split("."); + var refObject = _self; + + for (var i = 0; i < structure.length; i++) { + if (!refObject.hasOwnProperty(structure[i])) { + _self.throwSCORMError(_self, 401, "The data model element passed to GetValue (" + CMIElement + ") is not a valid SCORM data model element."); + return ""; + } + + refObject = refObject[structure[i]]; + } + + return refObject || ""; + } + + /** + * Returns the message that corresponds to errrorNumber. + */ + function getLmsErrorMessageDetails(errorNumber, detail) { + var basicMessage = ""; + var detailMessage = ""; + + // Set error number to string since inconsistent from modules if string or number + errorNumber = String(errorNumber); + + switch (errorNumber) { + case "0": + basicMessage = "No Error"; + detailMessage = "No error occurred, the previous API call was successful."; + break; + + case "101": + basicMessage = "General Exception"; + detailMessage = "No specific error code exists to describe the error. Use GetDiagnostic for more information."; + break; + + case "102": + basicMessage = "General Initialization Failure"; + detailMessage = "Call to Initialize failed for an unknown reason."; + break; + + case "103": + basicMessage = "Already Initialized"; + detailMessage = "Call to Initialize failed because Initialize was already called."; + break; + + case "104": + basicMessage = "Content Instance Terminated"; + detailMessage = "Call to Initialize failed because Terminate was already called."; + break; + + case "111": + basicMessage = "General Termination Failure"; + detailMessage = "Call to Terminate failed for an unknown reason."; + break; + + case "112": + basicMessage = "Termination Before Initialization"; + detailMessage = "Call to Terminate failed because it was made before the call to Initialize."; + break; + + case "113": + basicMessage = "Termination After Termination"; + detailMessage = "Call to Terminate failed because Terminate was already called."; + break; + + case "122": + basicMessage = "Retrieve Data Before Initialization"; + detailMessage = "Call to GetValue failed because it was made before the call to Initialize."; + break; + + case "123": + basicMessage = "Retrieve Data After Termination"; + detailMessage = "Call to GetValue failed because it was made after the call to Terminate."; + break; + + case "132": + basicMessage = "Store Data Before Initialization"; + detailMessage = "Call to SetValue failed because it was made before the call to Initialize."; + break; + + case "133": + basicMessage = "Store Data After Termination"; + detailMessage = "Call to SetValue failed because it was made after the call to Terminate."; + break; + + case "142": + basicMessage = "Commit Before Initialization"; + detailMessage = "Call to Commit failed because it was made before the call to Initialize."; + break; + + case "143": + basicMessage = "Commit After Termination"; + detailMessage = "Call to Commit failed because it was made after the call to Terminate."; + break; + + case "201": + basicMessage = "General Argument Error"; + detailMessage = "An invalid argument was passed to an API method (usually indicates that Initialize, Commit or Terminate did not receive the expected empty string argument."; + break; + + case "301": + basicMessage = "General Get Failure"; + detailMessage = "Indicates a failed GetValue call where no other specific error code is applicable. Use GetDiagnostic for more information."; + break; + + case "351": + basicMessage = "General Set Failure"; + detailMessage = "Indicates a failed SetValue call where no other specific error code is applicable. Use GetDiagnostic for more information."; + break; + + case "391": + basicMessage = "General Commit Failure"; + detailMessage = "Indicates a failed Commit call where no other specific error code is applicable. Use GetDiagnostic for more information."; + break; + + case "401": + basicMessage = "Undefined Data Model Element"; + detailMessage = "The data model element name passed to GetValue or SetValue is not a valid SCORM data model element."; + break; + + case "402": + basicMessage = "Unimplemented Data Model Element"; + detailMessage = "The data model element indicated in a call to GetValue or SetValue is valid, but was not implemented by this LMS. In SCORM 2004, this error would indicate an LMS that is not fully SCORM conformant."; + break; + + case "403": + basicMessage = "Data Model Element Value Not Initialized"; + detailMessage = "Attempt to read a data model element that has not been initialized by the LMS or through a SetValue call. This error condition is often reached during normal execution of a SCO."; + break; + + case "404": + basicMessage = "Data Model Element Is Read Only"; + detailMessage = "SetValue was called with a data model element that can only be read."; + break; + + case "405": + basicMessage = "Data Model Element Is Write Only"; + detailMessage = "GetValue was called on a data model element that can only be written to."; + break; + + case "406": + basicMessage = "Data Model Element Type Mismatch"; + detailMessage = "SetValue was called with a value that is not consistent with the data format of the supplied data model element."; + break; + + case "407": + basicMessage = "Data Model Element Value Out Of Range"; + detailMessage = "The numeric value supplied to a SetValue call is outside of the numeric range allowed for the supplied data model element."; + break; + + case "408": + basicMessage = "Data Model Dependency Not Established"; + detailMessage = "Some data model elements cannot be set until another data model element was set. This error condition indicates that the prerequisite element was not set before the dependent element."; + break; + + default: + basicMessage = ""; + detailMessage = ""; + break; + } + + return detail ? detailMessage : basicMessage; + } + + return _self; + } + + /** + * Cmi data model + */ + function CMI(API) { + return { + __version: "1.0", + get _version() { return this.__version; }, + set _version(version) { API.throwSCORMError(API, 404); }, + + _completion_status: "unknown", // Allowed values: "completed", "incomplete", "not attempted", "unknown" + get completion_status() { return this._completion_status; }, + set completion_status(completion_status) { this._completion_status = completion_status; }, + + _completion_threshold: "", // Data type: real (10,7). Range: 0.0 to 1.0 + get completion_threshold() { return this._completion_threshold; }, + set completion_threshold(completion_threshold) { API.throwSCORMError(API, 404); }, + + _credit: "credit", // Allowed values: "credit", "no-credit" + get credit() { return this._credit; }, + set credit(credit) { API.throwSCORMError(API, 404); }, + + _entry: "", // Allowed values: "ab-initio", "resume", "" + get entry() { return this._entry; }, + set entry(entry) { API.throwSCORMError(API, 404); }, + + _exit: "", // Allowed values: "time-out", "suspend", "logout", "normal", "" + get exit() { return (!this.jsonString) ? API.throwSCORMError(API, 405) : this._exit; }, + set exit(exit) { this._exit = exit; }, + + _launch_data: "", // SPM 4000 characters + get launch_data() { return this._launch_data; }, + set launch_data(launch_data) { API.throwSCORMError(API, 404); }, + + _learner_id: "", // SPM 4000 characters + get learner_id() { return this._learner_id; }, + set learner_id(learner_id) { API.throwSCORMError(API, 404); }, + + _learner_name: "", // SPM 250 characters + get learner_name() { return this._learner_name; }, + set learner_name(learner_name) { API.throwSCORMError(API, 404); }, + + _location: "", // SPM 1000 characters + get location() { return this._location; }, + set location(location) { this._location = location; }, + + _max_time_allowed: "", // Data type: timeinterval (second,10,2) + get max_time_allowed() { return this._max_time_allowed; }, + set max_time_allowed(max_time_allowed) { API.throwSCORMError(API, 404); }, + + _mode: "normal", // Allowed values: "browse", "normal", "review" + get mode() { return this._mode; }, + set mode(mode) { API.throwSCORMError(API, 404); }, + + _progress_measure: "", // Data type: real (10,7). Range: 0.0 to 1.0 + get progress_measure() { return this._progress_measure; }, + set progress_measure(progress_measure) { this._progress_measure = progress_measure; }, + + _scaled_passing_score: "", // Data type: real (10,7). Range: -1.0 to 1.0 + get scaled_passing_score() { return this._scaled_passing_score; }, + set scaled_passing_score(scaled_passing_score) { API.throwSCORMError(API, 404); }, + + _session_time: "", // Data type: timeinterval (second,10,2) + get session_time() { return (!this.jsonString) ? API.throwSCORMError(API, 405) : this._session_time; }, + set session_time(session_time) { this._session_time = session_time; }, + + _success_status: "unknown", // Allowed values: "passed", "failed", "unknown" + get success_status() { return this._success_status; }, + set success_status(success_status) { this._success_status = success_status; }, + + _suspend_data: "", // SPM 64000 characters + get suspend_data() { return this._suspend_data; }, + set suspend_data(suspend_data) { this._suspend_data = suspend_data; }, + + _time_limit_action: "continue,no message", // Allowed values: "exit,message", "continue,message", "exit,no message", "continue,no message" + get time_limit_action() { return this._time_limit_action; }, + set time_limit_action(time_limit_action) { API.throwSCORMError(API, 404); }, + + _total_time: "0", // Data type: timeinterval (second,10,2) + get total_time() { return this._total_time; }, + set total_time(total_time) { API.throwSCORMError(API, 404); }, + + comments_from_learner: { + // SPM 250 comments from learner + __children: "comment,location,timestamp", + get _children() { return this.__children; }, + set _children(_children) { API.throwSCORMError(API, 404); }, + + childArray: [], + get _count() { return String(this.childArray.length); }, + set _count(count) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }, + + comments_from_lms: { + // SPM 100 comments from the LMS + __children: "comment,location,timestamp", + get _children() { return this.__children; }, + set _children(_children) { API.throwSCORMError(API, 404); }, + + childArray: [], + get _count() { return String(this.childArray.length); }, + set _count(count) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }, + + interactions: { + // SPM 250 interactions + __children: "id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description", + get _children() { return this.__children; }, + set _children(_children) { API.throwSCORMError(API, 404); }, + + childArray: [], + get _count() { return this.childArray.length; }, + set _count(_count) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }, + + learner_preference: { + __children: "audio_level,language,delivery_speed,audio_captioning", + get _children() { return this.__children; }, + set _children(_children) { API.throwSCORMError(API, 404); }, + + _audio_level: "1", // Data type: real (10,7). Range: 0.0 to infinity + get audio_level() { return this._audio_level; }, + set audio_level(audio_level) { this._audio_level = audio_level; }, + + _language: "", // SPM 250 characters + get language() { return this._language; }, + set language(language) { this._language = language; }, + + _delivery_speed: "1", // Data type: real (10,7). Range: 0.0 to infinity + get delivery_speed() { return this._delivery_speed; }, + set delivery_speed(delivery_speed) { this._delivery_speed = delivery_speed; }, + + _audio_captioning: "0", // Allowed values: "-1", "0", "1" + get audio_captioning() { return this._audio_captioning; }, + set audio_captioning(audio_captioning) { this._audio_captioning = audio_captioning; }, + + toJSON: jsonFormatter + }, + + objectives: { + // SPM 100 objectives + __children: "id,score,success_status,completion_status,progress_measure,description", + get _children() { return this.__children; }, + set _children(_children) { API.throwSCORMError(API, 404); }, + + childArray: [], + get _count() { return this.childArray.length; }, + set _count(count) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }, + + score: { + __children: "scaled,raw,min,max", + get _children() { return this.__children; }, + set _children(children) { API.throwSCORMError(API, 404); }, + + _scaled: "", // Data type: real (10,7). Range: -1.0 to 1.0 + get scaled() { return this._scaled; }, + set scaled(scaled) { this._scaled = scaled; }, + + _raw: "", // Data type: real (10,7) + get raw() { return this._raw; }, + set raw(raw) { this._raw = raw; }, + + _min: "", // Data type: real (10,7) + get min() { return this._min; }, + set min(min) { this._min = min; }, + + _max: "", // Data type: real (10,7) + get max() { return this._max; }, + set max(max) { this._max = max; }, + + toJSON: jsonFormatter + }, + + toJSON: jsonFormatter + }; + } + + function CommentsFromLearnerObject(_API) { + return { + _comment: "", // SPM 4000 characters + get comment() { return this._comment; }, + set comment(comment) { this._comment = comment; }, + + _location: "", // SPM 250 characters + get location() { return this._location; }, + set location(location) { this._location = location; }, + + _timestamp: "", // Data type: time (second,10,0) accurate to one second + get timestamp() { return this._timestamp; }, + set timestamp(timestamp) { this._timestamp = timestamp; }, + + toJSON: jsonFormatter + }; + } + + function CommentsFromLMSObject(API) { + return { + _comment: "", // SPM 4000 characters + get comment() { return this._comment; }, + set comment(comment) { API.throwSCORMError(API, 404); }, + + _location: "", // SPM 250 characters + get location() { return this._location; }, + set location(location) { API.throwSCORMError(API, 404); }, + + _timestamp: "", // Data type: time (second,10,0) accurate to one second + get timestamp() { return this._timestamp; }, + set timestamp(timestamp) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }; + } + + function InteractionsObject(API) { + return { + _id: "", // SPM 4000 characters + get id() { return this._id; }, + set id(id) { this._id = id; }, + + _type: "", // Allowed values: "true-false", "choice", "fill-in", "long-fill-in", "likert", "matching", "performance", "sequencing", "numeric", "other" + get type() { return this._type; }, + set type(type) { this._type = type; }, + + _timestamp: "", // Data type: time (second,10,0) accurate to one second + get timestamp() { return this._timestamp; }, + set timestamp(timestamp) { this._timestamp = timestamp; }, + + _weighting: "", // Data type: real (10,7) + get weighting() { return this._weighting; }, + set weighting(weighting) { this._weighting = weighting; }, + + _learner_response: "", // Data type changes based on interaction's type + get learner_response() { return this._learner_response; }, + set learner_response(learner_response) { this._learner_response = learner_response; }, + + _result: "", // Allowed values: "correct", "incorrect", "unanticipated", "neutral", real(10,7) + get result() { return this._result; }, + set result(result) { this._result = result; }, + + _latency: "", // Data type: timeinterval (second,10,2) + get latency() { return this._latency; }, + set latency(latency) { this._latency = latency; }, + + _description: "", // SPM 250 characters + get description() { return this._description; }, + set description(description) { this._description = description; }, + + objectives: { + // SPM 10 interaction's objectives + childArray: [], + get _count() { return this.childArray.length; }, + set _count(_count) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }, + + correct_responses: { + // SPM changes based on interaction's type + childArray: [], + get _count() { return this.childArray.length; }, + set _count(_count) { API.throwSCORMError(API, 404); }, + + toJSON: jsonFormatter + }, + + toJSON: jsonFormatter + }; + } + + function InteractionsObjectivesObject(_API) { + return { + _id: "", // SPM 4000 characters + get id() { return this._id; }, + set id(id) { this._id = id; }, + + toJSON: jsonFormatter + }; + } + + function InteractionsCorrectResponsesObject(_API) { + return { + _pattern: "", // Data type changes based on interaction's type + get pattern() { return this._pattern; }, + set pattern(pattern) { this._pattern = pattern; }, + + toJSON: jsonFormatter + }; + } + + function ObjectivesObject(API) { + return { + _id: "", // SPM 4000 characters + get id() { return this._id; }, + set id(id) { this._id = id; }, + + _success_status: "unknown", // Allowed values: "passed", "failed", "unknown" + get success_status() { return this._success_status; }, + set success_status(success_status) { this._success_status = success_status; }, + + _completion_status: "unknown", // Allowed values: "completed", "incomplete", "not attempted", "unknown" + get completion_status() { return this._completion_status; }, + set completion_status(completion_status) { this._completion_status = completion_status; }, + + _progress_measure: "", // Data type: real (10,7). Range: 0.0 to 1.0 + get progress_measure() { return this._progress_measure; }, + set progress_measure(progress_measure) { this._progress_measure = progress_measure; }, + + _description: "", // SPM 250 characters + get description() { return this._description; }, + set description(description) { this._description = description; }, + + score: { + __children: "scaled,raw,min,max", + get _children() { return this.__children; }, + set _children(children) { API.throwSCORMError(API, 404); }, + + _scaled: "", // Data type: real (10,7). Range: -1.0 to 1.0 + get scaled() { return this._scaled; }, + set scaled(scaled) { this._scaled = scaled; }, + + _raw: "", // Data type: real (10,7) + get raw() { return this._raw; }, + set raw(raw) { this._raw = raw; }, + + _min: "", // Data type: real (10,7) + get min() { return this._min; }, + set min(min) { this._min = min; }, + + _max: "", // Data type: real (10,7) + get max() { return this._max; }, + set max(max) { this._max = max; }, + + toJSON: jsonFormatter + }, + + toJSON: jsonFormatter + }; + } + + /** + * Adl data model + */ + function ADL(API) { + return { + nav: { + _request: "", + get request() { return this._request; }, + set request(request) { this._request = request; }, + + request_valid: { + _continue: "unknown", + get continue() { return this._continue; }, + set continue(_) { API.throwSCORMError(API, 404); }, + + _previous: "unknown", + get previous() { return this._previous; }, + set previous(_) { API.throwSCORMError(API, 404); }, + + _choice: "unknown", + get choice() { return this._choice; }, + set choice(_) { API.throwSCORMError(API, 404); }, + + _jump: "unknown", + get jump() { return this._jump; }, + set jump(_) { API.throwSCORMError(API, 404); } + } + } + }; + } +})(); From 0f081efbb8d62eba9f878e2a74364be617e4b5e4 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Thu, 12 Apr 2018 16:01:36 -0400 Subject: [PATCH 2/8] Rename 2004 API method to be standard with 1.2 method name --- src/scormAPI2004.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scormAPI2004.js b/src/scormAPI2004.js index fe95b6e..470b242 100644 --- a/src/scormAPI2004.js +++ b/src/scormAPI2004.js @@ -13,9 +13,9 @@ var constants = window.simplifyScorm.constants; var jsonFormatter = window.simplifyScorm.jsonFormatter; - window.API_1484_11 = new API_1484_11(); + window.API_1484_11 = new ScormAPI2004(); - function API_1484_11() { + function ScormAPI2004() { var _self = this; BaseAPI.call(_self); From bc7c99c579d0afa66b083a7bbc5c709848036bc2 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Fri, 13 Apr 2018 10:59:17 -0400 Subject: [PATCH 3/8] Fix throwScormError calls, handle default ADL functionality, list unhandled ADL compliance requirements --- src/scormAPI2004.compliance.md | 9 ++- src/scormAPI2004.js | 141 +++++++++++++++++++-------------- 2 files changed, 90 insertions(+), 60 deletions(-) diff --git a/src/scormAPI2004.compliance.md b/src/scormAPI2004.compliance.md index 722c6e7..56b05b0 100644 --- a/src/scormAPI2004.compliance.md +++ b/src/scormAPI2004.compliance.md @@ -212,4 +212,11 @@ ### Run-Time Navigation Data Model Compliance Requirements (Page 59) | REQ ID | Requirement | | ------ | ----------- | -| TODO | TODO | +| REQ_47.2.1 | If the **adl.nav.request** is for a choice navigation request, then the format of the characterstring shall be: `{target=}choice`. Where `` represents the target of the pending choice navigation request. | +| REQ_47.2.2 | If the **adl.nav.request** is for a jump navigation request, then the format of the characterstring shall be: `{target=}jump`. Where `` represents the target of the pending jump navigation request. | +| REQ_47.4 | Upon normal termination of a SCO (the SCO set **cmi.exit** to `""` or `normal`) the LMS shall process the navigation request. | +| REQ_47.5 | If the SCO terminates in a suspended state (the SCO set **cmi.exit** to `suspend`), the LMS shall not process the navigation request identified by this element, but should instead process a Suspend or SuspendAll request, as appropriate. | +| REQ_48.2 | The LMS shall determine if the request to continue (**adl.nav.request_valid.continue**) is valid based on the state of the Activity Tree. | +| REQ_49.2 | The LMS shall determine if the request to go previous (**adl.nav.request_valid.previous**) is valid based on the state of the Activity Tree. | +| REQ_50.2 | The **adl.nav.request_valid.choice.{target=<STRING>}** data model element shall be implemented to return one of the following restricted vocabulary tokens: `true`, `false`, `unknown`

The LMS shall determine if the choice request is valid based on the state of the Activity Tree. | +| REQ_119.2 | The **adl.nav.request_valid.jump.{target=<STRING>}** data model element shall be implemented to return one of the following restricted vocabulary tokens: `true`, `false`, `unknown`

The LMS shall determine if the jump request is valid based on the state of the Activity Tree. | diff --git a/src/scormAPI2004.js b/src/scormAPI2004.js index 470b242..4ded530 100644 --- a/src/scormAPI2004.js +++ b/src/scormAPI2004.js @@ -46,9 +46,9 @@ var returnValue = constants.SCORM_FALSE; if (_self.currentState === constants.STATE_INITIALIZED) { - _self.throwSCORMError(_self, 103); + _self.throwSCORMError(103); } else if (_self.currentState === constants.STATE_TERMINATED) { - _self.throwSCORMError(_self, 104); + _self.throwSCORMError(104); } else { _self.currentState = constants.STATE_INITIALIZED; _self.lastErrorCode = 0; @@ -69,9 +69,9 @@ var returnValue = constants.SCORM_FALSE; if (_self.currentState === constants.STATE_NOT_INITIALIZED) { - _self.throwSCORMError(_self, 112); + _self.throwSCORMError(112); } else if (_self.currentState === constants.STATE_TERMINATED) { - _self.throwSCORMError(_self, 113); + _self.throwSCORMError(113); } else { _self.currentState = constants.STATE_TERMINATED; _self.lastErrorCode = 0; @@ -92,9 +92,9 @@ var returnValue = ""; if (_self.currentState === constants.STATE_NOT_INITIALIZED) { - _self.throwSCORMError(_self, 122); + _self.throwSCORMError(122); } else if (_self.currentState === constants.STATE_TERMINATED) { - _self.throwSCORMError(_self, 123); + _self.throwSCORMError(123); } else { _self.lastErrorCode = 0; returnValue = getCMIValue(CMIElement); @@ -115,9 +115,9 @@ var returnValue = ""; if (_self.currentState === constants.STATE_NOT_INITIALIZED) { - _self.throwSCORMError(_self, 132); + _self.throwSCORMError(132); } else if (_self.currentState === constants.STATE_TERMINATED) { - _self.throwSCORMError(_self, 133); + _self.throwSCORMError(133); } else { _self.lastErrorCode = 0; returnValue = setCMIValue(CMIElement, value); @@ -138,9 +138,9 @@ var returnValue = constants.SCORM_FALSE; if (_self.currentState === constants.STATE_NOT_INITIALIZED) { - _self.throwSCORMError(_self, 142); + _self.throwSCORMError(142); } else if (_self.currentState === constants.STATE_TERMINATED) { - _self.throwSCORMError(_self, 143); + _self.throwSCORMError(143); } else { _self.lastErrorCode = 0; returnValue = constants.SCORM_TRUE; @@ -222,17 +222,21 @@ var returnValue = constants.SCORM_FALSE; for (var i = 0; i < structure.length; i++) { + var attribute = structure[i]; + if (i === structure.length - 1) { - if (!refObject.hasOwnProperty(structure[i])) { - _self.throwSCORMError(_self, 401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); + if ((attribute.substr(0, 8) == "{target=") && (typeof refObject._isTargetValid == "function")) { + _self.throwSCORMError(404); + } else if (!refObject.hasOwnProperty(attribute)) { + _self.throwSCORMError(401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); } else { - refObject[structure[i]] = value; + refObject[attribute] = value; if (_self.lastErrorCode == 0) { returnValue = constants.SCORM_TRUE; } } } else { - refObject = refObject[structure[i]]; + refObject = refObject[attribute]; if (refObject.hasOwnProperty("childArray")) { var index = parseInt(structure[i + 1], 10); @@ -261,7 +265,7 @@ } if (!newChild) { - _self.throwSCORMError(_self, 401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); + _self.throwSCORMError(401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); } else { refObject.childArray.push(newChild); @@ -298,12 +302,17 @@ var refObject = _self; for (var i = 0; i < structure.length; i++) { - if (!refObject.hasOwnProperty(structure[i])) { - _self.throwSCORMError(_self, 401, "The data model element passed to GetValue (" + CMIElement + ") is not a valid SCORM data model element."); + var attribute = structure[i]; + + if ((attribute.substr(0, 8) == "{target=") && (typeof refObject._isTargetValid == "function")) { + var target = attribute.substr(8, attribute.length - 9); + return refObject._isTargetValid(target); + } else if (!refObject.hasOwnProperty(attribute)) { + _self.throwSCORMError(401, "The data model element passed to GetValue (" + CMIElement + ") is not a valid SCORM data model element."); return ""; } - refObject = refObject[structure[i]]; + refObject = refObject[attribute]; } return refObject || ""; @@ -469,7 +478,7 @@ return { __version: "1.0", get _version() { return this.__version; }, - set _version(version) { API.throwSCORMError(API, 404); }, + set _version(version) { API.throwSCORMError(404); }, _completion_status: "unknown", // Allowed values: "completed", "incomplete", "not attempted", "unknown" get completion_status() { return this._completion_status; }, @@ -477,31 +486,31 @@ _completion_threshold: "", // Data type: real (10,7). Range: 0.0 to 1.0 get completion_threshold() { return this._completion_threshold; }, - set completion_threshold(completion_threshold) { API.throwSCORMError(API, 404); }, + set completion_threshold(completion_threshold) { API.throwSCORMError(404); }, _credit: "credit", // Allowed values: "credit", "no-credit" get credit() { return this._credit; }, - set credit(credit) { API.throwSCORMError(API, 404); }, + set credit(credit) { API.throwSCORMError(404); }, _entry: "", // Allowed values: "ab-initio", "resume", "" get entry() { return this._entry; }, - set entry(entry) { API.throwSCORMError(API, 404); }, + set entry(entry) { API.throwSCORMError(404); }, _exit: "", // Allowed values: "time-out", "suspend", "logout", "normal", "" - get exit() { return (!this.jsonString) ? API.throwSCORMError(API, 405) : this._exit; }, + get exit() { return (!this.jsonString) ? API.throwSCORMError(405) : this._exit; }, set exit(exit) { this._exit = exit; }, _launch_data: "", // SPM 4000 characters get launch_data() { return this._launch_data; }, - set launch_data(launch_data) { API.throwSCORMError(API, 404); }, + set launch_data(launch_data) { API.throwSCORMError(404); }, _learner_id: "", // SPM 4000 characters get learner_id() { return this._learner_id; }, - set learner_id(learner_id) { API.throwSCORMError(API, 404); }, + set learner_id(learner_id) { API.throwSCORMError(404); }, _learner_name: "", // SPM 250 characters get learner_name() { return this._learner_name; }, - set learner_name(learner_name) { API.throwSCORMError(API, 404); }, + set learner_name(learner_name) { API.throwSCORMError(404); }, _location: "", // SPM 1000 characters get location() { return this._location; }, @@ -509,11 +518,11 @@ _max_time_allowed: "", // Data type: timeinterval (second,10,2) get max_time_allowed() { return this._max_time_allowed; }, - set max_time_allowed(max_time_allowed) { API.throwSCORMError(API, 404); }, + set max_time_allowed(max_time_allowed) { API.throwSCORMError(404); }, _mode: "normal", // Allowed values: "browse", "normal", "review" get mode() { return this._mode; }, - set mode(mode) { API.throwSCORMError(API, 404); }, + set mode(mode) { API.throwSCORMError(404); }, _progress_measure: "", // Data type: real (10,7). Range: 0.0 to 1.0 get progress_measure() { return this._progress_measure; }, @@ -521,10 +530,10 @@ _scaled_passing_score: "", // Data type: real (10,7). Range: -1.0 to 1.0 get scaled_passing_score() { return this._scaled_passing_score; }, - set scaled_passing_score(scaled_passing_score) { API.throwSCORMError(API, 404); }, + set scaled_passing_score(scaled_passing_score) { API.throwSCORMError(404); }, _session_time: "", // Data type: timeinterval (second,10,2) - get session_time() { return (!this.jsonString) ? API.throwSCORMError(API, 405) : this._session_time; }, + get session_time() { return (!this.jsonString) ? API.throwSCORMError(405) : this._session_time; }, set session_time(session_time) { this._session_time = session_time; }, _success_status: "unknown", // Allowed values: "passed", "failed", "unknown" @@ -537,21 +546,21 @@ _time_limit_action: "continue,no message", // Allowed values: "exit,message", "continue,message", "exit,no message", "continue,no message" get time_limit_action() { return this._time_limit_action; }, - set time_limit_action(time_limit_action) { API.throwSCORMError(API, 404); }, + set time_limit_action(time_limit_action) { API.throwSCORMError(404); }, _total_time: "0", // Data type: timeinterval (second,10,2) get total_time() { return this._total_time; }, - set total_time(total_time) { API.throwSCORMError(API, 404); }, + set total_time(total_time) { API.throwSCORMError(404); }, comments_from_learner: { // SPM 250 comments from learner __children: "comment,location,timestamp", get _children() { return this.__children; }, - set _children(_children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, childArray: [], get _count() { return String(this.childArray.length); }, - set _count(count) { API.throwSCORMError(API, 404); }, + set _count(count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -560,11 +569,11 @@ // SPM 100 comments from the LMS __children: "comment,location,timestamp", get _children() { return this.__children; }, - set _children(_children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, childArray: [], get _count() { return String(this.childArray.length); }, - set _count(count) { API.throwSCORMError(API, 404); }, + set _count(count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -573,11 +582,11 @@ // SPM 250 interactions __children: "id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description", get _children() { return this.__children; }, - set _children(_children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, childArray: [], get _count() { return this.childArray.length; }, - set _count(_count) { API.throwSCORMError(API, 404); }, + set _count(_count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -585,7 +594,7 @@ learner_preference: { __children: "audio_level,language,delivery_speed,audio_captioning", get _children() { return this.__children; }, - set _children(_children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, _audio_level: "1", // Data type: real (10,7). Range: 0.0 to infinity get audio_level() { return this._audio_level; }, @@ -610,11 +619,11 @@ // SPM 100 objectives __children: "id,score,success_status,completion_status,progress_measure,description", get _children() { return this.__children; }, - set _children(_children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, childArray: [], get _count() { return this.childArray.length; }, - set _count(count) { API.throwSCORMError(API, 404); }, + set _count(count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -622,7 +631,7 @@ score: { __children: "scaled,raw,min,max", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, _scaled: "", // Data type: real (10,7). Range: -1.0 to 1.0 get scaled() { return this._scaled; }, @@ -669,15 +678,15 @@ return { _comment: "", // SPM 4000 characters get comment() { return this._comment; }, - set comment(comment) { API.throwSCORMError(API, 404); }, + set comment(comment) { API.throwSCORMError(404); }, _location: "", // SPM 250 characters get location() { return this._location; }, - set location(location) { API.throwSCORMError(API, 404); }, + set location(location) { API.throwSCORMError(404); }, _timestamp: "", // Data type: time (second,10,0) accurate to one second get timestamp() { return this._timestamp; }, - set timestamp(timestamp) { API.throwSCORMError(API, 404); }, + set timestamp(timestamp) { API.throwSCORMError(404); }, toJSON: jsonFormatter }; @@ -721,7 +730,7 @@ // SPM 10 interaction's objectives childArray: [], get _count() { return this.childArray.length; }, - set _count(_count) { API.throwSCORMError(API, 404); }, + set _count(_count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -730,7 +739,7 @@ // SPM changes based on interaction's type childArray: [], get _count() { return this.childArray.length; }, - set _count(_count) { API.throwSCORMError(API, 404); }, + set _count(_count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -784,7 +793,7 @@ score: { __children: "scaled,raw,min,max", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(API, 404); }, + set _children(children) { API.throwSCORMError(404); }, _scaled: "", // Data type: real (10,7). Range: -1.0 to 1.0 get scaled() { return this._scaled; }, @@ -815,28 +824,42 @@ function ADL(API) { return { nav: { - _request: "", + _request: "_none_", // Allowed values: "continue", "previous", "choice", "jump", "exit", "exitAll", "abandon", "abandonAll", "_none_" get request() { return this._request; }, set request(request) { this._request = request; }, request_valid: { - _continue: "unknown", + _continue: "unknown", // Allowed values: "true", "false", "unknown" get continue() { return this._continue; }, - set continue(_) { API.throwSCORMError(API, 404); }, + set continue(_) { API.throwSCORMError(404); }, - _previous: "unknown", + _previous: "unknown", // Allowed values: "true", "false", "unknown" get previous() { return this._previous; }, - set previous(_) { API.throwSCORMError(API, 404); }, + set previous(_) { API.throwSCORMError(404); }, - _choice: "unknown", - get choice() { return this._choice; }, - set choice(_) { API.throwSCORMError(API, 404); }, + choice: { + _isTargetValid: adlNavRequestValidChoice + }, - _jump: "unknown", - get jump() { return this._jump; }, - set jump(_) { API.throwSCORMError(API, 404); } + jump: { + _isTargetValid: adlNavRequestValidJump + } } } }; } + + /** + * Determine if the choice request is valid + */ + function adlNavRequestValidChoice(_target) { + return "unknown"; + } + + /** + * Determine if the jump request is valid + */ + function adlNavRequestValidJump(_target) { + return "unknown"; + } })(); From 958abd975b2962d5b73d897d34a6a5c70a523645 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Tue, 1 May 2018 08:12:04 -0400 Subject: [PATCH 4/8] Allow read-only attributes to be written before initialization Also bumped version to 1.1.0, because of the addition of Scorm 2004 support --- package.json | 2 +- src/scormAPI2004.js | 70 ++++++++++++++++++++++----------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index d9c9d3a..7d6d55e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simplify-scorm", - "version": "1.0.1", + "version": "1.1.0", "description": "", "license": "MIT", "private": true, diff --git a/src/scormAPI2004.js b/src/scormAPI2004.js index 4ded530..4631385 100644 --- a/src/scormAPI2004.js +++ b/src/scormAPI2004.js @@ -45,9 +45,9 @@ function LMSInitialize() { var returnValue = constants.SCORM_FALSE; - if (_self.currentState === constants.STATE_INITIALIZED) { + if (_self.isInitialized()) { _self.throwSCORMError(103); - } else if (_self.currentState === constants.STATE_TERMINATED) { + } else if (_self.isTerminated()) { _self.throwSCORMError(104); } else { _self.currentState = constants.STATE_INITIALIZED; @@ -68,9 +68,9 @@ function LMSTerminate() { var returnValue = constants.SCORM_FALSE; - if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + if (_self.isNotInitialized()) { _self.throwSCORMError(112); - } else if (_self.currentState === constants.STATE_TERMINATED) { + } else if (_self.isTerminated()) { _self.throwSCORMError(113); } else { _self.currentState = constants.STATE_TERMINATED; @@ -91,9 +91,9 @@ function LMSGetValue(CMIElement) { var returnValue = ""; - if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + if (_self.isNotInitialized()) { _self.throwSCORMError(122); - } else if (_self.currentState === constants.STATE_TERMINATED) { + } else if (_self.isTerminated()) { _self.throwSCORMError(123); } else { _self.lastErrorCode = 0; @@ -114,9 +114,9 @@ function LMSSetValue(CMIElement, value) { var returnValue = ""; - if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + if (_self.isNotInitialized()) { _self.throwSCORMError(132); - } else if (_self.currentState === constants.STATE_TERMINATED) { + } else if (_self.isTerminated()) { _self.throwSCORMError(133); } else { _self.lastErrorCode = 0; @@ -137,9 +137,9 @@ function LMSCommit() { var returnValue = constants.SCORM_FALSE; - if (_self.currentState === constants.STATE_NOT_INITIALIZED) { + if (_self.isNotInitialized()) { _self.throwSCORMError(142); - } else if (_self.currentState === constants.STATE_TERMINATED) { + } else if (_self.isTerminated()) { _self.throwSCORMError(143); } else { _self.lastErrorCode = 0; @@ -478,7 +478,7 @@ return { __version: "1.0", get _version() { return this.__version; }, - set _version(version) { API.throwSCORMError(404); }, + set _version(_version) { API.throwSCORMError(404); }, _completion_status: "unknown", // Allowed values: "completed", "incomplete", "not attempted", "unknown" get completion_status() { return this._completion_status; }, @@ -486,15 +486,15 @@ _completion_threshold: "", // Data type: real (10,7). Range: 0.0 to 1.0 get completion_threshold() { return this._completion_threshold; }, - set completion_threshold(completion_threshold) { API.throwSCORMError(404); }, + set completion_threshold(completion_threshold) { API.isNotInitialized() ? this._completion_threshold = completion_threshold : API.throwSCORMError(404); }, _credit: "credit", // Allowed values: "credit", "no-credit" get credit() { return this._credit; }, - set credit(credit) { API.throwSCORMError(404); }, + set credit(credit) { API.isNotInitialized() ? this._credit = credit : API.throwSCORMError(404); }, _entry: "", // Allowed values: "ab-initio", "resume", "" get entry() { return this._entry; }, - set entry(entry) { API.throwSCORMError(404); }, + set entry(entry) { API.isNotInitialized() ? this._entry = entry : API.throwSCORMError(404); }, _exit: "", // Allowed values: "time-out", "suspend", "logout", "normal", "" get exit() { return (!this.jsonString) ? API.throwSCORMError(405) : this._exit; }, @@ -502,15 +502,15 @@ _launch_data: "", // SPM 4000 characters get launch_data() { return this._launch_data; }, - set launch_data(launch_data) { API.throwSCORMError(404); }, + set launch_data(launch_data) { API.isNotInitialized() ? this._launch_data = launch_data : API.throwSCORMError(404); }, _learner_id: "", // SPM 4000 characters get learner_id() { return this._learner_id; }, - set learner_id(learner_id) { API.throwSCORMError(404); }, + set learner_id(learner_id) { API.isNotInitialized() ? this._learner_id = learner_id : API.throwSCORMError(404); }, _learner_name: "", // SPM 250 characters get learner_name() { return this._learner_name; }, - set learner_name(learner_name) { API.throwSCORMError(404); }, + set learner_name(learner_name) { API.isNotInitialized() ? this._learner_name = learner_name : API.throwSCORMError(404); }, _location: "", // SPM 1000 characters get location() { return this._location; }, @@ -518,11 +518,11 @@ _max_time_allowed: "", // Data type: timeinterval (second,10,2) get max_time_allowed() { return this._max_time_allowed; }, - set max_time_allowed(max_time_allowed) { API.throwSCORMError(404); }, + set max_time_allowed(max_time_allowed) { API.isNotInitialized() ? this._max_time_allowed = max_time_allowed : API.throwSCORMError(404); }, _mode: "normal", // Allowed values: "browse", "normal", "review" get mode() { return this._mode; }, - set mode(mode) { API.throwSCORMError(404); }, + set mode(mode) { API.isNotInitialized() ? this._mode = mode : API.throwSCORMError(404); }, _progress_measure: "", // Data type: real (10,7). Range: 0.0 to 1.0 get progress_measure() { return this._progress_measure; }, @@ -530,7 +530,7 @@ _scaled_passing_score: "", // Data type: real (10,7). Range: -1.0 to 1.0 get scaled_passing_score() { return this._scaled_passing_score; }, - set scaled_passing_score(scaled_passing_score) { API.throwSCORMError(404); }, + set scaled_passing_score(scaled_passing_score) { API.isNotInitialized() ? this._scaled_passing_score = scaled_passing_score : API.throwSCORMError(404); }, _session_time: "", // Data type: timeinterval (second,10,2) get session_time() { return (!this.jsonString) ? API.throwSCORMError(405) : this._session_time; }, @@ -546,21 +546,21 @@ _time_limit_action: "continue,no message", // Allowed values: "exit,message", "continue,message", "exit,no message", "continue,no message" get time_limit_action() { return this._time_limit_action; }, - set time_limit_action(time_limit_action) { API.throwSCORMError(404); }, + set time_limit_action(time_limit_action) { API.isNotInitialized() ? this._time_limit_action = time_limit_action : API.throwSCORMError(404); }, _total_time: "0", // Data type: timeinterval (second,10,2) get total_time() { return this._total_time; }, - set total_time(total_time) { API.throwSCORMError(404); }, + set total_time(total_time) { API.isNotInitialized() ? this._total_time = total_time : API.throwSCORMError(404); }, comments_from_learner: { // SPM 250 comments from learner __children: "comment,location,timestamp", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, childArray: [], get _count() { return String(this.childArray.length); }, - set _count(count) { API.throwSCORMError(404); }, + set _count(_count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -569,11 +569,11 @@ // SPM 100 comments from the LMS __children: "comment,location,timestamp", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, childArray: [], get _count() { return String(this.childArray.length); }, - set _count(count) { API.throwSCORMError(404); }, + set _count(_count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -582,7 +582,7 @@ // SPM 250 interactions __children: "id,type,objectives,timestamp,correct_responses,weighting,learner_response,result,latency,description", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, childArray: [], get _count() { return this.childArray.length; }, @@ -594,7 +594,7 @@ learner_preference: { __children: "audio_level,language,delivery_speed,audio_captioning", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, _audio_level: "1", // Data type: real (10,7). Range: 0.0 to infinity get audio_level() { return this._audio_level; }, @@ -619,11 +619,11 @@ // SPM 100 objectives __children: "id,score,success_status,completion_status,progress_measure,description", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, childArray: [], get _count() { return this.childArray.length; }, - set _count(count) { API.throwSCORMError(404); }, + set _count(_count) { API.throwSCORMError(404); }, toJSON: jsonFormatter }, @@ -631,7 +631,7 @@ score: { __children: "scaled,raw,min,max", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, _scaled: "", // Data type: real (10,7). Range: -1.0 to 1.0 get scaled() { return this._scaled; }, @@ -678,15 +678,15 @@ return { _comment: "", // SPM 4000 characters get comment() { return this._comment; }, - set comment(comment) { API.throwSCORMError(404); }, + set comment(comment) { API.isNotInitialized() ? this._comment = comment : API.throwSCORMError(404); }, _location: "", // SPM 250 characters get location() { return this._location; }, - set location(location) { API.throwSCORMError(404); }, + set location(location) { API.isNotInitialized() ? this._location = location : API.throwSCORMError(404); }, _timestamp: "", // Data type: time (second,10,0) accurate to one second get timestamp() { return this._timestamp; }, - set timestamp(timestamp) { API.throwSCORMError(404); }, + set timestamp(timestamp) { API.isNotInitialized() ? this._timestamp = timestamp : API.throwSCORMError(404); }, toJSON: jsonFormatter }; @@ -793,7 +793,7 @@ score: { __children: "scaled,raw,min,max", get _children() { return this.__children; }, - set _children(children) { API.throwSCORMError(404); }, + set _children(_children) { API.throwSCORMError(404); }, _scaled: "", // Data type: real (10,7). Range: -1.0 to 1.0 get scaled() { return this._scaled; }, From 0e2481fa1494b9b3f3d5efdec853296e9eec61f4 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Tue, 24 Jul 2018 09:23:46 -0400 Subject: [PATCH 5/8] Fix unknown path javascript error, and clear SCORM error on successful command --- src/baseAPI.js | 16 +++++++++++++--- src/scormAPI.js | 17 ++++++++++++++--- src/scormAPI2004.js | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/baseAPI.js b/src/baseAPI.js index 11d3e0a..b425fe5 100644 --- a/src/baseAPI.js +++ b/src/baseAPI.js @@ -13,6 +13,7 @@ // Utility Functions _self.apiLog = apiLog; _self.apiLogLevel = constants.LOG_LEVEL_ERROR; + _self.clearSCORMError = clearSCORMError; _self.getLmsErrorMessageDetails = getLmsErrorMessageDetails; _self.isInitialized = isInitialized; _self.isNotInitialized = isNotInitialized; @@ -50,7 +51,16 @@ } /** - * Formats the scorm messages for easy reading + * Clears the last SCORM error code on success + */ + function clearSCORMError(success) { + if (success !== constants.SCORM_FALSE) { + this.lastErrorCode = 0; + } + } + + /** + * Formats the SCORM messages for easy reading * * @param functionName * @param CMIElement @@ -120,7 +130,7 @@ } /** - * Provides a mechanism for attaching to a specific scorm event + * Provides a mechanism for attaching to a specific SCORM event * * @param listenerString * @param callback @@ -166,7 +176,7 @@ } /** - * Throws a scorm error + * Throws a SCORM error * * @param errorNumber * @param message diff --git a/src/scormAPI.js b/src/scormAPI.js index e884b0c..fb25bb9 100644 --- a/src/scormAPI.js +++ b/src/scormAPI.js @@ -5,6 +5,8 @@ * Scorm 1.2 Overview for Developers: https://scorm.com/scorm-explained/technical-scorm/scorm-12-overview-for-developers/ * Run-Time Reference: http://scorm.com/scorm-explained/technical-scorm/run-time/run-time-reference/ */ + window.simplifyScorm.ScormAPI = ScormAPI; + var BaseAPI = window.simplifyScorm.BaseAPI; var constants = window.simplifyScorm.constants; var jsonFormatter = window.simplifyScorm.jsonFormatter; @@ -51,6 +53,7 @@ } _self.apiLog("LMSInitialize", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -68,6 +71,7 @@ } _self.apiLog("LMSFinish", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -85,6 +89,7 @@ } _self.apiLog("LMSGetValue", CMIElement, ": returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -98,11 +103,12 @@ var returnValue = ""; if (_self.checkState()) { - setCMIValue(CMIElement, value); + returnValue = setCMIValue(CMIElement, value); _self.processListeners("LMSSetValue", CMIElement, value); } - _self.apiLog("LMSSetValue", CMIElement, ": " + value + ": result: " + returnValue, constants.LOG_LEVEL_INFO); + _self.apiLog("LMSSetValue", CMIElement, ": " + value + ": returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -121,6 +127,7 @@ } _self.apiLog("LMSCommit", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -216,6 +223,10 @@ } } else { refObject = refObject[structure[i]]; + if (!refObject) { + _self.throwSCORMError(101, "setCMIValue did not find an element for: " + CMIElement); + break; + } if (refObject.hasOwnProperty("childArray")) { var index = parseInt(structure[i + 1], 10); @@ -255,7 +266,7 @@ } if (found === constants.SCORM_FALSE) { - _self.apiLog("There was an error setting the value for: " + CMIElement + ", value of: " + value, constants.LOG_LEVEL_WARNING); + _self.apiLog("LMSSetValue", null, "There was an error setting the value for: " + CMIElement + ", value of: " + value, constants.LOG_LEVEL_WARNING); } return found; diff --git a/src/scormAPI2004.js b/src/scormAPI2004.js index 4631385..8b3784f 100644 --- a/src/scormAPI2004.js +++ b/src/scormAPI2004.js @@ -9,6 +9,8 @@ * * SPM = Smallest Permitted Maximum */ + window.simplifyScorm.ScormAPI2004 = ScormAPI2004; + var BaseAPI = window.simplifyScorm.BaseAPI; var constants = window.simplifyScorm.constants; var jsonFormatter = window.simplifyScorm.jsonFormatter; @@ -37,6 +39,7 @@ // Utility Functions _self.getLmsErrorMessageDetails = getLmsErrorMessageDetails; + _self.loadFromJSON = loadFromJSON; /** * @param Empty String @@ -57,6 +60,7 @@ } _self.apiLog("Initialize", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -80,6 +84,7 @@ } _self.apiLog("Terminate", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -102,6 +107,7 @@ } _self.apiLog("GetValue", CMIElement, ": returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -125,6 +131,7 @@ } _self.apiLog("SetValue", CMIElement, ": " + value + ": result: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -148,6 +155,7 @@ } _self.apiLog("Commit", null, "returned: " + returnValue, constants.LOG_LEVEL_INFO); + _self.clearSCORMError(returnValue); return returnValue; } @@ -237,6 +245,10 @@ } } else { refObject = refObject[attribute]; + if (!refObject) { + _self.throwSCORMError(401, "The data model element passed to SetValue (" + CMIElement + ") is not a valid SCORM data model element."); + break; + } if (refObject.hasOwnProperty("childArray")) { var index = parseInt(structure[i + 1], 10); @@ -281,7 +293,7 @@ } if (returnValue === constants.SCORM_FALSE) { - _self.apiLog("There was an error setting the value for: " + CMIElement + ", value of: " + value, constants.LOG_LEVEL_WARNING); + _self.apiLog("SetValue", null, "There was an error setting the value for: " + CMIElement + ", value of: " + value, constants.LOG_LEVEL_WARNING); } return returnValue; @@ -468,6 +480,35 @@ return detail ? detailMessage : basicMessage; } + /** + * Loads CMI data from a JSON object. + */ + function loadFromJSON(json, CMIElement) { + if (!_self.isNotInitialized()) { + console.error("loadFromJSON can only be called before the call to Initialize."); + return; + } + + CMIElement = CMIElement || "cmi"; + + for (var key in json) { + if (json.hasOwnProperty(key) && json[key]) { + var currentCMIElement = CMIElement + "." + key; + var value = json[key]; + + if (value["childArray"]) { + for (var i = 0; i < value["childArray"].length; i++) { + _self.loadFromJSON(value["childArray"][i], currentCMIElement + "." + i); + } + } else if (value.constructor === Object) { + _self.loadFromJSON(value, currentCMIElement); + } else { + setCMIValue(currentCMIElement, value); + } + } + } + } + return _self; } From 00689e3a0423372d6e9759bd864427d9acc7d414 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Mon, 20 Aug 2018 15:51:51 -0400 Subject: [PATCH 6/8] Add method to replace a Scorm API with another --- src/scormAPI.js | 28 ++++++++++++++++++++++++++++ src/scormAPI2004.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/scormAPI.js b/src/scormAPI.js index fb25bb9..79149a9 100644 --- a/src/scormAPI.js +++ b/src/scormAPI.js @@ -35,6 +35,7 @@ _self.checkState = checkState; _self.getLmsErrorMessageDetails = getLmsErrorMessageDetails; _self.loadFromJSON = loadFromJSON; + _self.replaceWithAnotherScormAPI = replaceWithAnotherScormAPI; /** * @returns {string} bool @@ -410,6 +411,33 @@ } } + /** + * Replace the whole API with another + */ + function replaceWithAnotherScormAPI(newAPI) { + // API Signature + _self.LMSInitialize = newAPI.LMSInitialize; + _self.LMSFinish = newAPI.LMSFinish; + _self.LMSGetValue = newAPI.LMSGetValue; + _self.LMSSetValue = newAPI.LMSSetValue; + _self.LMSCommit = newAPI.LMSCommit; + _self.LMSGetLastError = newAPI.LMSGetLastError; + _self.LMSGetErrorString = newAPI.LMSGetErrorString; + _self.LMSGetDiagnostic = newAPI.LMSGetDiagnostic; + + // Data Model + _self.cmi = newAPI.cmi; + + // Utility Functions + _self.checkState = newAPI.checkState; + _self.getLmsErrorMessageDetails = newAPI.getLmsErrorMessageDetails; + _self.loadFromJSON = newAPI.loadFromJSON; + _self.replaceWithAnotherScormAPI = newAPI.replaceWithAnotherScormAPI; + + // API itself + _self = newAPI; // eslint-disable-line consistent-this + } + return _self; } diff --git a/src/scormAPI2004.js b/src/scormAPI2004.js index 8b3784f..fac942c 100644 --- a/src/scormAPI2004.js +++ b/src/scormAPI2004.js @@ -40,6 +40,7 @@ // Utility Functions _self.getLmsErrorMessageDetails = getLmsErrorMessageDetails; _self.loadFromJSON = loadFromJSON; + _self.replaceWithAnotherScormAPI = replaceWithAnotherScormAPI; /** * @param Empty String @@ -509,6 +510,33 @@ } } + /** + * Replace the whole API with another + */ + function replaceWithAnotherScormAPI(newAPI) { + // API Signature + _self.Initialize = newAPI.Initialize; + _self.Terminate = newAPI.Terminate; + _self.GetValue = newAPI.GetValue; + _self.SetValue = newAPI.SetValue; + _self.Commit = newAPI.Commit; + _self.GetLastError = newAPI.GetLastError; + _self.GetErrorString = newAPI.GetErrorString; + _self.GetDiagnostic = newAPI.GetDiagnostic; + + // Data Model + _self.cmi = newAPI.cmi; + _self.adl = newAPI.adl; + + // Utility Functions + _self.getLmsErrorMessageDetails = newAPI.getLmsErrorMessageDetails; + _self.loadFromJSON = newAPI.loadFromJSON; + _self.replaceWithAnotherScormAPI = newAPI.replaceWithAnotherScormAPI; + + // API itself + _self = newAPI; // eslint-disable-line consistent-this + } + return _self; } From 9054efb3b8358be8a4b47ec9b900ec235e695602 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Sun, 26 Aug 2018 11:54:51 -0400 Subject: [PATCH 7/8] Update README with new features --- README.md | 529 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 395 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 7a89c07..b0e8110 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,442 @@ # Simplify Scorm -Simplify Scorm is a Javascript API for SCORM 1.2 Run-Time Implementations. +Simplify Scorm is a Javascript API for SCORM 1.2 and SCORM 2004 Run-Time Implementations. -The purpose of this software is to provide a quick, easy way to implement a SCORM 1.2 API (Run-Time) and integrate it with your backend API. -SCORM 1.2 has defined the data model required for this integration here: http://scorm.com/scorm-explained/technical-scorm/run-time/run-time-reference/ +The purpose of this software is to provide a quick, easy way to implement both a SCORM 1.2 Run-Time API and a SCORM 2004 Run-Time API, and integrate them with your backend API. +SCORM has defined the data model required for this integration here: http://scorm.com/scorm-explained/technical-scorm/run-time/run-time-reference/ -To use, you must include the scormAPI.js file on the launching page of your SCORM 1.2 application +To use, you must include the scormAPI.js file, or the minified version scormAPI.min.js, on the launching page of your SCORM 1.2 or SCORM 2004 application. ```html ``` -This will create a SCORM API object on the window (required by SCORM 1.2) and thats it!! +Hopefully this makes your life easier, and lets you get up and running much faster in your SCORM development! -No Really +# Table of Contents -This will handle all the scorm interactions, and record everything on the object at +- [Simplify Scorm](#simplify-scorm) +- [Table of Contents](#table-of-contents) +- [SCORM 1.2](#scorm-12) + * [Listeners](#listeners) + * [Saving Your CMI](#saving-your-cmi) + * [Initial Values](#initial-values) + * [Logging](#logging) + * [Resetting](#resetting) +- [SCORM 2004](#scorm-2004) + * [Listeners](#listeners-1) + * [Saving Your CMI](#saving-your-cmi-1) + * [Initial Values](#initial-values-1) + * [Logging](#logging-1) + * [Resetting](#resetting-1) + +# SCORM 1.2 + +Simplify Scorm will create a `window.API` object, required by SCORM 1.2, and will handle all the scorm interactions. Everything will be recorded on the object at `window.API.cmi`. + +## Listeners + +For convenience, hooks are available for all the SCORM API Signature functions: +`LMSInitialize`, +`LMSFinish`, +`LMSGetValue`, +`LMSSetValue`, +`LMSCommit`, +`LMSGetLastError`, +`LMSGetErrorString`, +`LMSGetDiagnostic` + +You can add your hook into these by adding a listener to the `window.API` object: ```javascript -window.API.cmi +window.API.on("LMSInitialize", function() { + [...] +}); ``` -## Listeners +You can also listen for events on specific SCORM CMI elements: + +```javascript +window.API.on("LMSSetValue.cmi.core.student_id", function(CMIElement, value) { + [...] +}); +``` + +## Saving Your CMI + +To save the CMI data, you can convert the CMI object to JSON and get a simplified data object to send to your backend API. +You should hook into the `LMSFinish` event to know when to save the finished session CMI data. +You can also hook into the `LMSCommit` event to save the progress along the way. + +```javascript +var simplifiedObject = window.API.cmi.toJSON(); +``` + +
+ Example output + + ```json + { + "suspend_data": "viewed=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31|lastviewedslide=31|7#1##,3,3,3,7,3,3,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11#0#b5e89fbb-7cfb-46f0-a7cb-758165d3fe7e=236~262~2542812732762722742772682802752822882852892872832862962931000~3579~32590001001010101010101010101001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001010010010010010010010010011010010010010010010010010010010010112101021000171000~236a71d398e-4023-4967-88fe-1af18721422d06passed6failed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105wrong110000000000000000000000000000000000~3185000000000000000000000000000000000000000000000000000000000000000000000000000000000~283~2191w11~21113101w41689~256~2100723031840~21007230314509062302670~2110723031061120000000000000000000~240~234531618~21601011000100000002814169400,#-1", + "launch_data": "", + "comments": "", + "comments_from_lms": "", + "core": { + "student_id": "", + "student_name": "", + "lesson_location": "", + "credit": "", + "lesson_status": "incomplete", + "total_time": "", + "lesson_mode": "normal", + "exit": "suspend", + "session_time": "0000:00:33.90", + "score": { + "raw": "", + "max": "100", + "min": "" + } + }, + "objectives": { + "childArray": [ + ] + }, + "student_data": { + "mastery_score": "", + "max_time_allowed": "", + "time_limit_action": "" + }, + "student_preference": { + "audio": "", + "language": "", + "speed": "", + "text": "" + }, + "interactions": { + "childArray": [ + { + "id": "Question14_1", + "time": "11:05:21", + "type": "choice", + "weighting": "1", + "student_response": "HTH", + "result": "wrong", + "latency": "0000:00:01.68", + "objectives": { + "childArray": [ + { + "id": "Question14_1" + } + ] + }, + "correct_responses": { + "childArray": [ + { + "pattern": "CPR" + } + ] + } + } + ] + } + } + ``` +
-For convenience, I have also added hooks into all the SCORM API Signature functions: +## Initial Values -LMSInitialize -LMSFinish -LMSGetValue -LMSSetValue -LMSCommit -LMSGetLastError -LMSGetErrorString -LMSGetDiagnostic +If you want to initially load data from your backend API, you must do it before launching your SCORM 1.2 player. After the player has initialized, you will not be able to change any read-only values. -You can add your hook into these by adding a listener to the window.API object: +You can initialize your variables on the CMI object individually: ```javascript -window.API.on('LMSInitialize', function() { +window.API.cmi.core.student_id = "123"; +``` + +You can also initialize the CMI object in bulk by supplying a JSON object. Note that it can be a partial SCORM 1.2 CMI JSON object: + +```javascript +window.API.loadFromJSON(json); +``` + +
+ Example JSON input + + ```javascript + var json = { + "suspend_data": "viewed=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31|lastviewedslide=31|7#1##,3,3,3,7,3,3,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11#0#b5e89fbb-7cfb-46f0-a7cb-758165d3fe7e=236~262~2542812732762722742772682802752822882852892872832862962931000~3579~32590001001010101010101010101001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001010010010010010010010010011010010010010010010010010010010010112101021000171000~236a71d398e-4023-4967-88fe-1af18721422d06passed6failed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105wrong110000000000000000000000000000000000~3185000000000000000000000000000000000000000000000000000000000000000000000000000000000~283~2191w11~21113101w41689~256~2100723031840~21007230314509062302670~2110723031061120000000000000000000~240~234531618~21601011000100000002814169400,#-1", + "core": { + "student_id": "123", + "student_name": "Bob The Builder" + }, + "interactions": { + "childArray": [ + { + "id": "Question14_1", + "time": "11:05:21", + "type": "choice", + "weighting": "1", + "student_response": "HTH", + "result": "wrong", + "latency": "0000:00:01.68", + "objectives": { + "childArray": [ + { + "id": "Question14_1" + } + ] + }, + "correct_responses": { + "childArray": [ + { + "pattern": "CPR" + } + ] + } + } + ] + } + }; + ``` +
+ +## Logging + +By default, the API is set to only output errors in the Javascript console. You can change this by setting apiLogLevel to your desired level: + +```javascript +window.API.apiLogLevel = 1; // Log everything (Debug) +window.API.apiLogLevel = 2; // Log useful information, warning and errors +window.API.apiLogLevel = 3; // Log warnings and errors +window.API.apiLogLevel = 4; // Log errors only +window.API.apiLogLevel = 5; // No logging +``` + +## Resetting + +There may come a time when you need to reset the SCORM 1.2 API. Two methods are available. + +You can create a new API and replace the one available on `window.API`: + +```javascript +window.API = new window.simplifyScorm.ScormAPI(); +``` + +You can also create a new API and ask the existing one to use the new API instead. +This is useful when a SCORM object allows retrying after a failure, and you want to track each attempt in a separate CMI object. +SCORM objects often keep a reference to the original API, which is why this manipulation is needed: + +```javascript +var newAPI = new window.simplifyScorm.ScormAPI(); +window.API.replaceWithAnotherScormAPI(newAPI); +window.API = newAPI; +``` + +# SCORM 2004 + +Simplify Scorm will create a `window.API_1484_11` object, required by SCORM 2004, and will handle all the scorm interactions. Everything will be recorded on the object at `window.API_1484_11.cmi`. + +It is designed to work with the SCORM 2004 4th Edition specification. However, none of the ADL features have been implemented yet. + +## Listeners + +For convenience, hooks are available for all the SCORM API Signature functions: +`Initialize`, +`Terminate`, +`GetValue`, +`SetValue`, +`Commit`, +`GetLastError`, +`GetErrorString`, +`GetDiagnostic` + +You can add your hook into these by adding a listener to the `window.API_1484_11` object: + +```javascript +window.API_1484_11.on("Initialize", function() { + [...] }); ``` -You can all specify specific SCORM CMI events to listen to: +You can also listen for events on specific SCORM CMI elements: ```javascript -window.API.on('LMSSetValue.cmi.core.student_id', function(CMIElement, value) { +window.API_1484_11.on("SetValue.cmi.learner_id ", function(CMIElement, value) { + [...] }); ``` ## Saving Your CMI -Obviously, you will need to tie into the close, or complete event, or otherwise monitor your SCORM player to finalize the session, at that time, you can perform a JSON.stringify on the CMI object to retrieve a simplified data form for sending to your backend API, + +To save the CMI data, you can convert the CMI object to JSON and get a simplified data object to send to your backend API. +You should hook into the `Terminate` event to know when to save the finished session CMI data. +You can also hook into the `Commit` event to save the progress along the way. ```javascript -var simplifiedObject = window.API.cmi.toJSON(); +var simplifiedObject = window.API_1484_11.cmi.toJSON(); ``` -Example Output: -```json -{ - "suspend_data": "viewed=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31|lastviewedslide=31|7#1##,3,3,3,7,3,3,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11#0#b5e89fbb-7cfb-46f0-a7cb-758165d3fe7e=236~262~2542812732762722742772682802752822882852892872832862962931000~3579~32590001001010101010101010101001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001010010010010010010010010011010010010010010010010010010010010112101021000171000~236a71d398e-4023-4967-88fe-1af18721422d06passed6failed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105wrong110000000000000000000000000000000000~3185000000000000000000000000000000000000000000000000000000000000000000000000000000000~283~2191w11~21113101w41689~256~2100723031840~21007230314509062302670~2110723031061120000000000000000000~240~234531618~21601011000100000002814169400,#-1", - "launch_data": "", - "comments": "", - "comments_from_lms": "", - "core": { - "student_id": "", - "student_name": "", - "lesson_location": "", - "credit": "", - "lesson_status": "incomplete", - "total_time": "", - "lesson_mode": "normal", +
+ Example output + + ```json + { + "completion_status": "incomplete", + "completion_threshold": "", + "credit": "credit", + "entry": "", "exit": "suspend", - "session_time": "0000:00:33.90", + "launch_data": "", + "learner_id": "", + "learner_name": "", + "location": "", + "max_time_allowed": "", + "mode": "normal", + "progress_measure": "", + "scaled_passing_score": "", + "session_time": "PT3M30S", + "success_status": "unknown", + "suspend_data": "viewed=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31|lastviewedslide=31|7#1##,3,3,3,7,3,3,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11#0#b5e89fbb-7cfb-46f0-a7cb-758165d3fe7e=236~262~2542812732762722742772682802752822882852892872832862962931000~3579~32590001001010101010101010101001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001010010010010010010010010011010010010010010010010010010010010112101021000171000~236a71d398e-4023-4967-88fe-1af18721422d06passed6failed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105wrong110000000000000000000000000000000000~3185000000000000000000000000000000000000000000000000000000000000000000000000000000000~283~2191w11~21113101w41689~256~2100723031840~21007230314509062302670~2110723031061120000000000000000000~240~234531618~21601011000100000002814169400,#-1", + "time_limit_action": "continue,no message", + "total_time": "", + "comments_from_learner": { + "childArray": [ + ] + }, + "comments_from_lms": { + "childArray": [ + ] + }, + "interactions": { + "childArray": [ + { + "id": "Question14_1", + "type": "choice", + "timestamp": "2018-08-26T11:05:21", + "weighting": "1", + "learner_response": "HTH", + "result": "wrong", + "latency": "PT2M30S", + "description": "", + "objectives": { + "childArray": [ + { + "id": "Question14_1" + } + ] + }, + "correct_responses": { + "childArray": [ + { + "pattern": "CPR" + } + ] + } + } + ] + }, + "learner_preference": { + "audio_level": "1", + "language": "", + "delivery_speed": "1", + "audio_captioning": "0" + }, + "objectives": { + "childArray": [ + ] + }, "score": { + "scaled": "", "raw": "", - "max": "100", - "min": "" + "min": "", + "max": "" } - }, - "objectives": { - "childArray": [ - - ] - }, - "student_data": { - "mastery_score": "", - "max_time_allowed": "", - "time_limit_action": "" - }, - "student_preference": { - "audio": "", - "language": "", - "speed": "", - "text": "" - }, - "interactions": { - "childArray": [ - { - "id": "Question14_1", - "time": "11:05:21", - "type": "choice", - "weighting": "1", - "student_response": "HTH", - "result": "wrong", - "latency": "0000:00:01.68", - "objectives": { - "childArray": [ - { - "id": "Question14_1" - } - ] - }, - "correct_responses": { - "childArray": [ - { - "pattern": "CPR" - } - ] - } - } - ] } -} -``` + ``` +
## Initial Values -Lastly, if you are doing an initial load of your data from the backend API, you will need to initialize your variables on the CMI object before launching your SCORM 1.2 player. After the player has initialized - you will not be able to set any read only values: - -```javascript -window.API.cmi.core.student_id = '123'; -``` - -You can also initialize the CMI object in bulk by supplying a JSON object whose structure matches the output of `window.API.cmi.toJSON()`. Note that it can be a partial CMI object: - -```javascript -var json = { - "suspend_data": "...", - "core": { - "student_id": "", - "student_name": "" - }, - "interactions": { - "childArray": [ - { - "id": "Question14_1", - "time": "11:05:21", - "type": "choice", - "weighting": "1", - "student_response": "HTH", - "result": "wrong", - "latency": "0000:00:01.68", - "objectives": { - "childArray": [ - { - "id": "Question14_1" - } - ] - }, - "correct_responses": { - "childArray": [ - { - "pattern": "CPR" - } - ] - } - } - ] - } -}; -window.API.loadFromJSON(json); + +If you want to initially load data from your backend API, you must do it before launching your SCORM 2004 player. After the player has initialized, you will not be able to change any read-only values. + +You can initialize your variables on the CMI object individually: + +```javascript +window.API_1484_11.cmi.learner_id = "123"; ``` +You can also initialize the CMI object in bulk by supplying a JSON object. Note that it can be a partial SCORM 2004 CMI JSON object: + +```javascript +window.API_1484_11.loadFromJSON(json); +``` + +
+ Example JSON input + + ```javascript + var json = { + "learner_id": "123", + "learner_name": "Bob The Builder", + "suspend_data": "viewed=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31|lastviewedslide=31|7#1##,3,3,3,7,3,3,7,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,11#0#b5e89fbb-7cfb-46f0-a7cb-758165d3fe7e=236~262~2542812732762722742772682802752822882852892872832862962931000~3579~32590001001010101010101010101001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001010010010010010010010010011010010010010010010010010010010010112101021000171000~236a71d398e-4023-4967-88fe-1af18721422d06passed6failed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105wrong110000000000000000000000000000000000~3185000000000000000000000000000000000000000000000000000000000000000000000000000000000~283~2191w11~21113101w41689~256~2100723031840~21007230314509062302670~2110723031061120000000000000000000~240~234531618~21601011000100000002814169400,#-1", + "interactions": { + "childArray": [ + { + "id": "Question14_1", + "type": "choice", + "timestamp": "2018-08-26T11:05:21", + "weighting": "1", + "learner_response": "HTH", + "result": "wrong", + "latency": "PT2M30S", + "objectives": { + "childArray": [ + { + "id": "Question14_1" + } + ] + }, + "correct_responses": { + "childArray": [ + { + "pattern": "CPR" + } + ] + } + } + ] + } + }; + ``` +
+ ## Logging -By default, the API is set to only output errors in the Javascript console, * if you want to change this level * you can change it by setting the the apiLogLevel to the appropriate level: + +By default, the API is set to only output errors in the Javascript console. You can change this by setting apiLogLevel to your desired level: ```javascript -window.API.apiLogLevel = 5; // No logging +window.API_1484_11.apiLogLevel = 1; // Log everything (Debug) +window.API_1484_11.apiLogLevel = 2; // Log useful information, warning and errors +window.API_1484_11.apiLogLevel = 3; // Log warnings and errors +window.API_1484_11.apiLogLevel = 4; // Log errors only +window.API_1484_11.apiLogLevel = 5; // No logging +``` + +## Resetting + +There may come a time when you need to reset the SCORM 2004 API. Two methods are available. + +You can create a new API and replace the one available on `window.API_1484_11`: + +```javascript +window.API_1484_11 = new window.simplifyScorm.ScormAPI2004(); ``` -Hopefully this makes your life easier, and lets you get up and running much faster in your SCORM developing!!! +You can also create a new API and ask the existing one to use the new API instead. +This is useful when a SCORM object allows retrying after a failure, and you want to track each attempt in a separate CMI object. +SCORM objects often keep a reference to the original API, which is why this manipulation is needed: +```javascript +var newAPI = new window.simplifyScorm.ScormAPI2004(); +window.API_1484_11.replaceWithAnotherScormAPI(newAPI); +window.API_1484_11 = newAPI; +``` From 7bba11460d704311e0728c3531f7675db6577fb5 Mon Sep 17 00:00:00 2001 From: Olivier Bellemare Date: Sun, 26 Aug 2018 12:01:35 -0400 Subject: [PATCH 8/8] Add tests for modified code --- test/baseAPI.spec.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/baseAPI.spec.js b/test/baseAPI.spec.js index 4fdc506..a0f817b 100644 --- a/test/baseAPI.spec.js +++ b/test/baseAPI.spec.js @@ -210,6 +210,28 @@ }); }); + describe("#clearSCORMError", function() { + var anyLastErrorCode = 101; + + beforeEach(function() { + api.lastErrorCode = anyLastErrorCode; + }); + + context("given a success", function() { + it("should clear the last SCORM error", function() { + api.clearSCORMError(constants.SCORM_TRUE); + expect(api.lastErrorCode).to.equal(0); + }); + }); + + context("given a failure", function() { + it("should not clear the last SCORM error", function() { + api.clearSCORMError(constants.SCORM_FALSE); + expect(api.lastErrorCode).to.equal(anyLastErrorCode); + }); + }); + }); + describe("#getLmsErrorMessageDetails", function() { it("should return no error", function() { expect(api.getLmsErrorMessageDetails()).to.equal("No error");