笔者在前一篇文章Scorm标准学习——基于Scorm1.2(一)中对LMS及Scorm标准体系结构做了介绍。这里将对Scorm运行时环境(RTE)进行介绍,主要涉及RTE API和数据模型(Data Model),仍基于Scorm1.2标准。更为详细的信息可参考Scorm1.2标准的官方文档中关于RTE的论述。 ##Scorm RTE概述## Scorm标准的目的之一是使得课件与平台相独立,使得课件的复用和重组成为可能。CAM定义了Scorm课件的组织结构规范,而RTE则为课件与平台的交互提供了统一的标准。Scorm RTE定义了通用的内容对象运行机制,包括Launch、API以及Data Model。
Launch: 定义了内容对象的发布,描述了如何启动一个Scorm内容对象(Asset和SCO两种),并确立了内容对象和LMS之间的通信机制。
API: 定义了LMS与内容对象之间的通信API,使得内容对象在需要时通过API与LMS进行交互存取数据。API会将获得的数据模型实例存储在用户端的内存中(如一个JavaScript对象),并在需要时将该数据发送至LMS平台。
Data Model: 描述了跟踪用户学习通用数据模型,即内容对象在与LMS进行通信时所依赖的数据结构,包括用户的学习时间、完成状态、成绩等元素。LMS需要对每个用户的每个内容对象维护一个Data Model数据结构。
API提供了内容对象与LMS通信的接口,而DM则描述了通信所使用的数据模型,API+DM使得内容对象在不同的LMS系统中具有了通用性。
##API接口## API由LMS提供实现,向内容对象暴露接口。内容对象(以SCO为例)在需要与LMS交互时会主动调用LMS提供的API接口,LMS的API实现一般是以http的方式向LMS Server发出请求,最终由LMS完成数据的存取。因API暴露在前端,一般采用JavaScript脚本或Java Applet实现。
###API函数### Scorm1.2中API函数可分为三类,如下表:
表1 Scorm API函数分类
<table width=100% border="1"> <tr bgcolor="#BBBBBB"> <th width=30%>函数分类</th> <th width=40%>描述</th> <th width=30%>函数</th> </tr> <tr> <td width=30% valign="middle">Session Methods</td> <td width=40% valign="middle">会话函数:通过API实例标记内容对象与LMS之间的通信</td> <td width=30% align=center>LMSInitialize<br>LMSFinish</td> </tr> <tr> <td width=30% valign="middle">Data-transfer Methods</td> <td width=40% valign="middle">数据传输函数:用于内容对象与LMS之间的数据传递</td> <td width=30% align=center>LMSSetValue<br>LMSGetValue<br>LMSCommit</td> </tr> <tr> <td width=30% valign="middle">Support Methods</td> <td width=40% valign="middle">支持函数:用于获取辅助信息,包括错误信息和错误诊断</td> <td width=30% align=center>LMSGetLastError<br>LMSGetErrorString<br>LMSGetDiagnostic</td> </tr> </table> 如表1所示,Scorm标准中定义了8个API函数(Scorm2004稍有不同),其具体功能如下: **LMSInitialize:** 初始化函数,负责在当前学习的内容对象和LMS之间建立通信连接,并从LMS取得该当前用户关于当前内容对象的学习记录信息,即整个DM数据结构。内容对象在载入时均会通过该方法获得初始运行时数据。函数成功执行返回"true"(字符串,非布尔值,下同),否则返回"false"。 **LMSFinish:** 结束函数,结束一个内容对象与LMS的通信连接。内容对象在退出前均会调用该方法。通常实现时,该方法会调用LMSCommit提交一次最近的数据。当LMS平台在API实现时,选择HTTP协议进行无状态通信,则该函数本身将不产生数据通信,仅仅是调用LMSCommit提交一次数据。成功执行返回"true",否则返回"false"。 **LMSSetValue:** 负责更新一个数据模型的属性值。该方法原型为LMSSetValue(element, value),参数element标识要设置的属性,value则是属性的值。内容对象在需要更改DM的某一属性值时会调用该函数,如当用户完成学习一个内容对象后需将该内容对象的cmi.core.lesson_status更新为completed。通常该函数只是更新客户端内存中该属性的值,而非直接提交至LMS平台。函数成功执行返回"true",否则返回"false"。 **LMSGetValue:** 获取一个数据模型的属性值。该方法有一个element参数,表示属性名。通常该方法直接从客户端内存中取出所需的值。成功执行则返回对应的属性值,否则返回空字符串。 **LMSCommit:** 提交函数,负责将客户端内存中目前的RTE数据提交至LMS平台,由平台完成最后的分析写入工作。采用JS实现的API可用AJAX进行数据提交。成功返回"true",否则返回"false"。 **LMSGetLastError:** 获得错误码,当内容对象调用API时返回"false"或产生其他错误时,可调用该函数获得具体的错误代码,API实现中需要设置相应的错误码。 **LMSGetErrorString:** 获得错误码对应的字符串说明,参数为错误码。 **LMSGetDiagnostic:** 获得针对当前错误的诊断信息,参数为错误码。 ###错误码### Scorm1.2定义的错误码如表2(Scorm2004错误码定义更为详细): *表2 Scorm1.2错误码* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20%>错误码</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle" align="center">0</td> <td width=60% valign="middle">无错误</td> </tr> <tr> <td width=20% valign="middle" align="center">101</td> <td width=60% valign="middle">一般错误</td> </tr> <tr> <td width=20% valign="middle" align="center">201</td> <td width=60% valign="middle">参数无效</td> </tr> <tr> <td width=20% valign="middle" align="center">202</td> <td width=60% valign="middle">元素属性无子节点(children属性)</td> </tr> <tr> <td width=20% valign="middle" align="center">203</td> <td width=60% valign="middle">属性非集合类型,无count属性</td> </tr> <tr> <td width=20% valign="middle" align="center">301</td> <td width=60% valign="middle">LMS未初始化</td> </tr> <tr> <td width=20% valign="middle" align="center">401</td> <td width=60% valign="middle">未实现的数据元素模型</td> </tr> <tr> <td width=20% valign="middle" align="center">402</td> <td width=60% valign="middle">无效的setvalue操作,传递的element是关键字</td> </tr> <tr> <td width=20% valign="middle" align="center">403</td> <td width=60% valign="middle">元素属性只读</td> </tr> <tr> <td width=20% valign="middle" align="center">404</td> <td width=60% valign="middle">元素属性只写</td> </tr> <tr> <td width=20% valign="middle" align="center">405</td> <td width=60% valign="middle">错误的数据类型</td> </tr> </table> ##Data Model## Scorm1.2 RTE数据模型部分定义了跟踪内容对象的数据结构。LMS平台需要为每个用户的每个内容单元维护一组数据模型实例,该实例数据记录了该用户对应于该内容对象的学习状态(如分数、进度、完成状态等)。DM中所有的属性均以cmi开头,以"."分隔。同时数据模型中定了了三个关键字:_version、_children、_count,保留的关键字受LMS管理,且均为只读(如使用LMSSetValue设置_version属性,则会返回false,且API应在实现时将错误码置为402)。 **_version:** 标识了LMS平台支持的数据模型版本,该属性不可用于数据模型元素上(即只能以cmi._version方式获取) **_children:** 返回某一数据模型元素的所有子属性(元素),以字符串形式返回,并以执行的分隔符分隔 **_count:** 返回一个数据模型元素集合中包含的元素总数,仅用于集合类型的数据模型 ###数据类型### Scorm1.2为各种数据模型元素定义了如下类型: *表3 Scorm1.2数据类型* <table width=100% border="1"> <tr bgcolor=#EEEEEE> <th width=20% align="left">数据类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">CMIBlank</td> <td width=60% valign="middle">空字符串</td> </tr> <tr> <td width=20% valign="middle">CMIBoolean</td> <td width=60% valign="middle">bool字符串ture或false</td> </tr> <tr> <td width=20% valign="middle">CMIDecimal</td> <td width=60% valign="middle">带小数的数字</td> </tr> <tr> <td width=20% valign="middle">CMIFeedback</td> <td width=60% valign="middle">描述interaction属性中的内容的结构</td> </tr> <tr> <td width=20% valign="middle">CMIIdentifier</td> <td width=60% valign="middle">字母数字混合的字符串,不包含空格和非打印字符,不超过255</td> </tr> <tr> <td width=20% valign="middle">CMIInteger</td> <td width=60% valign="middle">整数(0, 65536)</td> </tr> <tr> <td width=20% valign="middle">CMISInteger</td> <td width=60% valign="middle">有符号整数(-32768, 32768)</td> </tr> <tr> <td width=20% valign="middle">CMIString255</td> <td width=60% valign="middle">ASCII字符串,不超过255</td> </tr> <tr> <td width=20% valign="middle">CMIString4096</td> <td width=60% valign="middle">ASCII字符串,不超过4096</td> </tr> <tr> <td width=20% valign="middle">CMITime</td> <td width=60% valign="middle">时间类型,HH:MM:SS.SS</td> </tr> <tr> <td width=20% valign="middle">CMITimespan</td> <td width=60% valign="middle">时间间隔,格式同上</td> </tr> <tr> <td width=20% valign="middle">CMIVocabulary</td> <td width=60% valign="middle">单词表,类似枚举类型</td> </tr> </table> ###数据模型元素### 数据模型元素可以分为9类,参见表4-表12。 **cmi.core:** 核心数据,该组中元素属性LMS必须支持 **cmi.suspend_data:** 暂存数据,存储内容对象在恢复时所需的特定数据 **cmi.launch_data:** 内容对象运行时所需的数据 **cmi.comments:** 关于内容对象的评论数据,一般来自用户评论 **cmi.comments_from_lms:** LMS平台对内容对象的评论信息 **cmi.objectives:** 针对内容对象的个人目标集合 **cmi.student_data:** 基于用户偏好的内容对象自定义信息,如运行时间限制 **cmi.student_preference:** 针对内容对象的用户偏好设置 **cmi.interactions:** 用户交互信息 *表4 cmi.core元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.core._children</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">返回所有属性</td> </tr> <tr> <td width=20% valign="middle">cmi.core.student_id</td> <td width=20% align="left">CMIIdentifier</td> <td width=60% valign="middle">当前用户(学生)ID</td> </tr> <tr> <td width=20% valign="middle">cmi.core.student_name</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">当前用户名</td> </tr> <tr> <td width=20% valign="middle">cmi.core.lesson_location</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">该用户学习当前内容对象的位置(如一个swf中的第几页)</td> </tr> <tr> <td width=20% valign="middle">cmi.core.credit</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">是否有学分(credit/no-credit)</td> </tr> <tr> <td width=20% valign="middle">cmi.core.lesson_status</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">内容对象状态(passed/completed/failed/incomplete/browsed/not attempted)</td> </tr> <tr> <td width=20% valign="middle">cmi.core.entry</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">进入内容对象的动作(ab-initio/resume/空)</td> </tr> <tr> <td width=20% valign="middle">cmi.core.score._children</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">返回score的子属性</td> </tr> <tr> <td width=20% valign="middle">cmi.core.score.raw</td> <td width=20% align="left">CMIBlank/CMIDecimal</td> <td width=60% valign="middle">最后一次成绩</td> </tr> <tr> <td width=20% valign="middle">cmi.core.score.min</td> <td width=20% align="left">CMIBlank/CMIDecimal</td> <td width=60% valign="middle">最低成绩</td> </tr> <tr> <td width=20% valign="middle">cmi.core.score.max</td> <td width=20% align="left">CMIBlank/CMIDecimal</td> <td width=60% valign="middle">最高成绩</td> </tr> <tr> <td width=20% valign="middle">cmi.core.total_time</td> <td width=20% align="left">CMITimespan</td> <td width=60% valign="middle">学习总时间</td> </tr> <tr> <td width=20% valign="middle">cmi.core.lesson_mode</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">内容对象学习状态(normal/review/browse)</td> </tr> <tr> <td width=20% valign="middle">cmi.core.exit</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">退出途径(time-out/suspend/logout/空)</td> </tr> <tr> <td width=20% valign="middle">cmi.core.session_time</td> <td width=20% align="left">CMITimespan</td> <td width=60% valign="middle">当次学习时间</td> </tr> </table> *表5 cmi.suspend_data元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.suspend_data</td> <td width=20% align="left">CMIString4096</td> <td width=60% valign="middle">存储自定义暂存数据</td> </tr> </table> *表6 cmi.launch_data元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.launch_data</td> <td width=20% align="left">CMIString4096</td> <td width=60% valign="middle">存储内容对象运行时所需数据</td> </tr> </table> *表7 cmi.comments元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.comments</td> <td width=20% align="left">CMIString4096</td> <td width=60% valign="middle">存储评论数据</td> </tr> </table> *表8 cmi.comments_from_lms元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.comments_from_lms</td> <td width=20% align="left">CMIString4096</td> <td width=60% valign="middle">存储LMS关于内容对象的评论数据</td> </tr> </table> *表9 cmi.objectives元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.objectives._children</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">返回所有属性</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives._count</td> <td width=20% align="left">CMIInteger</td> <td width=60% valign="middle">返回集合中元素总数</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives.n.id</td> <td width=20% align="left">CMIIdentifier</td> <td width=60% valign="middle">一个目标对象ID</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives.n.score._children</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">获得score子属性</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives.n.score.raw</td> <td width=20% align="left">CMIBlank/CMIDecimal</td> <td width=60% valign="middle">目标分数</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives.n.score.min</td> <td width=20% align="left">CMIBlank/CMIDecimal</td> <td width=60% valign="middle">目标分数最小值</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives.n.score.max</td> <td width=20% align="left">CMIBlank/CMIDecimal</td> <td width=60% valign="middle">目标分数最大值</td> </tr> <tr> <td width=20% valign="middle">cmi.objectives.n.status</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">目标完成状态(passed/completed/failed/incomplete/browsed/not attempted)</td> </tr> </table> *表10 cmi.student_data元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.student_data._children</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">返回子属性</td> </tr> <tr> <td width=20% valign="middle">cmi.student_data.mastery_score</td> <td width=20% align="left">CMIDecimal</td> <td width=60% valign="middle">分数阈值(及格分)</td> </tr> <tr> <td width=20% valign="middle">cmi.student_data.max_time_allowed</td> <td width=20% align="left">CMITimespan</td> <td width=60% valign="middle">最大允许时间</td> </tr> <tr> <td width=20% valign="middle">cmi.student_data.time_limit_action</td> <td width=20% align="left">CMIVocabulary</td> <td width=60% valign="middle">超时后动作<br>(exit,message/exit,no message/<br>continue,message/continue,no message)</td> </tr> </table> *表11 cmi.student_preference元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=20% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=60% align="left">描述</th> </tr> <tr> <td width=20% valign="middle">cmi.student_preference._children</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">返回子属性</td> </tr> <tr> <td width=20% valign="middle">cmi.student_preference.audio</td> <td width=20% align="left">CMISInteger</td> <td width=60% valign="middle">声音控制</td> </tr> <tr> <td width=20% valign="middle">cmi.student_preference.language</td> <td width=20% align="left">CMIString255</td> <td width=60% valign="middle">偏好语言</td> </tr> <tr> <td width=20% valign="middle">cmi.student_preference.speed</td> <td width=20% align="left">CMISInteger</td> <td width=60% valign="middle">速度</td> </tr> <tr> <td width=20% valign="middle">cmi.student_preference.text</td> <td width=20% align="left">CMISInteger</td> <td width=60% valign="middle">音频文字</td> </tr> </table> *表12 cmi.interactions元素属性* <table width=100% border="1"> <tr bgcolor=#BBBBBB> <th width=40% align="left">属性名</th> <th width=20% align="left">类型</th> <th width=40% align="left">描述</th> </tr> <tr> <td width=40% valign="middle">cmi.interactions._children</td> <td width=20% align="left">CMIString255</td> <td width=40% valign="middle">返回所有属性</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions._count</td> <td width=20% align="left">CMIInteger</td> <td width=40% valign="middle">返回集合中元素总数</td> </tr> <tr> <td width=20% valign="middle">cmi.interactions.n.id</td> <td width=20% align="left">CMIIdentifier</td> <td width=60% valign="middle">某一交互ID</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.objectives._count</td> <td width=20% align="left">CMIInteger</td> <td width=40% valign="middle">交互目标集合中总数</td> </tr> <tr> <td width=20% valign="middle">cmi.interactions.n.objectives.n.id</td> <td width=20% align="left">CMIIdentifier</td> <td width=60% valign="middle">交互的目标ID</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.time</td> <td width=20% align="left">CMITime</td> <td width=40% valign="middle">交互完成时间</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.type</td> <td width=20% align="left">CMIVocabulary</td> <td width=40% valign="middle">交互类型<br>(true-false/choice/fill-in/matching/<br>performance/sequencing/likert/numeric)</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.correct_responses._count</td> <td width=20% align="left">CMIInteger</td> <td width=40% valign="middle">存储一个交互的答案数</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.correct_responses.n.pattern</td> <td width=20% align="left">CMIFeedback</td> <td width=40% valign="middle">存储一个交互的答案</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.weighting</td> <td width=20% align="left">CMIDecimal</td> <td width=40% valign="middle">一个交互的权重</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.student_response</td> <td width=20% align="left">CMIFeedback</td> <td width=40% valign="middle">用户提供的答案</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.result</td> <td width=20% align="left">CMIVocabulary</td> <td width=40% valign="middle">由用户答案计算的交互结果<br>(correct/wrong/unanticipated/neutral/x.x)</td> </tr> <tr> <td width=40% valign="middle">cmi.interactions.n.latency</td> <td width=20% align="left">CMITimespan</td> <td width=40% valign="middle">交互时间间隔</td> </tr> </table>