Les fichiers SCORM sont devenus un format incontournable pour partager du contenu e-learning sur les LMS. Le SCORM est un standard créé au début des années 2000. Il est toujours très répandu, malgré la création de nouveaux standards plus modernes et le fait que chaque LMS implémente ce standard un peu différemment.
Nous allons voir dans cet article comment en créer un de zéro.
Pourquoi créer un fichier SCORM à la main ?
Pas mal de sites et d’outils existent aujourd’hui pour créer des fichiers SCORM en ligne, alors pourquoi s’embêter ?
Effectivement, si vous voulez faire un quiz ou une suite de pages qui se suivent, le faire à la main n’est probablement pas très utile. Mais si vous voulez faire quelque chose qui sort un peu des SCORMs basiques, il va falloir se retrousser les manches !
SCORM est-il le meilleur choix pour vous ?
Je vais vous répondre ce que tout bon développeur répondrait : ça dépend.
En fonction de vos besoins, il vaudrait mieux utiliser un standard plus moderne comme xAPI, ou simplement intégrer dans votre LMS une iframe qui pointe vers un de vos serveurs.
Mais là, nous encapsulons tout notre code dans un gros fichier SCORM, qui sera autonome et qui remontera automatiquement les notes dans notre LMS préféré.
De quoi est composé un fichier SCORM ?
Un fichier SCORM est un fichier zip, qui contient votre code et des méta-informations sous format XML. La plupart de ces méta-informations sont optionnelles, nous allons donc aller au plus simple !
1ère étape : Préparer votre code
Nous allons proposer à nos étudiants une réplique du jeu RoboZZle, codé avec Blockly pour nous simplifier le travail.
Nous allons partir de ce code-là :
Les mécaniques du jeu ne sont pas vraiment implémentées, mais ça nous suffira pour notre test. Si on lance level_1.html
, on peut voir que le jeu fonctionne.
2ème étape : Ajouter les méta-informations à votre fichier SCORM
Nous allons ajouter un fichier imsmanifest.xml
qui contiendra le minimum d’informations possible pour que notre fichier SCORM fonctionne.
1 | <?xml version="1.0" standalone="no" ?> |
2 | <manifest identifier="robozzle" version="1" |
3 | xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" |
4 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
5 | xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" |
6 | xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3" |
7 | xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3" |
8 | xmlns:imsss="http://www.imsglobal.org/xsd/imsss" |
9 | xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd |
10 | http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd |
11 | http://www.adlnet.org/xsd/adlseq_v1p3 adlseq_v1p3.xsd |
12 | http://www.adlnet.org/xsd/adlnav_v1p3 adlnav_v1p3.xsd |
13 | http://www.imsglobal.org/xsd/imsss imsss_v1p0.xsd"> |
14 | |
15 | <metadata> |
16 | <schema>ADL SCORM</schema> |
17 | <schemaversion>2004 3rd Edition</schemaversion> |
18 | </metadata> |
19 | |
20 | <organizations default="robozzle"> |
21 | <organization identifier="robozzle" adlseq:objectivesGlobalToSystem="false"> |
22 | <title>Robozzle</title> |
23 | <item identifier="lvl_1" identifierref="lvl_1_resource"> |
24 | <title>Level 1</title> |
25 | </item> |
26 | </organization> |
27 | </organizations> |
28 | |
29 | <resources> |
30 | <resource identifier="lvl_1_resource" type="webcontent" adlcp:scormType="sco" href="level_1.html"> |
31 | <file href="level_1.html"/> |
32 | <dependency identifierref="common_files"/> |
33 | </resource> |
34 | <resource identifier="common_files" type="webcontent" adlcp:scormType="asset"> |
35 | <file href="robozzle.css"/> |
36 | <file href="robozzle.js"/> |
37 | </resource> |
38 | </resources> |
39 | </manifest> |
Le bloc organizations
contient tous les item
de notre jeu. Un fichier SCORM peut contenir plusieurs items
, ce qui se traduit habituellement par un menu à côté du SCORM, une fois intégré sur le LMS. On peut imaginer cela comme différentes sections. Nous allons, pour l’instant, ignorer cette fonctionnalité et passer à la suite.
Le bloc resources
contient toutes les resources
de notre jeu. Pour chaque item (le lien entre item
et resource
est fait grâce au champ identifierref
), nous allons préciser le fichier à exécuter (grâce au champ href
), ainsi que les fichiers utilisés.
3ème étape : Compresser votre fichier SCORM
Comme nous l’avons dit plus tôt, un fichier SCORM est avant tout un fichier zip. Attention, le fichier imsmanifest.xml
doit forcément être à la racine du zip, pas dans un dossier.
Il faut donc sélectionner les différents fichiers utiles et les compresser (contrairement à sélectionner un dossier où tous vos fichiers se trouvent et compresser ce dossier).
Pour tester si notre fichier SCORM fonctionne, nous allons utiliser Soba LMS. Mais vous pouvez utiliser le LMS que vous voulez, comme Moodle par exemple.
Après avoir créé un parcours et ajouté un projet, nous pouvons mettre notre SCORM en 1ère section de ce projet. Et voilà ! Notre mini-jeu est en ligne sur notre LMS !
Communication entre le fichier SCORM et le LMS
Le standard SCORM établit également des règles pour la communication entre le LMS et le fichier SCORM. Cette communication passe par l’objet JavaScript window.parent.API_1484_11
(entre autres, c’est toujours compliqué avec SCORM !).
Cet objet est passé au code JavaScript contenu dans le fichier SCORM, et ce code est utilisé pour passer des pairs “clé/valeur”. Par exemple, vous pouvez passer la clé “lvl” (en vrai, cmi.suspend
) avec la valeur “3” pour indiquer que l’utilisateur est passé au niveau 3.
Une série de clés déjà définies permet au LMS d’agir en fonction de certains événements ou de passer au fichier SCORM des données standardisées.
Intégrer la communication avec le LMS
Nous allons modifier notre code pour utiliser window.parent.API_1484_11
. Tout d’abord, nous allons initialiser l’objet au début du code. Puis nous allons envoyer cmi.completion_status
à completed
pour indiquer que le niveau a été réussi.
Nous allons également envoyer puis récuperer la clé cmi.suspend
qui contiendra l’état du jeu à la fin. Cela sera pratique à des fins de statistiques pour voir le résultat des étudiants, et on en profitera pour réafficher l’état du jeu à l’étudiant s’il rafraîchit la page.
Enfin, nous allons envoyer la valeur cmi.exit
à suspend
. Cela sert à demander au LMS de ne pas traiter chaque chargement de la page comme une nouvelle tentative, mais comme la même. Normalement, nous devrions la définir au bon moment (avant de quitter la page par exemple), mais pour simplifier, nous allons l’envoyer dès le début.
1 | window.parent.API_1484_11.Initialize(""); |
2 | window.parent.API_1484_11.SetValue("cmi.exit", "suspend"); |
3 | |
4 | const workspace = Blockly.inject('blocklyDiv', {toolbox: document.getElementById('toolbox'), zoom: {startScale: 1.3}}); |
5 | const orientations = ['right', 'bottom', 'left', 'top'] |
6 | const base = robozzle.outerHTML |
7 | let timeout_id = null |
8 | |
9 | |
10 | let xml = window.parent.API_1484_11.GetValue("cmi.suspend_data"); |
11 | if (xml != "") { |
12 | let dom = Blockly.Xml.textToDom(xml); |
13 | Blockly.mainWorkspace.clear(); |
14 | Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, dom); |
15 | } |
16 | |
17 | workspace.addChangeListener((event) => { |
18 | if (event.type == Blockly.Events.BLOCK_MOVE) { |
19 | let xmlDom = Blockly.Xml.workspaceToDom(Blockly.mainWorkspace); |
20 | let xmlText = Blockly.Xml.domToPrettyText(xmlDom); |
21 | |
22 | window.parent.API_1484_11.SetValue("cmi.suspend_data", xmlText); |
23 | window.parent.API_1484_11.Commit("") |
24 | } |
25 | }) |
26 | |
27 | |
28 | async function __end_action() { |
29 | if (document.querySelector('.robot.outside')) { |
30 | alert('Perdu'); |
31 | |
32 | return false |
33 | } |
34 | |
35 | document.querySelector('.robot.star')?.classList?.remove('star') |
36 | |
37 | if (document.querySelector('.star') == undefined) { |
38 | alert('Gagné !'); |
39 | |
40 | window.parent.API_1484_11.SetValue("cmi.completion_status", "completed"); |
41 | window.parent.API_1484_11.Commit(""); |
42 | window.parent.API_1484_11.Terminate(""); |
43 | |
44 | return false |
45 | } |
46 | |
47 | await new Promise((resolve) => setTimeout(resolve, 500)) |
48 | |
49 | return true |
50 | } |
Il ne reste plus qu’à appeler les méthodes Commit
(pour signifier que les SetValue
doivent être sauvegardés sur le LMS) et Terminate
pour couper la connexion avec le LMS.
Gérer plusieurs niveaux
Nous avons plusieurs possibilités pour avoir plusieurs niveaux. Mais c’est à partir de maintenant que les LMS divergent un peu sur comment suivre le standard SCORM. Mais on va s’en sortir !
Option 1 : Utiliser le système d'items du `imsmanifest.xml`
Comme nous l’avons vu plus tôt, un fichier imsmanifest.xml
peut avoir plusieurs items
. Chaque item sera représenté comme une section différente du fichier SCORM.
Après modification, notre fichier imsmanifest.xml
ressemble à ça :
1 | <?xml version="1.0" standalone="no" ?> |
2 | <manifest identifier="robozzle" version="1" |
3 | xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" |
4 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
5 | xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_v1p3" |
6 | xmlns:adlseq="http://www.adlnet.org/xsd/adlseq_v1p3" |
7 | xmlns:adlnav="http://www.adlnet.org/xsd/adlnav_v1p3" |
8 | xmlns:imsss="http://www.imsglobal.org/xsd/imsss" |
9 | xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd |
10 | http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd |
11 | http://www.adlnet.org/xsd/adlseq_v1p3 adlseq_v1p3.xsd |
12 | http://www.adlnet.org/xsd/adlnav_v1p3 adlnav_v1p3.xsd |
13 | http://www.imsglobal.org/xsd/imsss imsss_v1p0.xsd"> |
14 | |
15 | <metadata> |
16 | <schema>ADL SCORM</schema> |
17 | <schemaversion>2004 3rd Edition</schemaversion> |
18 | </metadata> |
19 | |
20 | <organizations default="robozzle"> |
21 | <organization identifier="robozzle" adlseq:objectivesGlobalToSystem="false"> |
22 | <title>Robozzle</title> |
23 | |
24 | <item identifier="lvl_1" identifierref="lvl_1_resource"> |
25 | <title>Level 1</title> |
26 | </item> |
27 | <item identifier="lvl_2" identifierref="lvl_2_resource"> |
28 | <title>Level 2</title> |
29 | </item> |
30 | </organization> |
31 | </organizations> |
32 | |
33 | <resources> |
34 | <resource identifier="lvl_1_resource" type="webcontent" adlcp:scormType="sco" href="level_1.html"> |
35 | <file href="level_1.html"/> |
36 | <dependency identifierref="common_files"/> |
37 | </resource> |
38 | <resource identifier="lvl_2_resource" type="webcontent" adlcp:scormType="sco" href="level_2.html"> |
39 | <file href="level_2.html"/> |
40 | <dependency identifierref="common_files"/> |
41 | </resource> |
42 | <resource identifier="common_files" type="webcontent" adlcp:scormType="asset"> |
43 | <file href="robozzle.css"/> |
44 | <file href="robozzle.js"/> |
45 | </resource> |
46 | </resources> |
47 | </manifest> |
Nous avons ajouté un item
et un resource
pour notre 2ème niveau. Une fois uploadé sur Soba, notre jeu ressemble à ça :
Une fois au niveau 2, on peut rafraîchir la page et voir que Soba nous amène directement au niveau 2.
Option 2 : Laisser le code JS du fichier SCORM gérer les niveaux
Comme nous l’avons vu, nous pouvons communiquer les données que nous voulons au LMS, et il nous les renverra au prochain affichage. Ces données sont propres à chaque utilisateur.
Cela signifie qu’en modifiant notre code JS (on ne le fera pas là, je vous laisse cela comme exercice !), on peut lui faire afficher le niveau qui correspond au niveau actuel de l’apprenant. Ainsi, tous les LMS réagiront de manière identique !
Option 3 : Stocker les résultats sur un autre serveur
Le LMS devrait vous fournir des informations qui identifient l’apprenant qui affiche le fichier SCORM. Par exemple, la variable cmi.learner_id
sert à identifier de manière unique l’apprenant.
En utilisant ces variables, il est possible de stocker le résultat d’un apprenant sur un autre serveur et ainsi lui afficher son niveau en cours quand il se reconnecte.
Conclusion
La création d’un fichier SCORM basique n’est finalement pas très complexe. Il suffit d’ajouter un fichier XML et de communiquer avec le LMS via l’objet window.parent.API_1484_11
.
Mais créer un fichier SCORM qui suit le standard est, pour le coup, assez compliqué. N’hésitez pas à consulter le site officiel de SCORM et ce wrapper déjà codé, qui vous simplifiera la tâche si vous voulez aller plus loin.
On peut outrepasser les limitations de SCORM (ou de ses implémentations) en ayant notre propre serveur pour stocker les résultats.
Nous n’en avons pas parlé dans cet article, mais les niveaux peuvent être envoyés depuis notre autre serveur, et ainsi, on peut corriger/ajouter des niveaux à la volée, sans changer le fichier SCORM qui a été uploadé sur notre LMS.
Explorez les fonctionnalités clés de Soba LMS (on gère bien plus que les fichiers SCORM !), ou testez-les gratuitement et découvrez comment Soba LMS peut répondre à vos besoins !